generated from tpl/obsidian-sample-plugin
			Fix images, handlebars escaping, and organize covers by title sort value
This commit is contained in:
		
							parent
							
								
									d5e4810d8f
								
							
						
					
					
						commit
						cb0c66a48b
					
				| 
						 | 
				
			
			@ -1,7 +1,7 @@
 | 
			
		|||
{
 | 
			
		||||
	"id": "obsidian-book-tracker",
 | 
			
		||||
	"name": "Book Tracker",
 | 
			
		||||
	"version": "1.4.1",
 | 
			
		||||
	"version": "1.4.2",
 | 
			
		||||
	"minAppVersion": "0.15.0",
 | 
			
		||||
	"description": "Simplifies tracking your reading progress and managing your book collection in Obsidian.",
 | 
			
		||||
	"author": "FiFiTiDo",
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,6 +1,6 @@
 | 
			
		|||
{
 | 
			
		||||
	"name": "obsidian-book-tracker",
 | 
			
		||||
	"version": "1.4.1",
 | 
			
		||||
	"version": "1.4.2",
 | 
			
		||||
	"description": "Simplifies tracking your reading progress and managing your book collection in Obsidian.",
 | 
			
		||||
	"main": "main.js",
 | 
			
		||||
	"scripts": {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										55
									
								
								src/main.ts
								
								
								
								
							
							
						
						
									
										55
									
								
								src/main.ts
								
								
								
								
							| 
						 | 
				
			
			@ -10,7 +10,7 @@ import {
 | 
			
		|||
	DEFAULT_SETTINGS,
 | 
			
		||||
	BookTrackerSettingTab,
 | 
			
		||||
} from "@ui/settings";
 | 
			
		||||
import { Templater } from "@utils/Templater";
 | 
			
		||||
import { safeString, Templater } from "@utils/Templater";
 | 
			
		||||
import { CONTENT_TYPE_EXTENSIONS } from "./const";
 | 
			
		||||
import { Storage } from "@utils/Storage";
 | 
			
		||||
import { ReadingLog } from "@utils/ReadingLog";
 | 
			
		||||
| 
						 | 
				
			
			@ -34,6 +34,7 @@ import { registerReadingCalendarCodeBlockProcessor } from "@ui/code-blocks/Readi
 | 
			
		|||
import { registerAToZChallengeCodeBlockProcessor } from "@ui/code-blocks/AToZChallengeCodeBlock";
 | 
			
		||||
import moment from "@external/moment";
 | 
			
		||||
import { compressImage } from "@utils/image";
 | 
			
		||||
import { titleSortValue } from "@utils/text";
 | 
			
		||||
 | 
			
		||||
export default class BookTrackerPlugin extends Plugin {
 | 
			
		||||
	public settings: BookTrackerPluginSettings;
 | 
			
		||||
| 
						 | 
				
			
			@ -112,14 +113,14 @@ export default class BookTrackerPlugin extends Plugin {
 | 
			
		|||
	): 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);
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		const urlExtension = coverImageUrl.split(".").pop();
 | 
			
		||||
		const extension =
 | 
			
		||||
			CONTENT_TYPE_EXTENSIONS[contentType || ""] ?? urlExtension ?? "jpg";
 | 
			
		||||
 | 
			
		||||
		let filePath = this.settings.coverFolder + "/";
 | 
			
		||||
		if (this.settings.groupCoversByFirstLetter) {
 | 
			
		||||
			let groupName = fileName.charAt(0).toUpperCase();
 | 
			
		||||
			let groupName = titleSortValue(fileName).charAt(0).toUpperCase();
 | 
			
		||||
			if (!/^[A-Z]$/.test(groupName)) {
 | 
			
		||||
				groupName = "#";
 | 
			
		||||
			}
 | 
			
		||||
| 
						 | 
				
			
			@ -162,19 +163,43 @@ export default class BookTrackerPlugin extends Plugin {
 | 
			
		|||
	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(", "),
 | 
			
		||||
				title: safeString(book.title),
 | 
			
		||||
				authors: safeString(book.authors.map((a) => a.name).join(", ")),
 | 
			
		||||
			})
 | 
			
		||||
			.replace(/[/:*?<>|""]/g, "");
 | 
			
		||||
 | 
			
		||||
		const data: Record<string, unknown> = { book };
 | 
			
		||||
		// Wrap strings as safe strings to avoid Handlebars escaping
 | 
			
		||||
		const data: Record<string, unknown> = {
 | 
			
		||||
			book: {
 | 
			
		||||
				title: safeString(book.title),
 | 
			
		||||
				subtitle: safeString(book.subtitle),
 | 
			
		||||
				description: book.description,
 | 
			
		||||
				authors: book.authors.map((a) => ({
 | 
			
		||||
					...a,
 | 
			
		||||
					name: safeString(a.name),
 | 
			
		||||
				})),
 | 
			
		||||
				series: book.series
 | 
			
		||||
					? {
 | 
			
		||||
							...book.series,
 | 
			
		||||
							title: safeString(book.series.title),
 | 
			
		||||
					  }
 | 
			
		||||
					: null,
 | 
			
		||||
				publisher: safeString(book.publisher),
 | 
			
		||||
				publishedAt: book.publishedAt,
 | 
			
		||||
				genres: book.genres.map((g) => safeString(g)),
 | 
			
		||||
				coverImageUrl: safeString(book.coverImageUrl),
 | 
			
		||||
				pageCount: book.pageCount,
 | 
			
		||||
				isbn: safeString(book.isbn),
 | 
			
		||||
				isbn13: safeString(book.isbn13),
 | 
			
		||||
			},
 | 
			
		||||
		};
 | 
			
		||||
 | 
			
		||||
		if (this.settings.downloadCovers && book.coverImageUrl) {
 | 
			
		||||
			const coverImageFile = await this.downloadCoverImage(
 | 
			
		||||
				book.coverImageUrl,
 | 
			
		||||
				fileName
 | 
			
		||||
			);
 | 
			
		||||
			data.coverImagePath = coverImageFile.path;
 | 
			
		||||
			data.coverImagePath = safeString(coverImageFile.path);
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		const renderedContent = await this.templater.renderTemplateFile(
 | 
			
		||||
| 
						 | 
				
			
			@ -225,7 +250,15 @@ export default class BookTrackerPlugin extends Plugin {
 | 
			
		|||
 | 
			
		||||
		const getDate = (key: string) => {
 | 
			
		||||
			const value = fm?.[key];
 | 
			
		||||
			if (typeof value === "string" || value instanceof Date) {
 | 
			
		||||
			if (typeof value === "string") {
 | 
			
		||||
				if (value.includes(" ")) {
 | 
			
		||||
					return moment(value, "YYYY-MM-DD HH:mm:ss");
 | 
			
		||||
				} else if (value.includes("T")) {
 | 
			
		||||
					return moment(value, "YYYY-MM-DDTHH:mm:ss");
 | 
			
		||||
				} else {
 | 
			
		||||
					return moment(value, "YYYY-MM-DD");
 | 
			
		||||
				}
 | 
			
		||||
			} else if (value instanceof Date) {
 | 
			
		||||
				return moment(value);
 | 
			
		||||
			}
 | 
			
		||||
			return null;
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -13,6 +13,7 @@
 | 
			
		|||
	import DateFilter from "@ui/components/DateFilter.svelte";
 | 
			
		||||
	import BookCover from "@ui/components/BookCover.svelte";
 | 
			
		||||
	import { setAppContext } from "@ui/stores/app";
 | 
			
		||||
	import { titleSortValue } from "@utils/text";
 | 
			
		||||
 | 
			
		||||
	const { plugin }: SvelteCodeBlockProps = $props();
 | 
			
		||||
	setAppContext(plugin.app);
 | 
			
		||||
| 
						 | 
				
			
			@ -26,25 +27,15 @@
 | 
			
		|||
		disableMonthFilter: true,
 | 
			
		||||
	});
 | 
			
		||||
 | 
			
		||||
	const getSortValue = (value: string) => {
 | 
			
		||||
		if (value.startsWith("A ")) {
 | 
			
		||||
			return value.slice(2) + ", A";
 | 
			
		||||
		} else if (value.startsWith("An ")) {
 | 
			
		||||
			return value.slice(3) + ", An";
 | 
			
		||||
		} else if (value.startsWith("The ")) {
 | 
			
		||||
			return value.slice(4) + ", The";
 | 
			
		||||
		} else {
 | 
			
		||||
			return value;
 | 
			
		||||
		}
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
	const alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZ".split("");
 | 
			
		||||
 | 
			
		||||
	const metadata = $derived(
 | 
			
		||||
		metadataStore.metadata.reduce(
 | 
			
		||||
			(acc, meta) => {
 | 
			
		||||
				const title = meta.book.title;
 | 
			
		||||
				const firstLetter = getSortValue(title).charAt(0).toUpperCase();
 | 
			
		||||
				const firstLetter = titleSortValue(title)
 | 
			
		||||
					.charAt(0)
 | 
			
		||||
					.toUpperCase();
 | 
			
		||||
 | 
			
		||||
				if (!firstLetter.match(/[A-Z]/)) {
 | 
			
		||||
					return acc;
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -87,16 +87,17 @@
 | 
			
		|||
	}
 | 
			
		||||
 | 
			
		||||
	.book-cover {
 | 
			
		||||
		width: var(--book-cover-width);
 | 
			
		||||
		height: var(
 | 
			
		||||
			--book-cover-height,
 | 
			
		||||
			calc(var(--book-cover-width) / (var(--book-cover-aspect-ratio)))
 | 
			
		||||
		);
 | 
			
		||||
		&,
 | 
			
		||||
		& img {
 | 
			
		||||
			width: var(--book-cover-width);
 | 
			
		||||
			height: var(
 | 
			
		||||
				--book-cover-height,
 | 
			
		||||
				calc(var(--book-cover-width) / (var(--book-cover-aspect-ratio)))
 | 
			
		||||
			);
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		img {
 | 
			
		||||
			border-radius: var(--radius-l);
 | 
			
		||||
			height: 100%;
 | 
			
		||||
			width: 100%;
 | 
			
		||||
			object-fit: cover;
 | 
			
		||||
			object-position: center;
 | 
			
		||||
		}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -7,6 +7,7 @@
 | 
			
		|||
	import OpenFileLink from "./OpenFileLink.svelte";
 | 
			
		||||
	import BookCover from "./BookCover.svelte";
 | 
			
		||||
	import { getReadingLogContext } from "@ui/stores/reading-log.svelte";
 | 
			
		||||
	import { Platform } from "obsidian";
 | 
			
		||||
 | 
			
		||||
	interface Props {
 | 
			
		||||
		settings: ShelfSettings;
 | 
			
		||||
| 
						 | 
				
			
			@ -21,7 +22,10 @@
 | 
			
		|||
<div class="book-details-list">
 | 
			
		||||
	{#each metadataStore.metadata as meta}
 | 
			
		||||
		<div class="book-details">
 | 
			
		||||
			<BookCover book={meta.book} />
 | 
			
		||||
			<BookCover
 | 
			
		||||
				book={meta.book}
 | 
			
		||||
				size={Platform.isMobile ? "20rem" : "fill"}
 | 
			
		||||
			/>
 | 
			
		||||
			<div class="book-info">
 | 
			
		||||
				<OpenFileLink file={meta.file}>
 | 
			
		||||
					<h2 class="book-title">
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -25,6 +25,10 @@ Handlebars.registerHelper("indent", (text: string, indent = "  ") => {
 | 
			
		|||
	);
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
export function safeString(str: string) {
 | 
			
		||||
	return new Handlebars.SafeString(str);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export class Templater {
 | 
			
		||||
	public constructor(private readonly app: App) {}
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -47,8 +47,6 @@ export async function compressImage(
 | 
			
		|||
			}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	console.log(width, height);
 | 
			
		||||
 | 
			
		||||
	canvas.width = width!;
 | 
			
		||||
	canvas.height = height!;
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -0,0 +1,11 @@
 | 
			
		|||
export function titleSortValue(title: string): string {
 | 
			
		||||
	if (title.toLowerCase().startsWith("a ")) {
 | 
			
		||||
		return title.slice(2) + ", A";
 | 
			
		||||
	} else if (title.toLowerCase().startsWith("an ")) {
 | 
			
		||||
		return title.slice(3) + ", An";
 | 
			
		||||
	} else if (title.toLowerCase().startsWith("the ")) {
 | 
			
		||||
		return title.slice(4) + ", The";
 | 
			
		||||
	} else {
 | 
			
		||||
		return title;
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -6,5 +6,6 @@
 | 
			
		|||
	"1.3.1": "0.15.0",
 | 
			
		||||
	"1.3.2": "0.15.0",
 | 
			
		||||
	"1.4.0": "0.15.0",
 | 
			
		||||
	"1.4.1": "0.15.0"
 | 
			
		||||
	"1.4.1": "0.15.0",
 | 
			
		||||
	"1.4.2": "0.15.0"
 | 
			
		||||
}
 | 
			
		||||
		Loading…
	
		Reference in New Issue