Fix images, handlebars escaping, and organize covers by title sort value

This commit is contained in:
Evan Fiordeliso 2025-08-01 12:42:44 -04:00
parent d5e4810d8f
commit cb0c66a48b
10 changed files with 80 additions and 37 deletions

View File

@ -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",

View File

@ -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": {

View File

@ -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;

View File

@ -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;

View File

@ -87,16 +87,17 @@
}
.book-cover {
&,
& 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;
}

View File

@ -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">

View File

@ -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) {}

View File

@ -47,8 +47,6 @@ export async function compressImage(
}
}
console.log(width, height);
canvas.width = width!;
canvas.height = height!;

11
src/utils/text.ts Normal file
View File

@ -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;
}
}

View File

@ -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"
}