generated from tpl/obsidian-sample-plugin
251 lines
7.5 KiB
TypeScript
251 lines
7.5 KiB
TypeScript
import {
|
|
Notice,
|
|
Plugin,
|
|
requestUrl,
|
|
TFile,
|
|
type FrontMatterCache,
|
|
} from "obsidian";
|
|
import {
|
|
type BookTrackerPluginSettings,
|
|
DEFAULT_SETTINGS,
|
|
BookTrackerSettingTab,
|
|
} from "@ui/settings";
|
|
import { Templater } from "@utils/Templater";
|
|
import { CONTENT_TYPE_EXTENSIONS } from "./const";
|
|
import { Storage } from "@utils/Storage";
|
|
import { ReadingLog } from "@utils/ReadingLog";
|
|
import {
|
|
registerReadingLogCodeBlockProcessor,
|
|
registerReadingStatsCodeBlockProcessor,
|
|
} from "@ui/code-blocks";
|
|
import type { Book, BookMetadata, ReadingState } 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";
|
|
import { BackupReadingLogCommand } from "@commands/CreateReadingLogBackupCommand";
|
|
import { RestoreReadingLogBackupCommand } from "@commands/RestoreReadingLogBackupCommand";
|
|
import { Goodreads } from "@data-sources/Goodreads";
|
|
import { CreateBookFromGoodreadsUrlCommand } from "@commands/CreateBookFromGoodreadsUrlCommand";
|
|
import { registerShelfCodeBlockProcessor } from "@ui/code-blocks/ShelfCodeBlock";
|
|
import { ReloadReadingLogCommand } from "@commands/ReloadReadingLogCommand";
|
|
import { registerReadingCalendarCodeBlockProcessor } from "@ui/code-blocks/ReadingCalendarCodeBlock";
|
|
import { registerAToZChallengeCodeBlockProcessor } from "@ui/code-blocks/AToZChallengeCodeBlock";
|
|
import moment from "@external/moment";
|
|
import { compressImage } from "@utils/image";
|
|
|
|
export default class BookTrackerPlugin extends Plugin {
|
|
public settings: BookTrackerPluginSettings;
|
|
public templater: Templater;
|
|
public storage: Storage;
|
|
public readingLog: ReadingLog;
|
|
public goodreads: Goodreads = new Goodreads();
|
|
|
|
async onload() {
|
|
await this.loadSettings();
|
|
|
|
this.templater = new Templater(this.app);
|
|
this.storage = new Storage(this);
|
|
this.readingLog = new ReadingLog(this.storage);
|
|
|
|
this.addCommand(
|
|
new SearchGoodreadsCommand(
|
|
this.app,
|
|
this.goodreads,
|
|
this.createEntry.bind(this)
|
|
)
|
|
);
|
|
this.addCommand(new LogReadingStartedCommand(this.app, this.settings));
|
|
this.addCommand(new LogReadingProgressCommand(this));
|
|
this.addCommand(new LogReadingFinishedCommand(this));
|
|
this.addCommand(
|
|
new ResetReadingStatusCommand(
|
|
this.app,
|
|
this.readingLog,
|
|
this.settings
|
|
)
|
|
);
|
|
this.addCommand(new BackupReadingLogCommand(this.readingLog));
|
|
this.addCommand(
|
|
new RestoreReadingLogBackupCommand(
|
|
this.app,
|
|
this.storage,
|
|
this.readingLog
|
|
)
|
|
);
|
|
this.addCommand(
|
|
new CreateBookFromGoodreadsUrlCommand(
|
|
this.goodreads,
|
|
this.createEntry.bind(this)
|
|
)
|
|
);
|
|
this.addCommand(new ReloadReadingLogCommand(this.readingLog));
|
|
|
|
this.addSettingTab(new BookTrackerSettingTab(this));
|
|
|
|
registerReadingLogCodeBlockProcessor(this);
|
|
registerReadingStatsCodeBlockProcessor(this);
|
|
registerShelfCodeBlockProcessor(this);
|
|
registerReadingCalendarCodeBlockProcessor(this);
|
|
registerAToZChallengeCodeBlockProcessor(this);
|
|
}
|
|
|
|
onunload() {}
|
|
|
|
async loadSettings() {
|
|
this.settings = Object.assign(
|
|
{},
|
|
DEFAULT_SETTINGS,
|
|
await this.loadData()
|
|
);
|
|
}
|
|
|
|
async saveSettings() {
|
|
await this.saveData(this.settings);
|
|
}
|
|
|
|
async downloadCoverImage(
|
|
coverImageUrl: string,
|
|
fileName: string,
|
|
overwrite?: boolean
|
|
): Promise<TFile> {
|
|
const response = await requestUrl(coverImageUrl);
|
|
const contentType = response.headers["content-type"];
|
|
const extension = CONTENT_TYPE_EXTENSIONS[contentType || ""] || "";
|
|
if (extension === "") {
|
|
throw new Error("Unsupported content type: " + contentType);
|
|
}
|
|
|
|
let filePath = this.settings.coverFolder + "/";
|
|
if (this.settings.groupCoversByFirstLetter) {
|
|
let groupName = fileName.charAt(0).toUpperCase();
|
|
if (!/^[A-Z]$/.test(groupName)) {
|
|
groupName = "#";
|
|
}
|
|
|
|
filePath += groupName + "/";
|
|
}
|
|
filePath += fileName + "." + extension;
|
|
|
|
let file = this.app.vault.getFileByPath(filePath);
|
|
if (file) {
|
|
if (this.settings.overwriteExistingCovers || overwrite) {
|
|
await this.app.vault.modifyBinary(file, response.arrayBuffer);
|
|
} else {
|
|
new Notice("Cover image already exists: " + filePath);
|
|
return file;
|
|
}
|
|
} else {
|
|
file = await this.app.vault.createBinary(
|
|
filePath,
|
|
response.arrayBuffer
|
|
);
|
|
}
|
|
|
|
await compressImage(this.app, file, {
|
|
height: 400,
|
|
quality: 0.8,
|
|
maintainAspectRatio: true,
|
|
});
|
|
|
|
return file;
|
|
}
|
|
|
|
async createEntry(book: Book): Promise<void> {
|
|
const fileName = this.templater
|
|
.renderTemplate(this.settings.fileNameFormat, {
|
|
title: book.title,
|
|
authors: book.authors.map((a) => a.name).join(", "),
|
|
})
|
|
.replace(/[/:*?<>|""]/g, "");
|
|
|
|
const data: Record<string, unknown> = { book };
|
|
|
|
if (this.settings.downloadCovers && book.coverImageUrl) {
|
|
const coverImageFile = await this.downloadCoverImage(
|
|
book.coverImageUrl,
|
|
fileName
|
|
);
|
|
data.coverImagePath = coverImageFile.path;
|
|
}
|
|
|
|
const renderedContent = await this.templater.renderTemplateFile(
|
|
this.settings.templateFile,
|
|
data
|
|
);
|
|
|
|
const filePath = this.settings.tbrFolder + "/" + fileName + ".md";
|
|
const file = await this.app.vault.create(filePath, renderedContent);
|
|
await this.app.workspace.getLeaf().openFile(file);
|
|
}
|
|
|
|
getBookMetadata(file: TFile): BookMetadata | null {
|
|
const metadata = this.app.metadataCache.getFileCache(file);
|
|
if (!metadata) {
|
|
return null;
|
|
}
|
|
|
|
return this.frontmatterToMetadata(metadata.frontmatter);
|
|
}
|
|
|
|
frontmatterToMetadata(fm: FrontMatterCache | undefined): BookMetadata {
|
|
const getString = (key: string) => {
|
|
const value = fm?.[key];
|
|
if (typeof value === "string") {
|
|
return value;
|
|
}
|
|
return "";
|
|
};
|
|
|
|
const getStringArray = (key: string) => {
|
|
const value = fm?.[key];
|
|
if (Array.isArray(value)) {
|
|
return value as string[];
|
|
}
|
|
return [];
|
|
};
|
|
|
|
const getNumber = (key: string) => {
|
|
const value = fm?.[key];
|
|
if (typeof value === "number") {
|
|
return value;
|
|
} else if (typeof value === "string") {
|
|
return parseFloat(value);
|
|
}
|
|
return 0;
|
|
};
|
|
|
|
const getDate = (key: string) => {
|
|
const value = fm?.[key];
|
|
if (typeof value === "string" || value instanceof Date) {
|
|
return moment(value);
|
|
}
|
|
return null;
|
|
};
|
|
|
|
return {
|
|
title: getString(this.settings.titleProperty),
|
|
subtitle: getString(this.settings.subtitleProperty),
|
|
description: getString(this.settings.descriptionProperty),
|
|
authors: getStringArray(this.settings.authorsProperty),
|
|
seriesTitle: getString(this.settings.seriesTitleProperty),
|
|
seriesPosition: getNumber(this.settings.seriesPositionProperty),
|
|
startDate: getDate(this.settings.startDateProperty)!,
|
|
endDate: getDate(this.settings.endDateProperty)!,
|
|
status: getString(this.settings.statusProperty) as ReadingState,
|
|
rating: getNumber(this.settings.ratingProperty),
|
|
spice: getNumber(this.settings.spiceProperty),
|
|
format: getString(this.settings.formatProperty),
|
|
source: getStringArray(this.settings.sourceProperty),
|
|
categories: getStringArray(this.settings.categoriesProperty),
|
|
publisher: getString(this.settings.publisherProperty),
|
|
publishDate: getDate(this.settings.publishDateProperty)!,
|
|
pageCount: getNumber(this.settings.pageCountProperty),
|
|
isbn: getString(this.settings.isbnProperty),
|
|
coverImageUrl: getString(this.settings.coverImageUrlProperty),
|
|
localCoverPath: getString(this.settings.localCoverPathProperty),
|
|
};
|
|
}
|
|
}
|