From f279ae32970cad72040ba6ce1b59b073443c46f9 Mon Sep 17 00:00:00 2001 From: Erin Schnabel Date: Wed, 3 Jun 2026 10:14:16 -0400 Subject: [PATCH] =?UTF-8?q?=F0=9F=8E=A8=20Use=201.13.0=20Settings=20API?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- manifest-beta.json | 2 +- manifest.json | 2 +- package-lock.json | 23 +++- package.json | 2 +- src/dn-SettingsTab.ts | 313 ++++++++++++++++++++++-------------------- src/styles.css | 1 - 6 files changed, 189 insertions(+), 154 deletions(-) diff --git a/manifest-beta.json b/manifest-beta.json index 9c6502e..183c856 100644 --- a/manifest-beta.json +++ b/manifest-beta.json @@ -2,7 +2,7 @@ "id": "deck-notes", "name": "Deck Notes", "version": "0.7.6", - "minAppVersion": "1.12.0", + "minAppVersion": "1.13.0", "description": "Define card decks for activities, strategies, or any content you want to rotate through. Embed cards in your notes or browse them in a modal. Filter by tag hierarchies to narrow your selection.", "author": "ebullient", "authorUrl": "https://github.com/ebullient", diff --git a/manifest.json b/manifest.json index 9c6502e..183c856 100644 --- a/manifest.json +++ b/manifest.json @@ -2,7 +2,7 @@ "id": "deck-notes", "name": "Deck Notes", "version": "0.7.6", - "minAppVersion": "1.12.0", + "minAppVersion": "1.13.0", "description": "Define card decks for activities, strategies, or any content you want to rotate through. Embed cards in your notes or browse them in a modal. Filter by tag hierarchies to narrow your selection.", "author": "ebullient", "authorUrl": "https://github.com/ebullient", diff --git a/package-lock.json b/package-lock.json index ad063a7..9e8dc2e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -15,7 +15,7 @@ "auto-changelog": "^2.5.0", "esbuild": "^0.28.0", "eslint-plugin-obsidianmd": "^0.3.0", - "obsidian": "^1.12.3", + "obsidian": "^1.13.0", "tslib": "^2.8.1", "typescript": "6.0.2" } @@ -2432,6 +2432,21 @@ "undici-types": "~5.26.4" } }, + "node_modules/eslint-plugin-obsidianmd/node_modules/obsidian": { + "version": "1.12.3", + "resolved": "https://registry.npmjs.org/obsidian/-/obsidian-1.12.3.tgz", + "integrity": "sha512-HxWqe763dOqzXjnNiHmAJTRERN8KILBSqxDSEqbeSr7W8R8Jxezzbca+nz1LiiqXnMpM8lV2jzAezw3CZ4xNUw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/codemirror": "5.60.8", + "moment": "2.29.4" + }, + "peerDependencies": { + "@codemirror/state": "6.5.0", + "@codemirror/view": "6.38.6" + } + }, "node_modules/eslint-plugin-obsidianmd/node_modules/typescript": { "version": "5.4.5", "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.4.5.tgz", @@ -3992,9 +4007,9 @@ } }, "node_modules/obsidian": { - "version": "1.12.3", - "resolved": "https://registry.npmjs.org/obsidian/-/obsidian-1.12.3.tgz", - "integrity": "sha512-HxWqe763dOqzXjnNiHmAJTRERN8KILBSqxDSEqbeSr7W8R8Jxezzbca+nz1LiiqXnMpM8lV2jzAezw3CZ4xNUw==", + "version": "1.13.0", + "resolved": "https://registry.npmjs.org/obsidian/-/obsidian-1.13.0.tgz", + "integrity": "sha512-PHw5+SAPlJ0S3leFvJ0wgFg63Z3DavxL6+d1ll+8toXR2ZlYKc1rMWqdUv9LgUbTwPQUyY6yfhOMMivampRRiQ==", "dev": true, "license": "MIT", "dependencies": { diff --git a/package.json b/package.json index 55493b2..5892100 100644 --- a/package.json +++ b/package.json @@ -34,7 +34,7 @@ "auto-changelog": "^2.5.0", "esbuild": "^0.28.0", "eslint-plugin-obsidianmd": "^0.3.0", - "obsidian": "^1.12.3", + "obsidian": "^1.13.0", "tslib": "^2.8.1", "typescript": "6.0.2" }, diff --git a/src/dn-SettingsTab.ts b/src/dn-SettingsTab.ts index 96197a5..771b7c9 100644 --- a/src/dn-SettingsTab.ts +++ b/src/dn-SettingsTab.ts @@ -1,10 +1,15 @@ -import { type App, PluginSettingTab, Setting } from "obsidian"; -import type { DeckNotesSettings } from "./@types/settings"; +import { + type App, + Modal, + PluginSettingTab, + Setting, + type SettingDefinitionItem, + type Setting as SettingType, +} from "obsidian"; import type DeckNotesPlugin from "./dn-Plugin"; export class DeckNotesSettingsTab extends PluginSettingTab { plugin: DeckNotesPlugin; - newSettings!: DeckNotesSettings; constructor(app: App, plugin: DeckNotesPlugin) { super(app, plugin); @@ -12,155 +17,171 @@ export class DeckNotesSettingsTab extends PluginSettingTab { this.icon = "gallery-thumbnails"; } - async save() { - this.plugin.settings = this.newSettings; + async setControlValue(key: string, value: unknown): Promise { + (this.plugin.settings as unknown as Record)[key] = + value; await this.plugin.saveSettings(); } - private cloneSettings(): DeckNotesSettings { - return JSON.parse( - JSON.stringify(this.plugin.settings), - ) as DeckNotesSettings; - } + getSettingDefinitions(): SettingDefinitionItem[] { + return [ + { + name: "Usage", + render: (setting: SettingType) => { + setting.descEl.appendChild( + createFragment((f) => { + const p = f.createEl("p"); + p.appendText( + "Cards are created from markdown files with ", + ); + p.createEl("code", { text: "##" }); + p.appendText(" headings."); - reset() { - this.newSettings = this.cloneSettings(); - this.display(); - } + const ul = f.createEl("ul"); - display(): void { - if (!this.newSettings) { - this.newSettings = this.cloneSettings(); - } + const li1 = ul.createEl("li"); + li1.appendText("Use "); + li1.createEl("code", { text: "---" }); + li1.appendText( + " (horizontal rule) to mark the end of card content.", + ); - const { containerEl } = this; - containerEl.empty(); - - new Setting(this.containerEl) - .setName("Save settings") - .setClass("decknotes-save-reset") - .addButton((button) => - button - .setIcon("reset") - .setTooltip("Reset to previously saved values.") - .onClick(() => { - this.reset(); - }), - ) - .addButton((button) => { - button - .setIcon("save") - .setCta() - .setTooltip("Save all changes") - .onClick(async () => { - await this.save(); - }); - }); - - new Setting(containerEl) - .setName("Card paths") - .setDesc( - "Paths to folders containing card decks; " + - "one path relative to vault root per line.", - ) - .addTextArea((text) => - text - .setPlaceholder("journal/coping\nactivities/morning") - .setValue(this.newSettings.cardPaths.join("\n")) - .onChange((value) => { - this.newSettings.cardPaths = value - .split("\n") - .map((p) => p.trim()) - .filter((p) => p.length > 0); - }), - ) - .then((setting) => { - setting.controlEl - .querySelector("textarea") - ?.setAttribute("rows", "4"); - }); - - new Setting(containerEl) - .setName("Default deck tag") - .setDesc( - "Optional tag to narrow the selection of cards " + - "available for 'Show Card' command; for example, " + - "'activities' or 'activities/morning'.", - ) - .addText((text) => - text - .setPlaceholder("Activities") - .setValue(this.newSettings.defaultDeckTag) - .onChange((value) => { - this.newSettings.defaultDeckTag = value.trim(); - }), - ); - - new Setting(containerEl) - .setName("Selection mode") - .setDesc( - "Cards can be selected at random or based on " + - "when they were last viewed.", - ) - .addDropdown((dropdown) => - dropdown - .addOption("random", "Random") - .addOption("least-recent", "Least recently viewed") - .setValue(this.newSettings.selectionMode) - .onChange((value) => { - this.newSettings.selectionMode = value as - | "random" - | "least-recent"; - }), - ); - - new Setting(containerEl) - .setName("Callout type") - .setDesc( - "Callout type for embedded cards; " + - "for example, note, tip, or warning.", - ) - .addText((text) => - text - .setPlaceholder("example") - .setValue(this.newSettings.calloutType) - .onChange((value) => { - this.newSettings.calloutType = value.trim(); - }), - ); - - new Setting(containerEl).setHeading().setName("Usage"); - - containerEl.createEl("p", { - 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.", - }); - containerEl.createEl("p", { - 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.", - }); - - const div = this.containerEl.createDiv("deck-cards-coffee"); - div.createEl("a", { - href: "https://www.buymeacoffee.com/ebullient", - }).createEl("img", { - attr: { - src: "https://img.buymeacoffee.com/button-api/?text=Buy me a coffee&emoji=☕&slug=ebullient&button_colour=8e6787&font_colour=ebebeb&font_family=Inter&outline_colour=392a37&coffee_colour=ecc986", + const li2 = ul.createEl("li"); + li2.appendText("Use "); + li2.createEl("code", { + text: "#flashcards", + }); + li2.appendText( + " tags to identify decks. For example, ", + ); + li2.createEl("code", { + text: "#flashcards/activities", + }); + li2.appendText(" or "); + li2.createEl("code", { + text: "#flashcards/meditation", + }); + li2.appendText( + ". Tags can be added to the whole file (in frontmatter properties), or defined inline before each ", + ); + li2.createEl("code", { text: "##" }); + li2.appendText(". See the "); + li2.createEl("a", { + text: "README", + href: "https://github.com/ebullient/obsidian-deck-notes#card-format", + attr: { target: "_blank" }, + }); + li2.appendText(" for examples."); + }), + ); + }, }, - }); - } - - /** Save on exit */ - hide(): void { - void this.save(); + { + name: "Default deck tag", + desc: + "Optional tag to narrow the selection of cards available " + + "for 'Show Card' command; for example, 'activities' or " + + "'activities/morning'.", + control: { + type: "text", + key: "defaultDeckTag", + placeholder: "activities", + }, + }, + { + name: "Selection mode", + desc: "Cards can be selected at random or based on when they were last viewed.", + control: { + type: "dropdown", + key: "selectionMode", + options: { + random: "Random", + "least-recent": "Least recently viewed", + }, + }, + }, + { + name: "Callout type", + desc: "Callout type for embedded cards; for example, note, tip, or warning.", + control: { + type: "text", + key: "calloutType", + placeholder: "example", + }, + }, + { + type: "list", + heading: "Card paths", + desc: "Paths to folders containing card decks, relative to vault root.", + emptyState: "No card paths configured.", + addItem: { + name: "Add path", + action: () => + new AddCardPathModal(this.app, (path) => { + this.plugin.settings.cardPaths.push(path); + void this.plugin.saveSettings(); + this.update(); + }).open(), + }, + onDelete: async (idx: number) => { + this.plugin.settings.cardPaths.splice(idx, 1); + await this.plugin.saveSettings(); + this.update(); + }, + items: this.plugin.settings.cardPaths.map((path) => ({ + name: path, + })), + }, + { + name: "", + render: (setting: SettingType) => { + setting.descEl.addClass("deck-cards-coffee"); + setting.descEl + .createEl("a", { + href: "https://www.buymeacoffee.com/ebullient", + }) + .createEl("img", { + attr: { + src: "https://img.buymeacoffee.com/button-api/?text=Buy me a coffee&emoji=☕&slug=ebullient&button_colour=8e6787&font_colour=ebebeb&font_family=Inter&outline_colour=392a37&coffee_colour=ecc986", + }, + }); + }, + }, + ]; + } +} + +class AddCardPathModal extends Modal { + private onSubmit: (path: string) => void; + + constructor(app: App, onSubmit: (path: string) => void) { + super(app); + this.onSubmit = onSubmit; + } + + onOpen(): void { + const { contentEl } = this; + contentEl.createEl("h2", { text: "Add card path" }); + let input = ""; + new Setting(contentEl).setName("Path").addText((t) => + t.setPlaceholder("journal/coping").onChange((v) => { + input = v.trim(); + }), + ); + new Setting(contentEl).addButton((b) => + b + .setButtonText("Add") + .setCta() + .onClick(() => { + if (input) { + this.onSubmit(input); + this.close(); + } + }), + ); + } + + onClose(): void { + this.contentEl.empty(); } } diff --git a/src/styles.css b/src/styles.css index 9d50220..203502c 100644 --- a/src/styles.css +++ b/src/styles.css @@ -1,7 +1,6 @@ .deck-cards-coffee { text-align: center; img { - margin: auto; height: 30px; } }