diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..fe5f608 --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,51 @@ +name: Release + +on: + push: + tags: + - "*" + +permissions: + contents: write + id-token: write + attestations: write + +jobs: + release: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - uses: actions/setup-node@v4 + with: + node-version: "22" + + - name: Build + run: | + npm ci + npm run build + npm test + + - name: Verify tag matches manifest version + run: | + MANIFEST_VERSION=$(node -p "require('./manifest.json').version") + if [ "$MANIFEST_VERSION" != "${GITHUB_REF_NAME}" ]; then + echo "Tag ${GITHUB_REF_NAME} does not match manifest version ${MANIFEST_VERSION}" >&2 + exit 1 + fi + + - name: Attest build provenance + uses: actions/attest-build-provenance@v2 + with: + subject-path: | + main.js + manifest.json + + - name: Create release + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + gh release create "${GITHUB_REF_NAME}" \ + --title "${GITHUB_REF_NAME}" \ + --generate-notes \ + main.js manifest.json diff --git a/esbuild.config.mjs b/esbuild.config.mjs index 557bb5f..95721e0 100644 --- a/esbuild.config.mjs +++ b/esbuild.config.mjs @@ -1,6 +1,6 @@ import esbuild from "esbuild"; import process from "process"; -import builtins from "builtin-modules"; +import { builtinModules } from "node:module"; const banner = `/* THIS IS A GENERATED/BUNDLED FILE BY ESBUILD @@ -28,7 +28,7 @@ const context = await esbuild.context({ "@lezer/common", "@lezer/highlight", "@lezer/lr", - ...builtins, + ...builtinModules, ], format: "cjs", target: "es2018", diff --git a/manifest.json b/manifest.json index 7b3f9b6..9582f2a 100644 --- a/manifest.json +++ b/manifest.json @@ -1,8 +1,8 @@ { "id": "granola-api-sync", "name": "Granola API Sync", - "version": "1.0.1", - "minAppVersion": "1.5.0", + "version": "1.0.2", + "minAppVersion": "1.13.0", "description": "Sync meeting notes and full transcripts from Granola into your vault using the official Granola API. Just paste an API key, no credential scraping.", "author": "Arshia Navabi", "authorUrl": "https://github.com/ArshiaEcho", diff --git a/package-lock.json b/package-lock.json index d55ae72..d4c6817 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,16 +1,15 @@ { "name": "granola-api-sync", - "version": "1.0.0", + "version": "1.0.2", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "granola-api-sync", - "version": "1.0.0", + "version": "1.0.2", "license": "MIT", "devDependencies": { "@types/node": "^22.0.0", - "builtin-modules": "^4.0.0", "esbuild": "^0.24.0", "obsidian": "latest", "tslib": "^2.6.0", @@ -512,19 +511,6 @@ "@types/estree": "*" } }, - "node_modules/builtin-modules": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/builtin-modules/-/builtin-modules-4.0.0.tgz", - "integrity": "sha512-p1n8zyCkt1BVrKNFymOHjcDSAl7oq/gUvfgULv2EblgpPVQlQr9yHnWjg9IJ2MhfwPqiYqMMrr01OY7yQoK2yA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18.20" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/crelt": { "version": "1.0.7", "resolved": "https://registry.npmjs.org/crelt/-/crelt-1.0.7.tgz", diff --git a/package.json b/package.json index 7f63470..fba5eec 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "granola-api-sync", - "version": "1.0.1", + "version": "1.0.2", "description": "Sync meeting notes and full transcripts from Granola into Obsidian using the official Granola API.", "main": "main.js", "scripts": { @@ -18,7 +18,6 @@ "license": "MIT", "devDependencies": { "@types/node": "^22.0.0", - "builtin-modules": "^4.0.0", "esbuild": "^0.24.0", "obsidian": "latest", "tslib": "^2.6.0", diff --git a/src/main.ts b/src/main.ts index ab04eaf..61cfd10 100644 --- a/src/main.ts +++ b/src/main.ts @@ -3,7 +3,7 @@ import { Notice, Plugin, PluginSettingTab, - Setting, + SettingDefinitionItem, TFile, TFolder, normalizePath, @@ -43,6 +43,12 @@ interface GranolaNote extends GranolaNoteStub { summary_text?: string; } +interface GranolaListResponse { + notes?: GranolaNoteStub[]; + hasMore?: boolean; + cursor?: string | null; +} + interface GranolaSyncSettings { apiKey: string; notesFolder: string; @@ -78,7 +84,10 @@ interface PluginData { export function sanitizeFilename(title: string): string { let name = title.replace(/\//g, "__"); - name = name.replace(/[\\:*?"<>|\u0000-\u001f]/g, ""); + name = name.replace(/[\\:*?"<>|]/g, ""); + name = Array.from(name) + .filter((c) => c.charCodeAt(0) > 0x1f) + .join(""); name = name.replace(/\s+/g, " ").trim().replace(/^\.+|\.+$/g, ""); return name || "Untitled Granola Note"; } @@ -233,11 +242,11 @@ export default class GranolaApiSyncPlugin extends Plugin { this.addCommand({ id: "sync-now", name: "Sync now", - callback: () => this.sync(true), + callback: () => void this.sync(true), }); this.app.workspace.onLayoutReady(() => { - this.sync(false); + void this.sync(false); this.scheduleInterval(); }); @@ -245,7 +254,7 @@ export default class GranolaApiSyncPlugin extends Plugin { // backgrounded Obsidian can miss every interval tick. Catch up the // moment the window is used again (throttled to once a minute). this.registerDomEvent(window, "focus", () => { - if (Date.now() - this.lastSyncAt > 60_000) this.sync(false); + if (Date.now() - this.lastSyncAt > 60_000) void this.sync(false); }); } @@ -256,7 +265,7 @@ export default class GranolaApiSyncPlugin extends Plugin { } const minutes = Math.max(1, this.settings.intervalMinutes); this.intervalId = window.setInterval( - () => this.sync(false), + () => void this.sync(false), minutes * 60 * 1000 ); this.registerInterval(this.intervalId); @@ -280,10 +289,10 @@ export default class GranolaApiSyncPlugin extends Plugin { // ---------- API ---------- - private async apiGet( + private async apiGet( path: string, params: Record = {} - ): Promise> { + ): Promise { const qs = new URLSearchParams(params).toString(); const resp = await requestUrl({ url: `${API_BASE}${path}${qs ? "?" + qs : ""}`, @@ -295,16 +304,18 @@ export default class GranolaApiSyncPlugin extends Plugin { }); if (resp.status === 401) { throw new Error( - "API key rejected (401). Check the key in settings — note that Granola API keys require a Business plan." + "API key rejected (401). Check the key in settings. Note that Granola API keys require a Business plan." ); } if (resp.status === 429) { - throw new Error("Rate limited by Granola (429) — will retry on next sync."); + throw new Error( + "Rate limited by Granola (429). Will retry on next sync." + ); } if (resp.status >= 400) { throw new Error(`Granola API error ${resp.status} on ${path}`); } - return resp.json as Record; + return resp.json as T; } private async listRecentNotes(): Promise { @@ -318,10 +329,13 @@ export default class GranolaApiSyncPlugin extends Plugin { created_after: createdAfter, }; if (cursor) params.cursor = cursor; - const data = await this.apiGet("/notes", params); - notes.push(...((data.notes as GranolaNoteStub[]) ?? [])); - if (!data.hasMore) return notes; - cursor = data.cursor as string; + const data = await this.apiGet( + "/notes", + params + ); + notes.push(...(data.notes ?? [])); + if (!data.hasMore || !data.cursor) return notes; + cursor = data.cursor; } } @@ -330,7 +344,9 @@ export default class GranolaApiSyncPlugin extends Plugin { private async ensureFolder(path: string) { const p = normalizePath(path); if (!this.app.vault.getAbstractFileByPath(p)) { - await this.app.vault.createFolder(p).catch(() => {}); + await this.app.vault.createFolder(p).catch(() => { + // folder may have been created concurrently; harmless + }); } } @@ -343,9 +359,8 @@ export default class GranolaApiSyncPlugin extends Plugin { if (!(folder instanceof TFolder)) return idx; for (const child of folder.children) { if (!(child instanceof TFile) || child.extension !== "md") continue; - const gid = - this.app.metadataCache.getFileCache(child)?.frontmatter - ?.granola_id; + const fm = this.app.metadataCache.getFileCache(child)?.frontmatter; + const gid: unknown = fm?.granola_id; if (typeof gid === "string" && gid) idx.set(gid, child); } return idx; @@ -398,11 +413,11 @@ export default class GranolaApiSyncPlugin extends Plugin { let errors = 0; for (const stub of pending) { try { - const note = (await this.apiGet(`/notes/${stub.id}`, { - include: "transcript", - })) as unknown as GranolaNote; - const uuid = - note.web_url?.match(UUID_RE)?.[0] ?? note.id; + const note = await this.apiGet( + `/notes/${stub.id}`, + { include: "transcript" } + ); + const uuid = note.web_url?.match(UUID_RE)?.[0] ?? note.id; // adopt existing file by granola_id, else create // (date-suffix on title collision with a different id) @@ -430,7 +445,10 @@ export default class GranolaApiSyncPlugin extends Plugin { notePath, buildNoteMd(note, uuid, fname, this.settings) ); - if (this.settings.syncTranscripts && note.transcript?.length) { + if ( + this.settings.syncTranscripts && + note.transcript?.length + ) { await this.writeFile( `${this.settings.transcriptsFolder}/${fname}-transcript.md`, buildTranscriptMd(note, uuid, fname, this.settings) @@ -453,7 +471,7 @@ export default class GranolaApiSyncPlugin extends Plugin { await this.savePluginData(); if (errors) { this.notice( - `${synced} synced, ${errors} failed — see developer console.`, + `${synced} synced, ${errors} failed. See developer console.`, true ); } else { @@ -469,7 +487,7 @@ export default class GranolaApiSyncPlugin extends Plugin { } } -// ---------- settings UI ---------- +// ---------- settings UI (declarative API, Obsidian 1.13+) ---------- class GranolaSyncSettingTab extends PluginSettingTab { plugin: GranolaApiSyncPlugin; @@ -479,132 +497,99 @@ class GranolaSyncSettingTab extends PluginSettingTab { this.plugin = plugin; } - display(): void { - const { containerEl } = this; - containerEl.empty(); + getSettingDefinitions(): SettingDefinitionItem[] { + return [ + { + name: "Granola API key", + desc: "Create one in the Granola app under Settings → API → Create new key (requires a Granola Business plan). Stored locally in this vault.", + aliases: ["token", "authentication"], + control: { type: "text", key: "apiKey", placeholder: "grn_..." }, + }, + { + name: "Notes folder", + desc: "Vault folder where meeting notes are created.", + control: { type: "text", key: "notesFolder" }, + }, + { + name: "Sync transcripts", + desc: "Also save the full meeting transcript as a linked note.", + control: { type: "toggle", key: "syncTranscripts" }, + }, + { + name: "Transcripts folder", + visible: () => this.plugin.settings.syncTranscripts, + control: { type: "text", key: "transcriptsFolder" }, + }, + { + name: "Sync interval (minutes)", + desc: "How often to check for new or updated meetings.", + control: { + type: "slider", + key: "intervalMinutes", + min: 1, + max: 60, + step: 1, + }, + }, + { + name: "Days to look back", + desc: "Meetings created within this window are kept in sync (including late summary updates).", + control: { + type: "slider", + key: "daysBack", + min: 1, + max: 90, + step: 1, + }, + }, + { + name: "Your email", + desc: "Optional. Excludes you from attendee lists and labels your transcript lines with the name below.", + control: { + type: "text", + key: "myEmail", + placeholder: "you@company.com", + }, + }, + { + name: "Your name in transcripts", + control: { type: "text", key: "myLabel" }, + }, + { + name: "Show sync notifications", + desc: "Errors are always shown.", + control: { type: "toggle", key: "showNotices" }, + }, + ]; + } - new Setting(containerEl) - .setName("Granola API key") - .setDesc( - "Create one in the Granola app under Settings → API → Create new key (requires a Granola Business plan). Stored locally in this vault." - ) - .addText((text) => { - text.setPlaceholder("grn_...") - .setValue(this.plugin.settings.apiKey) - .onChange(async (v) => { - this.plugin.settings.apiKey = v.trim(); - await this.plugin.savePluginData(); - }); - text.inputEl.type = "password"; - }); + getControlValue(key: string): unknown { + return this.plugin.settings[key as keyof GranolaSyncSettings]; + } - new Setting(containerEl) - .setName("Notes folder") - .setDesc("Vault folder where meeting notes are created.") - .addText((t) => - t - .setValue(this.plugin.settings.notesFolder) - .onChange(async (v) => { - this.plugin.settings.notesFolder = - normalizePath(v || "Granola"); - await this.plugin.savePluginData(); - }) - ); - - new Setting(containerEl) - .setName("Sync transcripts") - .setDesc("Also save the full meeting transcript as a linked note.") - .addToggle((t) => - t - .setValue(this.plugin.settings.syncTranscripts) - .onChange(async (v) => { - this.plugin.settings.syncTranscripts = v; - await this.plugin.savePluginData(); - this.display(); - }) - ); - - if (this.plugin.settings.syncTranscripts) { - new Setting(containerEl) - .setName("Transcripts folder") - .addText((t) => - t - .setValue(this.plugin.settings.transcriptsFolder) - .onChange(async (v) => { - this.plugin.settings.transcriptsFolder = - normalizePath(v || "Granola/transcripts"); - await this.plugin.savePluginData(); - }) + async setControlValue(key: string, value: unknown): Promise { + const s = this.plugin.settings as unknown as Record; + switch (key) { + case "apiKey": + case "myEmail": + s[key] = String(value ?? "").trim(); + break; + case "myLabel": + s[key] = String(value ?? "").trim() || "Me"; + break; + case "notesFolder": + s[key] = normalizePath(String(value ?? "") || "Granola"); + break; + case "transcriptsFolder": + s[key] = normalizePath( + String(value ?? "") || "Granola/transcripts" ); + break; + default: + s[key] = value; } - - new Setting(containerEl) - .setName("Sync interval (minutes)") - .setDesc("How often to check for new or updated meetings.") - .addSlider((s) => - s - .setLimits(1, 60, 1) - .setValue(this.plugin.settings.intervalMinutes) - .setDynamicTooltip() - .onChange(async (v) => { - this.plugin.settings.intervalMinutes = v; - await this.plugin.savePluginData(); - this.plugin.scheduleInterval(); - }) - ); - - new Setting(containerEl) - .setName("Days to look back") - .setDesc( - "Meetings created within this window are kept in sync (including late summary updates)." - ) - .addSlider((s) => - s - .setLimits(1, 90, 1) - .setValue(this.plugin.settings.daysBack) - .setDynamicTooltip() - .onChange(async (v) => { - this.plugin.settings.daysBack = v; - await this.plugin.savePluginData(); - }) - ); - - new Setting(containerEl) - .setName("Your email") - .setDesc( - "Optional. Excludes you from attendee lists and labels your transcript lines with the name below." - ) - .addText((t) => - t - .setPlaceholder("you@company.com") - .setValue(this.plugin.settings.myEmail) - .onChange(async (v) => { - this.plugin.settings.myEmail = v.trim(); - await this.plugin.savePluginData(); - }) - ); - - new Setting(containerEl) - .setName("Your name in transcripts") - .addText((t) => - t - .setValue(this.plugin.settings.myLabel) - .onChange(async (v) => { - this.plugin.settings.myLabel = v.trim() || "Me"; - await this.plugin.savePluginData(); - }) - ); - - new Setting(containerEl) - .setName("Show sync notifications") - .setDesc("Errors are always shown.") - .addToggle((t) => - t - .setValue(this.plugin.settings.showNotices) - .onChange(async (v) => { - this.plugin.settings.showNotices = v; - await this.plugin.savePluginData(); - }) - ); + await this.plugin.savePluginData(); + if (key === "intervalMinutes") this.plugin.scheduleInterval(); + if (key === "syncTranscripts") this.update(); } } diff --git a/test/_bundle.mjs b/test/_bundle.mjs index c5ada3a..8306439 100644 --- a/test/_bundle.mjs +++ b/test/_bundle.mjs @@ -5,8 +5,6 @@ var PluginSettingTab = class { }; var Notice = class { }; -var Setting = class { -}; var TFile = class { }; var TFolder = class { @@ -31,7 +29,8 @@ var DEFAULT_SETTINGS = { }; function sanitizeFilename(title) { let name = title.replace(/\//g, "__"); - name = name.replace(/[\\:*?"<>|\u0000-\u001f]/g, ""); + name = name.replace(/[\\:*?"<>|]/g, ""); + name = Array.from(name).filter((c) => c.charCodeAt(0) > 31).join(""); name = name.replace(/\s+/g, " ").trim().replace(/^\.+|\.+$/g, ""); return name || "Untitled Granola Note"; } @@ -150,14 +149,14 @@ var GranolaApiSyncPlugin = class extends Plugin { this.addCommand({ id: "sync-now", name: "Sync now", - callback: () => this.sync(true) + callback: () => void this.sync(true) }); this.app.workspace.onLayoutReady(() => { - this.sync(false); + void this.sync(false); this.scheduleInterval(); }); this.registerDomEvent(window, "focus", () => { - if (Date.now() - this.lastSyncAt > 6e4) this.sync(false); + if (Date.now() - this.lastSyncAt > 6e4) void this.sync(false); }); } scheduleInterval() { @@ -167,7 +166,7 @@ var GranolaApiSyncPlugin = class extends Plugin { } const minutes = Math.max(1, this.settings.intervalMinutes); this.intervalId = window.setInterval( - () => this.sync(false), + () => void this.sync(false), minutes * 60 * 1e3 ); this.registerInterval(this.intervalId); @@ -198,11 +197,13 @@ var GranolaApiSyncPlugin = class extends Plugin { }); if (resp.status === 401) { throw new Error( - "API key rejected (401). Check the key in settings \u2014 note that Granola API keys require a Business plan." + "API key rejected (401). Check the key in settings. Note that Granola API keys require a Business plan." ); } if (resp.status === 429) { - throw new Error("Rate limited by Granola (429) \u2014 will retry on next sync."); + throw new Error( + "Rate limited by Granola (429). Will retry on next sync." + ); } if (resp.status >= 400) { throw new Error(`Granola API error ${resp.status} on ${path}`); @@ -220,9 +221,12 @@ var GranolaApiSyncPlugin = class extends Plugin { created_after: createdAfter }; if (cursor) params.cursor = cursor; - const data = await this.apiGet("/notes", params); + const data = await this.apiGet( + "/notes", + params + ); notes.push(...data.notes ?? []); - if (!data.hasMore) return notes; + if (!data.hasMore || !data.cursor) return notes; cursor = data.cursor; } } @@ -243,7 +247,8 @@ var GranolaApiSyncPlugin = class extends Plugin { if (!(folder instanceof TFolder)) return idx; for (const child of folder.children) { if (!(child instanceof TFile) || child.extension !== "md") continue; - const gid = this.app.metadataCache.getFileCache(child)?.frontmatter?.granola_id; + const fm = this.app.metadataCache.getFileCache(child)?.frontmatter; + const gid = fm?.granola_id; if (typeof gid === "string" && gid) idx.set(gid, child); } return idx; @@ -291,9 +296,10 @@ var GranolaApiSyncPlugin = class extends Plugin { let errors = 0; for (const stub of pending) { try { - const note = await this.apiGet(`/notes/${stub.id}`, { - include: "transcript" - }); + const note = await this.apiGet( + `/notes/${stub.id}`, + { include: "transcript" } + ); const uuid = note.web_url?.match(UUID_RE)?.[0] ?? note.id; let fname; const adopted = idx.get(uuid); @@ -340,7 +346,7 @@ var GranolaApiSyncPlugin = class extends Plugin { await this.savePluginData(); if (errors) { this.notice( - `${synced} synced, ${errors} failed \u2014 see developer console.`, + `${synced} synced, ${errors} failed. See developer console.`, true ); } else { @@ -360,74 +366,98 @@ var GranolaSyncSettingTab = class extends PluginSettingTab { super(app, plugin); this.plugin = plugin; } - display() { - const { containerEl } = this; - containerEl.empty(); - new Setting(containerEl).setName("Granola API key").setDesc( - "Create one in the Granola app under Settings \u2192 API \u2192 Create new key (requires a Granola Business plan). Stored locally in this vault." - ).addText((text) => { - text.setPlaceholder("grn_...").setValue(this.plugin.settings.apiKey).onChange(async (v) => { - this.plugin.settings.apiKey = v.trim(); - await this.plugin.savePluginData(); - }); - text.inputEl.type = "password"; - }); - new Setting(containerEl).setName("Notes folder").setDesc("Vault folder where meeting notes are created.").addText( - (t) => t.setValue(this.plugin.settings.notesFolder).onChange(async (v) => { - this.plugin.settings.notesFolder = normalizePath(v || "Granola"); - await this.plugin.savePluginData(); - }) - ); - new Setting(containerEl).setName("Sync transcripts").setDesc("Also save the full meeting transcript as a linked note.").addToggle( - (t) => t.setValue(this.plugin.settings.syncTranscripts).onChange(async (v) => { - this.plugin.settings.syncTranscripts = v; - await this.plugin.savePluginData(); - this.display(); - }) - ); - if (this.plugin.settings.syncTranscripts) { - new Setting(containerEl).setName("Transcripts folder").addText( - (t) => t.setValue(this.plugin.settings.transcriptsFolder).onChange(async (v) => { - this.plugin.settings.transcriptsFolder = normalizePath(v || "Granola/transcripts"); - await this.plugin.savePluginData(); - }) - ); + getSettingDefinitions() { + return [ + { + name: "Granola API key", + desc: "Create one in the Granola app under Settings \u2192 API \u2192 Create new key (requires a Granola Business plan). Stored locally in this vault.", + aliases: ["token", "authentication"], + control: { type: "text", key: "apiKey", placeholder: "grn_..." } + }, + { + name: "Notes folder", + desc: "Vault folder where meeting notes are created.", + control: { type: "text", key: "notesFolder" } + }, + { + name: "Sync transcripts", + desc: "Also save the full meeting transcript as a linked note.", + control: { type: "toggle", key: "syncTranscripts" } + }, + { + name: "Transcripts folder", + visible: () => this.plugin.settings.syncTranscripts, + control: { type: "text", key: "transcriptsFolder" } + }, + { + name: "Sync interval (minutes)", + desc: "How often to check for new or updated meetings.", + control: { + type: "slider", + key: "intervalMinutes", + min: 1, + max: 60, + step: 1 + } + }, + { + name: "Days to look back", + desc: "Meetings created within this window are kept in sync (including late summary updates).", + control: { + type: "slider", + key: "daysBack", + min: 1, + max: 90, + step: 1 + } + }, + { + name: "Your email", + desc: "Optional. Excludes you from attendee lists and labels your transcript lines with the name below.", + control: { + type: "text", + key: "myEmail", + placeholder: "you@company.com" + } + }, + { + name: "Your name in transcripts", + control: { type: "text", key: "myLabel" } + }, + { + name: "Show sync notifications", + desc: "Errors are always shown.", + control: { type: "toggle", key: "showNotices" } + } + ]; + } + getControlValue(key) { + return this.plugin.settings[key]; + } + async setControlValue(key, value) { + const s = this.plugin.settings; + switch (key) { + case "apiKey": + case "myEmail": + s[key] = String(value ?? "").trim(); + break; + case "myLabel": + s[key] = String(value ?? "").trim() || "Me"; + break; + case "notesFolder": + s[key] = normalizePath(String(value ?? "") || "Granola"); + break; + case "transcriptsFolder": + s[key] = normalizePath( + String(value ?? "") || "Granola/transcripts" + ); + break; + default: + s[key] = value; } - new Setting(containerEl).setName("Sync interval (minutes)").setDesc("How often to check for new or updated meetings.").addSlider( - (s) => s.setLimits(1, 60, 1).setValue(this.plugin.settings.intervalMinutes).setDynamicTooltip().onChange(async (v) => { - this.plugin.settings.intervalMinutes = v; - await this.plugin.savePluginData(); - this.plugin.scheduleInterval(); - }) - ); - new Setting(containerEl).setName("Days to look back").setDesc( - "Meetings created within this window are kept in sync (including late summary updates)." - ).addSlider( - (s) => s.setLimits(1, 90, 1).setValue(this.plugin.settings.daysBack).setDynamicTooltip().onChange(async (v) => { - this.plugin.settings.daysBack = v; - await this.plugin.savePluginData(); - }) - ); - new Setting(containerEl).setName("Your email").setDesc( - "Optional. Excludes you from attendee lists and labels your transcript lines with the name below." - ).addText( - (t) => t.setPlaceholder("you@company.com").setValue(this.plugin.settings.myEmail).onChange(async (v) => { - this.plugin.settings.myEmail = v.trim(); - await this.plugin.savePluginData(); - }) - ); - new Setting(containerEl).setName("Your name in transcripts").addText( - (t) => t.setValue(this.plugin.settings.myLabel).onChange(async (v) => { - this.plugin.settings.myLabel = v.trim() || "Me"; - await this.plugin.savePluginData(); - }) - ); - new Setting(containerEl).setName("Show sync notifications").setDesc("Errors are always shown.").addToggle( - (t) => t.setValue(this.plugin.settings.showNotices).onChange(async (v) => { - this.plugin.settings.showNotices = v; - await this.plugin.savePluginData(); - }) - ); + await this.plugin.savePluginData(); + if (key === "intervalMinutes") this.plugin.scheduleInterval(); + if (key === "syncTranscripts") this.update(); } }; export { diff --git a/test/out/note.md b/test/out/note.md index 0529eff..e94c77c 100644 --- a/test/out/note.md +++ b/test/out/note.md @@ -3,7 +3,7 @@ granola_id: 874b7bfd-0aa2-43a4-94c2-247d0edba2ef title: Finn Grahe and Arshia Navabi type: note created: 2026-07-16T18:59:50.339Z -updated: 2026-07-16T20:06:42.499Z +updated: 2026-07-16T23:53:10.393Z attendees: - finn@resonumai.com transcript: '[[inbox/granola/transcripts/Finn Grahe and Arshia Navabi-transcript.md]]' diff --git a/test/out/note.py.md b/test/out/note.py.md index 0529eff..e94c77c 100644 --- a/test/out/note.py.md +++ b/test/out/note.py.md @@ -3,7 +3,7 @@ granola_id: 874b7bfd-0aa2-43a4-94c2-247d0edba2ef title: Finn Grahe and Arshia Navabi type: note created: 2026-07-16T18:59:50.339Z -updated: 2026-07-16T20:06:42.499Z +updated: 2026-07-16T23:53:10.393Z attendees: - finn@resonumai.com transcript: '[[inbox/granola/transcripts/Finn Grahe and Arshia Navabi-transcript.md]]' diff --git a/test/out/transcript.md b/test/out/transcript.md index e4e88a9..bdf6957 100644 --- a/test/out/transcript.md +++ b/test/out/transcript.md @@ -3,7 +3,7 @@ granola_id: 874b7bfd-0aa2-43a4-94c2-247d0edba2ef title: Finn Grahe and Arshia Navabi - Transcript type: transcript created: 2026-07-16T18:59:50.339Z -updated: 2026-07-16T20:06:42.499Z +updated: 2026-07-16T23:53:10.393Z attendees: - finn@resonumai.com note: "[[inbox/granola/Finn Grahe and Arshia Navabi.md]]" diff --git a/test/out/transcript.py.md b/test/out/transcript.py.md index e4e88a9..bdf6957 100644 --- a/test/out/transcript.py.md +++ b/test/out/transcript.py.md @@ -3,7 +3,7 @@ granola_id: 874b7bfd-0aa2-43a4-94c2-247d0edba2ef title: Finn Grahe and Arshia Navabi - Transcript type: transcript created: 2026-07-16T18:59:50.339Z -updated: 2026-07-16T20:06:42.499Z +updated: 2026-07-16T23:53:10.393Z attendees: - finn@resonumai.com note: "[[inbox/granola/Finn Grahe and Arshia Navabi.md]]" diff --git a/versions.json b/versions.json index a0e2b65..178b254 100644 --- a/versions.json +++ b/versions.json @@ -1,4 +1,5 @@ { "1.0.0": "1.5.0", - "1.0.1": "1.5.0" + "1.0.1": "1.5.0", + "1.0.2": "1.13.0" }