Add Create Book from Clipboard Command

This commit is contained in:
Evan Fiordeliso 2026-06-11 17:23:45 -04:00
parent 2f89a703d3
commit 9213e1ef0d
3 changed files with 69 additions and 14 deletions

View File

@ -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<void>
) {
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.");
}
}

View File

@ -34,6 +34,7 @@ import {
import { CoverImageDownloaderService } from "./services/CoverImageDownloaderService"; import { CoverImageDownloaderService } from "./services/CoverImageDownloaderService";
import { ImageCompressorService } from "./services/ImageCompressorService"; import { ImageCompressorService } from "./services/ImageCompressorService";
import type { Book, BookMetadata, ReadingState } from "./types"; import type { Book, BookMetadata, ReadingState } from "./types";
import { CreateBookFromClipboardCommand } from "@commands/CreateBookFromClipboardCommand";
export default class BookTrackerPlugin extends Plugin { export default class BookTrackerPlugin extends Plugin {
public settings: BookTrackerPluginSettings; public settings: BookTrackerPluginSettings;
@ -86,6 +87,7 @@ export default class BookTrackerPlugin extends Plugin {
this.readingLog this.readingLog
) )
); );
this.addCommand(new CreateBookFromClipboardCommand(this.createEntry.bind(this)))
this.addCommand( this.addCommand(
new CreateBookFromGoodreadsUrlCommand( new CreateBookFromGoodreadsUrlCommand(
this.goodreads, this.goodreads,

View File

@ -2,30 +2,47 @@ import moment from "@external/moment";
import { STATUS_IN_PROGRESS, STATUS_READ, STATUS_TO_BE_READ } from "./const"; import { STATUS_IN_PROGRESS, STATUS_READ, STATUS_TO_BE_READ } from "./const";
import z from "zod/v4"; 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 { export interface Author {
name: string; name: string;
description?: string; description?: string;
} }
export const seriesSchema = z.object({
title: z.string(),
position: z.number().or(z.nan()),
})
export interface Series { export interface Series {
title: string; title: string;
position: number; position: number;
} }
export interface Book { export const bookSchema = z.object({
title: string; title: z.string(),
subtitle: string; subtitle: z.string(),
description: string; description: z.string(),
authors: Author[]; authors: z.array(authorSchema),
series: Series | null; series: seriesSchema.nullable(),
publisher: string; publisher: z.string(),
publishedAt: Date; publishedAt: z.date(),
genres: string[]; genres: z.array(z.string()),
coverImageUrl: string; coverImageUrl: z.url(),
pageCount: number; pageCount: z.number().min(0),
isbn: string; isbn: z.string()
isbn13: 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<typeof bookSchema>;
export type ToBeReadState = typeof STATUS_TO_BE_READ; export type ToBeReadState = typeof STATUS_TO_BE_READ;
export type InProgressState = typeof STATUS_IN_PROGRESS; export type InProgressState = typeof STATUS_IN_PROGRESS;