🎨 Add API to embed card content in notes (as callout).

Move callout type to setting.
Defer registration of API and commands until cards have been read.
This commit is contained in:
Erin Schnabel 2025-10-07 14:58:10 -04:00
parent 1f90617591
commit a3087ac729
No known key found for this signature in database
7 changed files with 124 additions and 49 deletions

View file

@ -10,6 +10,7 @@ export interface SimpleFlashcardsSettings {
defaultDeckPath: string; // Default path for quick command
trackViews: boolean; // Enable view tracking
selectionMode: "random" | "least-recent";
calloutType: string; // Callout type for embedded cards (e.g., "readaloud")
}
export interface SimpleFlashcardsData {

9
src/@types/window-type.d.ts vendored Normal file
View file

@ -0,0 +1,9 @@
import type { SimpleFlashcardsApi } from "../flashcards-Api";
declare global {
interface Window {
simpleFlashcards?: {
api?: SimpleFlashcardsApi;
};
}
}

17
src/flashcards-Api.ts Normal file
View file

@ -0,0 +1,17 @@
import type SimpleFlashcardsPlugin from "./flashcards-Plugin";
export class SimpleFlashcardsApi {
plugin: SimpleFlashcardsPlugin;
constructor(plugin: SimpleFlashcardsPlugin) {
this.plugin = plugin;
}
/**
* Get a random card embed as formatted text
* @returns The card embed text, or null if no cards available
*/
embedCard(): string | null {
return this.plugin.getCardEmbedText();
}
}

View file

@ -5,4 +5,5 @@ export const DEFAULT_SETTINGS: SimpleFlashcardsSettings = {
defaultDeckPath: "",
trackViews: true,
selectionMode: "least-recent",
calloutType: "tip",
};

View file

@ -1,9 +1,10 @@
import { Notice, Plugin, TFile, TFolder } from "obsidian";
import { type Editor, Notice, Plugin, TFile, TFolder } from "obsidian";
import type {
Card,
SimpleFlashcardsData,
SimpleFlashcardsSettings,
} from "./@types/settings";
import { SimpleFlashcardsApi } from "./flashcards-Api";
import { CardParser } from "./flashcards-CardParser";
import { DEFAULT_SETTINGS } from "./flashcards-Constants";
import { FlashcardModal } from "./flashcards-Modal";
@ -14,6 +15,7 @@ export default class SimpleFlashcardsPlugin extends Plugin {
data!: SimpleFlashcardsData;
cachedCards: Card[] = [];
cardParser!: CardParser;
api!: SimpleFlashcardsApi;
async onload() {
console.log("Loading Simple Flashcards plugin");
@ -23,33 +25,54 @@ export default class SimpleFlashcardsPlugin extends Plugin {
this.addSettingTab(new SimpleFlashcardsSettingsTab(this.app, this));
// Command: Show Random Activity Card
this.addCommand({
id: "show-random-card",
name: "Show Random Activity Card",
callback: () => {
this.showRandomCard();
},
});
// Command: Refresh Activity Cards
this.addCommand({
id: "refresh-cards",
name: "Refresh Activity Cards",
callback: async () => {
await this.scanCards();
new Notice("Activity cards refreshed");
},
});
// Defer initial card scan to avoid blocking startup
// Defer initial card scan, API, and command registration to avoid blocking startup
this.app.workspace.onLayoutReady(async () => {
await this.scanCards();
// Initialize API
this.api = new SimpleFlashcardsApi(this);
if (!window.simpleFlashcards) {
window.simpleFlashcards = {};
}
window.simpleFlashcards.api = this.api;
// Command: Show Random Activity Card
this.addCommand({
id: "show-random-card",
name: "Show Activity Card",
callback: () => {
this.showRandomCard();
},
});
// Command: Embed Random Card
this.addCommand({
id: "embed-card",
name: "Embed Activity Card",
editorCallback: (editor) => {
this.insertCardEmbed(editor);
},
});
// Command: Refresh Activity Cards
this.addCommand({
id: "refresh-cards",
name: "Refresh Activity Cards",
callback: async () => {
await this.scanCards();
new Notice("Activity cards refreshed");
},
});
});
}
onunload() {
console.log("Unloading Simple Flashcards plugin");
// Clear API reference
if (window.simpleFlashcards) {
window.simpleFlashcards.api = undefined;
}
}
async loadSettings() {
@ -129,12 +152,7 @@ export default class SimpleFlashcardsPlugin extends Plugin {
});
}
async showRandomCard() {
// Lazy load cards if not yet scanned
if (this.cachedCards.length === 0) {
await this.scanCards();
}
showRandomCard() {
const deckPath = this.settings.defaultDeckPath || undefined;
const card = this.selectCard(deckPath);
@ -145,4 +163,41 @@ export default class SimpleFlashcardsPlugin extends Plugin {
new FlashcardModal(this.app, this, card, deckPath).open();
}
getCardEmbedText(): string | null {
const deckPath = this.settings.defaultDeckPath || undefined;
const card = this.selectCard(deckPath);
if (!card) {
return null;
}
// Create collapsible callout with card content
const contentLines = card.content
.split("\n")
.map((line) => `> ${line}`);
const embedText = [
`> [!${this.settings.calloutType}]+ ${card.heading}`,
...contentLines,
].join("\n");
// Record as viewed
if (this.settings.trackViews) {
this.recordView(card.key);
}
return embedText;
}
insertCardEmbed(editor: Editor) {
const embedText = this.getCardEmbedText();
if (!embedText) {
new Notice("No cards available. Check your settings.");
return;
}
editor.replaceSelection(embedText);
}
}

View file

@ -124,6 +124,20 @@ export class SimpleFlashcardsSettingsTab extends PluginSettingTab {
}),
);
new Setting(containerEl)
.setName("Callout Type")
.setDesc(
"Callout type for embedded cards (e.g., note, tip, warning)",
)
.addText((text) =>
text
.setPlaceholder("readaloud")
.setValue(this.newSettings.calloutType)
.onChange((value) => {
this.newSettings.calloutType = value.trim();
}),
);
containerEl.createEl("h3", { text: "Usage" });
const usage = containerEl.createEl("div");
usage.createEl("p", {

View file

@ -1,22 +0,0 @@
{
"compilerOptions": {
"baseUrl": ".",
"inlineSourceMap": true,
"inlineSources": true,
"module": "ESNext",
"target": "ES2020",
"allowJs": true,
"noImplicitAny": true,
"moduleResolution": "node",
"importHelpers": true,
"isolatedModules": true,
"strictNullChecks": true,
"lib": [
"DOM",
"ES2020"
]
},
"include": [
"**/*.ts"
]
}