generated from tpl/obsidian-sample-plugin
			A few small changes
This commit is contained in:
		
							parent
							
								
									891041c965
								
							
						
					
					
						commit
						19d56652eb
					
				| 
						 | 
				
			
			@ -43,7 +43,7 @@ export class ResetReadingStatusCommand extends EditorCheckCommand {
 | 
			
		|||
			frontMatter[this.settings.endDateProperty] = "";
 | 
			
		||||
		});
 | 
			
		||||
 | 
			
		||||
		this.readingLog.removeEntriesForBook(file.basename);
 | 
			
		||||
		this.readingLog.deleteEntriesForBook(file.basename);
 | 
			
		||||
 | 
			
		||||
		new Notice("Reading status reset for " + file.basename);
 | 
			
		||||
	}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,5 +1,5 @@
 | 
			
		|||
<script lang="ts">
 | 
			
		||||
	import { parseYaml } from "obsidian";
 | 
			
		||||
	import { parseYaml, TFile } from "obsidian";
 | 
			
		||||
	import { ReadingCalendarSettingsSchema } from "./ReadingCalendarCodeBlock";
 | 
			
		||||
	import type { SvelteCodeBlockProps } from "./SvelteCodeBlockRenderer";
 | 
			
		||||
	import {
 | 
			
		||||
| 
						 | 
				
			
			@ -15,7 +15,8 @@
 | 
			
		|||
		setReadingLogContext,
 | 
			
		||||
	} from "@ui/stores/reading-log.svelte";
 | 
			
		||||
	import { ArrowLeft, ArrowRight } from "lucide-svelte";
 | 
			
		||||
	import { onMount } from "svelte";
 | 
			
		||||
	import { onDestroy, onMount } from "svelte";
 | 
			
		||||
	import OpenFileLink from "@ui/components/OpenFileLink.svelte";
 | 
			
		||||
 | 
			
		||||
	const { plugin, source }: SvelteCodeBlockProps = $props();
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -85,6 +86,11 @@
 | 
			
		|||
		updateToday();
 | 
			
		||||
	});
 | 
			
		||||
 | 
			
		||||
	onDestroy(() => {
 | 
			
		||||
		metadataStore.destroy();
 | 
			
		||||
		readingLog.destroy();
 | 
			
		||||
	});
 | 
			
		||||
 | 
			
		||||
	const weeks = $derived.by(() => {
 | 
			
		||||
		// @ts-expect-error Moment is provided by Obsidian
 | 
			
		||||
		const firstDay = moment()
 | 
			
		||||
| 
						 | 
				
			
			@ -114,40 +120,53 @@
 | 
			
		|||
		return weeks;
 | 
			
		||||
	});
 | 
			
		||||
 | 
			
		||||
	interface CoverData {
 | 
			
		||||
		src: string;
 | 
			
		||||
		alt: string;
 | 
			
		||||
	interface BookData {
 | 
			
		||||
		coverSrc: string;
 | 
			
		||||
		coverAlt: string;
 | 
			
		||||
		file: TFile;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	interface BookMapItem {
 | 
			
		||||
		totalPagesRead: number;
 | 
			
		||||
		covers: CoverData[];
 | 
			
		||||
		books: BookData[];
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	const bookMap = $derived.by(() => {
 | 
			
		||||
		const bookMap = new Map<number, BookMapItem>();
 | 
			
		||||
		for (const item of readingLog.entries) {
 | 
			
		||||
			const key = item.createdAt.date();
 | 
			
		||||
	const bookMap = $derived(
 | 
			
		||||
		readingLog.entries.reduce(
 | 
			
		||||
			(acc, entry) => {
 | 
			
		||||
				const key = entry.createdAt.date();
 | 
			
		||||
 | 
			
		||||
			let coverPath = metadataStore.metadata.find(
 | 
			
		||||
				(entry) => entry.file.basename === item.book,
 | 
			
		||||
			)?.frontmatter?.[settings.coverProperty];
 | 
			
		||||
				const metadata = metadataStore.metadata.find(
 | 
			
		||||
					(other) => other.file.basename === entry.book,
 | 
			
		||||
				);
 | 
			
		||||
 | 
			
		||||
			coverPath = plugin.app.vault.getFileByPath(coverPath);
 | 
			
		||||
				if (!metadata) {
 | 
			
		||||
					return acc;
 | 
			
		||||
				}
 | 
			
		||||
 | 
			
		||||
			if (!coverPath) {
 | 
			
		||||
				continue;
 | 
			
		||||
			}
 | 
			
		||||
				const coverPath = metadata.frontmatter?.[
 | 
			
		||||
					settings.coverProperty
 | 
			
		||||
				] as string;
 | 
			
		||||
				const coverFile = plugin.app.vault.getFileByPath(coverPath);
 | 
			
		||||
				let coverSrc = "";
 | 
			
		||||
				if (coverFile) {
 | 
			
		||||
					coverSrc = plugin.app.vault.getResourcePath(coverFile);
 | 
			
		||||
				}
 | 
			
		||||
 | 
			
		||||
			coverPath = plugin.app.vault.getResourcePath(coverPath);
 | 
			
		||||
				const value = acc[key] ?? { totalPagesRead: 0, books: [] };
 | 
			
		||||
				value.totalPagesRead += entry.pagesRead;
 | 
			
		||||
				value.books.push({
 | 
			
		||||
					coverSrc,
 | 
			
		||||
					coverAlt: entry.book,
 | 
			
		||||
					file: metadata.file,
 | 
			
		||||
				});
 | 
			
		||||
				acc[key] = value;
 | 
			
		||||
 | 
			
		||||
			const value = bookMap.get(key) ?? { totalPagesRead: 0, covers: [] };
 | 
			
		||||
			value.totalPagesRead += item.pagesRead;
 | 
			
		||||
			value.covers.push({ src: coverPath, alt: item.book });
 | 
			
		||||
			bookMap.set(key, value);
 | 
			
		||||
		}
 | 
			
		||||
		return bookMap;
 | 
			
		||||
	});
 | 
			
		||||
				return acc;
 | 
			
		||||
			},
 | 
			
		||||
			{} as Record<number, BookMapItem>,
 | 
			
		||||
		),
 | 
			
		||||
	);
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<div class="reading-calendar">
 | 
			
		||||
| 
						 | 
				
			
			@ -207,6 +226,7 @@
 | 
			
		|||
				<tr>
 | 
			
		||||
					{#each week as day}
 | 
			
		||||
						{@const isThisMonth = day.month() === month}
 | 
			
		||||
						{@const date = day.date()}
 | 
			
		||||
						<td
 | 
			
		||||
							class:is-today={day.isSame(today, "day")}
 | 
			
		||||
							class:is-weekend={day.day() === 0 ||
 | 
			
		||||
| 
						 | 
				
			
			@ -218,23 +238,25 @@
 | 
			
		|||
						>
 | 
			
		||||
							<div class="header">
 | 
			
		||||
								<span>{day.date()}</span>
 | 
			
		||||
								{#if isThisMonth && bookMap.has(day.date())}
 | 
			
		||||
									{@const data = bookMap.get(day.date())!}
 | 
			
		||||
								{#if isThisMonth && date in bookMap}
 | 
			
		||||
									{@const data = bookMap[date]}
 | 
			
		||||
									<span class="total-pages-read">
 | 
			
		||||
										Pages: {data.totalPagesRead}
 | 
			
		||||
									</span>
 | 
			
		||||
								{/if}
 | 
			
		||||
							</div>
 | 
			
		||||
							<div class="covers">
 | 
			
		||||
								{#if isThisMonth && bookMap.has(day.date())}
 | 
			
		||||
									{@const data = bookMap.get(day.date())!}
 | 
			
		||||
									{#each data.covers as cover}
 | 
			
		||||
										{#if cover}
 | 
			
		||||
											<img
 | 
			
		||||
												src={cover.src}
 | 
			
		||||
												alt={cover.alt}
 | 
			
		||||
											/>
 | 
			
		||||
										{/if}
 | 
			
		||||
								{#if isThisMonth && date in bookMap}
 | 
			
		||||
									{@const data = bookMap[date]}
 | 
			
		||||
									{#each data.books as book}
 | 
			
		||||
										<div class="cover">
 | 
			
		||||
											<OpenFileLink file={book.file}>
 | 
			
		||||
												<img
 | 
			
		||||
													src={book.coverSrc}
 | 
			
		||||
													alt={book.coverAlt}
 | 
			
		||||
												/>
 | 
			
		||||
											</OpenFileLink>
 | 
			
		||||
										</div>
 | 
			
		||||
									{/each}
 | 
			
		||||
								{/if}
 | 
			
		||||
							</div>
 | 
			
		||||
| 
						 | 
				
			
			@ -312,59 +334,46 @@
 | 
			
		|||
				width: 100%;
 | 
			
		||||
				aspect-ratio: 2 / 3;
 | 
			
		||||
 | 
			
		||||
				img {
 | 
			
		||||
				.cover {
 | 
			
		||||
					border-radius: var(--radius-l);
 | 
			
		||||
				}
 | 
			
		||||
					position: absolute;
 | 
			
		||||
					height: 100%;
 | 
			
		||||
					width: 100%;
 | 
			
		||||
 | 
			
		||||
				&:has(img:first-child:nth-last-child(1)) {
 | 
			
		||||
					img {
 | 
			
		||||
						position: absolute;
 | 
			
		||||
						height: 100%;
 | 
			
		||||
						border-radius: var(--radius-l);
 | 
			
		||||
						width: 100%;
 | 
			
		||||
						height: 100%;
 | 
			
		||||
						object-fit: cover;
 | 
			
		||||
					}
 | 
			
		||||
				}
 | 
			
		||||
 | 
			
		||||
				&:has(img:first-child:nth-last-child(2)) {
 | 
			
		||||
					img:first-child {
 | 
			
		||||
						position: absolute;
 | 
			
		||||
						height: 100%;
 | 
			
		||||
						width: 100%;
 | 
			
		||||
				&:has(.cover:first-child:nth-last-child(2)) {
 | 
			
		||||
					.cover:first-child {
 | 
			
		||||
						clip-path: polygon(0 0, 100% 0, 0 100%);
 | 
			
		||||
					}
 | 
			
		||||
 | 
			
		||||
					img:last-child {
 | 
			
		||||
						position: absolute;
 | 
			
		||||
						height: 100%;
 | 
			
		||||
						width: 100%;
 | 
			
		||||
					.cover:last-child {
 | 
			
		||||
						clip-path: polygon(100% 100%, 100% 0, 0 100%);
 | 
			
		||||
					}
 | 
			
		||||
				}
 | 
			
		||||
 | 
			
		||||
				&:has(img:first-child:nth-last-child(3)) {
 | 
			
		||||
					img:first-child {
 | 
			
		||||
						position: absolute;
 | 
			
		||||
						height: 100%;
 | 
			
		||||
						width: 100%;
 | 
			
		||||
				&:has(.cover:first-child:nth-last-child(3)) {
 | 
			
		||||
					.cover:first-child {
 | 
			
		||||
						clip-path: polygon(0 0, 100% 0, 100% 20%, 0 50%);
 | 
			
		||||
					}
 | 
			
		||||
 | 
			
		||||
					img:nth-child(2) {
 | 
			
		||||
						position: absolute;
 | 
			
		||||
						height: 100%;
 | 
			
		||||
						width: 100%;
 | 
			
		||||
					.cover:nth-child(2) {
 | 
			
		||||
						clip-path: polygon(100% 80%, 100% 20%, 0 50%);
 | 
			
		||||
					}
 | 
			
		||||
 | 
			
		||||
					img:last-child {
 | 
			
		||||
						position: absolute;
 | 
			
		||||
						height: 100%;
 | 
			
		||||
						width: 100%;
 | 
			
		||||
					.cover:last-child {
 | 
			
		||||
						clip-path: polygon(0 50%, 100% 80%, 100% 100%, 0% 100%);
 | 
			
		||||
					}
 | 
			
		||||
				}
 | 
			
		||||
 | 
			
		||||
				&:has(img:first-child:nth-last-child(4)) {
 | 
			
		||||
					img:first-child {
 | 
			
		||||
				&:has(.cover:first-child:nth-last-child(4)) {
 | 
			
		||||
					.cover:first-child {
 | 
			
		||||
						position: absolute;
 | 
			
		||||
						height: 50%;
 | 
			
		||||
						width: 50%;
 | 
			
		||||
| 
						 | 
				
			
			@ -372,7 +381,7 @@
 | 
			
		|||
						left: 0;
 | 
			
		||||
					}
 | 
			
		||||
 | 
			
		||||
					img:nth-child(2) {
 | 
			
		||||
					.cover:nth-child(2) {
 | 
			
		||||
						position: absolute;
 | 
			
		||||
						height: 50%;
 | 
			
		||||
						width: 50%;
 | 
			
		||||
| 
						 | 
				
			
			@ -380,7 +389,7 @@
 | 
			
		|||
						right: 0;
 | 
			
		||||
					}
 | 
			
		||||
 | 
			
		||||
					img:nth-child(3) {
 | 
			
		||||
					.cover:nth-child(3) {
 | 
			
		||||
						position: absolute;
 | 
			
		||||
						height: 50%;
 | 
			
		||||
						width: 50%;
 | 
			
		||||
| 
						 | 
				
			
			@ -388,7 +397,7 @@
 | 
			
		|||
						left: 0;
 | 
			
		||||
					}
 | 
			
		||||
 | 
			
		||||
					img:last-child {
 | 
			
		||||
					.cover:last-child {
 | 
			
		||||
						position: absolute;
 | 
			
		||||
						height: 50%;
 | 
			
		||||
						width: 50%;
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -16,8 +16,14 @@
 | 
			
		|||
	const href = $derived(
 | 
			
		||||
		makeUri(file instanceof TFile ? file.path : getLinkpath(file)),
 | 
			
		||||
	);
 | 
			
		||||
 | 
			
		||||
	const title = $derived(
 | 
			
		||||
		file instanceof TFile
 | 
			
		||||
			? file.basename
 | 
			
		||||
			: file.split("/").pop()?.replace(".md", ""),
 | 
			
		||||
	);
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<a {href}>
 | 
			
		||||
<a {href} {title}>
 | 
			
		||||
	{@render children?.()}
 | 
			
		||||
</a>
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -111,12 +111,13 @@
 | 
			
		|||
						type: "linear",
 | 
			
		||||
						display: true,
 | 
			
		||||
						position: "left",
 | 
			
		||||
						ticks: { beginAtZero: true },
 | 
			
		||||
						beginAtZero: true,
 | 
			
		||||
					},
 | 
			
		||||
					y1: {
 | 
			
		||||
						type: "linear",
 | 
			
		||||
						display: !isMonthly,
 | 
			
		||||
						position: "right",
 | 
			
		||||
						beginAtZero: true,
 | 
			
		||||
						grid: { drawOnChartArea: false },
 | 
			
		||||
					},
 | 
			
		||||
				},
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,6 +1,10 @@
 | 
			
		|||
import type { ReadingLog, ReadingLogEntry } from "@utils/ReadingLog";
 | 
			
		||||
import { getContext, setContext } from "svelte";
 | 
			
		||||
import { createDateFilter, type DateFilterStore } from "./date-filter.svelte";
 | 
			
		||||
import {
 | 
			
		||||
	createDateFilter,
 | 
			
		||||
	type DateFilterStore,
 | 
			
		||||
	type DateFilterStoreOptions,
 | 
			
		||||
} from "./date-filter.svelte";
 | 
			
		||||
 | 
			
		||||
export interface ReadingLogStore extends DateFilterStore {
 | 
			
		||||
	get entries(): ReadingLogEntry[];
 | 
			
		||||
| 
						 | 
				
			
			@ -12,13 +16,16 @@ export interface ReadingLogStore extends DateFilterStore {
 | 
			
		|||
	destroy(): void;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function createReadingLog(readingLog: ReadingLog): ReadingLogStore {
 | 
			
		||||
export function createReadingLog(
 | 
			
		||||
	readingLog: ReadingLog,
 | 
			
		||||
	{ initialMonth = true, ...otherOpts }: DateFilterStoreOptions = {}
 | 
			
		||||
): ReadingLogStore {
 | 
			
		||||
	let entries: ReadingLogEntry[] = $state(readingLog.getEntries());
 | 
			
		||||
 | 
			
		||||
	const dateFilter = createDateFilter(
 | 
			
		||||
		() => entries,
 | 
			
		||||
		(entry) => entry.createdAt,
 | 
			
		||||
		{ initialMonth: true }
 | 
			
		||||
		{ initialMonth, ...otherOpts }
 | 
			
		||||
	);
 | 
			
		||||
 | 
			
		||||
	async function addEntry(entry: ReadingLogEntry): Promise<void> {
 | 
			
		||||
| 
						 | 
				
			
			@ -34,18 +41,24 @@ export function createReadingLog(readingLog: ReadingLog): ReadingLogStore {
 | 
			
		|||
	}
 | 
			
		||||
 | 
			
		||||
	async function removeEntry(entry: ReadingLogEntry): Promise<void> {
 | 
			
		||||
		await readingLog.removeEntry(entry);
 | 
			
		||||
		await readingLog.deleteEntry(entry);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	const loadHandler = readingLog.on("load", (payload) => {
 | 
			
		||||
		console.info("Reading log loaded");
 | 
			
		||||
		console.debug("Reading log entries:", payload.entries);
 | 
			
		||||
		entries = payload.entries;
 | 
			
		||||
	});
 | 
			
		||||
 | 
			
		||||
	const createdHandler = readingLog.on("created", (payload) => {
 | 
			
		||||
	const createdHandler = readingLog.on("create", (payload) => {
 | 
			
		||||
		console.info("Reading log entry created");
 | 
			
		||||
		console.debug("Reading log entry:", payload.entry);
 | 
			
		||||
		entries.push(payload.entry);
 | 
			
		||||
	});
 | 
			
		||||
 | 
			
		||||
	const updatedHandler = readingLog.on("updated", (payload) => {
 | 
			
		||||
	const updatedHandler = readingLog.on("update", (payload) => {
 | 
			
		||||
		console.info("Reading log entry updated");
 | 
			
		||||
		console.debug("Reading log entry:", payload.entry);
 | 
			
		||||
		const index = entries.findIndex(
 | 
			
		||||
			(entry) => entry.id === payload.entry.id
 | 
			
		||||
		);
 | 
			
		||||
| 
						 | 
				
			
			@ -54,7 +67,9 @@ export function createReadingLog(readingLog: ReadingLog): ReadingLogStore {
 | 
			
		|||
		}
 | 
			
		||||
	});
 | 
			
		||||
 | 
			
		||||
	const removedHandler = readingLog.on("removed", (payload) => {
 | 
			
		||||
	const removedHandler = readingLog.on("delete", (payload) => {
 | 
			
		||||
		console.info("Reading log entry deleted");
 | 
			
		||||
		console.debug("Reading log entry:", payload.entry);
 | 
			
		||||
		const index = entries.findIndex(
 | 
			
		||||
			(entry) => entry.id === payload.entry.id
 | 
			
		||||
		);
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -14,9 +14,9 @@ export interface ReadingLogEntry {
 | 
			
		|||
 | 
			
		||||
interface ReadingLogEventMap {
 | 
			
		||||
	load: { entries: ReadingLogEntry[] };
 | 
			
		||||
	created: { entry: ReadingLogEntry };
 | 
			
		||||
	updated: { entry: ReadingLogEntry };
 | 
			
		||||
	removed: { entry: ReadingLogEntry };
 | 
			
		||||
	create: { entry: ReadingLogEntry };
 | 
			
		||||
	update: { entry: ReadingLogEntry };
 | 
			
		||||
	delete: { entry: ReadingLogEntry };
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const DEFAULT_FILENAME = "reading-log.json";
 | 
			
		||||
| 
						 | 
				
			
			@ -126,7 +126,7 @@ export class ReadingLog extends EventEmitter<ReadingLogEventMap> {
 | 
			
		|||
	public async addRawEntry(entry: ReadingLogEntry) {
 | 
			
		||||
		this.entries.push(entry);
 | 
			
		||||
		await this.save();
 | 
			
		||||
		this.emit("created", { entry });
 | 
			
		||||
		this.emit("create", { entry });
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	public async updateEntry(entry: ReadingLogEntry): Promise<void> {
 | 
			
		||||
| 
						 | 
				
			
			@ -138,20 +138,20 @@ export class ReadingLog extends EventEmitter<ReadingLogEventMap> {
 | 
			
		|||
 | 
			
		||||
		this.entries[index] = entry;
 | 
			
		||||
		await this.save();
 | 
			
		||||
		this.emit("updated", { entry });
 | 
			
		||||
		this.emit("update", { entry });
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	public async removeEntry(entry: ReadingLogEntry): Promise<void> {
 | 
			
		||||
	public async deleteEntry(entry: ReadingLogEntry): Promise<void> {
 | 
			
		||||
		const index = this.entries.findIndex((other) => other.id === entry.id);
 | 
			
		||||
 | 
			
		||||
		if (index !== -1) {
 | 
			
		||||
			const removed = this.entries.splice(index, 1);
 | 
			
		||||
			await this.save();
 | 
			
		||||
			this.emit("removed", { entry: removed[0] });
 | 
			
		||||
			this.emit("delete", { entry: removed[0] });
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	public async removeEntriesForBook(book: string): Promise<void> {
 | 
			
		||||
	public async deleteEntriesForBook(book: string): Promise<void> {
 | 
			
		||||
		this.entries = this.entries.filter((entry) => entry.book !== book);
 | 
			
		||||
		await this.save();
 | 
			
		||||
	}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
		Reference in New Issue