diff --git a/src/gui/EditModal.tsx b/src/gui/EditModal.tsx index 1feb19d5..fe7cb324 100644 --- a/src/gui/EditModal.tsx +++ b/src/gui/EditModal.tsx @@ -1,5 +1,7 @@ import { App, Modal } from "obsidian"; import { t } from "src/lang/helpers"; +import { SRSettings } from "src/settings"; +import { includedSeperator } from "src/util/utils"; // from https://github.com/chhoumann/quickadd/blob/bce0b4cdac44b867854d6233796e3406dfd163c6/src/gui/GenericInputPrompt/GenericInputPrompt.ts#L5 export class FlashcardEditModal extends Modal { @@ -7,7 +9,8 @@ export class FlashcardEditModal extends Modal { public waitForClose: Promise; public title: HTMLDivElement; - public textArea: HTMLTextAreaElement; + public textAreaFront: HTMLTextAreaElement; + public textAreaBack: HTMLTextAreaElement; public response: HTMLDivElement; public saveButton: HTMLButtonElement; public cancelButton: HTMLButtonElement; @@ -17,18 +20,42 @@ export class FlashcardEditModal extends Modal { private rejectPromise: (reason?: any) => void; private didSaveChanges = false; private readonly modalText: string; + private textFront: string; + private textBack: string; + private seperator: string; + private multilineSeperator: boolean; - public static Prompt(app: App, placeholder: string): Promise { - const newPromptModal = new FlashcardEditModal(app, placeholder); + public static Prompt(app: App, settings: SRSettings, placeholder: string): Promise { + const newPromptModal = new FlashcardEditModal(app, settings, placeholder); return newPromptModal.waitForClose; } - constructor(app: App, existingText: string) { + constructor(app: App, settings: SRSettings, existingText: string) { super(app); this.modalText = existingText; this.changedText = existingText; + // Select the seperator used + this.seperator = includedSeperator(this.modalText, [ + settings.singleLineReversedCardSeparator, + settings.multilineReversedCardSeparator, + settings.singleLineCardSeparator, + settings.multilineCardSeparator, + ]); + // Split Text based on the Seperator + [this.textFront, this.textBack] = this.modalText.split(this.seperator); + // Trim leading \n for multiline + this.multilineSeperator = this.seperator + ? [settings.multilineCardSeparator, settings.multilineReversedCardSeparator].contains( + this.seperator, + ) + : false; + if (this.multilineSeperator) { + this.textBack = this.textBack.trimStart(); + this.textFront = this.textFront.trimEnd(); + } + this.waitForClose = new Promise((resolve, reject) => { this.resolvePromise = resolve; this.rejectPromise = reject; @@ -52,10 +79,18 @@ export class FlashcardEditModal extends Modal { this.title.setText(t("EDIT_CARD")); this.title.addClass("sr-title"); - this.textArea = this.contentEl.createEl("textarea"); - this.textArea.addClass("sr-input"); - this.textArea.setText(this.modalText ?? ""); - this.textArea.addEventListener("keydown", this.saveOnEnterCallback); + this.textAreaFront = this.contentEl.createEl("textarea"); + this.textAreaFront.addClass("sr-input"); + this.textAreaFront.setText(this.textFront ?? ""); + this.textAreaFront.addEventListener("keydown", this.saveOnEnterCallback); + + // Only for cards with seperator + if (this.seperator) { + this.textAreaBack = this.contentEl.createEl("textarea"); + this.textAreaBack.addClass("sr-input"); + this.textAreaBack.setText(this.textBack ?? ""); + this.textAreaBack.addEventListener("keydown", this.saveOnEnterCallback); + } this._createResponse(this.contentEl); } @@ -66,7 +101,7 @@ export class FlashcardEditModal extends Modal { onOpen() { super.onOpen(); - this.textArea.focus(); + this.textAreaFront.focus(); } /** @@ -93,7 +128,19 @@ export class FlashcardEditModal extends Modal { private save() { this.didSaveChanges = true; - this.changedText = this.textArea.value; + this.changedText = this.textAreaFront.value; + if (this.seperator) { + // New line at end of Front + if (this.multilineSeperator && !this.textAreaFront.value.endsWith("\n")) { + this.changedText += "\n"; + } + this.changedText += this.seperator; + // New line at start of Back + if (this.multilineSeperator && !this.textAreaBack.value.startsWith("\n")) { + this.changedText += "\n"; + } + this.changedText += this.textAreaBack.value; + } this.close(); } @@ -107,7 +154,7 @@ export class FlashcardEditModal extends Modal { } private removeInputListener() { - this.textArea.removeEventListener("keydown", this.saveOnEnterCallback); + this.textAreaFront.removeEventListener("keydown", this.saveOnEnterCallback); } // -> Response section diff --git a/src/gui/FlashcardModal.tsx b/src/gui/FlashcardModal.tsx index 9ff4b372..3a621519 100644 --- a/src/gui/FlashcardModal.tsx +++ b/src/gui/FlashcardModal.tsx @@ -118,7 +118,7 @@ export class FlashcardModal extends Modal { // Just the question/answer text; without any preceding topic tag const textPrompt = currentQ.questionText.actualQuestion; - const editModal = FlashcardEditModal.Prompt(this.app, textPrompt); + const editModal = FlashcardEditModal.Prompt(this.app, this.settings, textPrompt); editModal .then(async (modifiedCardText) => { this.reviewSequencer.updateCurrentQuestionText(modifiedCardText); diff --git a/src/util/utils.ts b/src/util/utils.ts index 6e4bfef8..11c3a1b9 100644 --- a/src/util/utils.ts +++ b/src/util/utils.ts @@ -219,3 +219,21 @@ export function parseObsidianFrontmatterTag(tagStr: string): string[] { } return result; } + +/** + * Checks a text contains one of the given seperators. + * Priority: Exact match > position in text > order of input seperators + * + * @param text Text to check one of the seperators is included + * @param seperators The seperators to check for + * @returns The first seperator that is included or null if none of them included. + */ +export function includedSeperator(text: string, seperators: string[]): string | null { + // https://www.30secondsofcode.org/js/s/array-has-only-one-match-or-many/ + const sep = seperators.filter((value) => text.includes(value)); + if (sep.length <= 0) { + return null; + } + sep.sort((a, b) => (a.includes(b) || b.includes(a) ? b.length - a.length : 0)); + return sep[0]; +} diff --git a/tests/unit/util/utils.test.ts b/tests/unit/util/utils.test.ts index bcfcf464..f887a420 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, + includedSeperator, literalStringReplace, } from "src/util/utils"; @@ -243,3 +244,25 @@ describe("findLineIndexOfSearchStringIgnoringWs", () => { expect(findLineIndexOfSearchStringIgnoringWs(lines, "??")).toEqual(2); }); }); + +describe("includedSperator", () => { + const sep = ["::", ":::", "?", "??"]; + + test("No Match", () => { + expect(includedSeperator("Question!!Answer", sep)).toBe(null); + expect(includedSeperator("Question:Answer", sep)).toBe(null); + }); + test("No sep", () => { + expect(includedSeperator("Question!!Answer", [])).toBe(null); + }); + test("One Match", () => { + expect(includedSeperator("Question::Answer", sep)).toBe("::"); + expect(includedSeperator("Question?Answer", sep)).toBe("?"); + }); + test("Multiple Match", () => { + expect(includedSeperator("Question::Answer?", sep)).toBe("::"); + expect(includedSeperator("Question:::Answer::", sep)).toBe(":::"); + expect(includedSeperator("Question??Answer::", sep)).toBe("::"); + expect(includedSeperator("Question??Answer?", sep)).toBe("??"); + }); +});