diff --git a/main.ts b/main.ts index 8707e77..7359ff6 100644 --- a/main.ts +++ b/main.ts @@ -3,8 +3,7 @@ import { ISettings } from 'src/settings'; import { SettingsTab } from 'src/gui/settings-tab'; import { CardsService } from 'src/services/cards'; import { Anki } from 'src/services/anki'; -import { noticeTimeout } from 'src/constants' -import { flashcardsIcon } from 'src/constants' +import { noticeTimeout, flashcardsIcon } from 'src/constants'; export default class ObsidianFlashcard extends Plugin { private settings: ISettings @@ -14,17 +13,17 @@ export default class ObsidianFlashcard extends Plugin { addIcon("flashcards", flashcardsIcon) // TODO test when file did not insert flashcards, but one of them is in Anki already - let anki = new Anki() + const anki = new Anki() this.settings = await this.loadData() || this.getDefaultSettings() this.cardsService = new CardsService(this.app, this.settings) - let statusBar = this.addStatusBarItem() + const statusBar = this.addStatusBarItem() this.addCommand({ id: 'generate-flashcard-current-file', name: 'Generate for the current file', checkCallback: (checking: boolean) => { - let activeFile = this.app.workspace.getActiveFile() + const activeFile = this.app.workspace.getActiveFile() if (activeFile) { if (!checking) { this.generateCards(activeFile) @@ -36,7 +35,7 @@ export default class ObsidianFlashcard extends Plugin { }); this.addRibbonIcon('flashcards', 'Generate flashcards', () => { - let activeFile = this.app.workspace.getActiveFile() + const activeFile = this.app.workspace.getActiveFile() if (activeFile) { this.generateCards(activeFile) } else { @@ -56,12 +55,12 @@ export default class ObsidianFlashcard extends Plugin { } private getDefaultSettings(): ISettings { - return { contextAwareMode: true, sourceSupport: false, codeHighlightSupport: false, inlineID: false, contextSeparator: " > ", deck: "Default", flashcardsTag: "card", defaultAnkiTag: "obsidian" } + return { contextAwareMode: true, sourceSupport: false, codeHighlightSupport: false, inlineID: false, contextSeparator: " > ", deck: "Default", flashcardsTag: "card", inlineSeparator: "::", inlineSeparatorReverse: ":::", defaultAnkiTag: "obsidian" } } private generateCards(activeFile: TFile) { this.cardsService.execute(activeFile).then(res => { - for (let r of res) { + for (const r of res) { new Notice(r, noticeTimeout) } console.log(res) diff --git a/src/gui/settings-tab.ts b/src/gui/settings-tab.ts index 1f45737..da20be3 100644 --- a/src/gui/settings-tab.ts +++ b/src/gui/settings-tab.ts @@ -1,5 +1,6 @@ import { Notice, PluginSettingTab, Setting } from "obsidian"; import { Anki } from "src/services/anki"; +import { escapeRegExp } from "src/utils"; export class SettingsTab extends PluginSettingTab { display(): void { @@ -103,6 +104,60 @@ export class SettingsTab extends PluginSettingTab { }); }); + new Setting(containerEl) + .setName("Inline card separator") + .setDesc( + "The separator to identifty the inline cards in the notes." + ) + .addText((text) => { + text + .setValue(plugin.settings.inlineSeparator) + .setPlaceholder("::") + .onChange((value) => { + // if the value is empty or is the same like the inlineseparatorreverse then set it to the default, otherwise save it + if (value.trim().length === 0 || value === plugin.settings.inlineSeparatorReverse) { + plugin.settings.inlineSeparator = "::"; + if (value.trim().length === 0) { + new Notice("The separator must be at least 1 character long"); + } else if (value === plugin.settings.inlineSeparatorReverse) { + new Notice("The separator must be different from the inline reverse separator"); + } + } else { + plugin.settings.inlineSeparator = escapeRegExp(value.trim()); + new Notice("The separator has been changed"); + } + plugin.saveData(plugin.settings); + }); + }); + + + new Setting(containerEl) + .setName("Inline reverse card separator") + .setDesc( + "The separator to identifty the inline revese cards in the notes." + ) + .addText((text) => { + text + .setValue(plugin.settings.inlineSeparatorReverse) + .setPlaceholder(":::") + .onChange((value) => { + // if the value is empty or is the same like the inlineseparatorreverse then set it to the default, otherwise save it + if (value.trim().length === 0 || value === plugin.settings.inlineSeparator) { + plugin.settings.inlineSeparatorReverse = ":::"; + if (value.trim().length === 0) { + new Notice("The separator must be at least 1 character long"); + } else if (value === plugin.settings.inlineSeparator) { + new Notice("The separator must be different from the inline separator"); + } + } else { + plugin.settings.inlineSeparatorReverse = escapeRegExp(value.trim()); + new Notice("The separator has been changed"); + } + plugin.saveData(plugin.settings); + }); + }); + + new Setting(containerEl) .setName("Default Anki tag") .setDesc("This tag will be added to each generated card on Anki") diff --git a/src/regex.ts b/src/regex.ts index 01491b4..1b79447 100644 --- a/src/regex.ts +++ b/src/regex.ts @@ -50,13 +50,17 @@ export class Regex { "(?:[/-]reverse)?)((?: *#[\\p{Letter}\\-\\/_]+)*) *?\\n+((?:[^\\n]\\n?)*?(?=\\^\\d{13}|$))(?:\\^(\\d{13}))?"; this.flashscardsWithTag = new RegExp(str, flags); - // https://regex101.com/r/Ixtzlv/1 + // https://regex101.com/r/8wmOo8/1 + const sepLongest = settings.inlineSeparator.length >= settings.inlineSeparatorReverse.length ? settings.inlineSeparator : settings.inlineSeparatorReverse; + const sepShortest = settings.inlineSeparator.length < settings.inlineSeparatorReverse.length ? settings.inlineSeparator : settings.inlineSeparatorReverse; + // sepLongest is the longest between the inlineSeparator and the inlineSeparatorReverse because if the order is ::|::: then always the first will be matched + // sepShortest is the shortest if (settings.inlineID) { str = - "( {0,3}[#]{0,6})?(?:(?:[\\t ]*)(?:\\d.|[-+*]|#{1,6}))?(.+?) ?(:{2,3}) ?(.+?)((?: *#[\\p{Letter}\\-\\/_]+)+)?(?:\\s+\\^(\\d{13})|$)"; + "( {0,3}[#]{0,6})?(?:(?:[\\t ]*)(?:\\d.|[-+*]|#{1,6}))?(.+?) ?(" + sepLongest + "|" + sepShortest + ") ?(.+?)((?: *#[\\p{Letter}\\-\\/_]+)+)?(?:\\s+\\^(\\d{13})|$)"; } else { str = - "( {0,3}[#]{0,6})?(?:(?:[\\t ]*)(?:\\d.|[-+*]|#{1,6}))?(.+?) ?(:{2,3}) ?(.+?)((?: *#[\\p{Letter}\\-\\/_]+)+|$)(?:\\n\\^(\\d{13}))?"; + "( {0,3}[#]{0,6})?(?:(?:[\\t ]*)(?:\\d.|[-+*]|#{1,6}))?(.+?) ?(" + sepLongest + "|" + sepShortest + ") ?(.+?)((?: *#[\\p{Letter}\\-\\/_]+)+|$)(?:\\n\\^(\\d{13}))?"; } this.cardsInlineStyle = new RegExp(str, flags); diff --git a/src/services/parser.ts b/src/services/parser.ts index 60f9fa6..42a887d 100644 --- a/src/services/parser.ts +++ b/src/services/parser.ts @@ -191,7 +191,7 @@ export class Parser { continue; } - const reversed: boolean = match[3].trim().toLowerCase() === ":::"; + const reversed: boolean = match[3] === this.settings.inlineSeparatorReverse; let headingLevel = -1; if (match[1]) { headingLevel = diff --git a/src/settings.ts b/src/settings.ts index b5df331..a2d17cc 100644 --- a/src/settings.ts +++ b/src/settings.ts @@ -6,5 +6,7 @@ export interface ISettings { contextSeparator: string; deck: string; flashcardsTag: string; + inlineSeparator: string; + inlineSeparatorReverse: string; defaultAnkiTag: string; } diff --git a/src/utils.ts b/src/utils.ts index 8b89a3b..b97c0c0 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -44,3 +44,8 @@ export function escapeMarkdown(string: string, skips: string[] = []) { : s.replace(replacement[0], replacement[1]); }, string); } + + + export function escapeRegExp(str: string) { + return str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); // $& means the whole matched string + } \ No newline at end of file