generated from tpl/obsidian-sample-plugin
			Move commands into classes
This commit is contained in:
		
							parent
							
								
									e4a416ec2f
								
							
						
					
					
						commit
						5a492f558c
					
				| 
						 | 
					@ -0,0 +1,99 @@
 | 
				
			||||||
 | 
					import type {
 | 
				
			||||||
 | 
						Editor,
 | 
				
			||||||
 | 
						Hotkey,
 | 
				
			||||||
 | 
						MarkdownFileInfo,
 | 
				
			||||||
 | 
						MarkdownView,
 | 
				
			||||||
 | 
						Command as ObsidianCommand,
 | 
				
			||||||
 | 
					} from "obsidian";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export abstract class Command implements ObsidianCommand {
 | 
				
			||||||
 | 
						private _icon?: string;
 | 
				
			||||||
 | 
						public get icon(): string | undefined {
 | 
				
			||||||
 | 
							return this._icon;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						protected setIcon(icon: string) {
 | 
				
			||||||
 | 
							this._icon = icon;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						private _mobileOnly?: boolean;
 | 
				
			||||||
 | 
						public get mobileOnly(): boolean | undefined {
 | 
				
			||||||
 | 
							return this._mobileOnly;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						protected setMobileOnly(mobileOnly: boolean) {
 | 
				
			||||||
 | 
							this._mobileOnly = mobileOnly;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						private _repeatable?: boolean;
 | 
				
			||||||
 | 
						public get repeatable(): boolean | undefined {
 | 
				
			||||||
 | 
							return this._repeatable;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						protected setRepeatable(repeatable: boolean) {
 | 
				
			||||||
 | 
							this._repeatable = repeatable;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						private _hotkeys: Hotkey[] = [];
 | 
				
			||||||
 | 
						public get hotkeys(): Hotkey[] {
 | 
				
			||||||
 | 
							return this._hotkeys;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						protected addHotkey(hotkey: Hotkey) {
 | 
				
			||||||
 | 
							this._hotkeys.push(hotkey);
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						protected removeHotkey(hotkey: Hotkey) {
 | 
				
			||||||
 | 
							this._hotkeys = this._hotkeys.filter(
 | 
				
			||||||
 | 
								(h) => h.key !== hotkey.key && h.modifiers !== h.modifiers
 | 
				
			||||||
 | 
							);
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						protected clearHotkeys() {
 | 
				
			||||||
 | 
							this._hotkeys = [];
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						constructor(public id: string, public name: string) {}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						callback?(): any;
 | 
				
			||||||
 | 
						checkCallback?(checking: boolean): boolean;
 | 
				
			||||||
 | 
						editorCallback?(
 | 
				
			||||||
 | 
							editor: Editor,
 | 
				
			||||||
 | 
							ctx: MarkdownView | MarkdownFileInfo
 | 
				
			||||||
 | 
						): boolean;
 | 
				
			||||||
 | 
						editorCheckCallback?(
 | 
				
			||||||
 | 
							checking: boolean,
 | 
				
			||||||
 | 
							editor: Editor,
 | 
				
			||||||
 | 
							ctx: MarkdownView | MarkdownFileInfo
 | 
				
			||||||
 | 
						): boolean | void;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export abstract class CheckCommand extends Command {
 | 
				
			||||||
 | 
						checkCallback(checking: boolean): boolean {
 | 
				
			||||||
 | 
							if (!this.check()) return false;
 | 
				
			||||||
 | 
							if (!checking) {
 | 
				
			||||||
 | 
								this.run();
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							return true;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						protected abstract check(): boolean;
 | 
				
			||||||
 | 
						protected abstract run(): void | Promise<void>;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export abstract class EditorCheckCommand extends Command {
 | 
				
			||||||
 | 
						editorCheckCallback(
 | 
				
			||||||
 | 
							checking: boolean,
 | 
				
			||||||
 | 
							editor: Editor,
 | 
				
			||||||
 | 
							ctx: MarkdownView | MarkdownFileInfo
 | 
				
			||||||
 | 
						): boolean | void {
 | 
				
			||||||
 | 
							if (!this.check(editor, ctx)) return false;
 | 
				
			||||||
 | 
							if (!checking) {
 | 
				
			||||||
 | 
								this.run(editor, ctx);
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							return true;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						protected abstract check(
 | 
				
			||||||
 | 
							editor: Editor,
 | 
				
			||||||
 | 
							ctx: MarkdownView | MarkdownFileInfo
 | 
				
			||||||
 | 
						): boolean;
 | 
				
			||||||
 | 
						protected abstract run(
 | 
				
			||||||
 | 
							editor: Editor,
 | 
				
			||||||
 | 
							ctx: MarkdownView | MarkdownFileInfo
 | 
				
			||||||
 | 
						): void | Promise<void>;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,75 @@
 | 
				
			||||||
 | 
					import {
 | 
				
			||||||
 | 
						type Editor,
 | 
				
			||||||
 | 
						type MarkdownView,
 | 
				
			||||||
 | 
						type MarkdownFileInfo,
 | 
				
			||||||
 | 
						type App,
 | 
				
			||||||
 | 
						Notice,
 | 
				
			||||||
 | 
						TFile,
 | 
				
			||||||
 | 
					} from "obsidian";
 | 
				
			||||||
 | 
					import { EditorCheckCommand } from "./Command";
 | 
				
			||||||
 | 
					import type { BookTrackerPluginSettings } from "@ui/settings";
 | 
				
			||||||
 | 
					import { RatingModal } from "@ui/modals";
 | 
				
			||||||
 | 
					import type { ReadingLog } from "@utils/ReadingLog";
 | 
				
			||||||
 | 
					import { READ_STATE } from "@src/const";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export class LogReadingFinishedCommand extends EditorCheckCommand {
 | 
				
			||||||
 | 
						constructor(
 | 
				
			||||||
 | 
							private readonly app: App,
 | 
				
			||||||
 | 
							private readonly readingLog: ReadingLog,
 | 
				
			||||||
 | 
							private readonly settings: BookTrackerPluginSettings
 | 
				
			||||||
 | 
						) {
 | 
				
			||||||
 | 
							super("log-reading-finished", "Log Reading Finished");
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						private getPageCount(file: TFile): number {
 | 
				
			||||||
 | 
							return (
 | 
				
			||||||
 | 
								(this.app.metadataCache.getFileCache(file)?.frontmatter?.[
 | 
				
			||||||
 | 
									this.settings.pageCountProperty
 | 
				
			||||||
 | 
								] as number | undefined) ?? 0
 | 
				
			||||||
 | 
							);
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						protected check(
 | 
				
			||||||
 | 
							_editor: Editor,
 | 
				
			||||||
 | 
							ctx: MarkdownView | MarkdownFileInfo
 | 
				
			||||||
 | 
						): boolean {
 | 
				
			||||||
 | 
							return !(
 | 
				
			||||||
 | 
								ctx.file === null ||
 | 
				
			||||||
 | 
								this.settings.statusProperty === "" ||
 | 
				
			||||||
 | 
								this.settings.endDateProperty === "" ||
 | 
				
			||||||
 | 
								this.settings.ratingProperty === "" ||
 | 
				
			||||||
 | 
								this.settings.pageCountProperty === "" ||
 | 
				
			||||||
 | 
								this.getPageCount(ctx.file) <= 0
 | 
				
			||||||
 | 
							);
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						protected async run(
 | 
				
			||||||
 | 
							_editor: Editor,
 | 
				
			||||||
 | 
							ctx: MarkdownView | MarkdownFileInfo
 | 
				
			||||||
 | 
						): Promise<void> {
 | 
				
			||||||
 | 
							const file = ctx.file!;
 | 
				
			||||||
 | 
							const fileName = file.basename;
 | 
				
			||||||
 | 
							const pageCount = this.getPageCount(file);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							const rating = await RatingModal.createAndOpen(
 | 
				
			||||||
 | 
								this.app,
 | 
				
			||||||
 | 
								this.settings.spiceProperty !== ""
 | 
				
			||||||
 | 
							);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							await this.readingLog.addEntry(fileName, pageCount, pageCount);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// @ts-expect-error Moment is provided by Obsidian
 | 
				
			||||||
 | 
							const endDate = moment().format("YYYY-MM-DD");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							this.app.fileManager.processFrontMatter(file, (frontMatter) => {
 | 
				
			||||||
 | 
								frontMatter[this.settings.statusProperty] = READ_STATE;
 | 
				
			||||||
 | 
								frontMatter[this.settings.endDateProperty] = endDate;
 | 
				
			||||||
 | 
								frontMatter[this.settings.ratingProperty] = rating;
 | 
				
			||||||
 | 
								if (this.settings.spiceProperty !== "") {
 | 
				
			||||||
 | 
									frontMatter[this.settings.spiceProperty] = rating;
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							new Notice("Reading finished for " + fileName);
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,66 @@
 | 
				
			||||||
 | 
					import {
 | 
				
			||||||
 | 
						type Editor,
 | 
				
			||||||
 | 
						type MarkdownView,
 | 
				
			||||||
 | 
						type MarkdownFileInfo,
 | 
				
			||||||
 | 
						type App,
 | 
				
			||||||
 | 
						Notice,
 | 
				
			||||||
 | 
						TFile,
 | 
				
			||||||
 | 
					} from "obsidian";
 | 
				
			||||||
 | 
					import { Command, EditorCheckCommand } from "./Command";
 | 
				
			||||||
 | 
					import type { BookTrackerPluginSettings } from "@ui/settings";
 | 
				
			||||||
 | 
					import { ReadingProgressModal } from "@ui/modals";
 | 
				
			||||||
 | 
					import type { ReadingLog } from "@utils/ReadingLog";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export class LogReadingProgressCommand extends EditorCheckCommand {
 | 
				
			||||||
 | 
						constructor(
 | 
				
			||||||
 | 
							private readonly app: App,
 | 
				
			||||||
 | 
							private readonly readingLog: ReadingLog,
 | 
				
			||||||
 | 
							private readonly settings: BookTrackerPluginSettings
 | 
				
			||||||
 | 
						) {
 | 
				
			||||||
 | 
							super("log-reading-progress", "Log Reading Progress");
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						private getPageCount(file: TFile): number {
 | 
				
			||||||
 | 
							return (
 | 
				
			||||||
 | 
								(this.app.metadataCache.getFileCache(file)?.frontmatter?.[
 | 
				
			||||||
 | 
									this.settings.pageCountProperty
 | 
				
			||||||
 | 
								] as number | undefined) ?? 0
 | 
				
			||||||
 | 
							);
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						protected check(
 | 
				
			||||||
 | 
							_editor: Editor,
 | 
				
			||||||
 | 
							ctx: MarkdownView | MarkdownFileInfo
 | 
				
			||||||
 | 
						): boolean {
 | 
				
			||||||
 | 
							return !(
 | 
				
			||||||
 | 
								ctx.file === null ||
 | 
				
			||||||
 | 
								this.settings.pageCountProperty === "" ||
 | 
				
			||||||
 | 
								this.getPageCount(ctx.file) <= 0
 | 
				
			||||||
 | 
							);
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						protected async run(
 | 
				
			||||||
 | 
							_editor: Editor,
 | 
				
			||||||
 | 
							ctx: MarkdownView | MarkdownFileInfo
 | 
				
			||||||
 | 
						): Promise<void> {
 | 
				
			||||||
 | 
							const file = ctx.file!;
 | 
				
			||||||
 | 
							const fileName = file.basename;
 | 
				
			||||||
 | 
							const pageCount = this.getPageCount(file);
 | 
				
			||||||
 | 
							const pageNumber = await ReadingProgressModal.createAndOpen(
 | 
				
			||||||
 | 
								this.app,
 | 
				
			||||||
 | 
								pageCount
 | 
				
			||||||
 | 
							);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							if (pageNumber <= 0 || pageNumber > pageCount) {
 | 
				
			||||||
 | 
								new Notice(
 | 
				
			||||||
 | 
									`Invalid page number: ${pageNumber}. It must be between 1 and ${pageCount}.`
 | 
				
			||||||
 | 
								);
 | 
				
			||||||
 | 
								return;
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							await this.readingLog.addEntry(fileName, pageNumber, pageCount);
 | 
				
			||||||
 | 
							new Notice(
 | 
				
			||||||
 | 
								`Logged reading progress for ${fileName}: Page ${pageNumber} of ${pageCount}.`
 | 
				
			||||||
 | 
							);
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,47 @@
 | 
				
			||||||
 | 
					import {
 | 
				
			||||||
 | 
						type Editor,
 | 
				
			||||||
 | 
						type MarkdownView,
 | 
				
			||||||
 | 
						type MarkdownFileInfo,
 | 
				
			||||||
 | 
						type App,
 | 
				
			||||||
 | 
						Notice,
 | 
				
			||||||
 | 
					} from "obsidian";
 | 
				
			||||||
 | 
					import { EditorCheckCommand } from "./Command";
 | 
				
			||||||
 | 
					import type { BookTrackerPluginSettings } from "@ui/settings";
 | 
				
			||||||
 | 
					import { IN_PROGRESS_STATE } from "@src/const";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export class LogReadingStartedCommand extends EditorCheckCommand {
 | 
				
			||||||
 | 
						constructor(
 | 
				
			||||||
 | 
							private readonly app: App,
 | 
				
			||||||
 | 
							private readonly settings: BookTrackerPluginSettings
 | 
				
			||||||
 | 
						) {
 | 
				
			||||||
 | 
							super("log-reading-started", "Log Reading Started");
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						protected check(
 | 
				
			||||||
 | 
							_editor: Editor,
 | 
				
			||||||
 | 
							ctx: MarkdownView | MarkdownFileInfo
 | 
				
			||||||
 | 
						): boolean {
 | 
				
			||||||
 | 
							return !(
 | 
				
			||||||
 | 
								ctx.file === null ||
 | 
				
			||||||
 | 
								this.settings.statusProperty === "" ||
 | 
				
			||||||
 | 
								this.settings.startDateProperty === ""
 | 
				
			||||||
 | 
							);
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						protected async run(
 | 
				
			||||||
 | 
							_editor: Editor,
 | 
				
			||||||
 | 
							ctx: MarkdownView | MarkdownFileInfo
 | 
				
			||||||
 | 
						): Promise<void> {
 | 
				
			||||||
 | 
							const file = ctx.file!;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// @ts-expect-error Moment is provided by Obsidian
 | 
				
			||||||
 | 
							const startDate = moment().format("YYYY-MM-DD");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							this.app.fileManager.processFrontMatter(file, (frontMatter) => {
 | 
				
			||||||
 | 
								frontMatter[this.settings.statusProperty] = IN_PROGRESS_STATE;
 | 
				
			||||||
 | 
								frontMatter[this.settings.startDateProperty] = startDate;
 | 
				
			||||||
 | 
							});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							new Notice("Reading started for " + file.basename);
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,50 @@
 | 
				
			||||||
 | 
					import {
 | 
				
			||||||
 | 
						Notice,
 | 
				
			||||||
 | 
						type App,
 | 
				
			||||||
 | 
						type Editor,
 | 
				
			||||||
 | 
						type MarkdownFileInfo,
 | 
				
			||||||
 | 
						type MarkdownView,
 | 
				
			||||||
 | 
					} from "obsidian";
 | 
				
			||||||
 | 
					import { Command, EditorCheckCommand } from "./Command";
 | 
				
			||||||
 | 
					import type { BookTrackerPluginSettings } from "@ui/settings";
 | 
				
			||||||
 | 
					import type { ReadingLog } from "@utils/ReadingLog";
 | 
				
			||||||
 | 
					import { TO_BE_READ_STATE } from "@src/const";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export class ResetReadingStatusCommand extends EditorCheckCommand {
 | 
				
			||||||
 | 
						constructor(
 | 
				
			||||||
 | 
							private readonly app: App,
 | 
				
			||||||
 | 
							private readonly readingLog: ReadingLog,
 | 
				
			||||||
 | 
							private readonly settings: BookTrackerPluginSettings
 | 
				
			||||||
 | 
						) {
 | 
				
			||||||
 | 
							super("reset-reading-status", "Reset Reading Status");
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						protected check(
 | 
				
			||||||
 | 
							_editor: Editor,
 | 
				
			||||||
 | 
							ctx: MarkdownView | MarkdownFileInfo
 | 
				
			||||||
 | 
						): boolean {
 | 
				
			||||||
 | 
							return !(
 | 
				
			||||||
 | 
								ctx.file === null ||
 | 
				
			||||||
 | 
								this.settings.statusProperty === "" ||
 | 
				
			||||||
 | 
								this.settings.startDateProperty === "" ||
 | 
				
			||||||
 | 
								this.settings.endDateProperty === ""
 | 
				
			||||||
 | 
							);
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						protected run(
 | 
				
			||||||
 | 
							_editor: Editor,
 | 
				
			||||||
 | 
							ctx: MarkdownView | MarkdownFileInfo
 | 
				
			||||||
 | 
						): void | Promise<void> {
 | 
				
			||||||
 | 
							const file = ctx.file!;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							this.app.fileManager.processFrontMatter(file, (frontMatter) => {
 | 
				
			||||||
 | 
								frontMatter[this.settings.statusProperty] = TO_BE_READ_STATE;
 | 
				
			||||||
 | 
								frontMatter[this.settings.startDateProperty] = "";
 | 
				
			||||||
 | 
								frontMatter[this.settings.endDateProperty] = "";
 | 
				
			||||||
 | 
							});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							this.readingLog.removeEntries(file.basename);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							new Notice("Reading status reset for " + file.basename);
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,47 @@
 | 
				
			||||||
 | 
					import { getBookByLegacyId, type SearchResult } from "@data-sources/goodreads";
 | 
				
			||||||
 | 
					import { GoodreadsSearchModal, GoodreadsSearchSuggestModal } from "@ui/modals";
 | 
				
			||||||
 | 
					import { App, Notice } from "obsidian";
 | 
				
			||||||
 | 
					import { Command } from "./Command";
 | 
				
			||||||
 | 
					import type { Book } from "@src/types";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export class SearchGoodreadsCommand extends Command {
 | 
				
			||||||
 | 
						constructor(
 | 
				
			||||||
 | 
							private readonly app: App,
 | 
				
			||||||
 | 
							private readonly cb: (book: Book) => void
 | 
				
			||||||
 | 
						) {
 | 
				
			||||||
 | 
							super("search-goodreads", "Search Goodreads");
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						async callback() {
 | 
				
			||||||
 | 
							let results: SearchResult[];
 | 
				
			||||||
 | 
							try {
 | 
				
			||||||
 | 
								results = await GoodreadsSearchModal.createAndOpen(this.app);
 | 
				
			||||||
 | 
							} catch (error) {
 | 
				
			||||||
 | 
								console.error("Failed to search Goodreads:", error);
 | 
				
			||||||
 | 
								new Notice(
 | 
				
			||||||
 | 
									"Failed to search Goodreads. Check console for details."
 | 
				
			||||||
 | 
								);
 | 
				
			||||||
 | 
								return;
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							const selectedResult = await GoodreadsSearchSuggestModal.createAndOpen(
 | 
				
			||||||
 | 
								this.app,
 | 
				
			||||||
 | 
								results
 | 
				
			||||||
 | 
							);
 | 
				
			||||||
 | 
							if (!selectedResult) {
 | 
				
			||||||
 | 
								new Notice("No book selected.");
 | 
				
			||||||
 | 
								return;
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							let book: Book;
 | 
				
			||||||
 | 
							try {
 | 
				
			||||||
 | 
								book = await getBookByLegacyId(selectedResult.legacyId);
 | 
				
			||||||
 | 
							} catch (error) {
 | 
				
			||||||
 | 
								console.error("Failed to get book:", error);
 | 
				
			||||||
 | 
								new Notice("Failed to get book. Check console for details.");
 | 
				
			||||||
 | 
								return;
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							this.cb(book);
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										283
									
								
								src/main.ts
								
								
								
								
							
							
						
						
									
										283
									
								
								src/main.ts
								
								
								
								
							| 
						 | 
					@ -4,23 +4,17 @@ import {
 | 
				
			||||||
	DEFAULT_SETTINGS,
 | 
						DEFAULT_SETTINGS,
 | 
				
			||||||
	BookTrackerSettingTab,
 | 
						BookTrackerSettingTab,
 | 
				
			||||||
} from "@ui/settings";
 | 
					} from "@ui/settings";
 | 
				
			||||||
import { getBookByLegacyId, type SearchResult } from "@data-sources/goodreads";
 | 
					 | 
				
			||||||
import { Templater } from "@utils/Templater";
 | 
					import { Templater } from "@utils/Templater";
 | 
				
			||||||
import {
 | 
					import { CONTENT_TYPE_EXTENSIONS } from "./const";
 | 
				
			||||||
	GoodreadsSearchModal,
 | 
					 | 
				
			||||||
	GoodreadsSearchSuggestModal,
 | 
					 | 
				
			||||||
	ReadingProgressModal,
 | 
					 | 
				
			||||||
	RatingModal,
 | 
					 | 
				
			||||||
} from "@ui/modals";
 | 
					 | 
				
			||||||
import {
 | 
					 | 
				
			||||||
	CONTENT_TYPE_EXTENSIONS,
 | 
					 | 
				
			||||||
	IN_PROGRESS_STATE,
 | 
					 | 
				
			||||||
	READ_STATE,
 | 
					 | 
				
			||||||
	TO_BE_READ_STATE,
 | 
					 | 
				
			||||||
} from "./const";
 | 
					 | 
				
			||||||
import { Storage } from "@utils/Storage";
 | 
					import { Storage } from "@utils/Storage";
 | 
				
			||||||
import { ReadingLog } from "@utils/ReadingLog";
 | 
					import { ReadingLog } from "@utils/ReadingLog";
 | 
				
			||||||
import { registerReadingLogCodeBlockProcessor } from "@ui/code-blocks";
 | 
					import { registerReadingLogCodeBlockProcessor } from "@ui/code-blocks";
 | 
				
			||||||
 | 
					import type { Book } from "./types";
 | 
				
			||||||
 | 
					import { SearchGoodreadsCommand } from "@commands/SearchGoodreadsCommand";
 | 
				
			||||||
 | 
					import { LogReadingStartedCommand } from "@commands/LogReadingStartedCommand";
 | 
				
			||||||
 | 
					import { LogReadingProgressCommand } from "@commands/LogReadingProgressCommand";
 | 
				
			||||||
 | 
					import { LogReadingFinishedCommand } from "@commands/LogReadingFinishedCommand";
 | 
				
			||||||
 | 
					import { ResetReadingStatusCommand } from "@commands/ResetReadingStatusCommand";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export default class BookTrackerPlugin extends Plugin {
 | 
					export default class BookTrackerPlugin extends Plugin {
 | 
				
			||||||
	settings: BookTrackerPluginSettings;
 | 
						settings: BookTrackerPluginSettings;
 | 
				
			||||||
| 
						 | 
					@ -35,35 +29,31 @@ export default class BookTrackerPlugin extends Plugin {
 | 
				
			||||||
		this.storage = new Storage(this.app, this);
 | 
							this.storage = new Storage(this.app, this);
 | 
				
			||||||
		this.readingLog = new ReadingLog(this.storage);
 | 
							this.readingLog = new ReadingLog(this.storage);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		this.addCommand({
 | 
							this.addCommand(
 | 
				
			||||||
			id: "search-goodreads",
 | 
								new SearchGoodreadsCommand(this.app, this.createEntry.bind(this))
 | 
				
			||||||
			name: "Search Goodreads",
 | 
							);
 | 
				
			||||||
			callback: () => this.searchGoodreads(),
 | 
							this.addCommand(new LogReadingStartedCommand(this.app, this.settings));
 | 
				
			||||||
		});
 | 
							this.addCommand(
 | 
				
			||||||
 | 
								new LogReadingProgressCommand(
 | 
				
			||||||
		this.addCommand({
 | 
									this.app,
 | 
				
			||||||
			id: "log-reading-started",
 | 
									this.readingLog,
 | 
				
			||||||
			name: "Log Reading Started",
 | 
									this.settings
 | 
				
			||||||
			callback: () => this.logReadingStarted(),
 | 
								)
 | 
				
			||||||
		});
 | 
							);
 | 
				
			||||||
 | 
							this.addCommand(
 | 
				
			||||||
		this.addCommand({
 | 
								new LogReadingFinishedCommand(
 | 
				
			||||||
			id: "log-reading-progress",
 | 
									this.app,
 | 
				
			||||||
			name: "Log Reading Progress",
 | 
									this.readingLog,
 | 
				
			||||||
			callback: () => this.logReadingProgress(),
 | 
									this.settings
 | 
				
			||||||
		});
 | 
								)
 | 
				
			||||||
 | 
							);
 | 
				
			||||||
		this.addCommand({
 | 
							this.addCommand(
 | 
				
			||||||
			id: "log-reading-completed",
 | 
								new ResetReadingStatusCommand(
 | 
				
			||||||
			name: "Log Reading Completed",
 | 
									this.app,
 | 
				
			||||||
			callback: () => this.logReadingFinished(),
 | 
									this.readingLog,
 | 
				
			||||||
		});
 | 
									this.settings
 | 
				
			||||||
 | 
								)
 | 
				
			||||||
		this.addCommand({
 | 
							);
 | 
				
			||||||
			id: "reset-reading-status",
 | 
					 | 
				
			||||||
			name: "Reset Reading Status",
 | 
					 | 
				
			||||||
			callback: () => this.resetReadingStatus(),
 | 
					 | 
				
			||||||
		});
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
		this.addSettingTab(new BookTrackerSettingTab(this));
 | 
							this.addSettingTab(new BookTrackerSettingTab(this));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -124,10 +114,8 @@ export default class BookTrackerPlugin extends Plugin {
 | 
				
			||||||
		return filePath;
 | 
							return filePath;
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	async createEntryFromGoodreads(legacyId: number): Promise<void> {
 | 
						async createEntry(book: Book): Promise<void> {
 | 
				
			||||||
		try {
 | 
							try {
 | 
				
			||||||
			const book = await getBookByLegacyId(legacyId);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
			const fileName = this.templater
 | 
								const fileName = this.templater
 | 
				
			||||||
				.renderTemplate(this.settings.fileNameFormat, {
 | 
									.renderTemplate(this.settings.fileNameFormat, {
 | 
				
			||||||
					title: book.title,
 | 
										title: book.title,
 | 
				
			||||||
| 
						 | 
					@ -159,209 +147,4 @@ export default class BookTrackerPlugin extends Plugin {
 | 
				
			||||||
			console.error("Failed to create book entry:", error);
 | 
								console.error("Failed to create book entry:", error);
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					 | 
				
			||||||
	async searchGoodreads(): Promise<void> {
 | 
					 | 
				
			||||||
		let results: SearchResult[];
 | 
					 | 
				
			||||||
		try {
 | 
					 | 
				
			||||||
			results = await GoodreadsSearchModal.createAndOpen(this.app);
 | 
					 | 
				
			||||||
		} catch (error) {
 | 
					 | 
				
			||||||
			console.error("Failed to search Goodreads:", error);
 | 
					 | 
				
			||||||
			new Notice(
 | 
					 | 
				
			||||||
				"Failed to search Goodreads. Check console for details."
 | 
					 | 
				
			||||||
			);
 | 
					 | 
				
			||||||
			return;
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
		const selectedBook = await GoodreadsSearchSuggestModal.createAndOpen(
 | 
					 | 
				
			||||||
			this.app,
 | 
					 | 
				
			||||||
			results
 | 
					 | 
				
			||||||
		);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		if (selectedBook) {
 | 
					 | 
				
			||||||
			await this.createEntryFromGoodreads(selectedBook.legacyId);
 | 
					 | 
				
			||||||
		} else {
 | 
					 | 
				
			||||||
			new Notice("No book selected.");
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	logReadingStarted(): void {
 | 
					 | 
				
			||||||
		const activeFile = this.app.workspace.getActiveFile();
 | 
					 | 
				
			||||||
		if (!activeFile) {
 | 
					 | 
				
			||||||
			new Notice("No active file to mark as currently reading.");
 | 
					 | 
				
			||||||
			return;
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		if (activeFile.extension !== "md") {
 | 
					 | 
				
			||||||
			new Notice("Active file is not a markdown file.");
 | 
					 | 
				
			||||||
			return;
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		if (!this.settings.statusProperty) {
 | 
					 | 
				
			||||||
			new Notice("Status property is not set in settings.");
 | 
					 | 
				
			||||||
			return;
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		if (!this.settings.startDateProperty) {
 | 
					 | 
				
			||||||
			new Notice("Start date property is not set in settings.");
 | 
					 | 
				
			||||||
			return;
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		// @ts-expect-error Moment is provided by Obsidian
 | 
					 | 
				
			||||||
		const startDate = moment().format("YYYY-MM-DD");
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		this.app.fileManager.processFrontMatter(activeFile, (frontMatter) => {
 | 
					 | 
				
			||||||
			frontMatter[this.settings.statusProperty] = IN_PROGRESS_STATE;
 | 
					 | 
				
			||||||
			frontMatter[this.settings.startDateProperty] = startDate;
 | 
					 | 
				
			||||||
		});
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		new Notice("Reading started for " + activeFile.name);
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	async logReadingProgress() {
 | 
					 | 
				
			||||||
		const activeFile = this.app.workspace.getActiveFile();
 | 
					 | 
				
			||||||
		if (!activeFile) {
 | 
					 | 
				
			||||||
			new Notice("No active file to log reading progress.");
 | 
					 | 
				
			||||||
			return;
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		if (activeFile.extension !== "md") {
 | 
					 | 
				
			||||||
			new Notice("Active file is not a markdown file.");
 | 
					 | 
				
			||||||
			return;
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		const fileName = activeFile.basename;
 | 
					 | 
				
			||||||
		if (!this.settings.pageCountProperty) {
 | 
					 | 
				
			||||||
			new Notice("Page count property is not set in settings.");
 | 
					 | 
				
			||||||
			return;
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		const pageCount =
 | 
					 | 
				
			||||||
			(this.app.metadataCache.getFileCache(activeFile)?.frontmatter?.[
 | 
					 | 
				
			||||||
				this.settings.pageCountProperty
 | 
					 | 
				
			||||||
			] as number | undefined) ?? 0;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		if (pageCount <= 0) {
 | 
					 | 
				
			||||||
			new Notice(
 | 
					 | 
				
			||||||
				"Page length property is not set or is invalid in the active file."
 | 
					 | 
				
			||||||
			);
 | 
					 | 
				
			||||||
			return;
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		const pageNumber = await ReadingProgressModal.createAndOpen(
 | 
					 | 
				
			||||||
			this.app,
 | 
					 | 
				
			||||||
			pageCount
 | 
					 | 
				
			||||||
		);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		if (pageNumber <= 0 || pageNumber > pageCount) {
 | 
					 | 
				
			||||||
			new Notice(
 | 
					 | 
				
			||||||
				`Invalid page number: ${pageNumber}. It must be between 1 and ${pageCount}.`
 | 
					 | 
				
			||||||
			);
 | 
					 | 
				
			||||||
			return;
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		await this.readingLog.addEntry(fileName, pageNumber, pageCount);
 | 
					 | 
				
			||||||
		new Notice(
 | 
					 | 
				
			||||||
			`Logged reading progress for ${fileName}: Page ${pageNumber} of ${pageCount}.`
 | 
					 | 
				
			||||||
		);
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	async logReadingFinished() {
 | 
					 | 
				
			||||||
		const activeFile = this.app.workspace.getActiveFile();
 | 
					 | 
				
			||||||
		if (!activeFile) {
 | 
					 | 
				
			||||||
			new Notice("No active file to mark as finished reading.");
 | 
					 | 
				
			||||||
			return;
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		if (activeFile.extension !== "md") {
 | 
					 | 
				
			||||||
			new Notice("Active file is not a markdown file.");
 | 
					 | 
				
			||||||
			return;
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		if (!this.settings.statusProperty) {
 | 
					 | 
				
			||||||
			new Notice("Status property is not set in settings.");
 | 
					 | 
				
			||||||
			return;
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		if (!this.settings.endDateProperty) {
 | 
					 | 
				
			||||||
			new Notice("End date property is not set in settings.");
 | 
					 | 
				
			||||||
			return;
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		if (!this.settings.ratingProperty) {
 | 
					 | 
				
			||||||
			new Notice("Rating property is not set in settings.");
 | 
					 | 
				
			||||||
			return;
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		const pageCount =
 | 
					 | 
				
			||||||
			(this.app.metadataCache.getFileCache(activeFile)?.frontmatter?.[
 | 
					 | 
				
			||||||
				this.settings.pageCountProperty
 | 
					 | 
				
			||||||
			] as number | undefined) ?? 0;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		if (pageCount <= 0) {
 | 
					 | 
				
			||||||
			new Notice(
 | 
					 | 
				
			||||||
				"Page count property is not set or is invalid in the active file."
 | 
					 | 
				
			||||||
			);
 | 
					 | 
				
			||||||
			return;
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		const rating = await RatingModal.createAndOpen(
 | 
					 | 
				
			||||||
			this.app,
 | 
					 | 
				
			||||||
			this.settings.spiceProperty !== ""
 | 
					 | 
				
			||||||
		);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		await this.readingLog.addEntry(
 | 
					 | 
				
			||||||
			activeFile.basename,
 | 
					 | 
				
			||||||
			pageCount,
 | 
					 | 
				
			||||||
			pageCount
 | 
					 | 
				
			||||||
		);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		// @ts-expect-error Moment is provided by Obsidian
 | 
					 | 
				
			||||||
		const endDate = moment().format("YYYY-MM-DD");
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		this.app.fileManager.processFrontMatter(activeFile, (frontMatter) => {
 | 
					 | 
				
			||||||
			frontMatter[this.settings.statusProperty] = READ_STATE;
 | 
					 | 
				
			||||||
			frontMatter[this.settings.endDateProperty] = endDate;
 | 
					 | 
				
			||||||
			frontMatter[this.settings.ratingProperty] = rating;
 | 
					 | 
				
			||||||
			if (this.settings.spiceProperty !== "") {
 | 
					 | 
				
			||||||
				frontMatter[this.settings.spiceProperty] = rating;
 | 
					 | 
				
			||||||
			}
 | 
					 | 
				
			||||||
		});
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		new Notice("Reading finished for " + activeFile.name);
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	resetReadingStatus(): any {
 | 
					 | 
				
			||||||
		const activeFile = this.app.workspace.getActiveFile();
 | 
					 | 
				
			||||||
		if (!activeFile) {
 | 
					 | 
				
			||||||
			new Notice("No active file to reset reading status.");
 | 
					 | 
				
			||||||
			return;
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		if (activeFile.extension !== "md") {
 | 
					 | 
				
			||||||
			new Notice("Active file is not a markdown file.");
 | 
					 | 
				
			||||||
			return;
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		if (!this.settings.statusProperty) {
 | 
					 | 
				
			||||||
			new Notice("Status property is not set in settings.");
 | 
					 | 
				
			||||||
			return;
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		if (!this.settings.startDateProperty) {
 | 
					 | 
				
			||||||
			new Notice("Start date property is not set in settings.");
 | 
					 | 
				
			||||||
			return;
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		if (!this.settings.endDateProperty) {
 | 
					 | 
				
			||||||
			new Notice("End date property is not set in settings.");
 | 
					 | 
				
			||||||
			return;
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		this.app.fileManager.processFrontMatter(activeFile, (frontMatter) => {
 | 
					 | 
				
			||||||
			frontMatter[this.settings.statusProperty] = TO_BE_READ_STATE;
 | 
					 | 
				
			||||||
			frontMatter[this.settings.startDateProperty] = "";
 | 
					 | 
				
			||||||
			frontMatter[this.settings.endDateProperty] = "";
 | 
					 | 
				
			||||||
		});
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		new Notice("Reading status reset for " + activeFile.name);
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -20,6 +20,7 @@
 | 
				
			||||||
      "ES7"
 | 
					      "ES7"
 | 
				
			||||||
    ],
 | 
					    ],
 | 
				
			||||||
    "paths": {
 | 
					    "paths": {
 | 
				
			||||||
 | 
					      "@commands/*": ["src/commands/*"],
 | 
				
			||||||
      "@data-sources/*": ["src/data-sources/*"],
 | 
					      "@data-sources/*": ["src/data-sources/*"],
 | 
				
			||||||
      "@ui/*": ["src/ui/*"],
 | 
					      "@ui/*": ["src/ui/*"],
 | 
				
			||||||
      "@utils/*": ["src/utils/*"],
 | 
					      "@utils/*": ["src/utils/*"],
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
		Loading…
	
		Reference in New Issue