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",
|
"id": "obsidian-book-tracker",
|
||||||
"name": "Book Tracker",
|
"name": "Book Tracker",
|
||||||
"version": "1.4.1",
|
"version": "1.4.2",
|
||||||
"minAppVersion": "0.15.0",
|
"minAppVersion": "0.15.0",
|
||||||
"description": "Simplifies tracking your reading progress and managing your book collection in Obsidian.",
|
"description": "Simplifies tracking your reading progress and managing your book collection in Obsidian.",
|
||||||
"author": "FiFiTiDo",
|
"author": "FiFiTiDo",
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "obsidian-book-tracker",
|
"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.",
|
"description": "Simplifies tracking your reading progress and managing your book collection in Obsidian.",
|
||||||
"main": "main.js",
|
"main": "main.js",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
|
55
src/main.ts
55
src/main.ts
|
@ -10,7 +10,7 @@ import {
|
||||||
DEFAULT_SETTINGS,
|
DEFAULT_SETTINGS,
|
||||||
BookTrackerSettingTab,
|
BookTrackerSettingTab,
|
||||||
} from "@ui/settings";
|
} from "@ui/settings";
|
||||||
import { Templater } from "@utils/Templater";
|
import { safeString, Templater } from "@utils/Templater";
|
||||||
import { CONTENT_TYPE_EXTENSIONS } from "./const";
|
import { CONTENT_TYPE_EXTENSIONS } from "./const";
|
||||||
import { Storage } from "@utils/Storage";
|
import { Storage } from "@utils/Storage";
|
||||||
import { ReadingLog } from "@utils/ReadingLog";
|
import { ReadingLog } from "@utils/ReadingLog";
|
||||||
|
@ -34,6 +34,7 @@ import { registerReadingCalendarCodeBlockProcessor } from "@ui/code-blocks/Readi
|
||||||
import { registerAToZChallengeCodeBlockProcessor } from "@ui/code-blocks/AToZChallengeCodeBlock";
|
import { registerAToZChallengeCodeBlockProcessor } from "@ui/code-blocks/AToZChallengeCodeBlock";
|
||||||
import moment from "@external/moment";
|
import moment from "@external/moment";
|
||||||
import { compressImage } from "@utils/image";
|
import { compressImage } from "@utils/image";
|
||||||
|
import { titleSortValue } from "@utils/text";
|
||||||
|
|
||||||
export default class BookTrackerPlugin extends Plugin {
|
export default class BookTrackerPlugin extends Plugin {
|
||||||
public settings: BookTrackerPluginSettings;
|
public settings: BookTrackerPluginSettings;
|
||||||
|
@ -112,14 +113,14 @@ export default class BookTrackerPlugin extends Plugin {
|
||||||
): Promise<TFile> {
|
): Promise<TFile> {
|
||||||
const response = await requestUrl(coverImageUrl);
|
const response = await requestUrl(coverImageUrl);
|
||||||
const contentType = response.headers["content-type"];
|
const contentType = response.headers["content-type"];
|
||||||
const extension = CONTENT_TYPE_EXTENSIONS[contentType || ""] || "";
|
|
||||||
if (extension === "") {
|
const urlExtension = coverImageUrl.split(".").pop();
|
||||||
throw new Error("Unsupported content type: " + contentType);
|
const extension =
|
||||||
}
|
CONTENT_TYPE_EXTENSIONS[contentType || ""] ?? urlExtension ?? "jpg";
|
||||||
|
|
||||||
let filePath = this.settings.coverFolder + "/";
|
let filePath = this.settings.coverFolder + "/";
|
||||||
if (this.settings.groupCoversByFirstLetter) {
|
if (this.settings.groupCoversByFirstLetter) {
|
||||||
let groupName = fileName.charAt(0).toUpperCase();
|
let groupName = titleSortValue(fileName).charAt(0).toUpperCase();
|
||||||
if (!/^[A-Z]$/.test(groupName)) {
|
if (!/^[A-Z]$/.test(groupName)) {
|
||||||
groupName = "#";
|
groupName = "#";
|
||||||
}
|
}
|
||||||
|
@ -162,19 +163,43 @@ export default class BookTrackerPlugin extends Plugin {
|
||||||
async createEntry(book: Book): Promise<void> {
|
async createEntry(book: Book): Promise<void> {
|
||||||
const fileName = this.templater
|
const fileName = this.templater
|
||||||
.renderTemplate(this.settings.fileNameFormat, {
|
.renderTemplate(this.settings.fileNameFormat, {
|
||||||
title: book.title,
|
title: safeString(book.title),
|
||||||
authors: book.authors.map((a) => a.name).join(", "),
|
authors: safeString(book.authors.map((a) => a.name).join(", ")),
|
||||||
})
|
})
|
||||||
.replace(/[/:*?<>|""]/g, "");
|
.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) {
|
if (this.settings.downloadCovers && book.coverImageUrl) {
|
||||||
const coverImageFile = await this.downloadCoverImage(
|
const coverImageFile = await this.downloadCoverImage(
|
||||||
book.coverImageUrl,
|
book.coverImageUrl,
|
||||||
fileName
|
fileName
|
||||||
);
|
);
|
||||||
data.coverImagePath = coverImageFile.path;
|
data.coverImagePath = safeString(coverImageFile.path);
|
||||||
}
|
}
|
||||||
|
|
||||||
const renderedContent = await this.templater.renderTemplateFile(
|
const renderedContent = await this.templater.renderTemplateFile(
|
||||||
|
@ -225,7 +250,15 @@ export default class BookTrackerPlugin extends Plugin {
|
||||||
|
|
||||||
const getDate = (key: string) => {
|
const getDate = (key: string) => {
|
||||||
const value = fm?.[key];
|
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 moment(value);
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
|
|
|
@ -13,6 +13,7 @@
|
||||||
import DateFilter from "@ui/components/DateFilter.svelte";
|
import DateFilter from "@ui/components/DateFilter.svelte";
|
||||||
import BookCover from "@ui/components/BookCover.svelte";
|
import BookCover from "@ui/components/BookCover.svelte";
|
||||||
import { setAppContext } from "@ui/stores/app";
|
import { setAppContext } from "@ui/stores/app";
|
||||||
|
import { titleSortValue } from "@utils/text";
|
||||||
|
|
||||||
const { plugin }: SvelteCodeBlockProps = $props();
|
const { plugin }: SvelteCodeBlockProps = $props();
|
||||||
setAppContext(plugin.app);
|
setAppContext(plugin.app);
|
||||||
|
@ -26,25 +27,15 @@
|
||||||
disableMonthFilter: true,
|
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 alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZ".split("");
|
||||||
|
|
||||||
const metadata = $derived(
|
const metadata = $derived(
|
||||||
metadataStore.metadata.reduce(
|
metadataStore.metadata.reduce(
|
||||||
(acc, meta) => {
|
(acc, meta) => {
|
||||||
const title = meta.book.title;
|
const title = meta.book.title;
|
||||||
const firstLetter = getSortValue(title).charAt(0).toUpperCase();
|
const firstLetter = titleSortValue(title)
|
||||||
|
.charAt(0)
|
||||||
|
.toUpperCase();
|
||||||
|
|
||||||
if (!firstLetter.match(/[A-Z]/)) {
|
if (!firstLetter.match(/[A-Z]/)) {
|
||||||
return acc;
|
return acc;
|
||||||
|
|
|
@ -87,16 +87,17 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
.book-cover {
|
.book-cover {
|
||||||
width: var(--book-cover-width);
|
&,
|
||||||
height: var(
|
& img {
|
||||||
--book-cover-height,
|
width: var(--book-cover-width);
|
||||||
calc(var(--book-cover-width) / (var(--book-cover-aspect-ratio)))
|
height: var(
|
||||||
);
|
--book-cover-height,
|
||||||
|
calc(var(--book-cover-width) / (var(--book-cover-aspect-ratio)))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
img {
|
img {
|
||||||
border-radius: var(--radius-l);
|
border-radius: var(--radius-l);
|
||||||
height: 100%;
|
|
||||||
width: 100%;
|
|
||||||
object-fit: cover;
|
object-fit: cover;
|
||||||
object-position: center;
|
object-position: center;
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,6 +7,7 @@
|
||||||
import OpenFileLink from "./OpenFileLink.svelte";
|
import OpenFileLink from "./OpenFileLink.svelte";
|
||||||
import BookCover from "./BookCover.svelte";
|
import BookCover from "./BookCover.svelte";
|
||||||
import { getReadingLogContext } from "@ui/stores/reading-log.svelte";
|
import { getReadingLogContext } from "@ui/stores/reading-log.svelte";
|
||||||
|
import { Platform } from "obsidian";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
settings: ShelfSettings;
|
settings: ShelfSettings;
|
||||||
|
@ -21,7 +22,10 @@
|
||||||
<div class="book-details-list">
|
<div class="book-details-list">
|
||||||
{#each metadataStore.metadata as meta}
|
{#each metadataStore.metadata as meta}
|
||||||
<div class="book-details">
|
<div class="book-details">
|
||||||
<BookCover book={meta.book} />
|
<BookCover
|
||||||
|
book={meta.book}
|
||||||
|
size={Platform.isMobile ? "20rem" : "fill"}
|
||||||
|
/>
|
||||||
<div class="book-info">
|
<div class="book-info">
|
||||||
<OpenFileLink file={meta.file}>
|
<OpenFileLink file={meta.file}>
|
||||||
<h2 class="book-title">
|
<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 {
|
export class Templater {
|
||||||
public constructor(private readonly app: App) {}
|
public constructor(private readonly app: App) {}
|
||||||
|
|
||||||
|
|
|
@ -47,8 +47,6 @@ export async function compressImage(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log(width, height);
|
|
||||||
|
|
||||||
canvas.width = width!;
|
canvas.width = width!;
|
||||||
canvas.height = height!;
|
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.1": "0.15.0",
|
||||||
"1.3.2": "0.15.0",
|
"1.3.2": "0.15.0",
|
||||||
"1.4.0": "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