From bfca7013f8c653df928500281eb097197a72fce6 Mon Sep 17 00:00:00 2001 From: ronzulu <75528127+ronzulu@users.noreply.github.com> Date: Fri, 2 Feb 2024 13:21:45 +1100 Subject: [PATCH 01/12] Nearly completed --- src/NoteFileLoader.ts | 4 ++- src/NoteParser.ts | 5 +-- src/NoteQuestionParser.ts | 14 ++++++--- src/Question.ts | 16 +++++++--- src/SRFile.ts | 21 +++++++++++++ src/gui/flashcard-modal.tsx | 4 +-- src/main.ts | 11 +++++-- src/util/RenderMarkdownWrapper.ts | 17 ++++++++-- src/util/TextDirection.ts | 4 +++ src/util/utils.ts | 8 +++++ tests/unit/Note.test.ts | 7 +++-- tests/unit/NoteFileLoader.test.ts | 5 +-- tests/unit/NoteParser.test.ts | 3 +- tests/unit/NoteQuestionParser.test.ts | 45 +++++++++++++++++---------- tests/unit/Question.test.ts | 5 +-- tests/unit/SampleItems.ts | 3 +- tests/unit/util/utils.test.ts | 24 +++++++++++++- 17 files changed, 150 insertions(+), 46 deletions(-) create mode 100644 src/util/TextDirection.ts diff --git a/src/NoteFileLoader.ts b/src/NoteFileLoader.ts index 83ed69e9..d2830b07 100644 --- a/src/NoteFileLoader.ts +++ b/src/NoteFileLoader.ts @@ -4,6 +4,7 @@ import { Question } from "./Question"; import { TopicPath } from "./TopicPath"; import { NoteQuestionParser } from "./NoteQuestionParser"; import { SRSettings } from "./settings"; +import { TextDirection } from "./util/TextDirection"; export class NoteFileLoader { fileText: string; @@ -16,13 +17,14 @@ export class NoteFileLoader { this.settings = settings; } - async load(noteFile: ISRFile, noteTopicPath: TopicPath): Promise { + async load(noteFile: ISRFile, defaultTextDirection: TextDirection, noteTopicPath: TopicPath): Promise { this.noteFile = noteFile; const questionParser: NoteQuestionParser = new NoteQuestionParser(this.settings); const questionList: Question[] = await questionParser.createQuestionList( noteFile, + defaultTextDirection, noteTopicPath, ); diff --git a/src/NoteParser.ts b/src/NoteParser.ts index 01a00ab6..8dc7686d 100644 --- a/src/NoteParser.ts +++ b/src/NoteParser.ts @@ -3,6 +3,7 @@ import { ISRFile } from "./SRFile"; import { Note } from "./Note"; import { SRSettings } from "./settings"; import { TopicPath } from "./TopicPath"; +import { TextDirection } from "./util/TextDirection"; export class NoteParser { settings: SRSettings; @@ -12,9 +13,9 @@ export class NoteParser { this.settings = settings; } - async parse(noteFile: ISRFile, folderTopicPath: TopicPath): Promise { + async parse(noteFile: ISRFile, defaultTextDirection: TextDirection, folderTopicPath: TopicPath): Promise { const questionParser: NoteQuestionParser = new NoteQuestionParser(this.settings); - const questions = await questionParser.createQuestionList(noteFile, folderTopicPath); + const questions = await questionParser.createQuestionList(noteFile, defaultTextDirection, folderTopicPath); const result: Note = new Note(noteFile, questions); return result; diff --git a/src/NoteQuestionParser.ts b/src/NoteQuestionParser.ts index fdd63bc2..b3027bb5 100644 --- a/src/NoteQuestionParser.ts +++ b/src/NoteQuestionParser.ts @@ -6,6 +6,7 @@ import { CardFrontBack, CardFrontBackUtil } from "./QuestionType"; import { SRSettings } from "./settings"; import { ISRFile } from "./SRFile"; import { TopicPath } from "./TopicPath"; +import { TextDirection } from "./util/TextDirection"; export class ParsedQuestionInfo { cardType: CardType; @@ -29,7 +30,7 @@ export class NoteQuestionParser { this.settings = settings; } - async createQuestionList(noteFile: ISRFile, folderTopicPath: TopicPath): Promise { + async createQuestionList(noteFile: ISRFile, defaultTextDirection: TextDirection, folderTopicPath: TopicPath): Promise { this.noteFile = noteFile; const noteText: string = await noteFile.read(); let noteTopicPath: TopicPath; @@ -39,11 +40,13 @@ export class NoteQuestionParser { const tagList: string[] = noteFile.getAllTags(); noteTopicPath = this.determineTopicPathFromTags(tagList); } - const result: Question[] = this.doCreateQuestionList(noteText, noteTopicPath); + let textDirection: TextDirection | null = noteFile.getTextDirection(); + if (textDirection == null) textDirection = defaultTextDirection; + const result: Question[] = this.doCreateQuestionList(noteText, textDirection, noteTopicPath); return result; } - private doCreateQuestionList(noteText: string, noteTopicPath: TopicPath): Question[] { + private doCreateQuestionList(noteText: string, textDirection: TextDirection, noteTopicPath: TopicPath): Question[] { this.noteText = noteText; this.noteTopicPath = noteTopicPath; @@ -51,7 +54,7 @@ export class NoteQuestionParser { const parsedQuestionInfoList: [CardType, string, number][] = this.parseQuestions(); for (const t of parsedQuestionInfoList) { const parsedQuestionInfo: ParsedQuestionInfo = new ParsedQuestionInfo(t[0], t[1], t[2]); - const question: Question = this.createQuestionObject(parsedQuestionInfo); + const question: Question = this.createQuestionObject(parsedQuestionInfo, textDirection); // Each rawCardText can turn into multiple CardFrontBack's (e.g. CardType.Cloze, CardType.SingleLineReversed) const cardFrontBackList: CardFrontBack[] = CardFrontBackUtil.expand( @@ -94,7 +97,7 @@ export class NoteQuestionParser { return result; } - private createQuestionObject(parsedQuestionInfo: ParsedQuestionInfo): Question { + private createQuestionObject(parsedQuestionInfo: ParsedQuestionInfo, textDirection: TextDirection): Question { const { cardType, cardText, lineNo } = parsedQuestionInfo; const questionContext: string[] = this.noteFile.getQuestionContext(lineNo); @@ -103,6 +106,7 @@ export class NoteQuestionParser { cardType, this.noteTopicPath, cardText, + textDirection, lineNo, questionContext, ); diff --git a/src/Question.ts b/src/Question.ts index 1e181381..9cf13b02 100644 --- a/src/Question.ts +++ b/src/Question.ts @@ -10,6 +10,7 @@ import { Note } from "./Note"; import { SRSettings } from "./settings"; import { TopicPath, TopicPathWithWs } from "./TopicPath"; import { MultiLineTextFinder } from "./util/MultiLineTextFinder"; +import { TextDirection } from "./util/TextDirection"; import { cyrb53, stringTrimStart } from "./util/utils"; export enum CardType { @@ -86,6 +87,9 @@ export class QuestionText { // The question text, e.g. "Q1::A1" with leading/trailing whitespace as described above actualQuestion: string; + // Either LTR or RTL + textDirection: TextDirection; + // The block identifier (optional), e.g. "^quote-of-the-day" // Format of block identifiers: // https://help.obsidian.md/Linking+notes+and+files/Internal+links#Link+to+a+block+in+a+note @@ -101,11 +105,13 @@ export class QuestionText { original: string, topicPathWithWs: TopicPathWithWs, actualQuestion: string, + textDirection: TextDirection, blockId: string, ) { this.original = original; this.topicPathWithWs = topicPathWithWs; this.actualQuestion = actualQuestion; + this.textDirection = textDirection; this.obsidianBlockId = blockId; // The hash is generated based on the topic and question, explicitly not the schedule or obsidian block ID @@ -116,10 +122,10 @@ export class QuestionText { return this.actualQuestion.endsWith("```"); } - static create(original: string, settings: SRSettings): QuestionText { + static create(original: string, textDirection: TextDirection, settings: SRSettings): QuestionText { const [topicPathWithWs, actualQuestion, blockId] = this.splitText(original, settings); - return new QuestionText(original, topicPathWithWs, actualQuestion, blockId); + return new QuestionText(original, topicPathWithWs, actualQuestion, textDirection, blockId); } static splitText(original: string, settings: SRSettings): [TopicPathWithWs, string, string] { @@ -257,7 +263,8 @@ export class Question { let newText = MultiLineTextFinder.findAndReplace(noteText, originalText, replacementText); if (newText) { - this.questionText = QuestionText.create(replacementText, settings); + // Don't support changing the textDirection setting + this.questionText = QuestionText.create(replacementText, this.questionText.textDirection, settings); } else { console.error( `updateQuestionText: Text not found: ${originalText.substring( @@ -283,11 +290,12 @@ export class Question { questionType: CardType, noteTopicPath: TopicPath, originalText: string, + textDirection: TextDirection, lineNo: number, context: string[], ): Question { const hasEditLaterTag = originalText.includes(settings.editLaterTag); - const questionText: QuestionText = QuestionText.create(originalText, settings); + const questionText: QuestionText = QuestionText.create(originalText, textDirection, settings); let topicPath: TopicPath = noteTopicPath; if (questionText.topicPathWithWs) { diff --git a/src/SRFile.ts b/src/SRFile.ts index 46378070..3a6bc2e9 100644 --- a/src/SRFile.ts +++ b/src/SRFile.ts @@ -6,12 +6,14 @@ import { HeadingCache, } from "obsidian"; import { getAllTagsFromText } from "./util/utils"; +import { TextDirection } from "./util/TextDirection"; export interface ISRFile { get path(): string; get basename(): string; getAllTags(): string[]; getQuestionContext(cardLine: number): string[]; + getTextDirection(): TextDirection | null; read(): Promise; write(content: string): Promise; } @@ -64,6 +66,21 @@ export class SrTFile implements ISRFile { return result; } + getTextDirection(): TextDirection | null { + let result: TextDirection = null; + const fileCache = this.metadataCache.getFileCache(this.file); + const frontMatter = fileCache?.frontmatter; + if (frontMatter && frontMatter?.direction) { + // Don't know why the try/catch is needed; but copied from Obsidian RTL plug-in getFrontMatterDirection() + try { + const str: string = (frontMatter.direction + '').toLowerCase(); + result = (str == "rtl") ? TextDirection.Rtl : TextDirection.Ltr; + } + catch (error) {} + } + return result; + } + async read(): Promise { return await this.vault.read(this.file); } @@ -99,6 +116,10 @@ export class UnitTestSRFile implements ISRFile { return []; } + getTextDirection(): TextDirection | null { + return null; + } + async read(): Promise { return this.content; } diff --git a/src/gui/flashcard-modal.tsx b/src/gui/flashcard-modal.tsx index a574de4d..ae25e8c1 100644 --- a/src/gui/flashcard-modal.tsx +++ b/src/gui/flashcard-modal.tsx @@ -424,7 +424,7 @@ export class FlashcardModal extends Modal { this.plugin, this.currentNote.filePath, ); - wrapper.renderMarkdownWrapper(this.currentCard.back, this.flashcardView); + wrapper.renderMarkdownWrapper(this.currentCard.back, this.flashcardView, this.currentQuestion.questionText.textDirection); } private async processReview(response: ReviewResponse): Promise { @@ -460,7 +460,7 @@ export class FlashcardModal extends Modal { this.plugin, this.currentNote.filePath, ); - await wrapper.renderMarkdownWrapper(this.currentCard.front, this.flashcardView); + await wrapper.renderMarkdownWrapper(this.currentCard.front, this.flashcardView, this.currentQuestion.questionText.textDirection); if (this.reviewMode == FlashcardReviewMode.Cram) { // Same for mobile/desktop diff --git a/src/main.ts b/src/main.ts index 1a46390e..c3946d5f 100644 --- a/src/main.ts +++ b/src/main.ts @@ -34,6 +34,8 @@ import { NoteEaseCalculator } from "./NoteEaseCalculator"; import { DeckTreeStatsCalculator } from "./DeckTreeStatsCalculator"; import { NoteEaseList } from "./NoteEaseList"; import { QuestionPostponementList } from "./QuestionPostponementList"; +import { TextDirection } from "./util/TextDirection"; +import { convertToStringOrEmpty } from "./util/utils"; interface PluginData { settings: SRSettings; @@ -326,7 +328,6 @@ export default class SRPlugin extends Plugin { if (cardOrder === undefined) cardOrder = CardOrder.DueFirstSequential; let deckOrder: DeckOrder = DeckOrder[settings.flashcardDeckOrder as keyof typeof DeckOrder]; if (deckOrder === undefined) deckOrder = DeckOrder.PrevDeckComplete_Sequential; - console.log(`createDeckTreeIterator: CardOrder: ${cardOrder}, DeckOrder: ${deckOrder}`); const iteratorOrder: IIteratorOrder = { deckOrder, @@ -534,11 +535,17 @@ export default class SRPlugin extends Plugin { async loadNote(noteFile: TFile, topicPath: TopicPath): Promise { const loader: NoteFileLoader = new NoteFileLoader(this.data.settings); - const note: Note = await loader.load(this.createSrTFile(noteFile), topicPath); + const note: Note = await loader.load(this.createSrTFile(noteFile), this.getObsidianRtlSetting(), topicPath); if (note.hasChanged) note.writeNoteFile(this.data.settings); return note; } + private getObsidianRtlSetting(): TextDirection { + // Get the direction with Obsidian's own setting + const v: any = (this.app.vault as any).getConfig('rightToLeft'); + return (convertToStringOrEmpty(v) == "true") ? TextDirection.Rtl : TextDirection.Ltr; + } + async saveReviewResponse(note: TFile, response: ReviewResponse): Promise { const fileCachedData = this.app.metadataCache.getFileCache(note) || {}; const frontmatter: FrontMatterCache | Record = diff --git a/src/util/RenderMarkdownWrapper.ts b/src/util/RenderMarkdownWrapper.ts index 8b82f763..1c890bba 100644 --- a/src/util/RenderMarkdownWrapper.ts +++ b/src/util/RenderMarkdownWrapper.ts @@ -6,6 +6,7 @@ import { NON_LETTER_SYMBOLS_REGEX, } from "../constants"; import SRPlugin from "../main"; +import { TextDirection } from "./TextDirection"; export class RenderMarkdownWrapper { private app: App; @@ -23,13 +24,20 @@ export class RenderMarkdownWrapper { async renderMarkdownWrapper( markdownString: string, containerEl: HTMLElement, + textDirection: TextDirection, recursiveDepth = 0, ): Promise { if (recursiveDepth > 4) return; - MarkdownRenderer.renderMarkdown(markdownString, containerEl, this.notePath, this.plugin); + let el: HTMLElement; + if (textDirection == TextDirection.Rtl) { + el = containerEl.createDiv(); + el.setAttribute("dir", "rtl"); + } else el = containerEl; - containerEl.findAll(".internal-embed").forEach((el) => { + MarkdownRenderer.renderMarkdown(markdownString, el, this.notePath, this.plugin); + + el.findAll(".internal-embed").forEach((el) => { const link = this.parseLink(el.getAttribute("src")); // file does not exist, display dead link @@ -145,6 +153,9 @@ export class RenderMarkdownWrapper { blockText = text; } - this.renderMarkdownWrapper(blockText, el, recursiveDepth + 1); + // We are operating here within the parent container. + // It already has the rtl div if necessary. + // We don't need another rtl div, so we can set direction to LTR + this.renderMarkdownWrapper(blockText, el, TextDirection.Ltr, recursiveDepth + 1); } } diff --git a/src/util/TextDirection.ts b/src/util/TextDirection.ts new file mode 100644 index 00000000..6c5fe18f --- /dev/null +++ b/src/util/TextDirection.ts @@ -0,0 +1,4 @@ +export enum TextDirection { + Ltr, + Rtl, +} diff --git a/src/util/utils.ts b/src/util/utils.ts index 6517c83e..6b2b9fd3 100644 --- a/src/util/utils.ts +++ b/src/util/utils.ts @@ -102,3 +102,11 @@ export function stringTrimStart(str: string): [string, string] { const ws: string = str.substring(0, wsCount); return [ws, trimmed]; } + +export function convertToStringOrEmpty(v: any): string { + let result: string = ""; + if (v != null && v != undefined) { + result = v + ""; + } + return result; +} diff --git a/tests/unit/Note.test.ts b/tests/unit/Note.test.ts index 96b59024..b595c41b 100644 --- a/tests/unit/Note.test.ts +++ b/tests/unit/Note.test.ts @@ -6,6 +6,7 @@ import { Note } from "src/Note"; import { Question } from "src/Question"; import { DEFAULT_SETTINGS } from "src/settings"; import { NoteFileLoader } from "src/NoteFileLoader"; +import { TextDirection } from "src/util/TextDirection"; let parser: NoteParser = new NoteParser(DEFAULT_SETTINGS); var noteFileLoader: NoteFileLoader = new NoteFileLoader(DEFAULT_SETTINGS); @@ -19,7 +20,7 @@ Q3::A3 `; let file: UnitTestSRFile = new UnitTestSRFile(noteText); let folderTopicPath = TopicPath.emptyPath; - let note: Note = await parser.parse(file, folderTopicPath); + let note: Note = await parser.parse(file, TextDirection.Ltr, folderTopicPath); let deck: Deck = Deck.emptyDeck; note.appendCardsToDeck(deck); let subdeck: Deck = deck.getDeck(new TopicPath(["flashcards", "test"])); @@ -37,7 +38,7 @@ Q3:::A3 `; let file: UnitTestSRFile = new UnitTestSRFile(noteText); let folderTopicPath = TopicPath.emptyPath; - let note: Note = await parser.parse(file, folderTopicPath); + let note: Note = await parser.parse(file, TextDirection.Ltr, folderTopicPath); let deck: Deck = Deck.emptyDeck; note.appendCardsToDeck(deck); let subdeck: Deck = deck.getDeck(new TopicPath(["flashcards", "test"])); @@ -59,7 +60,7 @@ Q3:::A3 `; let file: UnitTestSRFile = new UnitTestSRFile(originalText); - let note: Note = await noteFileLoader.load(file, TopicPath.emptyPath); + let note: Note = await noteFileLoader.load(file, TextDirection.Ltr, TopicPath.emptyPath); await note.writeNoteFile(DEFAULT_SETTINGS); let updatedText: string = file.content; diff --git a/tests/unit/NoteFileLoader.test.ts b/tests/unit/NoteFileLoader.test.ts index ab07ae5f..8cdaeaa2 100644 --- a/tests/unit/NoteFileLoader.test.ts +++ b/tests/unit/NoteFileLoader.test.ts @@ -3,6 +3,7 @@ import { NoteFileLoader } from "src/NoteFileLoader"; import { UnitTestSRFile } from "src/SRFile"; import { TopicPath } from "src/TopicPath"; import { DEFAULT_SETTINGS } from "src/settings"; +import { TextDirection } from "src/util/TextDirection"; var noteFileLoader: NoteFileLoader = new NoteFileLoader(DEFAULT_SETTINGS); @@ -16,7 +17,7 @@ Q3:::A3 `; let file: UnitTestSRFile = new UnitTestSRFile(noteText); - let note: Note = await noteFileLoader.load(file, TopicPath.emptyPath); + let note: Note = await noteFileLoader.load(file, TextDirection.Ltr, TopicPath.emptyPath); expect(note.hasChanged).toEqual(false); }); @@ -29,7 +30,7 @@ Q3:::A3 `; let file: UnitTestSRFile = new UnitTestSRFile(noteText); - let note: Note = await noteFileLoader.load(file, TopicPath.emptyPath); + let note: Note = await noteFileLoader.load(file, TextDirection.Ltr, TopicPath.emptyPath); expect(note.hasChanged).toEqual(true); }); }); diff --git a/tests/unit/NoteParser.test.ts b/tests/unit/NoteParser.test.ts index fb57ed00..84ecd0b4 100644 --- a/tests/unit/NoteParser.test.ts +++ b/tests/unit/NoteParser.test.ts @@ -5,6 +5,7 @@ import { Note } from "src/Note"; import { Question } from "src/Question"; import { DEFAULT_SETTINGS } from "src/settings"; import { setupStaticDateProvider_20230906 } from "src/util/DateProvider"; +import { TextDirection } from "src/util/TextDirection"; let parser: NoteParser = new NoteParser(DEFAULT_SETTINGS); @@ -21,7 +22,7 @@ Q3::A3 `; let file: UnitTestSRFile = new UnitTestSRFile(noteText); let folderTopicPath = TopicPath.emptyPath; - let note: Note = await parser.parse(file, folderTopicPath); + let note: Note = await parser.parse(file, TextDirection.Ltr, folderTopicPath); let questionList = note.questionList; expect(questionList.length).toEqual(3); }); diff --git a/tests/unit/NoteQuestionParser.test.ts b/tests/unit/NoteQuestionParser.test.ts index 8893dc35..4ff589ec 100644 --- a/tests/unit/NoteQuestionParser.test.ts +++ b/tests/unit/NoteQuestionParser.test.ts @@ -7,6 +7,7 @@ import { TopicPath } from "src/TopicPath"; import { createTest_NoteQuestionParser } from "./SampleItems"; import { ISRFile, UnitTestSRFile } from "src/SRFile"; import { setupStaticDateProvider_20230906 } from "src/util/DateProvider"; +import { TextDirection } from "src/util/TextDirection"; let parserWithDefaultSettings: NoteQuestionParser = createTest_NoteQuestionParser(DEFAULT_SETTINGS); let settings_ConvertFoldersToDecks: SRSettings = { ...DEFAULT_SETTINGS }; @@ -24,7 +25,7 @@ test("No questions in the text", async () => { let folderTopicPath: TopicPath = TopicPath.emptyPath; let noteFile: ISRFile = new UnitTestSRFile(noteText); - expect(await parserWithDefaultSettings.createQuestionList(noteFile, folderTopicPath)).toEqual( + expect(await parserWithDefaultSettings.createQuestionList(noteFile, TextDirection.Ltr, folderTopicPath)).toEqual( [], ); }); @@ -57,7 +58,7 @@ A::B }, ]; expect( - await parserWithDefaultSettings.createQuestionList(noteFile, folderTopicPath), + await parserWithDefaultSettings.createQuestionList(noteFile, TextDirection.Ltr, folderTopicPath), ).toMatchObject(expected); }); @@ -96,7 +97,7 @@ A::B }, ]; expect( - await parserWithDefaultSettings.createQuestionList(noteFile, folderTopicPath), + await parserWithDefaultSettings.createQuestionList(noteFile, TextDirection.Ltr, folderTopicPath), ).toMatchObject(expected); }); }); @@ -130,7 +131,7 @@ A::B ^d7cee0 }, ]; expect( - await parserWithDefaultSettings.createQuestionList(noteFile, folderTopicPath), + await parserWithDefaultSettings.createQuestionList(noteFile, TextDirection.Ltr, folderTopicPath), ).toMatchObject(expected); }); @@ -170,7 +171,7 @@ A::B ^d7cee0 }, ]; expect( - await parserWithDefaultSettings.createQuestionList(noteFile, folderTopicPath), + await parserWithDefaultSettings.createQuestionList(noteFile, TextDirection.Ltr, folderTopicPath), ).toMatchObject(expected); }); @@ -208,7 +209,7 @@ A::B ^d7cee0 }, ]; expect( - await parserWithDefaultSettings.createQuestionList(noteFile, folderTopicPath), + await parserWithDefaultSettings.createQuestionList(noteFile, TextDirection.Ltr, folderTopicPath), ).toMatchObject(expected); }); @@ -245,7 +246,7 @@ A::B ^d7cee0 }, ]; expect( - await parserWithDefaultSettings.createQuestionList(noteFile, folderTopicPath), + await parserWithDefaultSettings.createQuestionList(noteFile, TextDirection.Ltr, folderTopicPath), ).toMatchObject(expected); }); }); @@ -259,7 +260,8 @@ Q2::A2 let noteFile: ISRFile = new UnitTestSRFile(noteText); let folderTopicPath: TopicPath = TopicPath.emptyPath; let questionList: Question[] = await parser_ConvertFoldersToDecks.createQuestionList( - noteFile, + noteFile, + TextDirection.Ltr, folderTopicPath, ); expect(questionList.length).toEqual(2); @@ -275,7 +277,8 @@ Q3::A3 let folderTopicPath: TopicPath = new TopicPath(["flashcards", "science"]); let questionList: Question[] = await parser_ConvertFoldersToDecks.createQuestionList( - noteFile, + noteFile, + TextDirection.Ltr, folderTopicPath, ); expect(questionList.length).toEqual(3); @@ -301,7 +304,8 @@ describe("Handling tags within note", () => { let noteFile: ISRFile = new UnitTestSRFile(noteText); let folderTopicPath: TopicPath = new TopicPath(["folder", "subfolder"]); let questionList: Question[] = await parser2.createQuestionList( - noteFile, + noteFile, + TextDirection.Ltr, folderTopicPath, ); expect(questionList.length).toEqual(3); @@ -317,7 +321,8 @@ Q1::A1 let noteFile: ISRFile = new UnitTestSRFile(noteText); let folderTopicPath: TopicPath = new TopicPath(["folder", "subfolder"]); let questionList: Question[] = await parser2.createQuestionList( - noteFile, + noteFile, + TextDirection.Ltr, folderTopicPath, ); expect(questionList.length).toEqual(1); @@ -335,7 +340,8 @@ Q1::A1 let noteFile: ISRFile = new UnitTestSRFile(noteText); let folderTopicPath: TopicPath = new TopicPath(["folder", "subfolder"]); let questionList: Question[] = await parser2.createQuestionList( - noteFile, + noteFile, + TextDirection.Ltr, folderTopicPath, ); expect(questionList.length).toEqual(1); @@ -357,7 +363,8 @@ Q1::A1 let expectedPath: TopicPath = new TopicPath(["flashcards", "test"]); let folderTopicPath: TopicPath = TopicPath.emptyPath; let questionList: Question[] = await parserWithDefaultSettings.createQuestionList( - noteFile, + noteFile, + TextDirection.Ltr, folderTopicPath, ); expect(questionList.length).toEqual(3); @@ -376,7 +383,8 @@ Q1::A1 let folderTopicPath: TopicPath = TopicPath.emptyPath; let questionList: Question[] = await parserWithDefaultSettings.createQuestionList( - noteFile, + noteFile, + TextDirection.Ltr, folderTopicPath, ); expect(questionList.length).toEqual(3); @@ -397,7 +405,8 @@ Q1::A1 let expectedPath: TopicPath = new TopicPath(["flashcards", "test"]); let folderTopicPath: TopicPath = TopicPath.emptyPath; let questionList: Question[] = await parserWithDefaultSettings.createQuestionList( - noteFile, + noteFile, + TextDirection.Ltr, folderTopicPath, ); expect(questionList.length).toEqual(3); @@ -418,7 +427,8 @@ Q1::A1 let expectedPath: TopicPath = new TopicPath(["flashcards", "test"]); let folderTopicPath: TopicPath = TopicPath.emptyPath; let questionList: Question[] = await parserWithDefaultSettings.createQuestionList( - noteFile, + noteFile, + TextDirection.Ltr, folderTopicPath, ); expect(questionList.length).toEqual(3); @@ -439,7 +449,8 @@ Q1::A1 let expectedPath: TopicPath = new TopicPath(["flashcards", "science"]); let folderTopicPath: TopicPath = TopicPath.emptyPath; let questionList: Question[] = await parserWithDefaultSettings.createQuestionList( - noteFile, + noteFile, + TextDirection.Ltr, folderTopicPath, ); expect(questionList.length).toEqual(1); diff --git a/tests/unit/Question.test.ts b/tests/unit/Question.test.ts index 8ba2928f..e6c65c9b 100644 --- a/tests/unit/Question.test.ts +++ b/tests/unit/Question.test.ts @@ -1,6 +1,7 @@ import { TopicPath } from "src/TopicPath"; import { DEFAULT_SETTINGS, SRSettings } from "src/settings"; import { Question, QuestionText } from "src/Question"; +import { TextDirection } from "src/util/TextDirection"; let settings_cardCommentOnSameLine: SRSettings = { ...DEFAULT_SETTINGS }; settings_cardCommentOnSameLine.cardCommentOnSameLine = true; @@ -13,7 +14,7 @@ describe("Question", () => { "```\nprint('Hello World!')\nprint('Howdy?')\nlambda x: x[0]\n```"; let question: Question = new Question({ - questionText: new QuestionText(text, null, text, null), + questionText: new QuestionText(text, null, text, TextDirection.Ltr, null), }); expect(question.getHtmlCommentSeparator(DEFAULT_SETTINGS)).toEqual("\n"); @@ -24,7 +25,7 @@ describe("Question", () => { let text: string = "Q1::A1"; let question: Question = new Question({ - questionText: new QuestionText(text, null, text, null), + questionText: new QuestionText(text, null, text, TextDirection.Ltr, null), }); expect(question.getHtmlCommentSeparator(DEFAULT_SETTINGS)).toEqual("\n"); diff --git a/tests/unit/SampleItems.ts b/tests/unit/SampleItems.ts index 8c5434fe..4ccb2395 100644 --- a/tests/unit/SampleItems.ts +++ b/tests/unit/SampleItems.ts @@ -8,6 +8,7 @@ import { CardFrontBack, CardFrontBackUtil } from "src/QuestionType"; import { DEFAULT_SETTINGS, SRSettings } from "src/settings"; import { UnitTestSRFile } from "src/SRFile"; import { TopicPath } from "src/TopicPath"; +import { TextDirection } from "src/util/TextDirection"; export function createTest_NoteQuestionParser(settings: SRSettings): NoteQuestionParser { let questionParser: NoteQuestionParser = new NoteQuestionParser(settings); @@ -50,7 +51,7 @@ Q3::A3`; let deck: Deck = new Deck("Root", null); let topicPath: TopicPath = TopicPath.emptyPath; let noteParser: NoteParser = createTest_NoteParser(); - let note: Note = await noteParser.parse(file, folderTopicPath); + let note: Note = await noteParser.parse(file, TextDirection.Ltr, folderTopicPath); note.appendCardsToDeck(deck); return deck; } diff --git a/tests/unit/util/utils.test.ts b/tests/unit/util/utils.test.ts index 49e0c511..c7044c98 100644 --- a/tests/unit/util/utils.test.ts +++ b/tests/unit/util/utils.test.ts @@ -1,5 +1,5 @@ import { YAML_FRONT_MATTER_REGEX } from "src/constants"; -import { literalStringReplace } from "src/util/utils"; +import { convertToStringOrEmpty, literalStringReplace } from "src/util/utils"; describe("literalStringReplace", () => { test("Replacement string doesn't have any dollar signs", async () => { @@ -74,6 +74,28 @@ $$\\huge F_g=\\frac {G m_1 m_2}{d^2}$$ }); }); +describe("convertToStringOrEmpty", () => { + test("undefined returns empty string", () => { + expect(convertToStringOrEmpty(undefined)).toEqual(""); + }); + + test("null returns empty string", () => { + expect(convertToStringOrEmpty(null)).toEqual(""); + }); + + test("empty string returns empty string", () => { + expect(convertToStringOrEmpty("")).toEqual(""); + }); + + test("string returned unchanged", () => { + expect(convertToStringOrEmpty("Hello")).toEqual("Hello"); + }); + + test("number is converted to string", () => { + expect(convertToStringOrEmpty(5)).toEqual("5"); + }); +}); + function createTestStr1(sep: string): string { return `---${sep}sr-due: 2024-08-10${sep}sr-interval: 273${sep}sr-ease: 309${sep}---`; } From 8b101a0087f36eb5b1cf508a82ae406ceadddfa7 Mon Sep 17 00:00:00 2001 From: ronzulu <75528127+ronzulu@users.noreply.github.com> Date: Fri, 2 Feb 2024 13:41:58 +1100 Subject: [PATCH 02/12] Added RTL support for flashcards edit modal --- src/gui/flashcard-modal.tsx | 2 +- src/gui/flashcards-edit-modal.ts | 12 +++++++++--- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/src/gui/flashcard-modal.tsx b/src/gui/flashcard-modal.tsx index ae25e8c1..76a425df 100644 --- a/src/gui/flashcard-modal.tsx +++ b/src/gui/flashcard-modal.tsx @@ -393,7 +393,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, textPrompt, currentQ.questionText.textDirection); editModal .then(async (modifiedCardText) => { this.reviewSequencer.updateCurrentQuestionText(modifiedCardText); diff --git a/src/gui/flashcards-edit-modal.ts b/src/gui/flashcards-edit-modal.ts index a6c0c1e4..1f9fb152 100644 --- a/src/gui/flashcards-edit-modal.ts +++ b/src/gui/flashcards-edit-modal.ts @@ -1,5 +1,6 @@ import { App, ButtonComponent, Modal, TextAreaComponent } from "obsidian"; import { t } from "src/lang/helpers"; +import { TextDirection } from "src/util/TextDirection"; // from https://github.com/chhoumann/quickadd/blob/bce0b4cdac44b867854d6233796e3406dfd163c6/src/gui/GenericInputPrompt/GenericInputPrompt.ts#L5 export class FlashcardEditModal extends Modal { @@ -12,17 +13,19 @@ export class FlashcardEditModal extends Modal { private didSubmit = false; private inputComponent: TextAreaComponent; private readonly modalText: string; + private textDirection: TextDirection; - public static Prompt(app: App, placeholder: string): Promise { - const newPromptModal = new FlashcardEditModal(app, placeholder); + public static Prompt(app: App, placeholder: string, textDirection: TextDirection): Promise { + const newPromptModal = new FlashcardEditModal(app, placeholder, textDirection); return newPromptModal.waitForClose; } - constructor(app: App, existingText: string) { + constructor(app: App, existingText: string, textDirection: TextDirection) { super(app); this.titleEl.setText(t("EDIT_CARD")); this.titleEl.addClass("sr-centered"); this.modalText = existingText; this.input = existingText; + this.textDirection = textDirection; this.waitForClose = new Promise((resolve, reject) => { this.resolvePromise = resolve; @@ -38,6 +41,9 @@ export class FlashcardEditModal extends Modal { const mainContentContainer: HTMLDivElement = this.contentEl.createDiv(); mainContentContainer.addClass("sr-flashcard-input-area"); + if (this.textDirection == TextDirection.Rtl) { + mainContentContainer.setAttribute("dir", "rtl"); + } this.inputComponent = this.createInputField(mainContentContainer, this.modalText); this.createButtonBar(mainContentContainer); } From bcbeae08a739436301cff021f89fedad3e167103 Mon Sep 17 00:00:00 2001 From: ronzulu <75528127+ronzulu@users.noreply.github.com> Date: Mon, 8 Apr 2024 08:38:13 +1000 Subject: [PATCH 03/12] Changes as part of the merge --- src/gui/EditModal.tsx | 13 ++++++++++--- src/gui/FlashcardReviewView.tsx | 4 ++-- tests/unit/helpers/UnitTestSRFile.ts | 5 +++++ 3 files changed, 17 insertions(+), 5 deletions(-) diff --git a/src/gui/EditModal.tsx b/src/gui/EditModal.tsx index 1feb19d5..1acc354a 100644 --- a/src/gui/EditModal.tsx +++ b/src/gui/EditModal.tsx @@ -1,5 +1,6 @@ import { App, Modal } from "obsidian"; import { t } from "src/lang/helpers"; +import { TextDirection } from "src/util/TextDirection"; // from https://github.com/chhoumann/quickadd/blob/bce0b4cdac44b867854d6233796e3406dfd163c6/src/gui/GenericInputPrompt/GenericInputPrompt.ts#L5 export class FlashcardEditModal extends Modal { @@ -17,17 +18,19 @@ export class FlashcardEditModal extends Modal { private rejectPromise: (reason?: any) => void; private didSaveChanges = false; private readonly modalText: string; + private textDirection: TextDirection; - public static Prompt(app: App, placeholder: string): Promise { - const newPromptModal = new FlashcardEditModal(app, placeholder); + public static Prompt(app: App, placeholder: string, textDirection: TextDirection): Promise { + const newPromptModal = new FlashcardEditModal(app, placeholder, textDirection); return newPromptModal.waitForClose; } - constructor(app: App, existingText: string) { + constructor(app: App, existingText: string, textDirection: TextDirection) { super(app); this.modalText = existingText; this.changedText = existingText; + this.textDirection = textDirection; this.waitForClose = new Promise((resolve, reject) => { this.resolvePromise = resolve; @@ -52,10 +55,14 @@ 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); + if (this.textDirection == TextDirection.Rtl) { + this.textArea.setAttribute("dir", "rtl"); + } this._createResponse(this.contentEl); } diff --git a/src/gui/FlashcardReviewView.tsx b/src/gui/FlashcardReviewView.tsx index cb6024b4..5f7c808a 100644 --- a/src/gui/FlashcardReviewView.tsx +++ b/src/gui/FlashcardReviewView.tsx @@ -136,7 +136,7 @@ export class FlashcardReviewView { this.plugin, this._currentNote.filePath, ); - await wrapper.renderMarkdownWrapper(this._currentCard.front, this.content); + await wrapper.renderMarkdownWrapper(this._currentCard.front, this.content, this._currentQuestion.questionText.textDirection); // Setup response buttons this._resetResponseButtons(); @@ -290,7 +290,7 @@ export class FlashcardReviewView { this.plugin, this._currentNote.filePath, ); - wrapper.renderMarkdownWrapper(this._currentCard.back, this.content); + wrapper.renderMarkdownWrapper(this._currentCard.back, this.content, this._currentQuestion.questionText.textDirection); // Show response buttons this.answerButton.addClass("sr-is-hidden"); diff --git a/tests/unit/helpers/UnitTestSRFile.ts b/tests/unit/helpers/UnitTestSRFile.ts index 3110f892..5bce0d4d 100644 --- a/tests/unit/helpers/UnitTestSRFile.ts +++ b/tests/unit/helpers/UnitTestSRFile.ts @@ -1,6 +1,7 @@ import { TagCache } from "obsidian"; import { ISRFile } from "src/SRFile"; import { unitTest_GetAllTagsFromTextEx } from "./UnitTestHelper"; +import { TextDirection } from "src/util/TextDirection"; export class UnitTestSRFile implements ISRFile { content: string; @@ -28,6 +29,10 @@ export class UnitTestSRFile implements ISRFile { return []; } + getTextDirection(): TextDirection | null { + return null; + } + async read(): Promise { return this.content; } From b25ca2aa53c9d1315f671ad7e05620d4a1e21d8b Mon Sep 17 00:00:00 2001 From: ronzulu <75528127+ronzulu@users.noreply.github.com> Date: Wed, 17 Apr 2024 23:14:02 +1000 Subject: [PATCH 04/12] post upstream master merge fixes --- src/NoteQuestionParser.ts | 12 +++++------- src/Question.ts | 3 +-- src/main.ts | 17 +++-------------- 3 files changed, 9 insertions(+), 23 deletions(-) diff --git a/src/NoteQuestionParser.ts b/src/NoteQuestionParser.ts index c74b2255..87217ed9 100644 --- a/src/NoteQuestionParser.ts +++ b/src/NoteQuestionParser.ts @@ -47,9 +47,11 @@ export class NoteQuestionParser { // There is no point doing it if there aren't any topic paths // Create the question list + let textDirection: TextDirection | null = noteFile.getTextDirection(); + if (textDirection == null) textDirection = defaultTextDirection; this.questionList = this.doCreateQuestionList( noteText, - defaultTextDirection, + textDirection, folderTopicPath, this.tagCacheList, ); @@ -68,10 +70,7 @@ export class NoteQuestionParser { } else { this.questionList = [] as Question[]; } - let textDirection: TextDirection | null = noteFile.getTextDirection(); - if (textDirection == null) textDirection = defaultTextDirection; - const result: Question[] = this.doCreateQuestionList(noteText, textDirection, folderTopicPath, tagCacheList); - return result; + return this.questionList; } private doCreateQuestionList(noteText: string, textDirection: TextDirection, folderTopicPath: TopicPath, @@ -84,7 +83,7 @@ export class NoteQuestionParser { const result: Question[] = []; const parsedQuestionInfoList: ParsedQuestionInfo[] = this.parseQuestions(); for (const parsedQuestionInfo of parsedQuestionInfoList) { - const question: Question = this.createQuestionObject(parsedQuestionInfo); + const question: Question = this.createQuestionObject(parsedQuestionInfo, textDirection); // Each rawCardText can turn into multiple CardFrontBack's (e.g. CardType.Cloze, CardType.SingleLineReversed) const cardFrontBackList: CardFrontBack[] = CardFrontBackUtil.expand( @@ -135,7 +134,6 @@ export class NoteQuestionParser { this.settings, parsedQuestionInfo, null, // We haven't worked out the TopicPathList yet - this.folderTopicPath, textDirection, questionContext, ); diff --git a/src/Question.ts b/src/Question.ts index 36acea3e..53b5df5a 100644 --- a/src/Question.ts +++ b/src/Question.ts @@ -304,7 +304,7 @@ export class Question { context: string[], ): Question { const hasEditLaterTag = parsedQuestionInfo.text.includes(settings.editLaterTag); - const questionText: QuestionText = QuestionText.create(parsedQuestionInfo.text, settings); + const questionText: QuestionText = QuestionText.create(parsedQuestionInfo.text, textDirection, settings); let topicPathList: TopicPathList = noteTopicPathList; if (questionText.topicPathWithWs) { @@ -316,7 +316,6 @@ export class Question { topicPathList, questionText, hasEditLaterTag, - textDirection, questionContext: context, cards: null, hasChanged: false, diff --git a/src/main.ts b/src/main.ts index 495aa7ab..9dafe145 100644 --- a/src/main.ts +++ b/src/main.ts @@ -548,25 +548,19 @@ export default class SRPlugin extends Plugin { if (this.getActiveLeaf(REVIEW_QUEUE_VIEW_TYPE)) this.reviewQueueView.redraw(); } - async loadNote(noteFile: TFile, topicPath: TopicPath): Promise { + async loadNote(noteFile: TFile): Promise { const loader: NoteFileLoader = new NoteFileLoader(this.data.settings); -/* HEAD - const note: Note = await loader.load(this.createSrTFile(noteFile), this.getObsidianRtlSetting(), topicPath); - if (note.hasChanged) note.writeNoteFile(this.data.settings); - const srFile: ISRFile = this.createSrTFile(noteFile); const folderTopicPath: TopicPath = TopicPath.getFolderPathFromFilename( srFile, this.data.settings, ); - const note: Note = await loader.load(this.createSrTFile(noteFile), folderTopicPath); + const note: Note = await loader.load(this.createSrTFile(noteFile), this.getObsidianRtlSetting(), folderTopicPath); if (note.hasChanged) { note.writeNoteFile(this.data.settings); } -upstream/master - return note; */ - return null; + return note; } private getObsidianRtlSetting(): TextDirection { @@ -701,7 +695,6 @@ upstream/master } if (this.data.settings.burySiblingCards) { - const topicPath: TopicPath = this.findTopicPath(this.createSrTFile(note)); const noteX: Note = await this.loadNote(note); for (const question of noteX.questionList) { this.data.buryList.push(question.questionText.textHash); @@ -805,10 +798,6 @@ upstream/master await this.saveData(this.data); } - findTopicPath(note: ISRFile): TopicPath { - return TopicPath.getTopicPathOfFile(note, this.data.settings); - } - private getActiveLeaf(type: string): WorkspaceLeaf | null { const leaves = this.app.workspace.getLeavesOfType(type); if (leaves.length == 0) { From 3a46d892e901c7ebc5e16bbf32258c97fe4cc4b4 Mon Sep 17 00:00:00 2001 From: ronzulu <75528127+ronzulu@users.noreply.github.com> Date: Wed, 17 Apr 2024 23:21:58 +1000 Subject: [PATCH 05/12] Minor code improvement --- src/util/RenderMarkdownWrapper.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/util/RenderMarkdownWrapper.ts b/src/util/RenderMarkdownWrapper.ts index 1c890bba..3f5f6266 100644 --- a/src/util/RenderMarkdownWrapper.ts +++ b/src/util/RenderMarkdownWrapper.ts @@ -24,7 +24,7 @@ export class RenderMarkdownWrapper { async renderMarkdownWrapper( markdownString: string, containerEl: HTMLElement, - textDirection: TextDirection, + textDirection: TextDirection | null, recursiveDepth = 0, ): Promise { if (recursiveDepth > 4) return; @@ -155,7 +155,7 @@ export class RenderMarkdownWrapper { // We are operating here within the parent container. // It already has the rtl div if necessary. - // We don't need another rtl div, so we can set direction to LTR - this.renderMarkdownWrapper(blockText, el, TextDirection.Ltr, recursiveDepth + 1); + // We don't need another rtl div, so we can set direction to null + this.renderMarkdownWrapper(blockText, el, null, recursiveDepth + 1); } } From b419b0d2034c4e4e1ed64f4f1dd814ab5417e849 Mon Sep 17 00:00:00 2001 From: ronzulu <75528127+ronzulu@users.noreply.github.com> Date: Wed, 17 Apr 2024 23:41:11 +1000 Subject: [PATCH 06/12] lint and format --- CHANGELOG.md | 2 +- CONTRIBUTING.md | 2 +- src/NoteFileLoader.ts | 8 ++- src/NoteParser.ts | 13 +++- src/NoteQuestionParser.ts | 25 +++++-- src/Question.ts | 22 ++++-- src/SRFile.ts | 25 +++---- src/gui/EditModal.tsx | 7 +- src/gui/FlashcardReviewView.tsx | 12 +++- src/main.ts | 14 ++-- src/util/RenderMarkdownWrapper.ts | 6 +- src/util/TextDirection.ts | 1 + src/util/utils.ts | 1 + tests/unit/NoteQuestionParser.test.ts | 96 +++++++++++++++++++++------ tests/unit/helpers/UnitTestSRFile.ts | 2 +- tests/unit/util/utils.test.ts | 3 +- 16 files changed, 174 insertions(+), 65 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/src/NoteFileLoader.ts b/src/NoteFileLoader.ts index 4b0b72c4..f63e0670 100644 --- a/src/NoteFileLoader.ts +++ b/src/NoteFileLoader.ts @@ -17,7 +17,11 @@ export class NoteFileLoader { this.settings = settings; } - async load(noteFile: ISRFile, defaultTextDirection: TextDirection, folderTopicPath: TopicPath): Promise { + async load( + noteFile: ISRFile, + defaultTextDirection: TextDirection, + folderTopicPath: TopicPath, + ): Promise { this.noteFile = noteFile; const questionParser: NoteQuestionParser = new NoteQuestionParser(this.settings); @@ -25,7 +29,7 @@ export class NoteFileLoader { const onlyKeepQuestionsWithTopicPath: boolean = true; const questionList: Question[] = await questionParser.createQuestionList( noteFile, - defaultTextDirection, + defaultTextDirection, folderTopicPath, onlyKeepQuestionsWithTopicPath, ); diff --git a/src/NoteParser.ts b/src/NoteParser.ts index 93cda213..f6368954 100644 --- a/src/NoteParser.ts +++ b/src/NoteParser.ts @@ -13,9 +13,18 @@ export class NoteParser { this.settings = settings; } - async parse(noteFile: ISRFile, defaultTextDirection: TextDirection, folderTopicPath: TopicPath): Promise { + async parse( + noteFile: ISRFile, + defaultTextDirection: TextDirection, + folderTopicPath: TopicPath, + ): Promise { const questionParser: NoteQuestionParser = new NoteQuestionParser(this.settings); - const questions = await questionParser.createQuestionList(noteFile, defaultTextDirection, folderTopicPath, true); + const questions = await questionParser.createQuestionList( + noteFile, + defaultTextDirection, + folderTopicPath, + true, + ); const result: Note = new Note(noteFile, questions); return result; diff --git a/src/NoteQuestionParser.ts b/src/NoteQuestionParser.ts index 87217ed9..6419b725 100644 --- a/src/NoteQuestionParser.ts +++ b/src/NoteQuestionParser.ts @@ -25,8 +25,12 @@ export class NoteQuestionParser { this.settings = settings; } - async createQuestionList(noteFile: ISRFile, defaultTextDirection: TextDirection, folderTopicPath: TopicPath, - onlyKeepQuestionsWithTopicPath: boolean): Promise { + async createQuestionList( + noteFile: ISRFile, + defaultTextDirection: TextDirection, + folderTopicPath: TopicPath, + onlyKeepQuestionsWithTopicPath: boolean, + ): Promise { this.noteFile = noteFile; // For efficiency, we first get the tag list from the Obsidian cache // (this only gives the tag names, not the line numbers, but this is sufficient for this first step) @@ -51,7 +55,7 @@ export class NoteQuestionParser { if (textDirection == null) textDirection = defaultTextDirection; this.questionList = this.doCreateQuestionList( noteText, - textDirection, + textDirection, folderTopicPath, this.tagCacheList, ); @@ -73,8 +77,12 @@ export class NoteQuestionParser { return this.questionList; } - private doCreateQuestionList(noteText: string, textDirection: TextDirection, folderTopicPath: TopicPath, - tagCacheList: TagCache[]): Question[] { + private doCreateQuestionList( + noteText: string, + textDirection: TextDirection, + folderTopicPath: TopicPath, + tagCacheList: TagCache[], + ): Question[] { this.noteText = noteText; this.noteLines = splitTextIntoLineArray(noteText); this.folderTopicPath = folderTopicPath; @@ -126,7 +134,10 @@ export class NoteQuestionParser { return result; } - private createQuestionObject(parsedQuestionInfo: ParsedQuestionInfo, textDirection: TextDirection): Question { + private createQuestionObject( + parsedQuestionInfo: ParsedQuestionInfo, + textDirection: TextDirection, + ): Question { const questionContext: string[] = this.noteFile.getQuestionContext( parsedQuestionInfo.firstLineNum, ); @@ -134,7 +145,7 @@ export class NoteQuestionParser { this.settings, parsedQuestionInfo, null, // We haven't worked out the TopicPathList yet - textDirection, + textDirection, questionContext, ); return result; diff --git a/src/Question.ts b/src/Question.ts index 53b5df5a..6541ca24 100644 --- a/src/Question.ts +++ b/src/Question.ts @@ -106,7 +106,7 @@ export class QuestionText { original: string, topicPathWithWs: TopicPathWithWs, actualQuestion: string, - textDirection: TextDirection, + textDirection: TextDirection, blockId: string, ) { this.original = original; @@ -123,7 +123,11 @@ export class QuestionText { return this.actualQuestion.endsWith("```"); } - static create(original: string, textDirection: TextDirection, settings: SRSettings): QuestionText { + static create( + original: string, + textDirection: TextDirection, + settings: SRSettings, + ): QuestionText { const [topicPathWithWs, actualQuestion, blockId] = this.splitText(original, settings); return new QuestionText(original, topicPathWithWs, actualQuestion, textDirection, blockId); @@ -271,7 +275,11 @@ export class Question { let newText = MultiLineTextFinder.findAndReplace(noteText, originalText, replacementText); if (newText) { // Don't support changing the textDirection setting - this.questionText = QuestionText.create(replacementText, this.questionText.textDirection, settings); + this.questionText = QuestionText.create( + replacementText, + this.questionText.textDirection, + settings, + ); } else { console.error( `updateQuestionText: Text not found: ${originalText.substring( @@ -300,11 +308,15 @@ export class Question { settings: SRSettings, parsedQuestionInfo: ParsedQuestionInfo, noteTopicPathList: TopicPathList, - textDirection: TextDirection, + textDirection: TextDirection, context: string[], ): Question { const hasEditLaterTag = parsedQuestionInfo.text.includes(settings.editLaterTag); - const questionText: QuestionText = QuestionText.create(parsedQuestionInfo.text, textDirection, settings); + const questionText: QuestionText = QuestionText.create( + parsedQuestionInfo.text, + textDirection, + settings, + ); let topicPathList: TopicPathList = noteTopicPathList; if (questionText.topicPathWithWs) { diff --git a/src/SRFile.ts b/src/SRFile.ts index 6792e0c5..45a29134 100644 --- a/src/SRFile.ts +++ b/src/SRFile.ts @@ -16,7 +16,7 @@ export interface ISRFile { getAllTagsFromCache(): string[]; getAllTagsFromText(): TagCache[]; getQuestionContext(cardLine: number): string[]; - getTextDirection(): TextDirection | null; + getTextDirection(): TextDirection; read(): Promise; write(content: string): Promise; } @@ -110,18 +110,19 @@ export class SrTFile implements ISRFile { return result; } - getTextDirection(): TextDirection | null { - let result: TextDirection = null; - const fileCache = this.metadataCache.getFileCache(this.file); - const frontMatter = fileCache?.frontmatter; - if (frontMatter && frontMatter?.direction) { + getTextDirection(): TextDirection { + let result: TextDirection = TextDirection.Unspecified; + const fileCache = this.metadataCache.getFileCache(this.file); + const frontMatter = fileCache?.frontmatter; + if (frontMatter && frontMatter?.direction) { // Don't know why the try/catch is needed; but copied from Obsidian RTL plug-in getFrontMatterDirection() - try { - const str: string = (frontMatter.direction + '').toLowerCase(); - result = (str == "rtl") ? TextDirection.Rtl : TextDirection.Ltr; - } - catch (error) {} - } + try { + const str: string = (frontMatter.direction + "").toLowerCase(); + result = str == "rtl" ? TextDirection.Rtl : TextDirection.Ltr; + } catch (error) { + // continue regardless of error + } + } return result; } diff --git a/src/gui/EditModal.tsx b/src/gui/EditModal.tsx index 1acc354a..35c9a7cd 100644 --- a/src/gui/EditModal.tsx +++ b/src/gui/EditModal.tsx @@ -20,7 +20,11 @@ export class FlashcardEditModal extends Modal { private readonly modalText: string; private textDirection: TextDirection; - public static Prompt(app: App, placeholder: string, textDirection: TextDirection): Promise { + public static Prompt( + app: App, + placeholder: string, + textDirection: TextDirection, + ): Promise { const newPromptModal = new FlashcardEditModal(app, placeholder, textDirection); return newPromptModal.waitForClose; } @@ -55,7 +59,6 @@ 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 ?? ""); diff --git a/src/gui/FlashcardReviewView.tsx b/src/gui/FlashcardReviewView.tsx index 5f7c808a..3b0f0fa6 100644 --- a/src/gui/FlashcardReviewView.tsx +++ b/src/gui/FlashcardReviewView.tsx @@ -136,7 +136,11 @@ export class FlashcardReviewView { this.plugin, this._currentNote.filePath, ); - await wrapper.renderMarkdownWrapper(this._currentCard.front, this.content, this._currentQuestion.questionText.textDirection); + await wrapper.renderMarkdownWrapper( + this._currentCard.front, + this.content, + this._currentQuestion.questionText.textDirection, + ); // Setup response buttons this._resetResponseButtons(); @@ -290,7 +294,11 @@ export class FlashcardReviewView { this.plugin, this._currentNote.filePath, ); - wrapper.renderMarkdownWrapper(this._currentCard.back, this.content, this._currentQuestion.questionText.textDirection); + wrapper.renderMarkdownWrapper( + this._currentCard.back, + this.content, + this._currentQuestion.questionText.textDirection, + ); // Show response buttons this.answerButton.addClass("sr-is-hidden"); diff --git a/src/main.ts b/src/main.ts index 9dafe145..eac21dcc 100644 --- a/src/main.ts +++ b/src/main.ts @@ -556,7 +556,11 @@ export default class SRPlugin extends Plugin { this.data.settings, ); - const note: Note = await loader.load(this.createSrTFile(noteFile), this.getObsidianRtlSetting(), folderTopicPath); + const note: Note = await loader.load( + this.createSrTFile(noteFile), + this.getObsidianRtlSetting(), + folderTopicPath, + ); if (note.hasChanged) { note.writeNoteFile(this.data.settings); } @@ -565,12 +569,14 @@ export default class SRPlugin extends Plugin { private getObsidianRtlSetting(): TextDirection { // Get the direction with Obsidian's own setting - const v: any = (this.app.vault as any).getConfig('rightToLeft'); - return (convertToStringOrEmpty(v) == "true") ? TextDirection.Rtl : TextDirection.Ltr; + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const v: any = (this.app.vault as any).getConfig("rightToLeft"); + return convertToStringOrEmpty(v) == "true" ? TextDirection.Rtl : TextDirection.Ltr; } - + async saveReviewResponse(note: TFile, response: ReviewResponse): Promise { const fileCachedData = this.app.metadataCache.getFileCache(note) || {}; + // eslint-disable-next-line @typescript-eslint/no-explicit-any const frontmatter: FrontMatterCache | Record = fileCachedData.frontmatter || {}; diff --git a/src/util/RenderMarkdownWrapper.ts b/src/util/RenderMarkdownWrapper.ts index 3f5f6266..bd7a3404 100644 --- a/src/util/RenderMarkdownWrapper.ts +++ b/src/util/RenderMarkdownWrapper.ts @@ -24,7 +24,7 @@ export class RenderMarkdownWrapper { async renderMarkdownWrapper( markdownString: string, containerEl: HTMLElement, - textDirection: TextDirection | null, + textDirection: TextDirection, recursiveDepth = 0, ): Promise { if (recursiveDepth > 4) return; @@ -155,7 +155,7 @@ export class RenderMarkdownWrapper { // We are operating here within the parent container. // It already has the rtl div if necessary. - // We don't need another rtl div, so we can set direction to null - this.renderMarkdownWrapper(blockText, el, null, recursiveDepth + 1); + // We don't need another rtl div, so we can set direction to Unspecified + this.renderMarkdownWrapper(blockText, el, TextDirection.Unspecified, recursiveDepth + 1); } } diff --git a/src/util/TextDirection.ts b/src/util/TextDirection.ts index 6c5fe18f..a49807cf 100644 --- a/src/util/TextDirection.ts +++ b/src/util/TextDirection.ts @@ -1,4 +1,5 @@ export enum TextDirection { + Unspecified, Ltr, Rtl, } diff --git a/src/util/utils.ts b/src/util/utils.ts index fea81713..6b18bcc1 100644 --- a/src/util/utils.ts +++ b/src/util/utils.ts @@ -96,6 +96,7 @@ export function stringTrimStart(str: string): [string, string] { return [ws, trimmed]; } +// eslint-disable-next-line @typescript-eslint/no-explicit-any export function convertToStringOrEmpty(v: any): string { let result: string = ""; if (v != null && v != undefined) { diff --git a/tests/unit/NoteQuestionParser.test.ts b/tests/unit/NoteQuestionParser.test.ts index 063802c9..c5392204 100644 --- a/tests/unit/NoteQuestionParser.test.ts +++ b/tests/unit/NoteQuestionParser.test.ts @@ -28,7 +28,12 @@ describe("No flashcard questions", () => { let noteFile: ISRFile = new UnitTestSRFile(noteText); expect( - await parserWithDefaultSettings.createQuestionList(noteFile, TextDirection.Ltr, folderTopicPath, true), + await parserWithDefaultSettings.createQuestionList( + noteFile, + TextDirection.Ltr, + folderTopicPath, + true, + ), ).toEqual([]); }); @@ -38,7 +43,12 @@ describe("No flashcard questions", () => { let noteFile: ISRFile = new UnitTestSRFile(noteText); expect( - await parserWithDefaultSettings.createQuestionList(noteFile, TextDirection.Ltr, folderTopicPath, true), + await parserWithDefaultSettings.createQuestionList( + noteFile, + TextDirection.Ltr, + folderTopicPath, + true, + ), ).toEqual([]); }); }); @@ -70,7 +80,12 @@ A::B }, ]; expect( - await parserWithDefaultSettings.createQuestionList(noteFile, TextDirection.Ltr, folderTopicPath, true), + await parserWithDefaultSettings.createQuestionList( + noteFile, + TextDirection.Ltr, + folderTopicPath, + true, + ), ).toMatchObject(expected); }); @@ -109,7 +124,12 @@ A::B }, ]; expect( - await parserWithDefaultSettings.createQuestionList(noteFile, TextDirection.Ltr, folderTopicPath, true), + await parserWithDefaultSettings.createQuestionList( + noteFile, + TextDirection.Ltr, + folderTopicPath, + true, + ), ).toMatchObject(expected); }); @@ -131,7 +151,12 @@ A::B }, ]; expect( - await parserWithDefaultSettings.createQuestionList(noteFile, TextDirection.Ltr, folderTopicPath, true), + await parserWithDefaultSettings.createQuestionList( + noteFile, + TextDirection.Ltr, + folderTopicPath, + true, + ), ).toMatchObject(expected); }); @@ -160,7 +185,12 @@ In computer-science, a *heap* is a tree-based data-structure, that satisfies the }, ]; expect( - await parserWithDefaultSettings.createQuestionList(noteFile, TextDirection.Ltr, folderTopicPath, true), + await parserWithDefaultSettings.createQuestionList( + noteFile, + TextDirection.Ltr, + folderTopicPath, + true, + ), ).toMatchObject(expected); }); }); @@ -199,7 +229,12 @@ A::B ^d7cee0 }, ]; expect( - await parserWithDefaultSettings.createQuestionList(noteFile, TextDirection.Ltr, folderTopicPath, true), + await parserWithDefaultSettings.createQuestionList( + noteFile, + TextDirection.Ltr, + folderTopicPath, + true, + ), ).toMatchObject(expected); }); @@ -244,7 +279,12 @@ A::B ^d7cee0 }, ]; expect( - await parserWithDefaultSettings.createQuestionList(noteFile, TextDirection.Ltr, folderTopicPath, true), + await parserWithDefaultSettings.createQuestionList( + noteFile, + TextDirection.Ltr, + folderTopicPath, + true, + ), ).toMatchObject(expected); }); @@ -287,7 +327,12 @@ A::B ^d7cee0 }, ]; expect( - await parserWithDefaultSettings.createQuestionList(noteFile, TextDirection.Ltr, folderTopicPath, true), + await parserWithDefaultSettings.createQuestionList( + noteFile, + TextDirection.Ltr, + folderTopicPath, + true, + ), ).toMatchObject(expected); }); @@ -329,7 +374,12 @@ A::B ^d7cee0 }, ]; expect( - await parserWithDefaultSettings.createQuestionList(noteFile, TextDirection.Ltr, folderTopicPath, true), + await parserWithDefaultSettings.createQuestionList( + noteFile, + TextDirection.Ltr, + folderTopicPath, + true, + ), ).toMatchObject(expected); }); }); @@ -343,7 +393,7 @@ Q2::A2 let noteFile: ISRFile = new UnitTestSRFile(noteText); let folderTopicPath: TopicPath = TopicPath.emptyPath; let questionList: Question[] = await parser_ConvertFoldersToDecks.createQuestionList( - noteFile, + noteFile, TextDirection.Ltr, folderTopicPath, true, @@ -361,7 +411,7 @@ Q3::A3 let folderTopicPath: TopicPath = new TopicPath(["flashcards", "science"]); let questionList: Question[] = await parser_ConvertFoldersToDecks.createQuestionList( - noteFile, + noteFile, TextDirection.Ltr, folderTopicPath, true, @@ -388,7 +438,8 @@ Q3::A3 let folderTopicPath: TopicPath = TopicPath.emptyPath; let questionList: Question[] = await parserWithDefaultSettings.createQuestionList( - noteFile, TextDirection.Ltr, + noteFile, + TextDirection.Ltr, folderTopicPath, true, ); @@ -417,7 +468,8 @@ Multiline answer2 let noteFile: ISRFile = new UnitTestSRFile(noteText); let questionList: Question[] = await parserWithDefaultSettings.createQuestionList( - noteFile, TextDirection.Ltr, + noteFile, + TextDirection.Ltr, TopicPath.emptyPath, true, ); @@ -463,7 +515,7 @@ describe("Handling tags within note", () => { let noteFile: ISRFile = new UnitTestSRFile(noteText); let folderTopicPath: TopicPath = new TopicPath(["folder", "subfolder"]); let questionList: Question[] = await parser2.createQuestionList( - noteFile, + noteFile, TextDirection.Ltr, folderTopicPath, true, @@ -481,7 +533,7 @@ Q1::A1 let noteFile: ISRFile = new UnitTestSRFile(noteText); let folderTopicPath: TopicPath = new TopicPath(["folder", "subfolder"]); let questionList: Question[] = await parser2.createQuestionList( - noteFile, + noteFile, TextDirection.Ltr, folderTopicPath, true, @@ -501,7 +553,7 @@ Q1::A1 let noteFile: ISRFile = new UnitTestSRFile(noteText); let folderTopicPath: TopicPath = new TopicPath(["folder", "subfolder"]); let questionList: Question[] = await parser2.createQuestionList( - noteFile, + noteFile, TextDirection.Ltr, folderTopicPath, true, @@ -525,7 +577,7 @@ Q1::A1 let expectedPath: string = "#flashcards/test"; let folderTopicPath: TopicPath = TopicPath.emptyPath; let questionList: Question[] = await parserWithDefaultSettings.createQuestionList( - noteFile, + noteFile, TextDirection.Ltr, folderTopicPath, true, @@ -546,7 +598,7 @@ Q1::A1 let folderTopicPath: TopicPath = TopicPath.emptyPath; let questionList: Question[] = await parserWithDefaultSettings.createQuestionList( - noteFile, + noteFile, TextDirection.Ltr, folderTopicPath, true, @@ -569,7 +621,7 @@ Q1::A1 let expectedPath: TopicPath = new TopicPath(["flashcards", "test"]); let folderTopicPath: TopicPath = TopicPath.emptyPath; let questionList: Question[] = await parserWithDefaultSettings.createQuestionList( - noteFile, + noteFile, TextDirection.Ltr, folderTopicPath, true, @@ -591,7 +643,7 @@ Q1::A1 let folderTopicPath: TopicPath = TopicPath.emptyPath; let questionList: Question[] = await parserWithDefaultSettings.createQuestionList( - noteFile, + noteFile, TextDirection.Ltr, folderTopicPath, true, @@ -615,7 +667,7 @@ Q1::A1 let expectedPath: TopicPath = new TopicPath(["flashcards", "science"]); let folderTopicPath: TopicPath = TopicPath.emptyPath; let questionList: Question[] = await parserWithDefaultSettings.createQuestionList( - noteFile, + noteFile, TextDirection.Ltr, folderTopicPath, true, diff --git a/tests/unit/helpers/UnitTestSRFile.ts b/tests/unit/helpers/UnitTestSRFile.ts index 21292d53..51af0faf 100644 --- a/tests/unit/helpers/UnitTestSRFile.ts +++ b/tests/unit/helpers/UnitTestSRFile.ts @@ -34,7 +34,7 @@ export class UnitTestSRFile implements ISRFile { } getTextDirection(): TextDirection | null { - return null; + return TextDirection.Unspecified; } async read(): Promise { diff --git a/tests/unit/util/utils.test.ts b/tests/unit/util/utils.test.ts index e3f86c51..ad175717 100644 --- a/tests/unit/util/utils.test.ts +++ b/tests/unit/util/utils.test.ts @@ -1,6 +1,7 @@ import { YAML_FRONT_MATTER_REGEX } from "src/constants"; import { - convertToStringOrEmpty, extractFrontmatter, + convertToStringOrEmpty, + extractFrontmatter, findLineIndexOfSearchStringIgnoringWs, literalStringReplace, } from "src/util/utils"; From 7420c36aa1d9d533b6e5833133d255751d3c2b7e Mon Sep 17 00:00:00 2001 From: ronzulu <75528127+ronzulu@users.noreply.github.com> Date: Thu, 18 Apr 2024 12:35:34 +1000 Subject: [PATCH 07/12] Change log and documentation update --- docs/changelog.md | 4 ++++ docs/en/flashcards.md | 17 +++++++++++++++++ 2 files changed, 21 insertions(+) diff --git a/docs/changelog.md b/docs/changelog.md index b4443073..8bf48a97 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] + +- RTL support https://github.com/st3v3nmw/obsidian-spaced-repetition/issues/335 + #### [1.12.3](https://github.com/st3v3nmw/obsidian-spaced-repetition/compare/1.12.2...1.12.3) - Fixed slow load of flashcard modal (bug introduced in 1.12.0) [`#926`](https://github.com/st3v3nmw/obsidian-spaced-repetition/pull/926) diff --git a/docs/en/flashcards.md b/docs/en/flashcards.md index afabfdc2..903e2b57 100644 --- a/docs/en/flashcards.md +++ b/docs/en/flashcards.md @@ -154,6 +154,23 @@ The plugin will automatically search for folders that contain flashcards & use t This is an alternative to the tagging option and can be enabled in settings. +## RTL Support + +There are two ways that the plugin can be used with RTL languages, such as Arabic, Hebrew, Persian (Farsi). + +If all cards are in a RTL language, then simply enable the global Obsidian option `Editor → Right-to-left (RTL)`. + +If all cards within a single note have the same LTR/RTL direction, then frontmatter can be used to specify the text direction. For example: +``` +--- +direction: rtl +--- +``` + +This is the same way text direction is specified to the `RTL Support` plugin. + +Note that there is no current support for cards with different text directions within the same note. + ## Reviewing Once done creating cards, click on the flashcards button on the left ribbon to start reviewing the flashcards. After a card is reviewed, a HTML comment is added containing the next review day, the interval, and the card's ease. From 51cb3a0a66d037fe71abb1e81d90cfd15dc1d4d6 Mon Sep 17 00:00:00 2001 From: ronzulu <75528127+ronzulu@users.noreply.github.com> Date: Thu, 18 Apr 2024 12:42:10 +1000 Subject: [PATCH 08/12] Minor code change --- src/NoteQuestionParser.ts | 4 ++-- tests/unit/helpers/UnitTestSRFile.ts | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/NoteQuestionParser.ts b/src/NoteQuestionParser.ts index 6419b725..eeeffeba 100644 --- a/src/NoteQuestionParser.ts +++ b/src/NoteQuestionParser.ts @@ -51,8 +51,8 @@ export class NoteQuestionParser { // There is no point doing it if there aren't any topic paths // Create the question list - let textDirection: TextDirection | null = noteFile.getTextDirection(); - if (textDirection == null) textDirection = defaultTextDirection; + let textDirection: TextDirection = noteFile.getTextDirection(); + if (textDirection == TextDirection.Unspecified) textDirection = defaultTextDirection; this.questionList = this.doCreateQuestionList( noteText, textDirection, diff --git a/tests/unit/helpers/UnitTestSRFile.ts b/tests/unit/helpers/UnitTestSRFile.ts index 51af0faf..4c498496 100644 --- a/tests/unit/helpers/UnitTestSRFile.ts +++ b/tests/unit/helpers/UnitTestSRFile.ts @@ -33,7 +33,7 @@ export class UnitTestSRFile implements ISRFile { return []; } - getTextDirection(): TextDirection | null { + getTextDirection(): TextDirection { return TextDirection.Unspecified; } From f39addb4d54af183f43ee4d0da0d632a75cfda03 Mon Sep 17 00:00:00 2001 From: ronzulu <75528127+ronzulu@users.noreply.github.com> Date: Thu, 18 Apr 2024 12:53:15 +1000 Subject: [PATCH 09/12] Fixed EditModal RTL --- src/gui/FlashcardModal.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/gui/FlashcardModal.tsx b/src/gui/FlashcardModal.tsx index 9ff4b372..ba8f7eba 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, textPrompt, currentQ.questionText.textDirection); editModal .then(async (modifiedCardText) => { this.reviewSequencer.updateCurrentQuestionText(modifiedCardText); From 5f13eea6286405d4526900db283432406028cbf1 Mon Sep 17 00:00:00 2001 From: ronzulu <75528127+ronzulu@users.noreply.github.com> Date: Thu, 18 Apr 2024 13:01:05 +1000 Subject: [PATCH 10/12] lint and format --- docs/en/flashcards.md | 3 ++- src/gui/FlashcardModal.tsx | 6 +++++- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/docs/en/flashcards.md b/docs/en/flashcards.md index 903e2b57..8bfc5b1e 100644 --- a/docs/en/flashcards.md +++ b/docs/en/flashcards.md @@ -158,9 +158,10 @@ This is an alternative to the tagging option and can be enabled in settings. There are two ways that the plugin can be used with RTL languages, such as Arabic, Hebrew, Persian (Farsi). -If all cards are in a RTL language, then simply enable the global Obsidian option `Editor → Right-to-left (RTL)`. +If all cards are in a RTL language, then simply enable the global Obsidian option `Editor → Right-to-left (RTL)`. If all cards within a single note have the same LTR/RTL direction, then frontmatter can be used to specify the text direction. For example: + ``` --- direction: rtl diff --git a/src/gui/FlashcardModal.tsx b/src/gui/FlashcardModal.tsx index ba8f7eba..228d88af 100644 --- a/src/gui/FlashcardModal.tsx +++ b/src/gui/FlashcardModal.tsx @@ -118,7 +118,11 @@ 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, currentQ.questionText.textDirection); + const editModal = FlashcardEditModal.Prompt( + this.app, + textPrompt, + currentQ.questionText.textDirection, + ); editModal .then(async (modifiedCardText) => { this.reviewSequencer.updateCurrentQuestionText(modifiedCardText); From 398673423e6be3612611ab1c22ab7246f9a8c5f0 Mon Sep 17 00:00:00 2001 From: ronzulu <75528127+ronzulu@users.noreply.github.com> Date: Sun, 21 Apr 2024 17:57:24 +1000 Subject: [PATCH 11/12] Updated test cases to fix global coverage error --- tests/unit/NoteQuestionParser.test.ts | 35 +++++++++++++++++++++++---- 1 file changed, 30 insertions(+), 5 deletions(-) diff --git a/tests/unit/NoteQuestionParser.test.ts b/tests/unit/NoteQuestionParser.test.ts index 78334cdb..ceee063b 100644 --- a/tests/unit/NoteQuestionParser.test.ts +++ b/tests/unit/NoteQuestionParser.test.ts @@ -617,7 +617,12 @@ Stop trying ==to milk the crowd== for sympathy. // доить толпу }, ]; expect( - await parserWithDefaultSettings.createQuestionList(noteFile, folderTopicPath, true), + await parserWithDefaultSettings.createQuestionList( + noteFile, + TextDirection.Ltr, + folderTopicPath, + true, + ), ).toMatchObject(expected); }); @@ -743,7 +748,12 @@ What year was the Taliban Emirate founded?::1996 #flashcards }, ]; expect( - await parserWithDefaultSettings.createQuestionList(noteFile, folderTopicPath, true), + await parserWithDefaultSettings.createQuestionList( + noteFile, + TextDirection.Ltr, + folderTopicPath, + true, + ), ).toMatchObject(expected); }); }); @@ -780,7 +790,12 @@ In computer-science, a *heap* is a tree-based data-structure, that satisfies the }, ]; expect( - await parserWithDefaultSettings.createQuestionList(noteFile, folderTopicPath, true), + await parserWithDefaultSettings.createQuestionList( + noteFile, + TextDirection.Ltr, + folderTopicPath, + true, + ), ).toMatchObject(expected); }); @@ -811,7 +826,12 @@ In computer-science, a *heap* is a tree-based data-structure, that satisfies the }, ]; expect( - await parserWithDefaultSettings.createQuestionList(noteFile, folderTopicPath, true), + await parserWithDefaultSettings.createQuestionList( + noteFile, + TextDirection.Ltr, + folderTopicPath, + true, + ), ).toMatchObject(expected); }); @@ -851,7 +871,12 @@ A::B }, ]; expect( - await parserWithDefaultSettings.createQuestionList(noteFile, folderTopicPath, true), + await parserWithDefaultSettings.createQuestionList( + noteFile, + TextDirection.Ltr, + folderTopicPath, + true, + ), ).toMatchObject(expected); }); }); From 1aa0f1b07524b5d7e58a68eaa76dc51c774be9e7 Mon Sep 17 00:00:00 2001 From: ronzulu <75528127+ronzulu@users.noreply.github.com> Date: Tue, 23 Jul 2024 14:14:01 +1000 Subject: [PATCH 12/12] Format & lint --- docs/changelog.md | 1 - src/util/utils.ts | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/docs/changelog.md b/docs/changelog.md index cf3fc364..62df2beb 100644 --- a/docs/changelog.md +++ b/docs/changelog.md @@ -7,7 +7,6 @@ Generated by [`auto-changelog`](https://github.com/CookPete/auto-changelog). #### [Unreleased] - RTL support https://github.com/st3v3nmw/obsidian-spaced-repetition/issues/335 -[Unreleased] - Fixed notes selection when all notes are reviewed. [`#548`](https://github.com/st3v3nmw/obsidian-spaced-repetition/issues/548) #### [1.12.4](https://github.com/st3v3nmw/obsidian-spaced-repetition/compare/1.12.3...1.12.4) diff --git a/src/util/utils.ts b/src/util/utils.ts index 9da04be7..2924771d 100644 --- a/src/util/utils.ts +++ b/src/util/utils.ts @@ -1,7 +1,7 @@ import moment from "moment"; import { Moment } from "moment"; import { normalize, sep } from "path"; -import { PREFERRED_DATE_FORMAT, YAML_FRONT_MATTER_REGEX } from "src/constants"; +import { PREFERRED_DATE_FORMAT } from "src/constants"; type Hex = number;