🎨 Simpilify tracking last seen

This commit is contained in:
Erin Schnabel 2025-11-21 20:41:55 -05:00
parent 3921ebf361
commit c7df00f53d
No known key found for this signature in database
7 changed files with 55 additions and 31 deletions

View file

@ -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

View file

@ -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 {

View file

@ -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<string>();

View file

@ -3,7 +3,6 @@ import type { DeckNotesSettings } from "./@types/settings";
export const DEFAULT_SETTINGS: DeckNotesSettings = {
cardPaths: [],
defaultDeckTag: "",
trackViews: true,
selectionMode: "least-recent",
calloutType: "tip",
};

View file

@ -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);
}

View file

@ -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;
}

View file

@ -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.",
});
}