From 67bd96d2b18d734959dffdb92c39601decf9e854 Mon Sep 17 00:00:00 2001 From: KyleKlus Date: Fri, 10 May 2024 13:40:18 +0200 Subject: [PATCH 1/7] UPDATE: Extended the amount of information in the card title --- src/gui/FlashcardModal.tsx | 8 +- src/gui/FlashcardReviewView.tsx | 133 +++++++++++++++++++++++--------- styles.css | 24 ++++-- 3 files changed, 117 insertions(+), 48 deletions(-) diff --git a/src/gui/FlashcardModal.tsx b/src/gui/FlashcardModal.tsx index 9ff4b372..27e81fdf 100644 --- a/src/gui/FlashcardModal.tsx +++ b/src/gui/FlashcardModal.tsx @@ -80,9 +80,9 @@ export class FlashcardModal extends Modal { } onClose(): void { + this.mode = FlashcardModalMode.Closed; this.deckView.close(); this.flashcardView.close(); - this.mode = FlashcardModalMode.Closed; } private _showDecksList(): void { @@ -94,9 +94,9 @@ export class FlashcardModal extends Modal { this.deckView.hide(); } - private _showFlashcard(): void { + private _showFlashcard(deck:Deck): void { this._hideDecksList(); - this.flashcardView.show(); + this.flashcardView.show(deck); } private _hideFlashcard(): void { @@ -106,7 +106,7 @@ export class FlashcardModal extends Modal { private _startReviewOfDeck(deck: Deck) { this.reviewSequencer.setCurrentDeck(deck.getTopicPath()); if (this.reviewSequencer.hasCurrentCard) { - this._showFlashcard(); + this._showFlashcard(deck); } else { this._showDecksList(); } diff --git a/src/gui/FlashcardReviewView.tsx b/src/gui/FlashcardReviewView.tsx index cb6024b4..90c5a78d 100644 --- a/src/gui/FlashcardReviewView.tsx +++ b/src/gui/FlashcardReviewView.tsx @@ -27,7 +27,9 @@ export class FlashcardReviewView { public view: HTMLDivElement; public header: HTMLDivElement; + public titleWrapper: HTMLDivElement; public title: HTMLDivElement; + public subTitle: HTMLDivElement; public backButton: HTMLDivElement; public controls: HTMLDivElement; @@ -45,6 +47,7 @@ export class FlashcardReviewView { public easyButton: HTMLButtonElement; public answerButton: HTMLButtonElement; + private chosenDeck: Deck | null; private reviewSequencer: IFlashcardReviewSequencer; private settings: SRSettings; private reviewMode: FlashcardReviewMode; @@ -72,6 +75,7 @@ export class FlashcardReviewView { this.editClickHandler = editClickHandler; this.modalContentEl = contentEl; this.modalEl = modalEl; + this.chosenDeck = null; // Build ui this.init(); @@ -81,17 +85,23 @@ export class FlashcardReviewView { * Initializes all static elements in the FlashcardView */ init() { + this._createBackButton(); + this.view = this.modalContentEl.createDiv(); this.view.addClasses(["sr-flashcard", "sr-is-hidden"]); this.header = this.view.createDiv(); this.header.addClass("sr-header"); - this._createBackButton(); + this.titleWrapper = this.header.createDiv(); + this.titleWrapper.addClass("sr-title-wrapper"); - this.title = this.header.createDiv(); + this.title = this.titleWrapper.createDiv(); this.title.addClass("sr-title"); + this.subTitle = this.titleWrapper.createDiv(); + this.subTitle.addClasses(["sr-sub-title", "sr-is-hidden"]); + this.controls = this.header.createDiv(); this.controls.addClass("sr-controls"); @@ -112,63 +122,80 @@ export class FlashcardReviewView { } /** - * Shows the FlashcardView & rerenders all dynamic elements + * Shows the FlashcardView if it is hidden */ - async show() { - this.mode = FlashcardModalMode.Front; - const deck: Deck = this.reviewSequencer.currentDeck; - - // Setup title - this._setTitle(deck); - this.resetButton.disabled = true; - - // Setup context - if (this.settings.showContextInCards) { - this.context.setText( - this._formatQuestionContextText(this._currentQuestion.questionContext), - ); + async show(chosenDeck: Deck) { + if (!this.view.hasClass("sr-is-hidden")) { + return; } + this.chosenDeck = chosenDeck; - // Setup card content - this.content.empty(); - const wrapper: RenderMarkdownWrapper = new RenderMarkdownWrapper( - this.app, - this.plugin, - this._currentNote.filePath, - ); - await wrapper.renderMarkdownWrapper(this._currentCard.front, this.content); - - // Setup response buttons - this._resetResponseButtons(); + await this._drawContent(); // Prevents the following code, from running if this show is just a redraw and not an unhide - if (!this.view.hasClass("sr-is-hidden")) { - return; - } this.view.removeClass("sr-is-hidden"); this.backButton.removeClass("sr-is-hidden"); document.addEventListener("keydown", this._keydownHandler); } /** - * Hides the FlashcardView + * Refreshes all dynamic elements + */ + async refresh() { + await this._drawContent(); + } + + /** + * Hides the FlashcardView if it is visible */ hide() { // Prevents the following code, from running if this was executed multiple times after one another if (this.view.hasClass("sr-is-hidden")) { return; } + + document.removeEventListener("keydown", this._keydownHandler); this.view.addClass("sr-is-hidden"); this.backButton.addClass("sr-is-hidden"); - document.removeEventListener("keydown", this._keydownHandler); } /** * Closes the FlashcardView */ close() { - document.removeEventListener("keydown", this._keydownHandler); this.hide(); + document.removeEventListener("keydown", this._keydownHandler); + } + + // #region -> Functions & helpers + + private async _drawContent() { + this.mode = FlashcardModalMode.Front; + const currentDeck: Deck = this.reviewSequencer.currentDeck; + + // Setup title + this._setTitle(this.chosenDeck); + this._setSubTitle(this.chosenDeck, currentDeck); + this.resetButton.disabled = true; + + // Setup context + if (this.settings.showContextInCards) { + this.context.setText( + this._formatQuestionContextText(this._currentQuestion.questionContext), + ); + } + + // Setup card content + this.content.empty(); + const wrapper: RenderMarkdownWrapper = new RenderMarkdownWrapper( + this.app, + this.plugin, + this._currentNote.filePath, + ); + await wrapper.renderMarkdownWrapper(this._currentCard.front, this.content); + + // Setup response buttons + this._resetResponseButtons(); } // -> Functions & helpers @@ -332,7 +359,7 @@ export class FlashcardReviewView { } private async _handleSkipCard(): Promise { - if (this._currentCard != null) await this.show(); + if (this._currentCard != null) await this.refresh(); else this.backClickHandler(); } @@ -353,13 +380,47 @@ export class FlashcardReviewView { setIcon(this.backButton, "arrow-left"); this.backButton.setAttribute("aria-label", t("BACK")); this.backButton.addEventListener("click", () => { - /* this.plugin.data.historyDeck = ""; */ this.backClickHandler(); }); } private _setTitle(deck: Deck) { - this.title.setText(`${deck.deckName}: ${deck.getCardCount(CardListType.All, true)}`); + const isRandomMode = this.settings.flashcardCardOrder === "EveryCardRandomDeckAndCard"; + let text = deck.deckName; + if (!isRandomMode) { + const deckStats = this.reviewSequencer.getDeckStats(deck.getTopicPath()); + const cardsInQueue = deckStats.dueCount + deckStats.newCount; + text += `: ${cardsInQueue}`; + } + this.title.setText(text); + } + + private _setSubTitle(chosenDeck: Deck, currentDeck: Deck) { + if (chosenDeck.subdecks.length === 0) { + if (!this.subTitle.hasClass("sr-is-hidden")) { + this.subTitle.addClass("sr-is-hidden"); + } + return; + } + + if (this.subTitle.hasClass("sr-is-hidden")) { + this.subTitle.removeClass("sr-is-hidden"); + } + + let text = `${currentDeck.deckName}`; + + const isRandomMode = this.settings.flashcardCardOrder === "EveryCardRandomDeckAndCard"; + if (!isRandomMode) { + const subDecksWithCardsInQueue = chosenDeck.subdecks.filter((subDeck) => { + const deckStats = this.reviewSequencer.getDeckStats(subDeck.getTopicPath()); + return deckStats.dueCount + deckStats.newCount > 0; + }); + + text = `${t("DECKS")}: ${subDecksWithCardsInQueue.length} | ${text}`; + text += `: ${currentDeck.getCardCount(CardListType.All, false)}`; + } + + this.subTitle.setText(text); } // -> Controls diff --git a/styles.css b/styles.css index af8e7a1d..ca18adca 100644 --- a/styles.css +++ b/styles.css @@ -58,6 +58,13 @@ body:not(.native-scrollbars) #sr-modal .modal-close-button { line-height: var(--line-height-tight); } +.sr-sub-title { + font-size: var(--font-ui-medium); + text-align: center; + line-height: var(--line-height-tight); + color: var(--text-muted); +} + .sr-content { overflow-y: auto; } @@ -198,15 +205,16 @@ body:not(.native-scrollbars) #sr-modal .modal-close-button { padding-bottom: 8px; } -.sr-flashcard .sr-button:disabled { - cursor: not-allowed; +.sr-flashcard .sr-title-wrapper { + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; + gap: 4px; } -.sr-flashcard .sr-back-button { - position: absolute; - left: 0; - top: 0; - z-index: 21; +.sr-flashcard .sr-button:disabled { + cursor: not-allowed; } .sr-flashcard .sr-controls { @@ -281,4 +289,4 @@ body:not(.native-scrollbars) #sr-modal .modal-close-button { #sr-chart-period { appearance: menulist; border-right: 8px solid transparent; -} +} \ No newline at end of file From 62f0b89e8a3b040c5a5305570ac931e187c3ebeb Mon Sep 17 00:00:00 2001 From: KyleKlus Date: Fri, 10 May 2024 14:28:41 +0200 Subject: [PATCH 2/7] UPDATE: Improved card title in random mode --- src/gui/FlashcardReviewView.tsx | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/src/gui/FlashcardReviewView.tsx b/src/gui/FlashcardReviewView.tsx index 90c5a78d..a7143006 100644 --- a/src/gui/FlashcardReviewView.tsx +++ b/src/gui/FlashcardReviewView.tsx @@ -198,8 +198,6 @@ export class FlashcardReviewView { this._resetResponseButtons(); } - // -> Functions & helpers - private _keydownHandler = (e: KeyboardEvent) => { // Prevents any input, if the edit modal is open if ( @@ -387,11 +385,11 @@ export class FlashcardReviewView { private _setTitle(deck: Deck) { const isRandomMode = this.settings.flashcardCardOrder === "EveryCardRandomDeckAndCard"; let text = deck.deckName; - if (!isRandomMode) { - const deckStats = this.reviewSequencer.getDeckStats(deck.getTopicPath()); - const cardsInQueue = deckStats.dueCount + deckStats.newCount; - text += `: ${cardsInQueue}`; - } + + const deckStats = this.reviewSequencer.getDeckStats(deck.getTopicPath()); + const cardsInQueue = deckStats.dueCount + deckStats.newCount; + text += `: ${cardsInQueue}`; + this.title.setText(text); } From 0edead18a56e29ab5bb2e593a6fc6c79f655406a Mon Sep 17 00:00:00 2001 From: KyleKlus Date: Fri, 10 May 2024 14:32:54 +0200 Subject: [PATCH 3/7] UPDATE: Documented changes & formatted with prettier --- CHANGELOG.md | 2 +- CONTRIBUTING.md | 2 +- docs/changelog.md | 4 ++++ src/gui/FlashcardModal.tsx | 2 +- styles.css | 2 +- 5 files changed, 8 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1bed66b3..1cc39a66 120000 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1 +1 @@ -docs/changelog.md \ No newline at end of file +docs/changelog.md diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 651dc17d..9815d5bd 120000 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1 +1 @@ -docs/en/contributing.md \ No newline at end of file +docs/en/contributing.md diff --git a/docs/changelog.md b/docs/changelog.md index a740e670..5c123810 100644 --- a/docs/changelog.md +++ b/docs/changelog.md @@ -4,6 +4,10 @@ All notable changes to this project will be documented in this file. Dates are d Generated by [`auto-changelog`](https://github.com/CookPete/auto-changelog). +#### [Unreleased] + +- Extended card title functionality partly as described in issue [`#851`](https://github.com/st3v3nmw/obsidian-spaced-repetition/issues/851) + #### [1.12.4](https://github.com/st3v3nmw/obsidian-spaced-repetition/compare/1.12.3...1.12.4) - chore: fix package manager issue in CI [`#939`](https://github.com/st3v3nmw/obsidian-spaced-repetition/pull/939) diff --git a/src/gui/FlashcardModal.tsx b/src/gui/FlashcardModal.tsx index 27e81fdf..1d037ffe 100644 --- a/src/gui/FlashcardModal.tsx +++ b/src/gui/FlashcardModal.tsx @@ -94,7 +94,7 @@ export class FlashcardModal extends Modal { this.deckView.hide(); } - private _showFlashcard(deck:Deck): void { + private _showFlashcard(deck: Deck): void { this._hideDecksList(); this.flashcardView.show(deck); } diff --git a/styles.css b/styles.css index ca18adca..05072293 100644 --- a/styles.css +++ b/styles.css @@ -289,4 +289,4 @@ body:not(.native-scrollbars) #sr-modal .modal-close-button { #sr-chart-period { appearance: menulist; border-right: 8px solid transparent; -} \ No newline at end of file +} From eabfb010d7b1a1da47f091a24cd840ce9c7be3c8 Mon Sep 17 00:00:00 2001 From: Kyle Klus Date: Mon, 22 Jul 2024 09:28:59 +0200 Subject: [PATCH 4/7] Squashed commit of the following: commit 971e4af526e6acb1a3caef7aad07a82d6f5f23f5 Author: Anna Zubenko Date: Mon Jul 22 07:29:18 2024 +0200 FEAT-990 Mobile landscape mode and functional size sliders (#998) commit a89a81853ae1d2e69db9c078be472715e7480309 Author: 4Source <38220764+4Source@users.noreply.github.com> Date: Mon Jul 22 07:25:45 2024 +0200 [FIX] Cards missing when horizontal rule present in document (#970) * Use obsidians funtion to extractFrontmatter * Fix line pos shift * Stop using obsidian function because of UnitTest * Add UnitTest for Frontmatter and Horizontal line * Fix linting --------- Co-authored-by: Stephen Mwangi commit 77f15e14edf434716ad7c878ec97a64b03eb93de Author: Carlos Galdino Date: Mon Jul 22 06:02:11 2024 +0100 Filter due notes when all are scheduled (#947) Ignore notes due in the future. Fixes https://github.com/st3v3nmw/obsidian-spaced-repetition/issues/548 commit 83590be260a857bab9c1b4ce328dd8b96ea46e6b Author: Newdea <9208450+Newdea@users.noreply.github.com> Date: Sat Jul 20 15:10:57 2024 +0800 fix #1000 sidebar indent (#1001) commit e3a014650bc70add9093cd12283a630b635b47c8 Author: 4Source <38220764+4Source@users.noreply.github.com> Date: Sat Jul 20 09:02:14 2024 +0200 [FIX] Include link parsing for Review context (#964) * Include link parsing * Fix linting commit 3024264bb5d6e8a9d74192f2aa96226fabf69642 Author: 4Source <38220764+4Source@users.noreply.github.com> Date: Sat Jul 20 09:00:46 2024 +0200 [FIX] Not scroll back to top after review (#971) * Update deprecated function * Add scroll back to top * Fix linting commit 1dfd52e225a9a4618d9c75f66ef27086ec4fa757 Author: 4Source <38220764+4Source@users.noreply.github.com> Date: Sat Jul 20 09:00:33 2024 +0200 [FIX] Folder ignore sorts all folder starting with string (#972) * Implement isEqualOrSubPath * Implement UnitTest for isEqualOrSubPath * Replace separators with system seperator * Improved seperator replacement commit b175d22dcec1ccbb547440309e44f9a3a13912ea Author: artificialUsagi <41666068+artefaritaKuniklo@users.noreply.github.com> Date: Sat Jul 20 14:59:11 2024 +0800 add translation: zh-cn (#982) commit ca4c2617770f047472fd30fe242f4520eb635b32 Author: 4Source <38220764+4Source@users.noreply.github.com> Date: Sat May 25 10:13:50 2024 +0200 Improved issue templates (#963) * Replace bug_report with form instead of template * Replace feature_request with form instead of template * Fix linting commit 053da6ac316a914281eec7768410828bc7682315 Author: 4Source <38220764+4Source@users.noreply.github.com> Date: Fri May 24 19:59:06 2024 +0200 Update German localization (#961) * Update German localization * Fix linting --- .github/ISSUE_TEMPLATE/bug_report.md | 34 --- .github/ISSUE_TEMPLATE/bug_report.yml | 91 +++++++ .github/ISSUE_TEMPLATE/feature_request.md | 16 -- .github/ISSUE_TEMPLATE/feature_request.yml | 30 +++ docs/changelog.md | 3 +- docs/zh/algorithms.md | 30 +++ docs/zh/contributing.md | 156 ++++++++++++ docs/zh/flashcards.md | 240 ++++++++++++++++++ docs/zh/index.md | 53 ++++ docs/zh/notes.md | 71 ++++++ mkdocs.yml | 1 + src/gui/FlashcardModal.tsx | 2 + src/gui/FlashcardReviewView.tsx | 16 +- src/gui/Sidebar.tsx | 26 +- src/lang/locale/de.ts | 94 +++---- src/main.ts | 17 +- src/util/RenderMarkdownWrapper.ts | 2 +- src/util/utils.ts | 101 +++++--- styles.css | 42 ++- tests/unit/util/utils.test.ts | 281 +++++++++++++++++++++ 20 files changed, 1149 insertions(+), 157 deletions(-) delete mode 100644 .github/ISSUE_TEMPLATE/bug_report.md create mode 100644 .github/ISSUE_TEMPLATE/bug_report.yml delete mode 100644 .github/ISSUE_TEMPLATE/feature_request.md create mode 100644 .github/ISSUE_TEMPLATE/feature_request.yml create mode 100644 docs/zh/algorithms.md create mode 100644 docs/zh/contributing.md create mode 100644 docs/zh/flashcards.md create mode 100644 docs/zh/index.md create mode 100644 docs/zh/notes.md diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md deleted file mode 100644 index b8afae53..00000000 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ /dev/null @@ -1,34 +0,0 @@ ---- -name: Bug report -about: Create a bug report to help us improve the plugin. -title: "[BUG]" -labels: bug -assignees: "" ---- - -**Describe the bug** -A clear and concise description of what the bug is. - -**To Reproduce** -Steps to reproduce the behavior: - -1. Go to '...' -2. Click on '....' -3. Scroll down to '....' -4. See error - -**Expected behavior** -A clear and concise description of what you expected to happen. - -**Screenshots** -If applicable, add screenshots to help explain the bug. This could be the logs in the developer console (`CTRL` + `SHIFT` + `i`). - -**Versions (please complete the following information):** - -- OS: [e.g. iOS] -- Obsidian version: [e.g. v0.12.4] -- Plugin version: [e.g. v1.4.9] -- If on desktop, Installer version: [run `this.navigator.appVersion` on the developer console, e.g. `5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) obsidian/0.12.3 Chrome/89.0.4389.128 Electron/12.0.6 Safari/537.36"`] - -**Additional context** -Add any other context about the problem here (e.g. the markdown producing the error). diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml new file mode 100644 index 00000000..c5fefeba --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -0,0 +1,91 @@ +name: Bug report +description: Create a bug report to help us improve the plugin. +title: "[Bug]: " +labels: ["bug"] + +body: + - type: textarea + id: bug-description + attributes: + label: Describe the bug + description: A clear and concise description of what the bug is. + placeholder: Tell us what happened that you didn't expect [...] + validations: + required: true + - type: textarea + id: bug-reproduce-steps + attributes: + label: To Reproduce + description: Steps to reproduce the behavior + placeholder: | + 1. Go to '...' + 2. Click on '...' + 3. Scroll down to '...' + 4. See error" + validations: + required: true + - type: textarea + id: expected-behavior + attributes: + label: Expected behavior + description: A clear and concise description of what you expected to happen. + placeholder: Tell us what you think should have happened... + validations: + required: true + - type: textarea + id: screenshots + attributes: + label: Screenshots + description: If applicable, add screenshots to help explain the bug. This could be the logs in the developer console (`CTRL` + `SHIFT` + `i`). + placeholder: Paste image here + validations: + required: false + - type: input + id: operating-system + attributes: + label: OS of your device + placeholder: e.g. Win10 + validations: + required: true + - type: input + id: obsidian-version + attributes: + label: Obsidian version + placeholder: e.g. v0.12.4 + validations: + required: true + - type: input + id: plugin-version + attributes: + label: Plugin version + placeholder: "e.g. v1.4.9" + validations: + required: true + - type: input + id: installer-version + attributes: + label: Installer version + description: If on desktop [run `this.navigator.appVersion` on the developer console, e.g. `5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) obsidian/0.12.3 Chrome/89.0.4389.128 Electron/12.0.6 Safari/537.36"`] + placeholder: "e.g. v1.4.9" + validations: + required: false + - type: textarea + id: context + attributes: + label: Additional context + description: Add any other context about the problem here (e.g. the markdown producing the error). + placeholder: Explain any additional context [...] + validations: + required: false + - type: textarea + id: config-files + attributes: + label: Config file + description: | + Drag & drop your config file here [VAULT/.obsidian/plugins/obsidian-spaced-repetition/data.json] + Drag & drop your violated file containing your error here + placeholder: | + data.json + violated_file.md + validations: + required: false diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md deleted file mode 100644 index 4f153ba0..00000000 --- a/.github/ISSUE_TEMPLATE/feature_request.md +++ /dev/null @@ -1,16 +0,0 @@ ---- -name: Feature request -about: Suggest an idea for this plugin. -title: "[FEAT]" -labels: enhancement -assignees: "" ---- - -**Is your feature request related to a problem? Please describe.** -A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] - -**Describe the solution you'd like** -A clear and concise description of what you want to happen. - -**Additional context** -Add any other context or screenshots about the feature request here. diff --git a/.github/ISSUE_TEMPLATE/feature_request.yml b/.github/ISSUE_TEMPLATE/feature_request.yml new file mode 100644 index 00000000..79a8dfa0 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature_request.yml @@ -0,0 +1,30 @@ +name: Feature request +description: Suggest an idea for this plugin. +title: "[FEAT]: " +labels: ["enhancement"] + +body: + - type: textarea + id: problem-description + attributes: + label: Is your feature request related to a problem? Please describe. + description: A clear and concise description of what the problem is. + placeholder: I'm always frustrated when [...] + validations: + required: false + - type: textarea + id: solution-description + attributes: + label: Describe the solution you'd like + description: A clear and concise description of what you want to happen. + placeholder: I wanted that [...] + validations: + required: true + - type: textarea + id: context + attributes: + label: Additional context + description: Add any other context or screenshots about the feature request here. + placeholder: Explain any additional context [...] + validations: + required: false diff --git a/docs/changelog.md b/docs/changelog.md index 5c123810..1f0a2d94 100644 --- a/docs/changelog.md +++ b/docs/changelog.md @@ -4,8 +4,9 @@ All notable changes to this project will be documented in this file. Dates are d Generated by [`auto-changelog`](https://github.com/CookPete/auto-changelog). -#### [Unreleased] +[Unreleased] +- Fixed notes selection when all notes are reviewed. [`#548`](https://github.com/st3v3nmw/obsidian-spaced-repetition/issues/548) - Extended card title functionality partly as described in issue [`#851`](https://github.com/st3v3nmw/obsidian-spaced-repetition/issues/851) #### [1.12.4](https://github.com/st3v3nmw/obsidian-spaced-repetition/compare/1.12.3...1.12.4) diff --git a/docs/zh/algorithms.md b/docs/zh/algorithms.md new file mode 100644 index 00000000..81552691 --- /dev/null +++ b/docs/zh/algorithms.md @@ -0,0 +1,30 @@ +# 算法 + +## SM-2 + +!!! 警告 + + 该条目长时间未更新, + 请注意阅读 [源代码](https://github.com/st3v3nmw/obsidian-spaced-repetition/blob/master/src/scheduling.ts). + +(除 PageRanks 之外,卡片复习采用相同规划算法) + +- 该算法为 [Anki](https://faqs.ankiweb.net/what-spaced-repetition-algorithm.html) 所采用的基于 [SM-2 算法](https://www.supermemo.com/en/archives1990-2015/english/ol/sm2) 的变种。 +- 使用三级打分制,即在复习阶段自评对某个概念的掌握程度为`困难`,`记得`或`简单`。 +- 初始熟练度会根据链接笔记的平均熟练度、当前笔记的重要性和基本熟练度进行加权(使用 最大外链因子)。 + - `当存在外链时: 初始熟练度 = (1 - 链接加权) * 基础熟练度 + 链接加权 * 外链平均熟练度` + - `链接加权 = 最大外链因子 * min(1.0, log(外链数目 + 0.5) / log(64))` (以自适应不同情况) + - 不同概念/笔记的优先级由 PageRank 算法设定(笔记之间存在轻重缓急) + - 大多数情况下基础概念/笔记具有更高优先级 +- 当用户对某个概念/笔记的自评为: + - 简单, 熟练度增加 `20` 复习间隔更新为 `原复习间隔 * 更新后熟练度 / 100 * 1.3` (1.3 是简单奖励) + - 记得, 熟练度不变,复习间隔更新为 `原复习间隔 * old_ease / 100` + - 困难, 熟练度降低 `20`,复习间隔更新为 `原复习间隔 * 0.5` + - `0.5` 可在设置中更改 + - `最小熟练度 = 130` + - 当复习间隔不小于 `8` 天时 + - `间隔 += 随机取值({-扰动, 0, +扰动})` + - 设定 `扰动 = 向上取整(0.05 * 间隔)` + - [Anki 文档](https://faqs.ankiweb.net/what-spaced-repetition-algorithm.html): + > "[...] Anki 还会加入少量的随机扰动,以防止同时出现且评级相同的卡片获得相同的复习周期,导致其它们是在同一天被复习。" +- 复习规划信息将被存储于笔记的yaml front matter部分 diff --git a/docs/zh/contributing.md b/docs/zh/contributing.md new file mode 100644 index 00000000..bc89ca07 --- /dev/null +++ b/docs/zh/contributing.md @@ -0,0 +1,156 @@ +# Contributing + +First off, thanks for wanting to contribute to the Spaced Repetition plugin! + +## Bug Reports & Feature Requests + +- Check the [roadmap](https://github.com/st3v3nmw/obsidian-spaced-repetition/projects/2/) for upcoming features & fixes. +- Raise an issue [here](https://github.com/st3v3nmw/obsidian-spaced-repetition/issues/) if you have a feature request or a bug report. +- Visit the [discussions](https://github.com/st3v3nmw/obsidian-spaced-repetition/discussions/) section for Q&A help, feedback, and general discussion. + +## Translating + +### Steps + +To help translate the plugin to your language: + +1. Fork the [repository](https://github.com/st3v3nmw/obsidian-spaced-repetition). +2. Copy the entries from `src/lang/locale/en.ts` to the proper file in `src/lang/locale/` (i.e. `fr.ts` for French, or `sw.ts` for Swahili). The locale codes are [IETF language tags](https://en.wikipedia.org/wiki/IETF_language_tag). +3. Translate, +4. Then open a pull request, + +### Example + +Sample `en.ts` file: + +```typescript +// English + +export default { + EASY: "Easy", + SHOW_ANSWER: "Show Answer", + DAYS_STR_IVL: "${interval} days", + CHECK_ALGORITHM_WIKI: + 'For more information, check the algorithm implementation.', +}; +``` + +Equivalent `sw.ts` file: + +```typescript +// Swahili + +export default { + EASY: "Rahisi", + SHOW_ANSWER: "Onyesha Jibu", + DAYS_STR_IVL: "Siku ${interval}", + CHECK_ALGORITHM_WIKI: + 'Kwa habari zaidi, angalia utekelezaji wa algorithm.', +}; +``` + +A part of that last one is uhh, Google translated, I have a working understanding of Swahili but not enough to write computerese lol. + +Please note that: + +1. Only the strings(templates) on the right of the key should be translated. +2. Text inside `${}` isn't translated. This is used to replace variables in code. For instance, if interval = 4, it becomes `4 days` in English & `Siku 4` in Swahili. Quite nifty if you ask me. + +## Code + +1. Make your changes. +2. Run `pnpm dev` to test the changes inside Obsidian. +3. You could create symbolic links between the build files and the Obsidian vault, example: + + ```bash + # remove existing files in the Obsidian vault + rm ~/notes/.obsidian/plugins/obsidian-spaced-repetition/main.js ~/notes/.obsidian/plugins/obsidian-spaced-repetition/manifest.json ~/notes/.obsidian/plugins/obsidian-spaced-repetition/styles.css + # use absolute paths + ln -s /home/stephen/obsidian-spaced-repetition/build/main.js /home/stephen/notes/.obsidian/plugins/obsidian-spaced-repetition + ln -s /home/stephen/obsidian-spaced-repetition/manifest.json /home/stephen/notes/.obsidian/plugins/obsidian-spaced-repetition + ln -s /home/stephen/obsidian-spaced-repetition/styles.css /home/stephen/notes/.obsidian/plugins/obsidian-spaced-repetition + ``` + + - This can be coupled with the [Hot Reload plugin](https://github.com/pjeby/hot-reload) + +4. Document the "user-facing" changes e.g. new feature, UI change, etc. +5. If your "business logic" is properly decoupled from Obsidian APIs, write some unit tests. + - This project uses [jest](https://jestjs.io/), tests are stored in `tests/`. + - `pnpm test` +6. Add your change to the `[Unreleased]` section of the changelog (`docs/changelog.md`). + - The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), TL;DR: + - `Added` for new features. + - `Changed` for changes in existing functionality. + - `Deprecated` for soon-to-be removed features. + - `Removed` for now removed features. + - `Fixed` for any bug fixes. + - `Security` in case of vulnerabilities. + - You can also append a link to your GitHub profile, example: + - `Make flashcard text selectable [@st3v3nmw](https://github.com/st3v3nmw)` +7. Before pushing your changes, run the linter: `pnpm lint` + - Format the code in case any warnings are raised: `pnpm format` +8. Open the pull request. + +## Documentation + +The documentation consists of Markdown files which [MkDocs](https://www.mkdocs.org/) converts to static web pages. +Specifically, this project uses [MkDocs Material](https://squidfunk.github.io/mkdocs-material/getting-started/). + +These files reside in `docs/` in the respective language's folder. For instance, English docs are located in `docs/en/`. + +The docs are served on [https://www.stephenmwangi.com/obsidian-spaced-repetition/](https://www.stephenmwangi.com/obsidian-spaced-repetition/). + +For small changes, you can simply open an pull request for merging (against the `master` branch). +The changes will be live once a new [release](https://github.com/st3v3nmw/obsidian-spaced-repetition/releases) is made. + +For larger diffs, it's important that you check how your docs look like as explained below. + +### Viewing Docs Locally + +#### Initial Setup + +1. Create a virtual environment: `python3 -m venv venv` +2. Activate it: `. venv/bin/activate` +3. Install the required dependencies: `pip install -r requirements.txt` + +#### Viewing + +1. Activate the virtual environment: `. venv/bin/activate` +2. Serve the docs: `mkdocs serve` +3. View your documentation locally on [http://127.0.0.1:8000/obsidian-spaced-repetition/](http://127.0.0.1:8000/obsidian-spaced-repetition/), any changes you make will reflect on the browser instantly. + +### Translating Documentation + +1. Create a folder for your language in `docs/` if it doesn't exist. Use the language codes provided [here](https://squidfunk.github.io/mkdocs-material/setup/changing-the-language/#site-language). +2. Add the code from (1) to the MkDocs configuration (`mkdocs.yml` - `plugins.i18n.languages`). +3. Copy the files from the English (`en`) folder into the new folder. +4. Translate then open a pull request. + +## Maintenance + +### Releases + +Example using `v1.9.2`: + +1. Create a new branch: `git switch -c release-v1.9.2` +2. Bump the plugin version in `manifest.json` and `package.json` (following [Semantic Versioning](https://semver.org/spec/v2.0.0.html)). + - Semantic Versioning TL;DR, given a version number `MAJOR.MINOR.PATCH`, increment the: + - `MAJOR` version when you make incompatible API changes + - `MINOR` version when you add functionality in a backwards compatible manner + - `PATCH` version when you make backwards compatible bug fixes + - If the new version uses new Obsidian APIs, update `minAppVersion` and `versions.json` to reflect this. +3. Run `pnpm changelog` to update the CHANGELOG. +4. Commit and push the changes: + + ```bash + git add . + git commit -m "Bump version to v1.9.2" + git push --set-upstream origin release-v1.9.2 + ``` + +5. Open and merge the PR into `master`. +6. Locally, switch back to `master` and pull the changes: `git switch master && git pull` +7. Create a git tag with the version: `git tag 1.9.2` +8. Push the tag: `git push --tags`.
You're all set! [This GitHub action](https://github.com/st3v3nmw/obsidian-spaced-repetition/blob/master/.github/workflows/release.yml) should pick it up, create a release, publish it, and update the live documentation. + +[^1]: Check the Obsidian Tasks project which has [excellent contribution guidelines](https://github.com/obsidian-tasks-group/obsidian-tasks/blob/main/CONTRIBUTING.md). diff --git a/docs/zh/flashcards.md b/docs/zh/flashcards.md new file mode 100644 index 00000000..273293f8 --- /dev/null +++ b/docs/zh/flashcards.md @@ -0,0 +1,240 @@ +# 卡片 + +## 新建卡片 + +[Piotr Wozniak的知识标准化二十守则](https://supermemo.guru/wiki/20_rules_of_knowledge_formulation) 是创建复习卡片时的良好入门指南。 + +### 单行基础卡片 (Remnote风格) + +问题和答案以 `::` 分隔(可在设置中更改)。 + +```markdown +这是问题::这是答案 +``` + +### 单行双向卡片 + +创建 `正:::反` 与其反向卡片 `反:::正`. + +问题和答案以 `:::` 分隔(可在设置中更改)。 + +```markdown +这是问题:::这是答案 +``` + +注意:初次复习时插件会同时展示正向和反向卡片。 +如果打开 **将关联卡片隐藏至下一天?** 将仅展示正向卡片。 + +### 多行基础卡片 + +卡片的正反面以 `?` 分隔(可在设置中更改)。 + +```markdown +多行卡片的正面 +? +多行卡片的反面 +``` + +只要正反面字段都在 `?` 的作用域内,卡片内容可以跨越多行: + +```markdown +顾名思义 +多行卡片的内容 +可以跨越多行 +? +这也包括 +卡片的反面 +``` + +### 多行双向卡片 + +创建 `正??反` 与其反向卡片 `反??正`. + +卡片的正反面以 `??` 分隔(可在设置中更改)。 + +```markdown +多行卡片的正面 +?? +多行卡片的反面 +``` + +只要正反面字段都在 `??` 的作用域内,卡片内容可以跨越多行: + +```markdown +顾名思义 +多行卡片的内容 +可以跨越多行 +?? +这也包括 +卡片的反面 +``` + +注意:其隐藏机制同单行双向卡片 + +### 填空卡片 + +你可以轻松使用 `==高亮==` ,`**加粗**` ,或 `{{花括号}}` 创建挖空卡片. + +该特性可在设置中开关。 + +暂不支持 Anki 风格的 `{{c1:This text}} would {{c2:generate}} {{c1:2 cards}}` 挖空语法。该特性正在 [计划中](https://github.com/st3v3nmw/obsidian-spaced-repetition/issues/93/)。 + +## 卡组 + +![Screenshot from 2021-06-05 19-28-24](https://user-images.githubusercontent.com/43380836/120922211-78603400-c6d0-11eb-9d09-bdd5df1c9112.png) + +卡组名称右边的绿色和蓝色数字分别表示到期卡片和新卡片数目。 + +### 使用 Obsidian 标签 + +1. 在设置中设定制卡标签 (默认为 `#flashcards`)。 +2. 将您想要制卡的笔记打上该标签。 + +#### 标签层级 + +注意 `#flashcards` 可以匹配嵌套标签例如 `#flashcards/subdeck/subdeck`. + +#### 单个文件包含多个标签 + +单一文件中可以包含不同卡组的多个卡片内容。 + +这是因为一个标签的作用域直到下一个标签出现才会结束。 + +例如: + +```markdown +#flashcards/deckA +Question1 (in deckA)::Answer1 +Question2 (also in deckA)::Answer2 +Question3 (also in deckA)::Answer3 + +#flashcards/deckB +Question4 (in deckB)::Answer4 +Question5 (also in deckB)::Answer5 + +#flashcards/deckC +Question6 (in deckC)::Answer6 +``` + +#### 多个卡组包含同一卡片 + +通常情况下一张卡片只会出现在一个卡组。然而某些时候,一张卡片无法被恰当地归入单一卡组的层级结构中。 + +这种情况下,卡片可以被标记为归属为多个卡组。比如下面这张卡片属于三个卡组。 + +```markdown +#flashcards/language/words #flashcards/trivia #flashcards/learned-from-tv +A group of cats is called a::clowder +``` + +注意,在上面的例子中所有标签必须位于一行并以空格隔开。 + +#### 作用于特定问答的卡片 + +位于卡片内容同一行开头处的标签是「仅限当前问答」的。 + +例如: + +```markdown +#flashcards/deckA +Question1 (in deckA)::Answer1 +Question2 (also in deckA)::Answer2 +Question3 (also in deckA)::Answer3 + +#flashcards/deckB Question4 (in deckB)::Answer4 + +Question6 (in deckA)::Answer6 +``` + +此处 `Question6` 将出现在 `deckA` 但不会出现于 `deckB` 因 `deckB` 是仅作用于 `Question4` 的标签。 + +### 使用目录结构 + +插件将自动遍历目录结构并依次创建卡组和子卡组,例如 `Folder/sub-folder/sub-sub-folder` ⇔ `Deck/sub-deck/sub-sub-deck`。 + +这是使用标签指定卡组的替代方案,可以在设置中打开。 + +## 复习 + +制卡完成后即可在左边栏中点击图标开始复习。当一张卡片被复习后,将会被附上一个包含下一次复习时间、复习间隔和熟练度的HTML注释。 + +``` + +``` + +HTML注释在笔记预览页面中不可见。对于单行卡片,你可以在设置中选择让这个标签位于同一行还是另起一行。放置在同一行可以防止破坏markdown的列表渲染效果。 + +注意您可以按 `S` 跳过一张卡片(大小写不敏感)。 + +!!! 提示 + + 如果您在移动设备上遇到了悬浮框尺寸的问题,进入设置并将 _Flashcard Height Percentage_ 和 _Flashcard Width Percentage_ + 设为 100% 以适应屏幕。 + +### 快速复习 + +你可以在快速复习中使用如下快捷键: + +- `Space/Enter` => 显示答案 +- `0` => 重置进度 (等价于 Anki 中的 `Again`) +- `1` => 标记为 `Hard` +- `2` 或 `Space` => 标记为 `Good` +- `3` => 标记为 `Easy` + +### 上下文 + +如用于制卡的部分位于笔记标题之下,则卡片中会附加一个上下文标题。 + +例如: + +```markdown +#flashcards + +# Trivia + +## Capitals + +### Africa + +Kenya::Nairobi + +### North America + +Canada::Ottawa +``` + +卡片 `Kenya::Nairobi` 将会被附上 `Trivia > Capitals > Africa` 作为上下文标题而卡片 `Canada::Ottawa` 将会被附上 `Trivia > Capitals > North America` 作为上下文标题。 + +### 删除卡片 + +要删除一个卡片,只需删除复习规划标签和卡片相关文本。 + +### 忽略卡片 + +你可以使用诸如 ` -->` 的HTML标签来将其从复习队列中移除。你可以随时移除该标签。 + +## 集中复习 + +当前仅支持使用 集中复习此笔记中的卡片 命令。将复习所有卡组中来自该笔记的卡片。 + +## 数据统计 + +统计页面可以使用 `View Statistics` 命令打开。 + +### 预估 + +计算将要到期的卡片数量。 + + + +### 复习间隔 + +统计卡片再次出现的时间间隔。 + +### 熟练度 + +统计卡片熟练度。 + +### 卡片类型 + +统计卡片类型:新卡片,较新卡片, 熟悉卡片(复习间隔超过一个月)。 diff --git a/docs/zh/index.md b/docs/zh/index.md new file mode 100644 index 00000000..d27ab167 --- /dev/null +++ b/docs/zh/index.md @@ -0,0 +1,53 @@ +# Obsidian Spaced Repetition + + + +Fight the forgetting curve & note aging by reviewing flashcards & notes using spaced repetition on Obsidian.md + +- 阅读 [文档](https://www.stephenmwangi.com/obsidian-spaced-repetition/). +- 查看新特性和故障修复 [规划](https://github.com/st3v3nmw/obsidian-spaced-repetition/projects/2/) +- 如果您有新建议或故障报告,请提出 [请求](https://github.com/st3v3nmw/obsidian-spaced-repetition/issues/) +- 访问 [讨论](https://github.com/st3v3nmw/obsidian-spaced-repetition/discussions/) 板块以获取问答帮助,意见反馈 +- 感谢Obsidian社区 😄 的贡献,本插件已支持 _Arabic / العربية, Chinese (Simplified) / 简体中文, Chinese (Traditional) / 繁體中文, Czech / čeština, German / Deutsch, Italian / Italiano, Korean / 한국어, Japanese / 日本語, Polish / Polski, Portuguese (Brazil) / Português do Brasil, Spanish / Español, and Russian / русский_ + - 如果您愿意提供翻译上的帮助,请阅读 [翻译指南](https://www.stephenmwangi.com/obsidian-spaced-repetition/contributing/#translating_1). + +## 视频演示 + + + +## 安装 + +在Obsidian搜索社区插件-`Spaced Repetition`. + +### 手动安装 + +在您的Obsidian仓库中的 `.obsidian/plugins` 下创建 `obsidian-spaced-repetition` 目录。将 [最新发布的软件包](https://github.com/st3v3nmw/obsidian-spaced-repetition/releases) 解压并移动 `main.js`, `manifest.json` 和 `styles.css` 到该目录下。 + +## 相关资源 + +### YouTube 教程 + +#### 卡片 + +- [PRODUCTIVELY Learning New Things Using Obsidian by @FromSergio](https://youtu.be/DwSNZEW6jCU) + +#### 笔记 + +##### 渐进式写作 + +- [Obsidian: inbox review with spaced repetition by @aviskase](https://youtu.be/zG5r7QIY_TM) +- [Разгребатель инбокса заметок как у Andy Matuschak в Obsidian by @YuliyaBagriy_ru](https://youtu.be/CF6SSHB74cs) + +### 间隔重复系统 + +- [How to Remember Anything Forever-Ish by Nicky Case](https://ncase.me/remember/) +- [Spaced Repetition for Efficient Learning by Gwern](https://www.gwern.net/Spaced-repetition/) +- [20 rules of knowledge formulation by Dr. Piotr Wozniak](https://supermemo.guru/wiki/20_rules_of_knowledge_formulation) + +### 赞助 + +Buy Me a Coffee at ko-fi.com + +JetBrains Logo (Main) logo. diff --git a/docs/zh/notes.md b/docs/zh/notes.md new file mode 100644 index 00000000..fb9ad808 --- /dev/null +++ b/docs/zh/notes.md @@ -0,0 +1,71 @@ +# 笔记 + +- 笔记应当具有原子性:说清楚**一个**概念; +- 笔记之间应当高度关联; +- 先理解,后复习; +- 善用 [费曼学习法](https://fs.blog/2021/02/feynman-learning-technique/) + +## 开始使用 + +为需要复习的笔记添加 `#review` 标签。你可以在插件设置中修改此默认标签(也可以使用多个标签) + +## 新笔记 + +新笔记将展示在右栏的 `新` (复习序列)中,如图: + + + +## 复习 + +打开笔记即可复习。在菜单中选择 `复习: 简单`,`复习: 记得` 或 `复习: 较难`。 选择 `简单`,`记得` 还是 `较难` 取决于你对复习材料的理解程度。 + + + +在文件上右击可以调出相同选项: + + + +笔记将被添加到复习队列中: + + + +### 快速复习 + +我们提供快速进入复习模式的命令。你可以在 `设置 -> 快捷键` 中定制快捷键。这可以使您直接开始复习。 + +### 复习设置 + +可供定制的选项包括: + +- 随机打开笔记或按照优先级排序 +- 在复习完成后是否自动打开下一个笔记 + +## 复习计划 + +位于状态栏底部的 `复习: N 卡片已到期` 显示您今天需要复习的卡片数目(今日卡片 + 逾期卡片)。点击可打开一张卡片开始复习。 + +您也可以使用 `打开一个笔记开始复习` 命令。 + +## 复习序列 + +- 每日复习条目将按照优先级排序 (PageRank) + +## 渐进式写作 + +阅读 `@aviskase` 的 [介绍](https://github.com/st3v3nmw/obsidian-spaced-repetition/issues/15) + +视频资源: + +- 英文: [Obsidian: inbox review with spaced repetition](https://youtu.be/zG5r7QIY_TM) +- 俄文: [Yuliya Bagriy - Разгребатель инбокса заметок как у Andy Matuschak в Obsidian](https://www.youtube.com/watch?v=CF6SSHB74cs) + +### 概要 + +Andy Matuschak 在 [写作素材库中引入间隔重复系统](https://notes.andymatuschak.org/z7iCjRziX6V6unNWL81yc2dJicpRw2Cpp9MfQ). + +简而言之,可以进行四种操作 (此处 `x < y`): + +- 跳过笔记 (增加 `x` 天的复习间隔) == 标记为 `记得` +- 已阅,觉得有用 (降低复习间隔) == 标记为 `较难` +- 已阅,觉得没用 (增加 `y` 天的复习间隔) == 标记为 `简单` +- 转换为 evergreen 笔记 (中止使用间隔重复系统) diff --git a/mkdocs.yml b/mkdocs.yml index 375c0f56..6537cdbd 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -42,6 +42,7 @@ plugins: docs_structure: folder languages: en: English + zh: 简体中文 default_language: en markdown_extensions: diff --git a/src/gui/FlashcardModal.tsx b/src/gui/FlashcardModal.tsx index 1d037ffe..3ee3a712 100644 --- a/src/gui/FlashcardModal.tsx +++ b/src/gui/FlashcardModal.tsx @@ -48,7 +48,9 @@ export class FlashcardModal extends Modal { // Setup base containers this.modalEl.style.height = this.settings.flashcardHeightPercentage + "%"; + this.modalEl.style.maxHeight = this.settings.flashcardHeightPercentage + "%"; this.modalEl.style.width = this.settings.flashcardWidthPercentage + "%"; + this.modalEl.style.maxWidth = this.settings.flashcardWidthPercentage + "%"; this.modalEl.setAttribute("id", "sr-modal"); this.contentEl.addClass("sr-modal-content"); diff --git a/src/gui/FlashcardReviewView.tsx b/src/gui/FlashcardReviewView.tsx index a7143006..fb652b19 100644 --- a/src/gui/FlashcardReviewView.tsx +++ b/src/gui/FlashcardReviewView.tsx @@ -193,6 +193,8 @@ export class FlashcardReviewView { this._currentNote.filePath, ); await wrapper.renderMarkdownWrapper(this._currentCard.front, this.content); + // Set scroll position back to top + this.content.scrollTop = 0; // Setup response buttons this._resetResponseButtons(); @@ -364,9 +366,17 @@ export class FlashcardReviewView { private _formatQuestionContextText(questionContext: string[]): string { const separator: string = " > "; let result = this._currentNote.file.basename; - if (questionContext.length > 0) { - result += separator + questionContext.join(separator); - } + questionContext.forEach((context) => { + // Check for links trim [[ ]] + if (context.startsWith("[[") && context.endsWith("]]")) { + context = context.replace("[[", "").replace("]]", ""); + // Use replacement text if any + if (context.contains("|")) { + context = context.split("|")[1]; + } + } + result += separator + context; + }); return result + separator + "..."; } diff --git a/src/gui/Sidebar.tsx b/src/gui/Sidebar.tsx index be88025a..9e878844 100644 --- a/src/gui/Sidebar.tsx +++ b/src/gui/Sidebar.tsx @@ -43,8 +43,8 @@ export class ReviewQueueListView extends ItemView { public redraw(): void { const activeFile: TFile | null = this.app.workspace.getActiveFile(); - const rootEl: HTMLElement = createDiv("nav-folder mod-root"); - const childrenEl: HTMLElement = rootEl.createDiv("nav-folder-children"); + const rootEl: HTMLElement = createDiv(); + const childrenEl: HTMLElement = rootEl; for (const deckKey in this.plugin.reviewDecks) { const deck: ReviewDeck = this.plugin.reviewDecks[deckKey]; @@ -57,7 +57,7 @@ export class ReviewQueueListView extends ItemView { deckCollapsed, false, deck, - ).getElementsByClassName("nav-folder-children")[0] as HTMLElement; + ).getElementsByClassName("tree-item-children")[0] as HTMLElement; if (deck.newNotes.length > 0) { const newNotesFolderEl: HTMLElement = this.createRightPaneFolder( @@ -154,11 +154,11 @@ export class ReviewQueueListView extends ItemView { hidden: boolean, deck: ReviewDeck, ): HTMLElement { - const folderEl: HTMLDivElement = parentEl.createDiv("nav-folder"); - const folderTitleEl: HTMLDivElement = folderEl.createDiv("nav-folder-title"); - const childrenEl: HTMLDivElement = folderEl.createDiv("nav-folder-children"); + const folderEl: HTMLDivElement = parentEl.createDiv("tree-item"); + const folderTitleEl: HTMLDivElement = folderEl.createDiv("tree-item-self"); + const childrenEl: HTMLDivElement = folderEl.createDiv("tree-item-children"); const collapseIconEl: HTMLDivElement = folderTitleEl.createDiv( - "nav-folder-collapse-indicator collapse-icon", + "tree-item-collapse-indicator collapse-icon", ); collapseIconEl.innerHTML = COLLAPSE_ICON; @@ -166,7 +166,7 @@ export class ReviewQueueListView extends ItemView { (collapseIconEl.childNodes[0] as HTMLElement).style.transform = "rotate(-90deg)"; } - folderTitleEl.createDiv("nav-folder-title-content").setText(folderTitle); + folderTitleEl.createDiv("tree-item-content").setText(folderTitle); if (hidden) { folderEl.style.display = "none"; @@ -199,18 +199,18 @@ export class ReviewQueueListView extends ItemView { plugin: SRPlugin, ): void { const navFileEl: HTMLElement = folderEl - .getElementsByClassName("nav-folder-children")[0] - .createDiv("nav-file"); + .getElementsByClassName("tree-item-children")[0] + .createDiv("tree-item"); if (hidden) { navFileEl.style.display = "none"; } - const navFileTitle: HTMLElement = navFileEl.createDiv("nav-file-title"); + const navFileTitle: HTMLElement = navFileEl.createDiv("tree-item-self"); if (fileElActive) { navFileTitle.addClass("is-active"); } - navFileTitle.createDiv("nav-file-title-content").setText(file.basename); + navFileTitle.createDiv("tree-item-content").setText(file.basename); navFileTitle.addEventListener( "click", async (event: MouseEvent) => { @@ -239,7 +239,7 @@ export class ReviewQueueListView extends ItemView { } private changeFolderIconToExpanded(folderEl: HTMLElement): void { - const collapseIconEl = folderEl.find("div.nav-folder-collapse-indicator"); + const collapseIconEl = folderEl.find("div.tree-item-collapse-indicator"); (collapseIconEl.childNodes[0] as HTMLElement).style.transform = ""; } } diff --git a/src/lang/locale/de.ts b/src/lang/locale/de.ts index 5581cf46..d4e142d9 100644 --- a/src/lang/locale/de.ts +++ b/src/lang/locale/de.ts @@ -9,21 +9,21 @@ export default { DUE_CARDS: "Anstehende Karten", NEW_CARDS: "Neue Karten", TOTAL_CARDS: "Alle Karten", - BACK: "Back", - SKIP: "Skip", - EDIT_CARD: "Edit Card", + BACK: "Zurück", + SKIP: "Überspringen", + EDIT_CARD: "Karte bearbeiten", RESET_CARD_PROGRESS: "Kartenfortschritt zurücksetzten", HARD: "Schwer", GOOD: "Gut", EASY: "Einfach", SHOW_ANSWER: "Zeige Antwort", CARD_PROGRESS_RESET: "Kartenfortschritt wurde zurückgesetzt.", - SAVE: "Save", - CANCEL: "Cancel", - NO_INPUT: "No input provided.", - CURRENT_EASE_HELP_TEXT: "Current Ease: ", - CURRENT_INTERVAL_HELP_TEXT: "Current Interval: ", - CARD_GENERATED_FROM: "Generated from: ${notePath}", + SAVE: "Speichern", + CANCEL: "Abbrechen", + NO_INPUT: "Keine Eingabe erhalten.", + CURRENT_EASE_HELP_TEXT: "Aktuelle Schwierigkeit: ", + CURRENT_INTERVAL_HELP_TEXT: "Aktueller Intervall: ", + CARD_GENERATED_FROM: "Erstellt von: ${notePath}", // main.ts OPEN_NOTE_FOR_REVIEW: "Notiz zur Wiederholung öffnen", @@ -31,14 +31,14 @@ export default { REVIEW_DIFFICULTY_FILE_MENU: "Notiz abschliessen als: ${difficulty}", REVIEW_NOTE_DIFFICULTY_CMD: "Notiz abschliessen als: ${difficulty}", REVIEW_ALL_CARDS: "Alle Lernkarten wiederholen", - CRAM_ALL_CARDS: "Select a deck to cram", + CRAM_ALL_CARDS: "Wähle ein Stapel zum pauken", REVIEW_CARDS_IN_NOTE: "Lernkarten in dieser Notiz wiederholen", CRAM_CARDS_IN_NOTE: "Lernkarten in dieser Notiz pauken.", VIEW_STATS: "Statistiken anzeigen", - OPEN_REVIEW_QUEUE_VIEW: "Open Notes Review Queue in sidebar", + OPEN_REVIEW_QUEUE_VIEW: "Öffne Überprüfungswarteschlage in der Seitenleiste", STATUS_BAR: "Wiederholung: ${dueNotesCount} Notiz(en), ${dueFlashcardsCount} Karte(n) anstehend", - SYNC_TIME_TAKEN: "Sync dauerte ${t}ms", + SYNC_TIME_TAKEN: "Synchronisierung dauerte ${t}ms", NOTE_IN_IGNORED_FOLDER: "Notiz befindet sich in einem ausgeschlossenen Ordner (siehe Einstellungen).", PLEASE_TAG_NOTE: @@ -51,9 +51,9 @@ export default { DAYS_STR_IVL: "${interval} Tag(e)", MONTHS_STR_IVL: "${interval} Monat(e)", YEARS_STR_IVL: "${interval} Jahr(e)", - DAYS_STR_IVL_MOBILE: "${interval}d", + DAYS_STR_IVL_MOBILE: "${interval}t", MONTHS_STR_IVL_MOBILE: "${interval}m", - YEARS_STR_IVL_MOBILE: "${interval}y", + YEARS_STR_IVL_MOBILE: "${interval}j", // settings.ts SETTINGS_HEADER: "Spaced Repetition Plugin - Einstellungen", @@ -62,12 +62,12 @@ export default { FOLDERS_TO_IGNORE_DESC: "Mehrere Ordner mit Zeilenumbrüchen getrennt angeben. Bsp. OrdnerA[Zeilenumbruch]OrdnerB/Unterordner", FLASHCARDS: "Lernkarten", - FLASHCARD_EASY_LABEL: "Easy Button Text", - FLASHCARD_GOOD_LABEL: "Good Button Text", - FLASHCARD_HARD_LABEL: "Hard Button Text", - FLASHCARD_EASY_DESC: 'Customize the label for the "Easy" Button', - FLASHCARD_GOOD_DESC: 'Customize the label for the "Good" Button', - FLASHCARD_HARD_DESC: 'Customize the label for the "Hard" Button', + FLASHCARD_EASY_LABEL: "Einfach Knopf Text", + FLASHCARD_GOOD_LABEL: "Gut Knopf Text", + FLASHCARD_HARD_LABEL: "Schwer Knopf Text", + FLASHCARD_GOOD_DESC: 'Passe die Beschriftung für "Gut" Knopf an', + FLASHCARD_EASY_DESC: 'Passe die Beschriftung für "Einfach" Knopf an', + FLASHCARD_HARD_DESC: 'Passe die Beschriftung für "Schwer" Knopf an', FLASHCARD_TAGS: "Lernkarten Tags", FLASHCARD_TAGS_DESC: "Mehrere Tags mit Leerzeichen oder Zeilenumbrüchen getrennt angeben. Bsp. #karte #stapel2 #stapel3.", @@ -88,19 +88,24 @@ export default { RESET_DEFAULT: "Standardeinstellung wiederherstellen", CARD_MODAL_WIDTH_PERCENT: "Breite einer Lernkarte in Prozent", RANDOMIZE_CARD_ORDER: "Während der Wiederhoung die Reihenfolge zufällig mischen?", - REVIEW_CARD_ORDER_WITHIN_DECK: "Order cards in a deck are displayed during review", - REVIEW_CARD_ORDER_NEW_FIRST_SEQUENTIAL: "Sequentially within a deck (All new cards first)", - REVIEW_CARD_ORDER_DUE_FIRST_SEQUENTIAL: "Sequentially within a deck (All due cards first)", - REVIEW_CARD_ORDER_NEW_FIRST_RANDOM: "Randomly within a deck (All new cards first)", - REVIEW_CARD_ORDER_DUE_FIRST_RANDOM: "Randomly within a deck (All due cards first)", - REVIEW_CARD_ORDER_RANDOM_DECK_AND_CARD: "Random card from random deck", - REVIEW_DECK_ORDER: "Order decks are displayed during review", + REVIEW_CARD_ORDER_WITHIN_DECK: + "Reihenfolge der Karten innerhalb eines Stapels während der Wiederholung", + REVIEW_CARD_ORDER_NEW_FIRST_SEQUENTIAL: + "Sequentielle Reihenfolge innerhalb eines Stapels (Alle neuen Karten zuerst)", + REVIEW_CARD_ORDER_DUE_FIRST_SEQUENTIAL: + "Sequentielle Reihenfolge innerhalb eines Stapels (Alle fälligen Karten zuerst)", + REVIEW_CARD_ORDER_NEW_FIRST_RANDOM: + "Zufällige Reihenfolge innerhalb eines Stapels (Alle neuen Karten zuerst)", + REVIEW_CARD_ORDER_DUE_FIRST_RANDOM: + "Zufällige Reihenfolge innerhalb eines Stapels (Alle fälligen Karten zuerst)", + REVIEW_CARD_ORDER_RANDOM_DECK_AND_CARD: "Zufällige Karte von zufälligem Stapel", + REVIEW_DECK_ORDER: "Reihenfolge der Stapel während der Wiederholung", REVIEW_DECK_ORDER_PREV_DECK_COMPLETE_SEQUENTIAL: - "Sequentially (once all cards in previous deck reviewed)", + "Sequentielle Reihenfolge (sobald alle Karten im vorherigen Stapel wiederholt wurden)", REVIEW_DECK_ORDER_PREV_DECK_COMPLETE_RANDOM: - "Randomly (once all cards in previous deck reviewed)", - REVIEW_DECK_ORDER_RANDOM_DECK_AND_CARD: "Random card from random deck", - DISABLE_CLOZE_CARDS: "Lückentextkarten (cloze deletions) deaktivieren?", + "Zufällige Reihenfolge (sobald alle Karten im vorherigen Stapel wiederholt wurden)", + REVIEW_DECK_ORDER_RANDOM_DECK_AND_CARD: "Zufällige Karte von zufälligem Stapel", + DISABLE_CLOZE_CARDS: "Lückentextkarten deaktivieren?", CONVERT_HIGHLIGHTS_TO_CLOZES: "==Hervorgehobenen== Text in Lückentextkarten umwandeln?", CONVERT_BOLD_TEXT_TO_CLOZES: "**Fettgedruckten** Text in Lückentextkarten umwandeln?", CONVERT_CURLY_BRACKETS_TO_CLOZES: @@ -112,7 +117,7 @@ export default { MULTILINE_CARDS_SEPARATOR: "Trennzeichen für mehrzeilige Lernkarten", MULTILINE_REVERSED_CARDS_SEPARATOR: "Trennzeichen für mehrzeilige beidseitige Lernkarten", NOTES: "Notizen", - REVIEW_PANE_ON_STARTUP: "Enable note review pane on startup", + REVIEW_PANE_ON_STARTUP: "Öffne Überprüfungswarteschlage beim start", TAGS_TO_REVIEW: "Zu wiederholende Tags", TAGS_TO_REVIEW_DESC: "Mehrere Tags können mit Leerzeichen oder Zeilenumbrüchen getrennt angegeben werden. Bsp. #karte #tag1 #tag2.", @@ -129,15 +134,16 @@ export default { MIN_ONE_DAY: "Anzahl der Tage muss mindestens 1 sein.", VALID_NUMBER_WARNING: "Bitte eine gültige Zahl eingeben.", UI_PREFERENCES: "Einstellungen der Benutzeroberfläche", - INITIALLY_EXPAND_SUBDECKS_IN_TREE: "Deckbäume sollten anfänglich erweitert angezeigt werden", + INITIALLY_EXPAND_SUBDECKS_IN_TREE: + "Stapelverzeichnis soll beim öffnen erweitert angezeigt werden", INITIALLY_EXPAND_SUBDECKS_IN_TREE_DESC: - "Deaktivieren Sie dies, um verschachtelte Decks in derselben Karte zu reduzieren. Nützlich, wenn Sie Karten haben, die zu vielen Decks in derselben Datei gehören.", + "Deaktivieren Sie dies, um verschachtelte Stapel in derselben Karte zu reduzieren. Nützlich, wenn Sie Karten haben, die zu vielen Stapeln in derselben Datei gehören.", ALGORITHM: "Algorithmus", CHECK_ALGORITHM_WIKI: 'Weiterführende Informationen: Implementierung des Algorithmus (english).', - BASE_EASE: "Basis der Einfachheit", + BASE_EASE: "Basis der Schwierigkeit", BASE_EASE_DESC: "Minimum ist 130. Empfohlen wird ca. 250.", - BASE_EASE_MIN_WARNING: "Basis der Einfachheit muss mindestens 130 sein.", + BASE_EASE_MIN_WARNING: "Basis der Schwierigkeit muss mindestens 130 sein.", LAPSE_INTERVAL_CHANGE: "Anpassungsfaktor des Intervalls wenn eine Notiz / Karte 'Schwer' abgeschlossen wird", LAPSE_INTERVAL_CHANGE_DESC: "neuesIntervall = altesIntervall * anpassungsfaktor / 100.", @@ -145,14 +151,14 @@ export default { EASY_BONUS_DESC: "Der Einfachheit-Bonus gibt an um welchen Faktor (in Prozent) das Intervall länger sein soll, wenn eine Notiz / Karte 'Einfach' statt 'Gut' abgeschlossen wird. Minimum ist 100%.", EASY_BONUS_MIN_WARNING: "Der Einfachheit-Bonus muss mindestens 100 sein.", - MAX_INTERVAL: "Maximum interval in days", + MAX_INTERVAL: "Maximale Intervall in Tagen", MAX_INTERVAL_DESC: "Das maximale Intervall (in Tagen) für Wiederholungen. Standard sind 100 Jahre.", MAX_INTERVAL_MIN_WARNING: "Das maximale Interall muss mindestens ein Tag sein.", MAX_LINK_CONTRIB: "Maximaler Einfluss von Links", MAX_LINK_CONTRIB_DESC: "Maximaler Einfluss der Einfachheiten verlinkter Notizen zur gewichteten initialen Einfachheit einer neuen Lernkarte.", - LOGGING: "Logging", + LOGGING: "Protokollierung", DISPLAY_DEBUG_INFO: "Informationen zum Debugging in der Entwicklerkonsole anzeigen?", // sidebar.ts @@ -165,10 +171,10 @@ export default { // stats-modal.tsx STATS_TITLE: "Statistiken", - MONTH: "Month", - QUARTER: "Quarter", - YEAR: "Year", - LIFETIME: "Lifetime", + MONTH: "Monat", + QUARTER: "Quartal", + YEAR: "Jahr", + LIFETIME: "Lebenslang", FORECAST: "Prognose", FORECAST_DESC: "Anzahl der künftig anstehenden Karten", SCHEDULED: "Anstehend", @@ -179,8 +185,8 @@ export default { INTERVALS_DESC: "Intervalle bis Wiederholungen anstehen", COUNT: "Anzahl", INTERVALS_SUMMARY: "Durchschnittliches Intervall: ${avg}, Längstes Intervall: ${longest}", - EASES: "Einfachheit", - EASES_SUMMARY: "Durchschnittliche Einfachheit: ${avgEase}", + EASES: "Schwierigkeit", + EASES_SUMMARY: "Durchschnittliche Schwierigkeit: ${avgEase}", CARD_TYPES: "Kategorisierung", CARD_TYPES_DESC: "Verlegte Karten eingeschlossen", CARD_TYPE_NEW: "Neu", diff --git a/src/main.ts b/src/main.ts index a8c3c278..412a8379 100644 --- a/src/main.ts +++ b/src/main.ts @@ -41,6 +41,7 @@ import { NoteEaseCalculator } from "./NoteEaseCalculator"; import { DeckTreeStatsCalculator } from "./DeckTreeStatsCalculator"; import { NoteEaseList } from "./NoteEaseList"; import { QuestionPostponementList } from "./QuestionPostponementList"; +import { isEqualOrSubPath } from "./util/utils"; interface PluginData { settings: SRSettings; @@ -379,7 +380,7 @@ export default class SRPlugin extends Plugin { for (const noteFile of notes) { if ( this.data.settings.noteFoldersToIgnore.some((folder) => - noteFile.path.startsWith(folder), + isEqualOrSubPath(noteFile.path, folder), ) ) { continue; @@ -567,7 +568,11 @@ export default class SRPlugin extends Plugin { fileCachedData.frontmatter || {}; const tags = getAllTags(fileCachedData) || []; - if (this.data.settings.noteFoldersToIgnore.some((folder) => note.path.startsWith(folder))) { + if ( + this.data.settings.noteFoldersToIgnore.some((folder) => + isEqualOrSubPath(note.path, folder), + ) + ) { new Notice(t("NOTE_IN_IGNORED_FOLDER")); return; } @@ -756,11 +761,13 @@ export default class SRPlugin extends Plugin { this.lastSelectedReviewDeck = deckKey; const deck = this.reviewDecks[deckKey]; - if (deck.dueNotesCount > 0) { + const nowUnix = Date.now(); + const dueNotes = deck.scheduledNotes.filter((note) => note.dueUnix <= nowUnix); + if (dueNotes.length > 0) { const index = this.data.settings.openRandomNote - ? Math.floor(Math.random() * deck.dueNotesCount) + ? Math.floor(Math.random() * dueNotes.length) : 0; - await this.app.workspace.getLeaf().openFile(deck.scheduledNotes[index].note); + await this.app.workspace.getLeaf().openFile(dueNotes[index].note); return; } diff --git a/src/util/RenderMarkdownWrapper.ts b/src/util/RenderMarkdownWrapper.ts index 8b82f763..9e18784e 100644 --- a/src/util/RenderMarkdownWrapper.ts +++ b/src/util/RenderMarkdownWrapper.ts @@ -27,7 +27,7 @@ export class RenderMarkdownWrapper { ): Promise { if (recursiveDepth > 4) return; - MarkdownRenderer.renderMarkdown(markdownString, containerEl, this.notePath, this.plugin); + MarkdownRenderer.render(this.app, markdownString, containerEl, this.notePath, this.plugin); containerEl.findAll(".internal-embed").forEach((el) => { const link = this.parseLink(el.getAttribute("src")); diff --git a/src/util/utils.ts b/src/util/utils.ts index 6e4bfef8..7b872deb 100644 --- a/src/util/utils.ts +++ b/src/util/utils.ts @@ -1,5 +1,6 @@ import moment from "moment"; import { Moment } from "moment"; +import { normalize, sep } from "path"; import { PREFERRED_DATE_FORMAT, YAML_FRONT_MATTER_REGEX } from "src/constants"; type Hex = number; @@ -96,45 +97,75 @@ export function stringTrimStart(str: string): [string, string] { return [ws, trimmed]; } -// -// This returns [frontmatter, content] -// -// The returned content has the same number of lines as the supplied str string, but with the -// frontmatter lines (if present) blanked out. -// -// 1. We don't want the parser to see the frontmatter, as it would deem it to be part of a multi-line question -// if one started on the line immediately after the "---" closing marker. -// -// 2. The lines are blanked out rather than deleted so that line numbers are not affected -// e.g. for calls to getQuestionContext(cardLine: number) -// -export function extractFrontmatter(str: string): [string, string] { - let frontmatter: string = ""; - let content: string = ""; - let frontmatterEndLineNum: number = null; - if (YAML_FRONT_MATTER_REGEX.test) { - const lines: string[] = splitTextIntoLineArray(str); - - // The end "---" marker must be on the third line (index 2) or later - for (let i = 2; i < lines.length; i++) { - if (lines[i] == "---") { - frontmatterEndLineNum = i; - break; - } +/** + * Checks a path is equal or a subpath of the other rootPath + * + * @param toCheck The path to check it is equal or a subpath of path. + * @param rootPath The ref path to check the other is equal to or a subpath of this. + * @tutorial + * rootPath = "root/sub/sub2" + * if toCheck = "notRoot/..." -> false + * if toCheck = "root" -> true + * if toCheck = "root/sub" -> true + * if toCheck = "root/s" -> false + */ +export function isEqualOrSubPath(toCheck: string, rootPath: string): boolean { + const rootPathSections = normalize(rootPath.toLowerCase()) + .replaceAll(/(\\|\/)/g, sep) + .split(sep) + .filter((p) => p !== ""); + const pathSections = normalize(toCheck.toLowerCase()) + .replaceAll(/(\\|\/)/g, sep) + .split(sep) + .filter((p) => p !== ""); + if (pathSections.length < rootPathSections.length) { + return false; + } + for (let i = 0; i < rootPathSections.length; i++) { + if (rootPathSections[i] !== pathSections[i]) { + return false; } + } + return true; +} - if (frontmatterEndLineNum) { - const frontmatterStartLineNum: number = 0; - const frontmatterLines: string[] = []; - for (let i = frontmatterStartLineNum; i <= frontmatterEndLineNum; i++) { - frontmatterLines.push(lines[i]); - lines[i] = ""; - } - frontmatter = frontmatterLines.join("\n"); - content = lines.join("\n"); +/** + * The returned content has the same number of lines as the supplied string, but with the frontmatter lines (if present) blanked out. + * + * 1. We don't want the parser to see the frontmatter, as it would deem it to be part of a multi-line question if one started on the line immediately after the "---" closing marker. + * + * 2. The lines are blanked out rather than deleted so that line numbers are not affected e.g. for calls to getQuestionContext(cardLine: number) + * + * @param str The file content as string + * @returns [frontmatter, content] + */ +export function extractFrontmatter(str: string): [string, string] { + const lines = splitTextIntoLineArray(str); + let lineIndex = 0; + let hasFrontmatter = false; + do { + // Starts file with '---' + if (lineIndex === 0 && lines[lineIndex] === "---") { + hasFrontmatter = true; + } + // Line is end of front matter + else if (hasFrontmatter && lines[lineIndex] === "---") { + hasFrontmatter = false; + lineIndex++; + } + if (hasFrontmatter) { + lineIndex++; } + } while (hasFrontmatter && lineIndex < lines.length); + // No end of Frontmatter found + if (hasFrontmatter) { + lineIndex = 0; } - if (frontmatter.length == 0) content = str; + + const frontmatter: string = lines.slice(0, lineIndex).join("\n"); + const emptyLines: string[] = lineIndex > 0 ? Array(lineIndex).join(".").split(".") : []; + const content: string = emptyLines.concat(lines.slice(lineIndex)).join("\n"); + return [frontmatter, content]; } diff --git a/styles.css b/styles.css index 05072293..62b5e2a4 100644 --- a/styles.css +++ b/styles.css @@ -1,8 +1,40 @@ -.is-mobile #sr-modal { - --top-space: calc(var(--safe-area-inset-top) + var(--header-height) + var(--size-4-2)); - width: 100vw !important; - height: calc(100vh - var(--top-space)) !important; - margin-top: var(--top-space); +@media only screen and (orientation: landscape) { + .is-mobile .sr-flashcard { + flex-direction: row; + } + + .is-mobile .sr-header { + flex-direction: column; + flex: 0 1 0; + } + + .is-mobile .sr-content { + flex: 1 0 0; + } + + .is-mobile .sr-response { + flex-direction: column; + flex: 0 1 0; + } + + .is-mobile .sr-controls { + flex-direction: column; + } + + .is-mobile .sr-title { + display: none; + } + + .is-mobile .sr-response-button { + writing-mode: vertical-lr; + } +} + +#sr-modal { + position: absolute; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); } #sr-modal .modal-title { diff --git a/tests/unit/util/utils.test.ts b/tests/unit/util/utils.test.ts index bcfcf464..efb4a237 100644 --- a/tests/unit/util/utils.test.ts +++ b/tests/unit/util/utils.test.ts @@ -2,6 +2,7 @@ import { YAML_FRONT_MATTER_REGEX } from "src/constants"; import { extractFrontmatter, findLineIndexOfSearchStringIgnoringWs, + isEqualOrSubPath, literalStringReplace, } from "src/util/utils"; @@ -177,6 +178,176 @@ ${content}`; ${content}`; expect(c).toEqual(expectedContent); }); + + test("With frontmatter and content (Horizontal line)", () => { + const frontmatter: string = `--- +sr-due: 2024-01-17 +sr-interval: 16 +sr-ease: 278 +tags: + - flashcards/aws + - flashcards/datascience +---`; + const frontmatterBlankedOut: string = ` + + + + + + +`; + const content: string = `#flashcards/science/chemistry + + +--- +# Questions +--- + + +Chemistry Question from file underelephant 4A::goodby + + + +Chemistry Question from file underdog 4B::goodby + + + +--- + +Chemistry Question from file underdog 4C::goodby + + + +This single {{question}} turns into {{3 separate}} {{cards}} + + + +---`; + + const text: string = `${frontmatter} +${content}`; + const expectedContent: string = `${frontmatterBlankedOut} +${content}`; + + const [f, c] = extractFrontmatter(text); + expect(f).toEqual(frontmatter); + expect(c).toEqual(expectedContent); + }); + + test("With frontmatter and content (Horizontal line newLine)", () => { + const frontmatter: string = `--- +sr-due: 2024-01-17 +sr-interval: 16 +sr-ease: 278 +tags: + - flashcards/aws + - flashcards/datascience +---`; + const frontmatterBlankedOut: string = ` + + + + + + +`; + const content: string = `#flashcards/science/chemistry + + +--- +# Questions +--- + + +Chemistry Question from file underelephant 4A::goodby + + + +Chemistry Question from file underdog 4B::goodby + + + +--- + +Chemistry Question from file underdog 4C::goodby + + + +This single {{question}} turns into {{3 separate}} {{cards}} + + + +--- +`; + + const text: string = `${frontmatter} +${content}`; + const expectedContent: string = `${frontmatterBlankedOut} +${content}`; + + const [f, c] = extractFrontmatter(text); + expect(f).toEqual(frontmatter); + expect(c).toEqual(expectedContent); + }); + + test("With frontmatter and content (Horizontal line codeblock)", () => { + const frontmatter: string = `--- +sr-due: 2024-01-17 +sr-interval: 16 +sr-ease: 278 +tags: + - flashcards/aws + - flashcards/datascience +---`; + const frontmatterBlankedOut: string = ` + + + + + + +`; + const content: string = [ + "```", + "---", + "```", + "#flashcards/science/chemistry", + "# Questions", + " ", + "", + "Chemistry Question from file underelephant 4A::goodby", + "", + "", + "", + "Chemistry Question from file underdog 4B::goodby", + "", + "", + "```", + "---", + "```", + "", + "Chemistry Question from file underdog 4C::goodby", + "", + "", + "", + "This single {{question}} turns into {{3 separate}} {{cards}}", + "", + "", + "", + "```", + "---", + "```", + ].join("\n"); + + const text: string = `${frontmatter} +${content}`; + const expectedContent: string = `${frontmatterBlankedOut} +${content}`; + + const [f, c] = extractFrontmatter(text); + expect(f).toEqual(frontmatter); + expect(c).toEqual(expectedContent); + }); }); describe("findLineIndexOfSearchStringIgnoringWs", () => { @@ -243,3 +414,113 @@ describe("findLineIndexOfSearchStringIgnoringWs", () => { expect(findLineIndexOfSearchStringIgnoringWs(lines, "??")).toEqual(2); }); }); + +describe("isEqualOrSubPath", () => { + const winSep = "\\"; + const linSep = "/"; + const root = "root"; + const sub_1 = "plugins"; + const sub_2 = "obsidian-spaced-repetition"; + const sub_3 = "data"; + const noMatch = "notRoot"; + const caseMatch = "Root"; + + describe("Windows", () => { + const sep = winSep; + const rootPath = root + sep + sub_1; + + test("Upper and lower case letters", () => { + expect(isEqualOrSubPath(caseMatch, root)).toBe(true); + expect(isEqualOrSubPath(caseMatch.toUpperCase(), root)).toBe(true); + }); + + test("Seperator auto correction", () => { + expect(isEqualOrSubPath(root + winSep + sub_1, rootPath)).toBe(true); + expect(isEqualOrSubPath(root + winSep + sub_1 + winSep, rootPath)).toBe(true); + + expect(isEqualOrSubPath(root + linSep + sub_1, rootPath)).toBe(true); + expect(isEqualOrSubPath(root + linSep + sub_1 + linSep, rootPath)).toBe(true); + }); + + test("Differnent path", () => { + expect(isEqualOrSubPath(noMatch, rootPath)).toBe(false); + expect(isEqualOrSubPath(noMatch + sep, rootPath)).toBe(false); + expect(isEqualOrSubPath(noMatch + sep + sub_1, rootPath)).toBe(false); + expect(isEqualOrSubPath(noMatch + sep + sub_1 + sep + sub_2, rootPath)).toBe(false); + }); + + test("Partially Match path", () => { + expect(isEqualOrSubPath("roo", rootPath)).toBe(false); + expect(isEqualOrSubPath("roo" + sep, rootPath)).toBe(false); + expect(isEqualOrSubPath(root + sep + "plug", rootPath)).toBe(false); + expect(isEqualOrSubPath(root + sep + "plug" + sep, rootPath)).toBe(false); + }); + + test("Same path", () => { + expect(isEqualOrSubPath(rootPath, rootPath)).toBe(true); + }); + + test("Subpath", () => { + expect(isEqualOrSubPath(root, rootPath)).toBe(false); + expect(isEqualOrSubPath(root + sep, rootPath)).toBe(false); + expect(isEqualOrSubPath(root + sep + sub_1, rootPath)).toBe(true); + expect(isEqualOrSubPath(rootPath, rootPath + sep)).toBe(true); + expect(isEqualOrSubPath(rootPath + sep, rootPath)).toBe(true); + expect(isEqualOrSubPath(root + sep + sub_1 + sep, rootPath)).toBe(true); + expect(isEqualOrSubPath(root + sep + sub_1 + sep + sub_2, rootPath)).toBe(true); + expect(isEqualOrSubPath(root + sep + sub_1 + sep + sub_2 + sep, rootPath)).toBe(true); + expect(isEqualOrSubPath(root + sep + sub_1 + sep + sub_2 + sep + sub_3, rootPath)).toBe( + true, + ); + }); + }); + describe("Linux", () => { + const sep = linSep; + const rootPath = root + sep + sub_1; + + test("Upper and lower case letters", () => { + expect(isEqualOrSubPath(caseMatch, root)).toBe(true); + expect(isEqualOrSubPath(caseMatch.toUpperCase(), root)).toBe(true); + }); + + test("Seperator auto correction", () => { + expect(isEqualOrSubPath(root + winSep + sub_1, rootPath)).toBe(true); + expect(isEqualOrSubPath(root + winSep + sub_1 + winSep, rootPath)).toBe(true); + + expect(isEqualOrSubPath(root + linSep + sub_1, rootPath)).toBe(true); + expect(isEqualOrSubPath(root + linSep + sub_1 + linSep, rootPath)).toBe(true); + }); + + test("Differnent path", () => { + expect(isEqualOrSubPath(noMatch, rootPath)).toBe(false); + expect(isEqualOrSubPath(noMatch + sep, rootPath)).toBe(false); + expect(isEqualOrSubPath(noMatch + sep + sub_1, rootPath)).toBe(false); + expect(isEqualOrSubPath(noMatch + sep + sub_1 + sep + sub_2, rootPath)).toBe(false); + }); + + test("Partially Match path", () => { + expect(isEqualOrSubPath("roo", rootPath)).toBe(false); + expect(isEqualOrSubPath("roo" + sep, rootPath)).toBe(false); + expect(isEqualOrSubPath(root + sep + "plug", rootPath)).toBe(false); + expect(isEqualOrSubPath(root + sep + "plug" + sep, rootPath)).toBe(false); + }); + + test("Same path", () => { + expect(isEqualOrSubPath(rootPath, rootPath)).toBe(true); + }); + + test("Subpath", () => { + expect(isEqualOrSubPath(root, rootPath)).toBe(false); + expect(isEqualOrSubPath(root + sep, rootPath)).toBe(false); + expect(isEqualOrSubPath(root + sep + sub_1, rootPath)).toBe(true); + expect(isEqualOrSubPath(rootPath, rootPath + sep)).toBe(true); + expect(isEqualOrSubPath(rootPath + sep, rootPath)).toBe(true); + expect(isEqualOrSubPath(root + sep + sub_1 + sep, rootPath)).toBe(true); + expect(isEqualOrSubPath(root + sep + sub_1 + sep + sub_2, rootPath)).toBe(true); + expect(isEqualOrSubPath(root + sep + sub_1 + sep + sub_2 + sep, rootPath)).toBe(true); + expect(isEqualOrSubPath(root + sep + sub_1 + sep + sub_2 + sep + sub_3, rootPath)).toBe( + true, + ); + }); + }); +}); From 48a977c2f3170d9f27074ca5ff49b692ad553884 Mon Sep 17 00:00:00 2001 From: Kyle Klus Date: Mon, 22 Jul 2024 09:38:46 +0200 Subject: [PATCH 5/7] UPDATE: Quick clean up of unused code --- src/gui/FlashcardReviewView.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/src/gui/FlashcardReviewView.tsx b/src/gui/FlashcardReviewView.tsx index fb652b19..6c767449 100644 --- a/src/gui/FlashcardReviewView.tsx +++ b/src/gui/FlashcardReviewView.tsx @@ -393,7 +393,6 @@ export class FlashcardReviewView { } private _setTitle(deck: Deck) { - const isRandomMode = this.settings.flashcardCardOrder === "EveryCardRandomDeckAndCard"; let text = deck.deckName; const deckStats = this.reviewSequencer.getDeckStats(deck.getTopicPath()); From f72ecf5f8505ae421c6791f8c3eb524f4e38d9f8 Mon Sep 17 00:00:00 2001 From: Kyle Klus Date: Mon, 23 Sep 2024 12:14:38 +0200 Subject: [PATCH 6/7] UPDATE: Added changelog message again --- docs/docs/changelog.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docs/docs/changelog.md b/docs/docs/changelog.md index 4ac44f16..33b474d1 100644 --- a/docs/docs/changelog.md +++ b/docs/docs/changelog.md @@ -4,6 +4,10 @@ All notable changes to this project will be documented in this file. Dates are d Generated by [`auto-changelog`](https://github.com/CookPete/auto-changelog). +#### Unreleased + +Extended card title functionality partly as described in issue [`#851`](https://github.com/st3v3nmw/obsidian-spaced-repetition/issues/851) + #### [1.12.7](https://github.com/st3v3nmw/obsidian-spaced-repetition/compare/1.12.6...1.12.7) - fix: parsing code blocks & custom separators [`#1081`](https://github.com/st3v3nmw/obsidian-spaced-repetition/pull/1081) From 93c1b992097aa6de2a5e54b652e4f0c028e5ef67 Mon Sep 17 00:00:00 2001 From: Kyle Klus Date: Mon, 23 Sep 2024 12:39:03 +0200 Subject: [PATCH 7/7] UPDATE: Executed format script --- CHANGELOG.md | 2 +- CONTRIBUTING.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index cb4f94f7..1fcfbd40 120000 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1 +1 @@ -docs/docs/changelog.md \ No newline at end of file +docs/docs/changelog.md diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 54cb9a6d..f6af8f1f 120000 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1 +1 @@ -docs/docs/en/contributing.md \ No newline at end of file +docs/docs/en/contributing.md