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,
 | 
			
		||||
	BookTrackerSettingTab,
 | 
			
		||||
} from "@ui/settings";
 | 
			
		||||
import { getBookByLegacyId, type SearchResult } from "@data-sources/goodreads";
 | 
			
		||||
import { Templater } from "@utils/Templater";
 | 
			
		||||
import {
 | 
			
		||||
	GoodreadsSearchModal,
 | 
			
		||||
	GoodreadsSearchSuggestModal,
 | 
			
		||||
	ReadingProgressModal,
 | 
			
		||||
	RatingModal,
 | 
			
		||||
} from "@ui/modals";
 | 
			
		||||
import {
 | 
			
		||||
	CONTENT_TYPE_EXTENSIONS,
 | 
			
		||||
	IN_PROGRESS_STATE,
 | 
			
		||||
	READ_STATE,
 | 
			
		||||
	TO_BE_READ_STATE,
 | 
			
		||||
} from "./const";
 | 
			
		||||
import { CONTENT_TYPE_EXTENSIONS } from "./const";
 | 
			
		||||
import { Storage } from "@utils/Storage";
 | 
			
		||||
import { ReadingLog } from "@utils/ReadingLog";
 | 
			
		||||
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 {
 | 
			
		||||
	settings: BookTrackerPluginSettings;
 | 
			
		||||
| 
						 | 
				
			
			@ -35,35 +29,31 @@ export default class BookTrackerPlugin extends Plugin {
 | 
			
		|||
		this.storage = new Storage(this.app, this);
 | 
			
		||||
		this.readingLog = new ReadingLog(this.storage);
 | 
			
		||||
 | 
			
		||||
		this.addCommand({
 | 
			
		||||
			id: "search-goodreads",
 | 
			
		||||
			name: "Search Goodreads",
 | 
			
		||||
			callback: () => this.searchGoodreads(),
 | 
			
		||||
		});
 | 
			
		||||
 | 
			
		||||
		this.addCommand({
 | 
			
		||||
			id: "log-reading-started",
 | 
			
		||||
			name: "Log Reading Started",
 | 
			
		||||
			callback: () => this.logReadingStarted(),
 | 
			
		||||
		});
 | 
			
		||||
 | 
			
		||||
		this.addCommand({
 | 
			
		||||
			id: "log-reading-progress",
 | 
			
		||||
			name: "Log Reading Progress",
 | 
			
		||||
			callback: () => this.logReadingProgress(),
 | 
			
		||||
		});
 | 
			
		||||
 | 
			
		||||
		this.addCommand({
 | 
			
		||||
			id: "log-reading-completed",
 | 
			
		||||
			name: "Log Reading Completed",
 | 
			
		||||
			callback: () => this.logReadingFinished(),
 | 
			
		||||
		});
 | 
			
		||||
 | 
			
		||||
		this.addCommand({
 | 
			
		||||
			id: "reset-reading-status",
 | 
			
		||||
			name: "Reset Reading Status",
 | 
			
		||||
			callback: () => this.resetReadingStatus(),
 | 
			
		||||
		});
 | 
			
		||||
		this.addCommand(
 | 
			
		||||
			new SearchGoodreadsCommand(this.app, this.createEntry.bind(this))
 | 
			
		||||
		);
 | 
			
		||||
		this.addCommand(new LogReadingStartedCommand(this.app, this.settings));
 | 
			
		||||
		this.addCommand(
 | 
			
		||||
			new LogReadingProgressCommand(
 | 
			
		||||
				this.app,
 | 
			
		||||
				this.readingLog,
 | 
			
		||||
				this.settings
 | 
			
		||||
			)
 | 
			
		||||
		);
 | 
			
		||||
		this.addCommand(
 | 
			
		||||
			new LogReadingFinishedCommand(
 | 
			
		||||
				this.app,
 | 
			
		||||
				this.readingLog,
 | 
			
		||||
				this.settings
 | 
			
		||||
			)
 | 
			
		||||
		);
 | 
			
		||||
		this.addCommand(
 | 
			
		||||
			new ResetReadingStatusCommand(
 | 
			
		||||
				this.app,
 | 
			
		||||
				this.readingLog,
 | 
			
		||||
				this.settings
 | 
			
		||||
			)
 | 
			
		||||
		);
 | 
			
		||||
 | 
			
		||||
		this.addSettingTab(new BookTrackerSettingTab(this));
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -124,10 +114,8 @@ export default class BookTrackerPlugin extends Plugin {
 | 
			
		|||
		return filePath;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	async createEntryFromGoodreads(legacyId: number): Promise<void> {
 | 
			
		||||
	async createEntry(book: Book): Promise<void> {
 | 
			
		||||
		try {
 | 
			
		||||
			const book = await getBookByLegacyId(legacyId);
 | 
			
		||||
 | 
			
		||||
			const fileName = this.templater
 | 
			
		||||
				.renderTemplate(this.settings.fileNameFormat, {
 | 
			
		||||
					title: book.title,
 | 
			
		||||
| 
						 | 
				
			
			@ -159,209 +147,4 @@ export default class BookTrackerPlugin extends Plugin {
 | 
			
		|||
			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"
 | 
			
		||||
    ],
 | 
			
		||||
    "paths": {
 | 
			
		||||
      "@commands/*": ["src/commands/*"],
 | 
			
		||||
      "@data-sources/*": ["src/data-sources/*"],
 | 
			
		||||
      "@ui/*": ["src/ui/*"],
 | 
			
		||||
      "@utils/*": ["src/utils/*"],
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
		Reference in New Issue