generated from tpl/obsidian-sample-plugin
			Add reading calendar
This commit is contained in:
		
							parent
							
								
									ac10cf646f
								
							
						
					
					
						commit
						ffb8cc8d9c
					
				| 
						 | 
					@ -1,7 +1,7 @@
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
	"id": "obsidian-book-tracker",
 | 
						"id": "obsidian-book-tracker",
 | 
				
			||||||
	"name": "Book Tracker",
 | 
						"name": "Book Tracker",
 | 
				
			||||||
	"version": "1.0.0",
 | 
						"version": "1.1.0",
 | 
				
			||||||
	"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.0.0",
 | 
						"version": "1.1.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.",
 | 
				
			||||||
	"main": "main.js",
 | 
						"main": "main.js",
 | 
				
			||||||
	"scripts": {
 | 
						"scripts": {
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -24,6 +24,7 @@ import { Goodreads } from "@data-sources/Goodreads";
 | 
				
			||||||
import { CreateBookFromGoodreadsUrlCommand } from "@commands/CreateBookFromGoodreadsUrlCommand";
 | 
					import { CreateBookFromGoodreadsUrlCommand } from "@commands/CreateBookFromGoodreadsUrlCommand";
 | 
				
			||||||
import { registerShelfCodeBlockProcessor } from "@ui/code-blocks/ShelfCodeBlock";
 | 
					import { registerShelfCodeBlockProcessor } from "@ui/code-blocks/ShelfCodeBlock";
 | 
				
			||||||
import { ReloadReadingLogCommand } from "@commands/ReloadReadingLogCommand";
 | 
					import { ReloadReadingLogCommand } from "@commands/ReloadReadingLogCommand";
 | 
				
			||||||
 | 
					import { registerReadingCalendarCodeBlockProcessor } from "@ui/code-blocks/ReadingCalendarCodeBlock";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export default class BookTrackerPlugin extends Plugin {
 | 
					export default class BookTrackerPlugin extends Plugin {
 | 
				
			||||||
	public settings: BookTrackerPluginSettings;
 | 
						public settings: BookTrackerPluginSettings;
 | 
				
			||||||
| 
						 | 
					@ -89,6 +90,7 @@ export default class BookTrackerPlugin extends Plugin {
 | 
				
			||||||
		registerReadingLogCodeBlockProcessor(this);
 | 
							registerReadingLogCodeBlockProcessor(this);
 | 
				
			||||||
		registerReadingStatsCodeBlockProcessor(this);
 | 
							registerReadingStatsCodeBlockProcessor(this);
 | 
				
			||||||
		registerShelfCodeBlockProcessor(this);
 | 
							registerShelfCodeBlockProcessor(this);
 | 
				
			||||||
 | 
							registerReadingCalendarCodeBlockProcessor(this);
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	onunload() {}
 | 
						onunload() {}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,25 @@
 | 
				
			||||||
 | 
					import { registerCodeBlockRenderer } from ".";
 | 
				
			||||||
 | 
					import { SvelteCodeBlockRenderer } from "./SvelteCodeBlockRenderer";
 | 
				
			||||||
 | 
					import ReadingCalendarCodeBlockView from "./ReadingCalendarCodeBlockView.svelte";
 | 
				
			||||||
 | 
					import type BookTrackerPlugin from "@src/main";
 | 
				
			||||||
 | 
					import z from "zod/v4";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export function registerReadingCalendarCodeBlockProcessor(
 | 
				
			||||||
 | 
						plugin: BookTrackerPlugin
 | 
				
			||||||
 | 
					): void {
 | 
				
			||||||
 | 
						registerCodeBlockRenderer(
 | 
				
			||||||
 | 
							plugin,
 | 
				
			||||||
 | 
							"readingcalendar",
 | 
				
			||||||
 | 
							(source, el) =>
 | 
				
			||||||
 | 
								new SvelteCodeBlockRenderer(
 | 
				
			||||||
 | 
									ReadingCalendarCodeBlockView,
 | 
				
			||||||
 | 
									plugin,
 | 
				
			||||||
 | 
									source,
 | 
				
			||||||
 | 
									el
 | 
				
			||||||
 | 
								)
 | 
				
			||||||
 | 
						);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const ReadingCalendarSettingsSchema = z.object({
 | 
				
			||||||
 | 
						coverProperty: z.string(),
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,400 @@
 | 
				
			||||||
 | 
					<script lang="ts">
 | 
				
			||||||
 | 
						import { parseYaml } from "obsidian";
 | 
				
			||||||
 | 
						import { ReadingCalendarSettingsSchema } from "./ReadingCalendarCodeBlock";
 | 
				
			||||||
 | 
						import type { SvelteCodeBlockProps } from "./SvelteCodeBlockRenderer";
 | 
				
			||||||
 | 
						import {
 | 
				
			||||||
 | 
							createSettings,
 | 
				
			||||||
 | 
							setSettingsContext,
 | 
				
			||||||
 | 
						} from "@ui/stores/settings.svelte";
 | 
				
			||||||
 | 
						import {
 | 
				
			||||||
 | 
							createMetadata,
 | 
				
			||||||
 | 
							setMetadataContext,
 | 
				
			||||||
 | 
						} from "@ui/stores/metadata.svelte";
 | 
				
			||||||
 | 
						import {
 | 
				
			||||||
 | 
							createReadingLog,
 | 
				
			||||||
 | 
							setReadingLogContext,
 | 
				
			||||||
 | 
						} from "@ui/stores/reading-log.svelte";
 | 
				
			||||||
 | 
						import { ArrowLeft, ArrowRight } from "lucide-svelte";
 | 
				
			||||||
 | 
						import { onMount } from "svelte";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						const { plugin, source }: SvelteCodeBlockProps = $props();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						const settings = ReadingCalendarSettingsSchema.parse(parseYaml(source));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						const settingsStore = createSettings(plugin);
 | 
				
			||||||
 | 
						setSettingsContext(settingsStore);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						const metadataStore = createMetadata(plugin, null);
 | 
				
			||||||
 | 
						setMetadataContext(metadataStore);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						const readingLog = createReadingLog(plugin.readingLog);
 | 
				
			||||||
 | 
						setReadingLogContext(readingLog);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						let year = $state(new Date().getFullYear());
 | 
				
			||||||
 | 
						let month = $state(new Date().getMonth());
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						$effect(() => {
 | 
				
			||||||
 | 
							readingLog.filterYear = year;
 | 
				
			||||||
 | 
							readingLog.filterMonth = month + 1;
 | 
				
			||||||
 | 
						});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						const monthNames = [
 | 
				
			||||||
 | 
							"January",
 | 
				
			||||||
 | 
							"February",
 | 
				
			||||||
 | 
							"March",
 | 
				
			||||||
 | 
							"April",
 | 
				
			||||||
 | 
							"May",
 | 
				
			||||||
 | 
							"June",
 | 
				
			||||||
 | 
							"July",
 | 
				
			||||||
 | 
							"August",
 | 
				
			||||||
 | 
							"September",
 | 
				
			||||||
 | 
							"October",
 | 
				
			||||||
 | 
							"November",
 | 
				
			||||||
 | 
							"December",
 | 
				
			||||||
 | 
						];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						const daysOfWeek = [
 | 
				
			||||||
 | 
							"Sunday",
 | 
				
			||||||
 | 
							"Monday",
 | 
				
			||||||
 | 
							"Tuesday",
 | 
				
			||||||
 | 
							"Wednesday",
 | 
				
			||||||
 | 
							"Thursday",
 | 
				
			||||||
 | 
							"Friday",
 | 
				
			||||||
 | 
							"Saturday",
 | 
				
			||||||
 | 
						];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// @ts-expect-error Moment is provided by Obsidian
 | 
				
			||||||
 | 
						let today = $state(moment());
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						function msUntilMidnight() {
 | 
				
			||||||
 | 
							// @ts-expect-error Moment is provided by Obsidian
 | 
				
			||||||
 | 
							return moment().endOf("day").diff(today, "milliseconds");
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						function updateToday() {
 | 
				
			||||||
 | 
							setTimeout(() => {
 | 
				
			||||||
 | 
								// @ts-expect-error Moment is provided by Obsidian
 | 
				
			||||||
 | 
								today = moment();
 | 
				
			||||||
 | 
								updateToday();
 | 
				
			||||||
 | 
							}, msUntilMidnight() + 1000);
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						onMount(() => {
 | 
				
			||||||
 | 
							updateToday();
 | 
				
			||||||
 | 
						});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						const weeks = $derived.by(() => {
 | 
				
			||||||
 | 
							// @ts-expect-error Moment is provided by Obsidian
 | 
				
			||||||
 | 
							const firstDay = moment()
 | 
				
			||||||
 | 
								.year(year)
 | 
				
			||||||
 | 
								.month(month)
 | 
				
			||||||
 | 
								.startOf("month")
 | 
				
			||||||
 | 
								.startOf("week");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// @ts-expect-error Moment is provided by Obsidian
 | 
				
			||||||
 | 
							const lastDay = moment()
 | 
				
			||||||
 | 
								.year(year)
 | 
				
			||||||
 | 
								.month(month)
 | 
				
			||||||
 | 
								.endOf("month")
 | 
				
			||||||
 | 
								.endOf("week");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							const weeks = [];
 | 
				
			||||||
 | 
							let currentDay = firstDay.clone();
 | 
				
			||||||
 | 
							while (currentDay.isBefore(lastDay)) {
 | 
				
			||||||
 | 
								const week = [];
 | 
				
			||||||
 | 
								for (let i = 0; i < 7; i++) {
 | 
				
			||||||
 | 
									week.push(currentDay.clone());
 | 
				
			||||||
 | 
									currentDay.add(1, "day");
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
								weeks.push(week);
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							return weeks;
 | 
				
			||||||
 | 
						});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						interface CoverData {
 | 
				
			||||||
 | 
							src: string;
 | 
				
			||||||
 | 
							alt: string;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						interface BookMapItem {
 | 
				
			||||||
 | 
							totalPagesRead: number;
 | 
				
			||||||
 | 
							covers: CoverData[];
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						const bookMap = $derived.by(() => {
 | 
				
			||||||
 | 
							const bookMap = new Map<number, BookMapItem>();
 | 
				
			||||||
 | 
							for (const item of readingLog.entries) {
 | 
				
			||||||
 | 
								const key = item.createdAt.date();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								let coverPath = metadataStore.metadata.find(
 | 
				
			||||||
 | 
									(entry) => entry.file.basename === item.book,
 | 
				
			||||||
 | 
								)?.frontmatter?.[settings.coverProperty];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								coverPath = plugin.app.vault.getFileByPath(coverPath);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								if (!coverPath) {
 | 
				
			||||||
 | 
									continue;
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								coverPath = plugin.app.vault.getResourcePath(coverPath);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								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;
 | 
				
			||||||
 | 
						});
 | 
				
			||||||
 | 
					</script>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<div class="reading-calendar">
 | 
				
			||||||
 | 
						<div class="controls">
 | 
				
			||||||
 | 
							<div class="left">
 | 
				
			||||||
 | 
								<button
 | 
				
			||||||
 | 
									class="prev-month"
 | 
				
			||||||
 | 
									aria-label="Go to previous month"
 | 
				
			||||||
 | 
									onclick={() => {
 | 
				
			||||||
 | 
										if (month === 0) {
 | 
				
			||||||
 | 
											month = 11;
 | 
				
			||||||
 | 
											year--;
 | 
				
			||||||
 | 
										} else {
 | 
				
			||||||
 | 
											month--;
 | 
				
			||||||
 | 
										}
 | 
				
			||||||
 | 
									}}
 | 
				
			||||||
 | 
								>
 | 
				
			||||||
 | 
									<ArrowLeft />
 | 
				
			||||||
 | 
								</button>
 | 
				
			||||||
 | 
								<button
 | 
				
			||||||
 | 
									class="today"
 | 
				
			||||||
 | 
									aria-label="Go to the current month"
 | 
				
			||||||
 | 
									onclick={() => {
 | 
				
			||||||
 | 
										year = today.year();
 | 
				
			||||||
 | 
										month = today.month();
 | 
				
			||||||
 | 
									}}
 | 
				
			||||||
 | 
								>
 | 
				
			||||||
 | 
									Today
 | 
				
			||||||
 | 
								</button>
 | 
				
			||||||
 | 
							</div>
 | 
				
			||||||
 | 
							<h2>{monthNames[month]} {year}</h2>
 | 
				
			||||||
 | 
							<button
 | 
				
			||||||
 | 
								class="next-month"
 | 
				
			||||||
 | 
								aria-label="Go to next month"
 | 
				
			||||||
 | 
								onclick={() => {
 | 
				
			||||||
 | 
									if (month === 11) {
 | 
				
			||||||
 | 
										month = 0;
 | 
				
			||||||
 | 
										year++;
 | 
				
			||||||
 | 
									} else {
 | 
				
			||||||
 | 
										month++;
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
								}}
 | 
				
			||||||
 | 
							>
 | 
				
			||||||
 | 
								<ArrowRight />
 | 
				
			||||||
 | 
							</button>
 | 
				
			||||||
 | 
						</div>
 | 
				
			||||||
 | 
						<table>
 | 
				
			||||||
 | 
							<thead>
 | 
				
			||||||
 | 
								<tr>
 | 
				
			||||||
 | 
									{#each daysOfWeek as day}
 | 
				
			||||||
 | 
										<th>{day}</th>
 | 
				
			||||||
 | 
									{/each}
 | 
				
			||||||
 | 
								</tr>
 | 
				
			||||||
 | 
							</thead>
 | 
				
			||||||
 | 
							<tbody>
 | 
				
			||||||
 | 
								{#each weeks as week}
 | 
				
			||||||
 | 
									<tr>
 | 
				
			||||||
 | 
										{#each week as day}
 | 
				
			||||||
 | 
											{@const isThisMonth = day.month() === month}
 | 
				
			||||||
 | 
											<td
 | 
				
			||||||
 | 
												class:is-today={day.isSame(today, "day")}
 | 
				
			||||||
 | 
												class:is-weekend={day.day() === 0 ||
 | 
				
			||||||
 | 
													day.day() === 6}
 | 
				
			||||||
 | 
												class:is-prev-month={day.month() ===
 | 
				
			||||||
 | 
													(month === 0 ? 11 : month - 1)}
 | 
				
			||||||
 | 
												class:is-next-month={day.month() ===
 | 
				
			||||||
 | 
													(month + 1) % 12}
 | 
				
			||||||
 | 
											>
 | 
				
			||||||
 | 
												<div class="header">
 | 
				
			||||||
 | 
													<span>{day.date()}</span>
 | 
				
			||||||
 | 
													{#if isThisMonth && bookMap.has(day.date())}
 | 
				
			||||||
 | 
														{@const data = bookMap.get(day.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}
 | 
				
			||||||
 | 
														{/each}
 | 
				
			||||||
 | 
													{/if}
 | 
				
			||||||
 | 
												</div>
 | 
				
			||||||
 | 
											</td>
 | 
				
			||||||
 | 
										{/each}
 | 
				
			||||||
 | 
									</tr>
 | 
				
			||||||
 | 
								{/each}
 | 
				
			||||||
 | 
							</tbody>
 | 
				
			||||||
 | 
						</table>
 | 
				
			||||||
 | 
					</div>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<style lang="scss">
 | 
				
			||||||
 | 
						$cell-padding: var(--size-4-2);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						.reading-calendar {
 | 
				
			||||||
 | 
							display: flex;
 | 
				
			||||||
 | 
							flex-direction: column;
 | 
				
			||||||
 | 
							overflow: auto;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							.controls {
 | 
				
			||||||
 | 
								min-width: 800px;
 | 
				
			||||||
 | 
								display: grid;
 | 
				
			||||||
 | 
								grid-template-columns: max-content 1fr max-content;
 | 
				
			||||||
 | 
								gap: var(--size-4-4);
 | 
				
			||||||
 | 
								align-items: center;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								h2 {
 | 
				
			||||||
 | 
									text-align: center;
 | 
				
			||||||
 | 
									margin: 0;
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								.left {
 | 
				
			||||||
 | 
									display: flex;
 | 
				
			||||||
 | 
									gap: var(--size-2-2);
 | 
				
			||||||
 | 
									align-items: center;
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						table {
 | 
				
			||||||
 | 
							table-layout: fixed;
 | 
				
			||||||
 | 
							border-collapse: collapse;
 | 
				
			||||||
 | 
							min-width: 800px;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							th {
 | 
				
			||||||
 | 
								padding: $cell-padding;
 | 
				
			||||||
 | 
								width: calc(100% / 7);
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							td {
 | 
				
			||||||
 | 
								padding: $cell-padding;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								&.is-prev-month,
 | 
				
			||||||
 | 
								&.is-next-month {
 | 
				
			||||||
 | 
									color: var(--text-faint);
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								&.is-today {
 | 
				
			||||||
 | 
									color: var(--text-accent);
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								.header {
 | 
				
			||||||
 | 
									display: flex;
 | 
				
			||||||
 | 
									justify-content: space-between;
 | 
				
			||||||
 | 
									align-items: center;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
									.total-pages-read {
 | 
				
			||||||
 | 
										font-size: var(--font-smallest);
 | 
				
			||||||
 | 
										color: var(--text-muted);
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								.covers {
 | 
				
			||||||
 | 
									position: relative;
 | 
				
			||||||
 | 
									width: 100%;
 | 
				
			||||||
 | 
									aspect-ratio: 2 / 3;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
									img {
 | 
				
			||||||
 | 
										border-radius: var(--radius-l);
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
									&:has(img:first-child:nth-last-child(1)) {
 | 
				
			||||||
 | 
										img {
 | 
				
			||||||
 | 
											position: absolute;
 | 
				
			||||||
 | 
											height: 100%;
 | 
				
			||||||
 | 
											width: 100%;
 | 
				
			||||||
 | 
										}
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
									&:has(img:first-child:nth-last-child(2)) {
 | 
				
			||||||
 | 
										img:first-child {
 | 
				
			||||||
 | 
											position: absolute;
 | 
				
			||||||
 | 
											height: 100%;
 | 
				
			||||||
 | 
											width: 100%;
 | 
				
			||||||
 | 
											clip-path: polygon(0 0, 100% 0, 0 100%);
 | 
				
			||||||
 | 
										}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
										img:last-child {
 | 
				
			||||||
 | 
											position: absolute;
 | 
				
			||||||
 | 
											height: 100%;
 | 
				
			||||||
 | 
											width: 100%;
 | 
				
			||||||
 | 
											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%;
 | 
				
			||||||
 | 
											clip-path: polygon(0 0, 100% 0, 100% 20%, 0 50%);
 | 
				
			||||||
 | 
										}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
										img:nth-child(2) {
 | 
				
			||||||
 | 
											position: absolute;
 | 
				
			||||||
 | 
											height: 100%;
 | 
				
			||||||
 | 
											width: 100%;
 | 
				
			||||||
 | 
											clip-path: polygon(100% 80%, 100% 20%, 0 50%);
 | 
				
			||||||
 | 
										}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
										img:last-child {
 | 
				
			||||||
 | 
											position: absolute;
 | 
				
			||||||
 | 
											height: 100%;
 | 
				
			||||||
 | 
											width: 100%;
 | 
				
			||||||
 | 
											clip-path: polygon(0 50%, 100% 80%, 100% 100%, 0% 100%);
 | 
				
			||||||
 | 
										}
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
									&:has(img:first-child:nth-last-child(4)) {
 | 
				
			||||||
 | 
										img:first-child {
 | 
				
			||||||
 | 
											position: absolute;
 | 
				
			||||||
 | 
											height: 50%;
 | 
				
			||||||
 | 
											width: 50%;
 | 
				
			||||||
 | 
											top: 0;
 | 
				
			||||||
 | 
											left: 0;
 | 
				
			||||||
 | 
										}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
										img:nth-child(2) {
 | 
				
			||||||
 | 
											position: absolute;
 | 
				
			||||||
 | 
											height: 50%;
 | 
				
			||||||
 | 
											width: 50%;
 | 
				
			||||||
 | 
											top: 0;
 | 
				
			||||||
 | 
											right: 0;
 | 
				
			||||||
 | 
										}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
										img:nth-child(3) {
 | 
				
			||||||
 | 
											position: absolute;
 | 
				
			||||||
 | 
											height: 50%;
 | 
				
			||||||
 | 
											width: 50%;
 | 
				
			||||||
 | 
											bottom: 0;
 | 
				
			||||||
 | 
											left: 0;
 | 
				
			||||||
 | 
										}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
										img:last-child {
 | 
				
			||||||
 | 
											position: absolute;
 | 
				
			||||||
 | 
											height: 50%;
 | 
				
			||||||
 | 
											width: 50%;
 | 
				
			||||||
 | 
											bottom: 0;
 | 
				
			||||||
 | 
											right: 0;
 | 
				
			||||||
 | 
										}
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					</style>
 | 
				
			||||||
| 
						 | 
					@ -9,16 +9,12 @@ export function registerReadingLogCodeBlockProcessor(
 | 
				
			||||||
	registerCodeBlockRenderer(
 | 
						registerCodeBlockRenderer(
 | 
				
			||||||
		plugin,
 | 
							plugin,
 | 
				
			||||||
		"readinglog",
 | 
							"readinglog",
 | 
				
			||||||
		(_source, el) => new ReadingLogCodeBlockRenderer(el, plugin)
 | 
							(source, el) =>
 | 
				
			||||||
 | 
								new SvelteCodeBlockRenderer(
 | 
				
			||||||
 | 
									ReadingLogCodeBlockView,
 | 
				
			||||||
 | 
									plugin,
 | 
				
			||||||
 | 
									source,
 | 
				
			||||||
 | 
									el
 | 
				
			||||||
 | 
								)
 | 
				
			||||||
	);
 | 
						);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					 | 
				
			||||||
export class ReadingLogCodeBlockRenderer extends SvelteCodeBlockRenderer<
 | 
					 | 
				
			||||||
	typeof ReadingLogCodeBlockView
 | 
					 | 
				
			||||||
> {
 | 
					 | 
				
			||||||
	constructor(contentEl: HTMLElement, plugin: BookTrackerPlugin) {
 | 
					 | 
				
			||||||
		super(contentEl, ReadingLogCodeBlockView, { props: { plugin } });
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	onunload() {}
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -2,18 +2,14 @@
 | 
				
			||||||
	import type { ReadingLogEntry } from "@utils/ReadingLog";
 | 
						import type { ReadingLogEntry } from "@utils/ReadingLog";
 | 
				
			||||||
	import { Edit, Trash, Plus } from "lucide-svelte";
 | 
						import { Edit, Trash, Plus } from "lucide-svelte";
 | 
				
			||||||
	import { ReadingLogEntryEditModal } from "@ui/modals";
 | 
						import { ReadingLogEntryEditModal } from "@ui/modals";
 | 
				
			||||||
	import type BookTrackerPlugin from "@src/main";
 | 
					 | 
				
			||||||
	import { createReadingLog } from "@ui/stores/reading-log.svelte";
 | 
						import { createReadingLog } from "@ui/stores/reading-log.svelte";
 | 
				
			||||||
	import { ALL_TIME } from "@ui/stores/date-filter.svelte";
 | 
						import { ALL_TIME } from "@ui/stores/date-filter.svelte";
 | 
				
			||||||
	import { onDestroy, onMount } from "svelte";
 | 
						import { onDestroy, onMount } from "svelte";
 | 
				
			||||||
	import OpenFileLink from "@ui/components/OpenFileLink.svelte";
 | 
						import OpenFileLink from "@ui/components/OpenFileLink.svelte";
 | 
				
			||||||
	import { setAppContext } from "@ui/stores/app";
 | 
						import { setAppContext } from "@ui/stores/app";
 | 
				
			||||||
 | 
						import type { SvelteCodeBlockProps } from "./SvelteCodeBlockRenderer";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	interface Props {
 | 
						const { plugin }: SvelteCodeBlockProps = $props();
 | 
				
			||||||
		plugin: BookTrackerPlugin;
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	const { plugin }: Props = $props();
 | 
					 | 
				
			||||||
	setAppContext(plugin.app);
 | 
						setAppContext(plugin.app);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	const store = createReadingLog(plugin.readingLog);
 | 
						const store = createReadingLog(plugin.readingLog);
 | 
				
			||||||
| 
						 | 
					@ -32,7 +28,6 @@
 | 
				
			||||||
		const modal = new ReadingLogEntryEditModal(
 | 
							const modal = new ReadingLogEntryEditModal(
 | 
				
			||||||
			plugin,
 | 
								plugin,
 | 
				
			||||||
			async (entry) => {
 | 
								async (entry) => {
 | 
				
			||||||
				modal.close();
 | 
					 | 
				
			||||||
				await store.updateEntry(entry);
 | 
									await store.updateEntry(entry);
 | 
				
			||||||
			},
 | 
								},
 | 
				
			||||||
			entry,
 | 
								entry,
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -11,7 +11,13 @@ export function registerReadingStatsCodeBlockProcessor(
 | 
				
			||||||
	registerCodeBlockRenderer(
 | 
						registerCodeBlockRenderer(
 | 
				
			||||||
		plugin,
 | 
							plugin,
 | 
				
			||||||
		"readingstats",
 | 
							"readingstats",
 | 
				
			||||||
		(source, el) => new ReadingStatsCodeBlockRenderer(source, el, plugin)
 | 
							(source, el) =>
 | 
				
			||||||
 | 
								new SvelteCodeBlockRenderer(
 | 
				
			||||||
 | 
									ReadingStatsCodeBlockView,
 | 
				
			||||||
 | 
									plugin,
 | 
				
			||||||
 | 
									source,
 | 
				
			||||||
 | 
									el
 | 
				
			||||||
 | 
								)
 | 
				
			||||||
	);
 | 
						);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -98,19 +104,3 @@ export const ReadingStatsSectionSchema = z.object({
 | 
				
			||||||
});
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export type ReadingStatsSection = z.infer<typeof ReadingStatsSectionSchema>;
 | 
					export type ReadingStatsSection = z.infer<typeof ReadingStatsSectionSchema>;
 | 
				
			||||||
 | 
					 | 
				
			||||||
export class ReadingStatsCodeBlockRenderer extends SvelteCodeBlockRenderer<
 | 
					 | 
				
			||||||
	typeof ReadingStatsCodeBlockView
 | 
					 | 
				
			||||||
> {
 | 
					 | 
				
			||||||
	constructor(
 | 
					 | 
				
			||||||
		source: string,
 | 
					 | 
				
			||||||
		contentEl: HTMLElement,
 | 
					 | 
				
			||||||
		plugin: BookTrackerPlugin
 | 
					 | 
				
			||||||
	) {
 | 
					 | 
				
			||||||
		super(contentEl, ReadingStatsCodeBlockView, {
 | 
					 | 
				
			||||||
			props: { plugin, source },
 | 
					 | 
				
			||||||
		});
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	onunload() {}
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -20,7 +20,6 @@
 | 
				
			||||||
		createSettings,
 | 
							createSettings,
 | 
				
			||||||
		setSettingsContext,
 | 
							setSettingsContext,
 | 
				
			||||||
	} from "@ui/stores/settings.svelte";
 | 
						} from "@ui/stores/settings.svelte";
 | 
				
			||||||
	import type BookTrackerPlugin from "@src/main";
 | 
					 | 
				
			||||||
	import BookCountStat from "@ui/components/stats/BookCountStat.svelte";
 | 
						import BookCountStat from "@ui/components/stats/BookCountStat.svelte";
 | 
				
			||||||
	import { ALL_TIME } from "@ui/stores/date-filter.svelte";
 | 
						import { ALL_TIME } from "@ui/stores/date-filter.svelte";
 | 
				
			||||||
	import {
 | 
						import {
 | 
				
			||||||
| 
						 | 
					@ -28,13 +27,9 @@
 | 
				
			||||||
		setReadingLogContext,
 | 
							setReadingLogContext,
 | 
				
			||||||
	} from "@ui/stores/reading-log.svelte";
 | 
						} from "@ui/stores/reading-log.svelte";
 | 
				
			||||||
	import { setAppContext } from "@ui/stores/app";
 | 
						import { setAppContext } from "@ui/stores/app";
 | 
				
			||||||
 | 
						import type { SvelteCodeBlockProps } from "./SvelteCodeBlockRenderer";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	interface Props {
 | 
						const { plugin, source }: SvelteCodeBlockProps = $props();
 | 
				
			||||||
		plugin: BookTrackerPlugin;
 | 
					 | 
				
			||||||
		source: string;
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	const { plugin, source }: Props = $props();
 | 
					 | 
				
			||||||
	setAppContext(plugin.app);
 | 
						setAppContext(plugin.app);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	const settingsStore = createSettings(plugin);
 | 
						const settingsStore = createSettings(plugin);
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -11,7 +11,8 @@ export function registerShelfCodeBlockProcessor(
 | 
				
			||||||
	registerCodeBlockRenderer(
 | 
						registerCodeBlockRenderer(
 | 
				
			||||||
		plugin,
 | 
							plugin,
 | 
				
			||||||
		"shelf",
 | 
							"shelf",
 | 
				
			||||||
		(source, el) => new ShelfCodeBlockRenderer(plugin, source, el)
 | 
							(source, el) =>
 | 
				
			||||||
 | 
								new SvelteCodeBlockRenderer(ShelfCodeBockView, plugin, source, el)
 | 
				
			||||||
	);
 | 
						);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -33,17 +34,3 @@ export const ShelfSettingsSchema = z.object({
 | 
				
			||||||
});
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export type ShelfSettings = z.infer<typeof ShelfSettingsSchema>;
 | 
					export type ShelfSettings = z.infer<typeof ShelfSettingsSchema>;
 | 
				
			||||||
 | 
					 | 
				
			||||||
export class ShelfCodeBlockRenderer extends SvelteCodeBlockRenderer<
 | 
					 | 
				
			||||||
	typeof ShelfCodeBockView
 | 
					 | 
				
			||||||
> {
 | 
					 | 
				
			||||||
	constructor(
 | 
					 | 
				
			||||||
		plugin: BookTrackerPlugin,
 | 
					 | 
				
			||||||
		source: string,
 | 
					 | 
				
			||||||
		contentEl: HTMLElement
 | 
					 | 
				
			||||||
	) {
 | 
					 | 
				
			||||||
		super(contentEl, ShelfCodeBockView, { props: { plugin, source } });
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	onunload() {}
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,6 +1,5 @@
 | 
				
			||||||
<script lang="ts">
 | 
					<script lang="ts">
 | 
				
			||||||
	import { STATUS_READ } from "@src/const";
 | 
						import { STATUS_READ } from "@src/const";
 | 
				
			||||||
	import type BookTrackerPlugin from "@src/main";
 | 
					 | 
				
			||||||
	import {
 | 
						import {
 | 
				
			||||||
		createMetadata,
 | 
							createMetadata,
 | 
				
			||||||
		setMetadataContext,
 | 
							setMetadataContext,
 | 
				
			||||||
| 
						 | 
					@ -17,13 +16,9 @@
 | 
				
			||||||
	import TableView from "@ui/components/TableView.svelte";
 | 
						import TableView from "@ui/components/TableView.svelte";
 | 
				
			||||||
	import DetailsView from "@ui/components/DetailsView.svelte";
 | 
						import DetailsView from "@ui/components/DetailsView.svelte";
 | 
				
			||||||
	import { setAppContext } from "@ui/stores/app";
 | 
						import { setAppContext } from "@ui/stores/app";
 | 
				
			||||||
 | 
						import type { SvelteCodeBlockProps } from "./SvelteCodeBlockRenderer";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	interface Props {
 | 
						const { plugin, source }: SvelteCodeBlockProps = $props();
 | 
				
			||||||
		plugin: BookTrackerPlugin;
 | 
					 | 
				
			||||||
		source: string;
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	const { plugin, source }: Props = $props();
 | 
					 | 
				
			||||||
	setAppContext(plugin.app);
 | 
						setAppContext(plugin.app);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	const settings = ShelfSettingsSchema.parse(parseYaml(source));
 | 
						const settings = ShelfSettingsSchema.parse(parseYaml(source));
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,26 +1,34 @@
 | 
				
			||||||
import { mount, unmount, type Component, type MountOptions } from "svelte";
 | 
					import { mount, unmount, type Component } from "svelte";
 | 
				
			||||||
import { MarkdownRenderChild } from "obsidian";
 | 
					import { MarkdownRenderChild } from "obsidian";
 | 
				
			||||||
 | 
					import type BookTrackerPlugin from "@src/main";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export interface SvelteCodeBlockProps {
 | 
				
			||||||
 | 
						plugin: BookTrackerPlugin;
 | 
				
			||||||
 | 
						source: string;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export class SvelteCodeBlockRenderer<
 | 
					export class SvelteCodeBlockRenderer<
 | 
				
			||||||
	TComponent extends Component<TProps, TExports, TBindings>,
 | 
						TComponent extends Component<SvelteCodeBlockProps, TExports>,
 | 
				
			||||||
	TProps extends Record<string, any> = {},
 | 
						TExports extends Record<string, any> = {}
 | 
				
			||||||
	TExports extends Record<string, any> = {},
 | 
					 | 
				
			||||||
	TBindings extends keyof TProps | "" = string
 | 
					 | 
				
			||||||
> extends MarkdownRenderChild {
 | 
					> extends MarkdownRenderChild {
 | 
				
			||||||
	protected component: TExports | undefined;
 | 
						protected component: TExports | undefined;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	constructor(
 | 
						constructor(
 | 
				
			||||||
		private readonly contentEl: HTMLElement,
 | 
					 | 
				
			||||||
		private readonly componentCtor: TComponent,
 | 
							private readonly componentCtor: TComponent,
 | 
				
			||||||
		private readonly mountOpts: Omit<MountOptions<TProps>, "target">
 | 
							private readonly plugin: BookTrackerPlugin,
 | 
				
			||||||
 | 
							private readonly source: string,
 | 
				
			||||||
 | 
							private readonly contentEl: HTMLElement
 | 
				
			||||||
	) {
 | 
						) {
 | 
				
			||||||
		super(contentEl);
 | 
							super(contentEl);
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	onload(): void {
 | 
						onload(): void {
 | 
				
			||||||
		this.component = mount(this.componentCtor, {
 | 
							this.component = mount(this.componentCtor, {
 | 
				
			||||||
			...this.mountOpts,
 | 
					 | 
				
			||||||
			target: this.contentEl,
 | 
								target: this.contentEl,
 | 
				
			||||||
 | 
								props: {
 | 
				
			||||||
 | 
									plugin: this.plugin,
 | 
				
			||||||
 | 
									source: this.source,
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
		});
 | 
							});
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -11,9 +11,7 @@ export class GoodreadsSearchModal extends SvelteModal<
 | 
				
			||||||
		goodreads: Goodreads,
 | 
							goodreads: Goodreads,
 | 
				
			||||||
		onSearch: (error: any, results: SearchResult[]) => void = () => {}
 | 
							onSearch: (error: any, results: SearchResult[]) => void = () => {}
 | 
				
			||||||
	) {
 | 
						) {
 | 
				
			||||||
		super(app, GoodreadsSearchModalView, {
 | 
							super(app, GoodreadsSearchModalView, { goodreads, onSearch });
 | 
				
			||||||
			props: { goodreads, onSearch },
 | 
					 | 
				
			||||||
		});
 | 
					 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	static createAndOpen(
 | 
						static createAndOpen(
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -8,7 +8,7 @@ export class RatingModal extends SvelteModal<typeof RatingModalView> {
 | 
				
			||||||
		spiceConfigured: boolean,
 | 
							spiceConfigured: boolean,
 | 
				
			||||||
		onSubmit: (rating: number, spice: number) => void = () => {}
 | 
							onSubmit: (rating: number, spice: number) => void = () => {}
 | 
				
			||||||
	) {
 | 
						) {
 | 
				
			||||||
		super(app, RatingModalView, { props: { spiceConfigured, onSubmit } });
 | 
							super(app, RatingModalView, { spiceConfigured, onSubmit });
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	static createAndOpen(
 | 
						static createAndOpen(
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -8,11 +8,16 @@ export class ReadingLogEntryEditModal extends SvelteModal<
 | 
				
			||||||
> {
 | 
					> {
 | 
				
			||||||
	constructor(
 | 
						constructor(
 | 
				
			||||||
		plugin: BookTrackerPlugin,
 | 
							plugin: BookTrackerPlugin,
 | 
				
			||||||
		onSubmit?: (entry: ReadingLogEntry) => void,
 | 
							onSubmit: (entry: ReadingLogEntry) => void,
 | 
				
			||||||
		entry?: ReadingLogEntry
 | 
							entry?: ReadingLogEntry
 | 
				
			||||||
	) {
 | 
						) {
 | 
				
			||||||
		super(plugin.app, ReadingLogEntryEditModalView, {
 | 
							super(plugin.app, ReadingLogEntryEditModalView, {
 | 
				
			||||||
			props: { plugin, entry, onSubmit },
 | 
								plugin,
 | 
				
			||||||
 | 
								entry,
 | 
				
			||||||
 | 
								onSubmit: (entry: ReadingLogEntry) => {
 | 
				
			||||||
 | 
									onSubmit(entry);
 | 
				
			||||||
 | 
									this.close();
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
		});
 | 
							});
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -10,9 +10,7 @@ export class ReadingProgressModal extends SvelteModal<
 | 
				
			||||||
		pageCount: number,
 | 
							pageCount: number,
 | 
				
			||||||
		onSubmit: (pageNumber: number) => void = () => {}
 | 
							onSubmit: (pageNumber: number) => void = () => {}
 | 
				
			||||||
	) {
 | 
						) {
 | 
				
			||||||
		super(app, ReadingProgressModalView, {
 | 
							super(app, ReadingProgressModalView, { pageCount, onSubmit });
 | 
				
			||||||
			props: { pageCount, onSubmit },
 | 
					 | 
				
			||||||
		});
 | 
					 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	static createAndOpen(app: App, pageCount: number): Promise<number> {
 | 
						static createAndOpen(app: App, pageCount: number): Promise<number> {
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,26 +1,23 @@
 | 
				
			||||||
import { App, Modal } from "obsidian";
 | 
					import { App, Modal } from "obsidian";
 | 
				
			||||||
import { mount, unmount, type Component, type MountOptions } from "svelte";
 | 
					import { mount, unmount, type Component, type ComponentProps } from "svelte";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export class SvelteModal<
 | 
					export class SvelteModal<
 | 
				
			||||||
	TComponent extends Component<TProps, TExports, TBindings>,
 | 
						TComponent extends Component<Record<string, any>, any, any>
 | 
				
			||||||
	TProps extends Record<string, any> = {},
 | 
					 | 
				
			||||||
	TExports extends Record<string, any> = {},
 | 
					 | 
				
			||||||
	TBindings extends keyof TProps | "" = string
 | 
					 | 
				
			||||||
> extends Modal {
 | 
					> extends Modal {
 | 
				
			||||||
	protected component: TExports | undefined;
 | 
						protected component: TComponent | undefined;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	constructor(
 | 
						constructor(
 | 
				
			||||||
		app: App,
 | 
							app: App,
 | 
				
			||||||
		private readonly componentCtor: TComponent,
 | 
							private readonly componentCtor: TComponent,
 | 
				
			||||||
		private readonly mountOpts: Omit<MountOptions<TProps>, "target">
 | 
							private readonly props: ComponentProps<TComponent>
 | 
				
			||||||
	) {
 | 
						) {
 | 
				
			||||||
		super(app);
 | 
							super(app);
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	onOpen(): void {
 | 
						onOpen(): void {
 | 
				
			||||||
		this.component = mount(this.componentCtor, {
 | 
							this.component = mount(this.componentCtor, {
 | 
				
			||||||
			...this.mountOpts,
 | 
					 | 
				
			||||||
			target: this.contentEl,
 | 
								target: this.contentEl,
 | 
				
			||||||
 | 
								props: this.props,
 | 
				
			||||||
		});
 | 
							});
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -130,8 +130,13 @@ export class ReadingLog extends EventEmitter<ReadingLogEventMap> {
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	public async updateEntry(entry: ReadingLogEntry): Promise<void> {
 | 
						public async updateEntry(entry: ReadingLogEntry): Promise<void> {
 | 
				
			||||||
		this.entries[this.entries.findIndex((other) => other.id === entry.id)] =
 | 
							const index = this.entries.findIndex((other) => other.id === entry.id);
 | 
				
			||||||
			entry;
 | 
					
 | 
				
			||||||
 | 
							if (index === -1) {
 | 
				
			||||||
 | 
								throw new Error("Entry not found");
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							this.entries[index] = entry;
 | 
				
			||||||
		await this.save();
 | 
							await this.save();
 | 
				
			||||||
		this.emit("updated", { entry });
 | 
							this.emit("updated", { entry });
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -23,7 +23,7 @@ export class EventEmitter<TEventMap> {
 | 
				
			||||||
		this.listeners[type].push(handler);
 | 
							this.listeners[type].push(handler);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		return {
 | 
							return {
 | 
				
			||||||
			off() {
 | 
								off: () => {
 | 
				
			||||||
				this.off(type, handler);
 | 
									this.off(type, handler);
 | 
				
			||||||
			},
 | 
								},
 | 
				
			||||||
		};
 | 
							};
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,3 +1,4 @@
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
	"1.0.0": "0.15.0"
 | 
						"1.0.0": "0.15.0",
 | 
				
			||||||
 | 
						"1.1.0": "0.15.0"
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
		Loading…
	
		Reference in New Issue