From cb51cd5e8b5ff5f17b771c0b07edf599647f7aa4 Mon Sep 17 00:00:00 2001 From: woodsj1206 <47829885+woodsj1206@users.noreply.github.com> Date: Mon, 3 Nov 2025 01:29:16 -0500 Subject: [PATCH 1/4] Initial commit --- src/escape-html.ts | 13 +++++++++++++ src/gui/card-ui.tsx | 33 +++++++++++++++++++++++++++++++-- src/gui/settings.tsx | 18 ++++++++++++++++++ src/lang/locale/en.ts | 3 +++ src/question-type.ts | 18 +++++++++++++++++- src/settings.ts | 2 ++ 6 files changed, 84 insertions(+), 3 deletions(-) create mode 100644 src/escape-html.ts diff --git a/src/escape-html.ts b/src/escape-html.ts new file mode 100644 index 00000000..721fb927 --- /dev/null +++ b/src/escape-html.ts @@ -0,0 +1,13 @@ +/** + * Escapes HTML special characters in a string. + * @param s - The input string. + * @returns - The escaped string. + */ +export function escapeHtml(s: string): string { + return s + .replace(/&/g, "&") + .replace(//g, ">") + .replace(/"/g, """) + .replace(/'/g, "'"); +} \ No newline at end of file diff --git a/src/gui/card-ui.tsx b/src/gui/card-ui.tsx index 2ff5fbab..a50419c1 100644 --- a/src/gui/card-ui.tsx +++ b/src/gui/card-ui.tsx @@ -6,6 +6,7 @@ import { ReviewResponse } from "src/algorithms/base/repetition-item"; import { textInterval } from "src/algorithms/osr/note-scheduling"; import { Card } from "src/card"; import { Deck } from "src/deck"; +import { escapeHtml } from "src/escape-html"; import { FlashcardReviewMode, IFlashcardReviewSequencer as IFlashcardReviewSequencer, @@ -70,6 +71,9 @@ export class CardUI { public answerButton: HTMLButtonElement; public lastPressed: number; + public clozeInputs: NodeListOf; + public clozeAnswers: NodeListOf; + private chosenDeck: Deck | null; private totalCardsInSession: number = 0; private totalDecksInSession: number = 0; @@ -217,6 +221,15 @@ export class CardUI { // Update response buttons this._resetResponseButtons(); + + this.clozeInputs = document.querySelectorAll("#cloze-input"); + + this.clozeInputs.forEach((input) => { + input.addEventListener("change", (e) => { + e.target as HTMLInputElement; + }); + }); + } private get _currentCard(): Card { @@ -589,6 +602,21 @@ export class CardUI { this._currentQuestion.questionText.textDirection, ); + this.clozeAnswers = document.querySelectorAll("#cloze-answer"); + + if (this.clozeAnswers.length === this.clozeInputs.length) { + for (let i = 0; i < this.clozeAnswers.length; i++) { + const clozeInput = this.clozeInputs[i] as HTMLInputElement; + const clozeAnswer = this.clozeAnswers[i] as HTMLElement; + + const inputText = clozeInput.value.trim(); + const answerText = clozeAnswer.innerText.trim(); + + const answerElement = inputText === answerText ? `${escapeHtml(inputText)}` : `[${escapeHtml(inputText)}${answerText}]`; + clozeAnswer.innerHTML = answerElement; + } + } + // Show response buttons this.answerButton.addClass("sr-is-hidden"); this.hardButton.removeClass("sr-is-hidden"); @@ -619,9 +647,10 @@ export class CardUI { } private _keydownHandler = (e: KeyboardEvent) => { - // Prevents any input, if the edit modal is open or if the view is not in focus + // Prevents any input, if the edit modal is open, input area is in focus, or if the view is not in focus if ( - document.activeElement.nodeName === "TEXTAREA" || + document.activeElement.nodeName === "TEXTAREA" || + document.activeElement.nodeName === "INPUT" || this.mode === FlashcardMode.Closed || !this.plugin.getSRInFocusState() ) { diff --git a/src/gui/settings.tsx b/src/gui/settings.tsx index 20dc2092..d09bd970 100644 --- a/src/gui/settings.tsx +++ b/src/gui/settings.tsx @@ -197,6 +197,24 @@ export class SRSettingTab extends PluginSettingTab { ); containerEl.createEl("h3", { text: t("GROUP_FLASHCARD_SEPARATORS") }); + const convertClozePatternsToInputsEl = new Setting(containerEl).setName( + t("CONVERT_CLOZE_PATTERNS_TO_INPUTS"), + ); + convertClozePatternsToInputsEl.descEl.insertAdjacentHTML( + "beforeend", + t("CONVERT_CLOZE_PATTERNS_TO_INPUTS_DESC"), + ); + convertClozePatternsToInputsEl.addToggle((toggle) => + toggle + .setValue(this.plugin.data.settings.convertClozePatternsToInputs) + .onChange(async (value) => { + this.plugin.data.settings.convertClozePatternsToInputs = value; + await this.plugin.savePluginData(); + + this.display(); + }), + ); + const convertHighlightsToClozesEl = new Setting(containerEl).setName( t("CONVERT_HIGHLIGHTS_TO_CLOZES"), ); diff --git a/src/lang/locale/en.ts b/src/lang/locale/en.ts index 5f9f0aed..73fc162f 100644 --- a/src/lang/locale/en.ts +++ b/src/lang/locale/en.ts @@ -118,6 +118,9 @@ export default { "Randomly (once all cards in previous deck reviewed)", REVIEW_DECK_ORDER_RANDOM_DECK_AND_CARD: "Random card from random deck", DISABLE_CLOZE_CARDS: "Disable cloze cards?", + CONVERT_CLOZE_PATTERNS_TO_INPUTS: "Convert cloze patterns to input fields", + CONVERT_CLOZE_PATTERNS_TO_INPUTS_DESC: + "Replace cloze patterns with input fields when reviewing cloze cards.", CONVERT_HIGHLIGHTS_TO_CLOZES: "Convert ==highlights== to clozes", CONVERT_HIGHLIGHTS_TO_CLOZES_DESC: 'Add/remove the ${defaultPattern} from your "Cloze Patterns"', diff --git a/src/question-type.ts b/src/question-type.ts index b11c2174..8387d62e 100644 --- a/src/question-type.ts +++ b/src/question-type.ts @@ -96,7 +96,9 @@ class QuestionTypeCloze implements IQuestionTypeHandler { expand(questionText: string, settings: SRSettings): CardFrontBack[] { const clozecrafter = new ClozeCrafter(settings.clozePatterns); const clozeNote = clozecrafter.createClozeNote(questionText); - const clozeFormatter = new QuestionTypeClozeFormatter(); + + // Determine which question formatter to use based on settings (Cloze patterns as inputs or not). + const clozeFormatter = settings.convertClozePatternsToInputs ? new QuestionTypeClozeInputFormatter() : new QuestionTypeClozeFormatter(); let front: string, back: string; const result: CardFrontBack[] = []; @@ -124,6 +126,20 @@ export class QuestionTypeClozeFormatter implements IClozeFormatter { } } +export class QuestionTypeClozeInputFormatter implements IClozeFormatter { + asking(answer?: string, hint?: string): string { + return `${!hint ? "" : `[${hint}]`}`; + } + + showingAnswer(answer: string, _hint?: string): string { + return `${answer}`; + } + + hiding(answer?: string, hint?: string): string { + return `${!hint ? "[...]" : `[${hint}]`}`; + } +} + export class QuestionTypeFactory { static create(questionType: CardType): IQuestionTypeHandler { let handler: IQuestionTypeHandler; diff --git a/src/settings.ts b/src/settings.ts index d2c8a3da..41999a9a 100644 --- a/src/settings.ts +++ b/src/settings.ts @@ -13,6 +13,7 @@ export interface SRSettings { randomizeCardOrder: boolean; flashcardCardOrder: string; flashcardDeckOrder: string; + convertClozePatternsToInputs: boolean; convertHighlightsToClozes: boolean; convertBoldTextToClozes: boolean; convertCurlyBracketsToClozes: boolean; @@ -73,6 +74,7 @@ export const DEFAULT_SETTINGS: SRSettings = { randomizeCardOrder: null, flashcardCardOrder: "DueFirstRandom", flashcardDeckOrder: "PrevDeckComplete_Sequential", + convertClozePatternsToInputs: false, convertHighlightsToClozes: true, convertBoldTextToClozes: false, convertCurlyBracketsToClozes: false, From 4fb2bccb188187d4cca0af96fc682f36b95e8f57 Mon Sep 17 00:00:00 2001 From: woodsj1206 <47829885+woodsj1206@users.noreply.github.com> Date: Mon, 3 Nov 2025 08:22:13 -0500 Subject: [PATCH 2/4] refactoring, add comments --- src/gui/card-ui.tsx | 57 ++++++++++++++++++++++++++------------------- 1 file changed, 33 insertions(+), 24 deletions(-) diff --git a/src/gui/card-ui.tsx b/src/gui/card-ui.tsx index a50419c1..fd2f85ba 100644 --- a/src/gui/card-ui.tsx +++ b/src/gui/card-ui.tsx @@ -71,8 +71,8 @@ export class CardUI { public answerButton: HTMLButtonElement; public lastPressed: number; - public clozeInputs: NodeListOf; - public clozeAnswers: NodeListOf; + private clozeInputs: NodeListOf; + private clozeAnswers: NodeListOf; private chosenDeck: Deck | null; private totalCardsInSession: number = 0; @@ -222,14 +222,8 @@ export class CardUI { // Update response buttons this._resetResponseButtons(); - this.clozeInputs = document.querySelectorAll("#cloze-input"); - - this.clozeInputs.forEach((input) => { - input.addEventListener("change", (e) => { - e.target as HTMLInputElement; - }); - }); - + // Setup cloze input listeners + this._setupClozeInputListeners(); } private get _currentCard(): Card { @@ -569,6 +563,33 @@ export class CardUI { } } + private _setupClozeInputListeners(): void { + this.clozeInputs = document.querySelectorAll("#cloze-input"); + + this.clozeInputs.forEach((input) => { + input.addEventListener("change", (e) => { + const input = e.target as HTMLInputElement; + }); + }); + } + + private _evaluateClozeAnswers(): void { + this.clozeAnswers = document.querySelectorAll("#cloze-answer"); + + if (this.clozeAnswers.length === this.clozeInputs.length) { + for (let i = 0; i < this.clozeAnswers.length; i++) { + const clozeInput = this.clozeInputs[i] as HTMLInputElement; + const clozeAnswer = this.clozeAnswers[i] as HTMLElement; + + const inputText = clozeInput.value.trim(); + const answerText = clozeAnswer.innerText.trim(); + + const answerElement = inputText === answerText ? `${escapeHtml(inputText)}` : `[${escapeHtml(inputText)}${answerText}]`; + clozeAnswer.innerHTML = answerElement; + } + } + } + private _showAnswer(): void { const timeNow = now(); if ( @@ -602,20 +623,8 @@ export class CardUI { this._currentQuestion.questionText.textDirection, ); - this.clozeAnswers = document.querySelectorAll("#cloze-answer"); - - if (this.clozeAnswers.length === this.clozeInputs.length) { - for (let i = 0; i < this.clozeAnswers.length; i++) { - const clozeInput = this.clozeInputs[i] as HTMLInputElement; - const clozeAnswer = this.clozeAnswers[i] as HTMLElement; - - const inputText = clozeInput.value.trim(); - const answerText = clozeAnswer.innerText.trim(); - - const answerElement = inputText === answerText ? `${escapeHtml(inputText)}` : `[${escapeHtml(inputText)}${answerText}]`; - clozeAnswer.innerHTML = answerElement; - } - } + // Evaluate cloze answers + this._evaluateClozeAnswers(); // Show response buttons this.answerButton.addClass("sr-is-hidden"); From 15c8c26b989b45579ab3067cdc57aa44bb810232 Mon Sep 17 00:00:00 2001 From: woodsj1206 <47829885+woodsj1206@users.noreply.github.com> Date: Mon, 3 Nov 2025 09:55:43 -0500 Subject: [PATCH 3/4] update formatting --- CHANGELOG.md | 2 +- CONTRIBUTING.md | 2 +- src/escape-html.ts | 14 +++++++------- src/gui/card-ui.tsx | 16 +++++++++------- src/question-type.ts | 6 ++++-- 5 files changed, 22 insertions(+), 18 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 diff --git a/src/escape-html.ts b/src/escape-html.ts index 721fb927..2fb46d9c 100644 --- a/src/escape-html.ts +++ b/src/escape-html.ts @@ -4,10 +4,10 @@ * @returns - The escaped string. */ export function escapeHtml(s: string): string { - return s - .replace(/&/g, "&") - .replace(//g, ">") - .replace(/"/g, """) - .replace(/'/g, "'"); -} \ No newline at end of file + return s + .replace(/&/g, "&") + .replace(//g, ">") + .replace(/"/g, """) + .replace(/'/g, "'"); +} diff --git a/src/gui/card-ui.tsx b/src/gui/card-ui.tsx index fd2f85ba..06aa36c4 100644 --- a/src/gui/card-ui.tsx +++ b/src/gui/card-ui.tsx @@ -221,7 +221,7 @@ export class CardUI { // Update response buttons this._resetResponseButtons(); - + // Setup cloze input listeners this._setupClozeInputListeners(); } @@ -565,11 +565,10 @@ export class CardUI { private _setupClozeInputListeners(): void { this.clozeInputs = document.querySelectorAll("#cloze-input"); - + this.clozeInputs.forEach((input) => { - input.addEventListener("change", (e) => { - const input = e.target as HTMLInputElement; - }); + // eslint-disable-next-line @typescript-eslint/no-unused-vars + input.addEventListener("change", (e) => {}); }); } @@ -584,7 +583,10 @@ export class CardUI { const inputText = clozeInput.value.trim(); const answerText = clozeAnswer.innerText.trim(); - const answerElement = inputText === answerText ? `${escapeHtml(inputText)}` : `[${escapeHtml(inputText)}${answerText}]`; + const answerElement = + inputText === answerText + ? `${escapeHtml(inputText)}` + : `[${escapeHtml(inputText)}${answerText}]`; clozeAnswer.innerHTML = answerElement; } } @@ -658,7 +660,7 @@ export class CardUI { private _keydownHandler = (e: KeyboardEvent) => { // Prevents any input, if the edit modal is open, input area is in focus, or if the view is not in focus if ( - document.activeElement.nodeName === "TEXTAREA" || + document.activeElement.nodeName === "TEXTAREA" || document.activeElement.nodeName === "INPUT" || this.mode === FlashcardMode.Closed || !this.plugin.getSRInFocusState() diff --git a/src/question-type.ts b/src/question-type.ts index 8387d62e..d4e29888 100644 --- a/src/question-type.ts +++ b/src/question-type.ts @@ -96,9 +96,11 @@ class QuestionTypeCloze implements IQuestionTypeHandler { expand(questionText: string, settings: SRSettings): CardFrontBack[] { const clozecrafter = new ClozeCrafter(settings.clozePatterns); const clozeNote = clozecrafter.createClozeNote(questionText); - + // Determine which question formatter to use based on settings (Cloze patterns as inputs or not). - const clozeFormatter = settings.convertClozePatternsToInputs ? new QuestionTypeClozeInputFormatter() : new QuestionTypeClozeFormatter(); + const clozeFormatter = settings.convertClozePatternsToInputs + ? new QuestionTypeClozeInputFormatter() + : new QuestionTypeClozeFormatter(); let front: string, back: string; const result: CardFrontBack[] = []; From 1b2ecc223c3cbe4b6eea70f1fb99243b4671f846 Mon Sep 17 00:00:00 2001 From: woodsj1206 <47829885+woodsj1206@users.noreply.github.com> Date: Wed, 5 Nov 2025 09:16:45 -0500 Subject: [PATCH 4/4] refactoring --- src/gui/card-ui.tsx | 4 ++-- src/question-type.ts | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/gui/card-ui.tsx b/src/gui/card-ui.tsx index 06aa36c4..7ec05c92 100644 --- a/src/gui/card-ui.tsx +++ b/src/gui/card-ui.tsx @@ -564,7 +564,7 @@ export class CardUI { } private _setupClozeInputListeners(): void { - this.clozeInputs = document.querySelectorAll("#cloze-input"); + this.clozeInputs = document.querySelectorAll(".cloze-input"); this.clozeInputs.forEach((input) => { // eslint-disable-next-line @typescript-eslint/no-unused-vars @@ -573,7 +573,7 @@ export class CardUI { } private _evaluateClozeAnswers(): void { - this.clozeAnswers = document.querySelectorAll("#cloze-answer"); + this.clozeAnswers = document.querySelectorAll(".cloze-answer"); if (this.clozeAnswers.length === this.clozeInputs.length) { for (let i = 0; i < this.clozeAnswers.length; i++) { diff --git a/src/question-type.ts b/src/question-type.ts index d4e29888..17b05edf 100644 --- a/src/question-type.ts +++ b/src/question-type.ts @@ -130,11 +130,11 @@ export class QuestionTypeClozeFormatter implements IClozeFormatter { export class QuestionTypeClozeInputFormatter implements IClozeFormatter { asking(answer?: string, hint?: string): string { - return `${!hint ? "" : `[${hint}]`}`; + return `${!hint ? "" : `[${hint}]`}`; } showingAnswer(answer: string, _hint?: string): string { - return `${answer}`; + return `${answer}`; } hiding(answer?: string, hint?: string): string {