diff --git a/src/commands/CreateBookFromClipboardCommand.ts b/src/commands/CreateBookFromClipboardCommand.ts new file mode 100644 index 0000000..22260b9 --- /dev/null +++ b/src/commands/CreateBookFromClipboardCommand.ts @@ -0,0 +1,36 @@ +import { Notice } from "obsidian"; +import { Command } from "./Command"; +import { bookSchema, type Book } from "@src/types"; + + +export class CreateBookFromClipboardCommand extends Command { + constructor( + private readonly createEntry: (book: Book) => void | PromiseLike + ) { + super( + "create-book-from-clipboard", + "Create Book from Clipboard" + ); + } + + async callback() { + const data = await navigator.clipboard.readText(); + const { data: book, success, error } = await bookSchema.safeParseAsync(JSON.parse(data)); + + if (!success) { + console.error(error.message); + new Notice("There is not a valid book in the clipboard. Check console for details"); + return + } + + try { + await this.createEntry(book); + } catch (error) { + console.error("Failed to create book:", error); + new Notice("Failed to create book. Check console for details."); + return; + } + + new Notice("Book created from clipboard data."); + } +} diff --git a/src/main.ts b/src/main.ts index cf078f3..be4320a 100644 --- a/src/main.ts +++ b/src/main.ts @@ -34,6 +34,7 @@ import { import { CoverImageDownloaderService } from "./services/CoverImageDownloaderService"; import { ImageCompressorService } from "./services/ImageCompressorService"; import type { Book, BookMetadata, ReadingState } from "./types"; +import { CreateBookFromClipboardCommand } from "@commands/CreateBookFromClipboardCommand"; export default class BookTrackerPlugin extends Plugin { public settings: BookTrackerPluginSettings; @@ -86,6 +87,7 @@ export default class BookTrackerPlugin extends Plugin { this.readingLog ) ); + this.addCommand(new CreateBookFromClipboardCommand(this.createEntry.bind(this))) this.addCommand( new CreateBookFromGoodreadsUrlCommand( this.goodreads, diff --git a/src/types.ts b/src/types.ts index 43d70c4..1451c7f 100644 --- a/src/types.ts +++ b/src/types.ts @@ -2,30 +2,47 @@ import moment from "@external/moment"; import { STATUS_IN_PROGRESS, STATUS_READ, STATUS_TO_BE_READ } from "./const"; import z from "zod/v4"; +export const authorSchema = z.object({ + name: z.string() + .transform(val => val.replace(/\s{2,}/g, ' ')), + description: z.string().optional(), +}); + export interface Author { name: string; description?: string; } +export const seriesSchema = z.object({ + title: z.string(), + position: z.number().or(z.nan()), +}) + export interface Series { title: string; position: number; } -export interface Book { - title: string; - subtitle: string; - description: string; - authors: Author[]; - series: Series | null; - publisher: string; - publishedAt: Date; - genres: string[]; - coverImageUrl: string; - pageCount: number; - isbn: string; - isbn13: string; -} +export const bookSchema = z.object({ + title: z.string(), + subtitle: z.string(), + description: z.string(), + authors: z.array(authorSchema), + series: seriesSchema.nullable(), + publisher: z.string(), + publishedAt: z.date(), + genres: z.array(z.string()), + coverImageUrl: z.url(), + pageCount: z.number().min(0), + isbn: z.string() + .transform(val => val.replace(/-+/g, '')) + .pipe(z.string().length(10)), + isbn13: z.string() + .transform(val => val.replace(/-+/g, '')) + .pipe(z.string().length(10)), +}); + +export type Book = z.infer; export type ToBeReadState = typeof STATUS_TO_BE_READ; export type InProgressState = typeof STATUS_IN_PROGRESS;