diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 3ca1aba..b0b5993 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -63,6 +63,11 @@ npm run format # Format code - **Error handling**: `try/catch` with user-friendly `Notice` messages - **Async**: Use `async/await` consistently +- **UI text (enforced by `obsidianmd/ui/sentence-case`)**: + - Use sentence case (capitalize first word only, except proper nouns) + - Setting descriptions must be complete sentences ending with periods + - Use semicolons to separate related clauses in a single sentence + - Avoid parenthetical notes; use semicolons instead ## Development Patterns diff --git a/src/@types/settings.d.ts b/src/@types/settings.d.ts index 704662b..5edd243 100644 --- a/src/@types/settings.d.ts +++ b/src/@types/settings.d.ts @@ -9,10 +9,9 @@ export interface Card { export interface DeckNotesSettings { cardPaths: string[]; // ["Journal/Coping", "Activities"] - defaultDeckTag: string; // Default tag for deck selection (e.g., "activities") - trackViews: boolean; // Enable view tracking + defaultDeckTag: string; // Default tag for deck selection selectionMode: "random" | "least-recent"; - calloutType: string; // Callout type for embedded cards (e.g., "readaloud") + calloutType: string; // Callout type for embedded cards } export interface DeckNotesData { diff --git a/src/dn-Api.ts b/src/dn-Api.ts index 9c9edb1..d123920 100644 --- a/src/dn-Api.ts +++ b/src/dn-Api.ts @@ -19,7 +19,8 @@ export class DeckNotesApi { /** * Get all unique deck tags discovered in cards - * @returns Array of normalized tag strings (e.g., ["activities", "activities/morning"]) + * @returns Array of normalized tag strings; for example, + * ["activities", "activities/morning"] */ getTags(): string[] { const tagsSet = new Set(); diff --git a/src/dn-Constants.ts b/src/dn-Constants.ts index 6ec6ead..7f0f10c 100644 --- a/src/dn-Constants.ts +++ b/src/dn-Constants.ts @@ -3,7 +3,6 @@ import type { DeckNotesSettings } from "./@types/settings"; export const DEFAULT_SETTINGS: DeckNotesSettings = { cardPaths: [], defaultDeckTag: "", - trackViews: true, selectionMode: "least-recent", calloutType: "tip", }; diff --git a/src/dn-Modal.ts b/src/dn-Modal.ts index 835d921..5054bea 100644 --- a/src/dn-Modal.ts +++ b/src/dn-Modal.ts @@ -120,7 +120,7 @@ export class CardModal extends Modal { private showNextCard() { // Record view for current card - if (this.card && this.plugin.settings.trackViews) { + if (this.card) { this.plugin.recordView(this.card.key); } diff --git a/src/dn-Plugin.ts b/src/dn-Plugin.ts index c40b4b8..908a368 100644 --- a/src/dn-Plugin.ts +++ b/src/dn-Plugin.ts @@ -12,6 +12,7 @@ export default class DeckNotesPlugin extends Plugin { cachedCards: Card[] = []; cardParser!: CardParser; api!: DeckNotesApi; + saveViewsTimer: NodeJS.Timeout | null = null; async onload() { console.debug("Loading Deck Notes plugin", `v${this.manifest.version}`); @@ -21,7 +22,8 @@ export default class DeckNotesPlugin extends Plugin { this.addSettingTab(new DeckNotesSettingsTab(this.app, this)); - // Defer initial card scan, API, and command registration to avoid blocking startup + // Defer initial card scan, API, and command registration + // to avoid blocking startup this.app.workspace.onLayoutReady(async () => { await this.scanCards(); @@ -65,6 +67,12 @@ export default class DeckNotesPlugin extends Plugin { onunload() { console.debug("Unloading Deck Notes plugin"); + // Flush any pending view saves + if (this.saveViewsTimer) { + clearTimeout(this.saveViewsTimer); + void this.saveViews(); + } + // Clear API reference if (window.deckNotes) { window.deckNotes.api = undefined; @@ -151,7 +159,19 @@ export default class DeckNotesPlugin extends Plugin { recordView(cardKey: string) { this.data.cardViews[cardKey] = Date.now(); - void this.saveData({ + + // Debounce saves: only write to disk after 2s of no card views + if (this.saveViewsTimer) { + clearTimeout(this.saveViewsTimer); + } + this.saveViewsTimer = setTimeout(() => { + void this.saveViews(); + }, 2000); + } + + async saveViews() { + this.saveViewsTimer = null; + await this.saveData({ ...this.settings, cardViews: this.data.cardViews, }); @@ -188,9 +208,7 @@ export default class DeckNotesPlugin extends Plugin { ].join("\n"); // Record as viewed - if (this.settings.trackViews) { - this.recordView(card.key); - } + this.recordView(card.key); return embedText; } diff --git a/src/dn-SettingsTab.ts b/src/dn-SettingsTab.ts index 96d5ac6..5963330 100644 --- a/src/dn-SettingsTab.ts +++ b/src/dn-SettingsTab.ts @@ -65,7 +65,8 @@ export class DeckNotesSettingsTab extends PluginSettingTab { new Setting(containerEl) .setName("Card paths") .setDesc( - "Paths to folders containing card files (one per line, relative to vault root)", + "Paths to folders containing card files; " + + "one path relative to vault root per line.", ) .addTextArea((text) => text @@ -87,7 +88,9 @@ export class DeckNotesSettingsTab extends PluginSettingTab { new Setting(containerEl) .setName("Default deck tag") .setDesc( - "Default tag for 'Show Random Activity Card' command (e.g., 'activities' or 'activities/morning'). Leave empty for all cards.", + "Optional tag to narrow the selection of cards " + + "available for 'Show Card' command; for example, " + + "'activities' or 'activities/morning'.", ) .addText((text) => text @@ -98,22 +101,12 @@ export class DeckNotesSettingsTab extends PluginSettingTab { }), ); - new Setting(containerEl) - .setName("Track views") - .setDesc( - "Track when cards were last viewed (enables least-recent selection)", - ) - .addToggle((toggle) => - toggle - .setValue(this.newSettings.trackViews) - .onChange((value) => { - this.newSettings.trackViews = value; - }), - ); - new Setting(containerEl) .setName("Selection mode") - .setDesc("How to select cards to display") + .setDesc( + "Cards can be selected at random or based on " + + "when they were last viewed.", + ) .addDropdown((dropdown) => dropdown .addOption("random", "Random") @@ -129,7 +122,8 @@ export class DeckNotesSettingsTab extends PluginSettingTab { new Setting(containerEl) .setName("Callout type") .setDesc( - "Callout type for embedded cards (e.g., note, tip, warning)", + "Callout type for embedded cards; " + + "for example, note, tip, or warning.", ) .addText((text) => text @@ -143,13 +137,21 @@ export class DeckNotesSettingsTab extends PluginSettingTab { new Setting(containerEl).setHeading().setName("Usage"); containerEl.createEl("p", { - text: "Cards are created from markdown files with H2 headings (##). Each heading becomes one card.", + text: + "Cards are created from markdown files with " + + "H2 headings (##). Each heading becomes one card.", }); containerEl.createEl("p", { - text: "Use --- (horizontal rule) to mark the end of card content. Anything after --- will be ignored.", + text: + "Use --- (horizontal rule) to mark the end of " + + "card content. Anything after --- will be ignored.", }); containerEl.createEl("p", { - text: "Tag cards with #flashcards/deck-name (e.g., #flashcards/activities/morning). Tags can be in frontmatter or inline before each H2. Lines starting with #flashcards are stripped from display.", + text: + "Tag cards with #flashcards/deck-name; for example, " + + "#flashcards/activities/morning. Tags can be in " + + "frontmatter or inline before each H2. Lines starting " + + "with #flashcards are stripped from display.", }); }