From 42d42e92c2df991a1f97b1275ac8bce9601affd4 Mon Sep 17 00:00:00 2001 From: Evan Fiordeliso Date: Wed, 10 Jun 2026 09:48:43 -0400 Subject: [PATCH] Add Update Cover from URL command --- src/commands/UpdateCoverFromURLCommand.ts | 36 +++++++++++++++++++++++ src/main.ts | 17 +++++++---- 2 files changed, 48 insertions(+), 5 deletions(-) create mode 100644 src/commands/UpdateCoverFromURLCommand.ts diff --git a/src/commands/UpdateCoverFromURLCommand.ts b/src/commands/UpdateCoverFromURLCommand.ts new file mode 100644 index 0000000..c119575 --- /dev/null +++ b/src/commands/UpdateCoverFromURLCommand.ts @@ -0,0 +1,36 @@ +import { type Editor, type MarkdownView, type MarkdownFileInfo, type App, type TFile, Notice } from "obsidian"; +import { EditorCheckCommand } from "./Command"; +import type { BookTrackerPluginSettings } from "@ui/settings"; + +export class UpdateCoverFromURLCommand extends EditorCheckCommand { + constructor( + private readonly app: App, + private readonly settings: BookTrackerPluginSettings, + private readonly downloadCoverImage: (url: string, fileName: string, overwrite?: boolean) => Promise, + ) { + super("update-cover-from-url", "Update Cover from URL"); + } + + protected check(_editor: Editor, ctx: MarkdownView | MarkdownFileInfo): boolean { + return ctx.file != null; + } + + protected async run(_editor: Editor, ctx: MarkdownView | MarkdownFileInfo): Promise { + const file = ctx.file!; + const url = await navigator.clipboard.readText(); + + let coverFile: TFile; + try { + coverFile = await this.downloadCoverImage(url, file.basename, true); + } catch (error) { + console.error("Failed to download cover image:", error); + new Notice("Failed to download cover image. Check console for details."); + return; + } + + this.app.fileManager.processFrontMatter(file, (fm) => { + fm[this.settings.coverImageUrlProperty] = url; + }); + new Notice("Updated cover image.") + } +} diff --git a/src/main.ts b/src/main.ts index 8c1b30d..1b5432a 100644 --- a/src/main.ts +++ b/src/main.ts @@ -35,6 +35,7 @@ import { registerAToZChallengeCodeBlockProcessor } from "@ui/code-blocks/AToZCha import moment from "@external/moment"; import { compressImage } from "@utils/image"; import { titleSortValue } from "@utils/text"; +import { UpdateCoverFromURLCommand } from "@commands/UpdateCoverFromURLCommand"; export default class BookTrackerPlugin extends Plugin { public settings: BookTrackerPluginSettings; @@ -90,6 +91,7 @@ export default class BookTrackerPlugin extends Plugin { ) ); this.addCommand(new ReloadReadingLogCommand(this.readingLog)); + this.addCommand(new UpdateCoverFromURLCommand(this.app, this.settings, this.downloadCoverImage.bind(this))); this.addSettingTab(new BookTrackerSettingTab(this)); @@ -100,7 +102,7 @@ export default class BookTrackerPlugin extends Plugin { registerAToZChallengeCodeBlockProcessor(this); } - onunload() {} + onunload() { } async loadSettings() { this.settings = Object.assign( @@ -149,7 +151,12 @@ export default class BookTrackerPlugin extends Plugin { overwrite?: boolean ): Promise { const response = await requestUrl(coverImageUrl); - const contentType = response.headers["content-type"]; + const header = Object.keys(response.headers).find(k => k.toLowerCase() === "content-type") ?? "Content-Type"; + const contentType = response.headers[header] ?? "application/octet-stream"; + + if (!contentType.startsWith("image/") && contentType !== "application/octet-stream") { + throw new Error("Unexpected content type: " + contentType); + } const urlExtension = coverImageUrl.split(".").pop(); const extension = @@ -217,9 +224,9 @@ export default class BookTrackerPlugin extends Plugin { })), series: book.series ? { - ...book.series, - title: safeString(book.series.title), - } + ...book.series, + title: safeString(book.series.title), + } : null, publisher: safeString(book.publisher), publishedAt: book.publishedAt,