generated from tpl/obsidian-sample-plugin
			Rename bookshelf code block to shelf and add table view
This commit is contained in:
		
							parent
							
								
									94fe4d5f1c
								
							
						
					
					
						commit
						db732fd8a6
					
				| 
						 | 
					@ -22,7 +22,7 @@ import { BackupReadingLogCommand } from "@commands/CreateReadingLogBackupCommand
 | 
				
			||||||
import { RestoreReadingLogBackupCommand } from "@commands/RestoreReadingLogBackupCommand";
 | 
					import { RestoreReadingLogBackupCommand } from "@commands/RestoreReadingLogBackupCommand";
 | 
				
			||||||
import { Goodreads } from "@data-sources/Goodreads";
 | 
					import { Goodreads } from "@data-sources/Goodreads";
 | 
				
			||||||
import { CreateBookFromGoodreadsUrlCommand } from "@commands/CreateBookFromGoodreadsUrlCommand";
 | 
					import { CreateBookFromGoodreadsUrlCommand } from "@commands/CreateBookFromGoodreadsUrlCommand";
 | 
				
			||||||
import { registerBookshelfCodeBlockProcessor } from "@ui/code-blocks/BookshelfCodeBlock";
 | 
					import { registerShelfCodeBlockProcessor } from "@ui/code-blocks/ShelfCodeBlock";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export default class BookTrackerPlugin extends Plugin {
 | 
					export default class BookTrackerPlugin extends Plugin {
 | 
				
			||||||
	public settings: BookTrackerPluginSettings;
 | 
						public settings: BookTrackerPluginSettings;
 | 
				
			||||||
| 
						 | 
					@ -86,7 +86,7 @@ export default class BookTrackerPlugin extends Plugin {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		registerReadingLogCodeBlockProcessor(this);
 | 
							registerReadingLogCodeBlockProcessor(this);
 | 
				
			||||||
		registerReadingStatsCodeBlockProcessor(this);
 | 
							registerReadingStatsCodeBlockProcessor(this);
 | 
				
			||||||
		registerBookshelfCodeBlockProcessor(this);
 | 
							registerShelfCodeBlockProcessor(this);
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	onunload() {}
 | 
						onunload() {}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,35 +0,0 @@
 | 
				
			||||||
import { registerCodeBlockRenderer } from ".";
 | 
					 | 
				
			||||||
import { SvelteCodeBlockRenderer } from "./SvelteCodeBlockRenderer";
 | 
					 | 
				
			||||||
import BookshelfCodeBlockView from "./BookshelfCodeBlockView.svelte";
 | 
					 | 
				
			||||||
import type BookTrackerPlugin from "@src/main";
 | 
					 | 
				
			||||||
import z from "zod/v4";
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
export function registerBookshelfCodeBlockProcessor(
 | 
					 | 
				
			||||||
	plugin: BookTrackerPlugin
 | 
					 | 
				
			||||||
): void {
 | 
					 | 
				
			||||||
	registerCodeBlockRenderer(
 | 
					 | 
				
			||||||
		plugin,
 | 
					 | 
				
			||||||
		"bookshelf",
 | 
					 | 
				
			||||||
		(source, el) => new BookshelfCodeBlockRenderer(plugin, source, el)
 | 
					 | 
				
			||||||
	);
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
export const BookshelfSettingsSchema = z.object({
 | 
					 | 
				
			||||||
	titleProperty: z.string(),
 | 
					 | 
				
			||||||
	subtitleProperty: z.optional(z.string()),
 | 
					 | 
				
			||||||
	authorsProperty: z.string(),
 | 
					 | 
				
			||||||
});
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
export class BookshelfCodeBlockRenderer extends SvelteCodeBlockRenderer<
 | 
					 | 
				
			||||||
	typeof BookshelfCodeBlockView
 | 
					 | 
				
			||||||
> {
 | 
					 | 
				
			||||||
	constructor(
 | 
					 | 
				
			||||||
		plugin: BookTrackerPlugin,
 | 
					 | 
				
			||||||
		source: string,
 | 
					 | 
				
			||||||
		contentEl: HTMLElement
 | 
					 | 
				
			||||||
	) {
 | 
					 | 
				
			||||||
		super(contentEl, BookshelfCodeBlockView, { props: { plugin, source } });
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	onunload() {}
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
| 
						 | 
					@ -1,166 +0,0 @@
 | 
				
			||||||
<script lang="ts">
 | 
					 | 
				
			||||||
	import { STATUS_TO_BE_READ } from "@src/const";
 | 
					 | 
				
			||||||
	import type BookTrackerPlugin from "@src/main";
 | 
					 | 
				
			||||||
	import type { ReadingState } from "@src/types";
 | 
					 | 
				
			||||||
	import Book from "@ui/components/bookshelf/Book.svelte";
 | 
					 | 
				
			||||||
	import Bookshelf from "@ui/components/bookshelf/Bookshelf.svelte";
 | 
					 | 
				
			||||||
	import BookStack from "@ui/components/bookshelf/BookStack.svelte";
 | 
					 | 
				
			||||||
	import BookStackElement from "@ui/components/bookshelf/BookStackElement.svelte";
 | 
					 | 
				
			||||||
	import {
 | 
					 | 
				
			||||||
		createMetadata,
 | 
					 | 
				
			||||||
		setMetadataContext,
 | 
					 | 
				
			||||||
		type FileMetadata,
 | 
					 | 
				
			||||||
	} from "@ui/stores/metadata.svelte";
 | 
					 | 
				
			||||||
	import {
 | 
					 | 
				
			||||||
		createSettings,
 | 
					 | 
				
			||||||
		setSettingsContext,
 | 
					 | 
				
			||||||
	} from "@ui/stores/settings.svelte";
 | 
					 | 
				
			||||||
	import { COLOR_NAMES, type ColorName } from "@utils/color";
 | 
					 | 
				
			||||||
	import { randomElement, randomFloat, randomInt } from "@utils/rand";
 | 
					 | 
				
			||||||
	import { onDestroy } from "svelte";
 | 
					 | 
				
			||||||
	import { BookshelfSettingsSchema } from "./BookshelfCodeBlock";
 | 
					 | 
				
			||||||
	import { parseYaml, TFile } from "obsidian";
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	interface Props {
 | 
					 | 
				
			||||||
		plugin: BookTrackerPlugin;
 | 
					 | 
				
			||||||
		source: string;
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	interface BookData {
 | 
					 | 
				
			||||||
		title: string;
 | 
					 | 
				
			||||||
		subtitle?: string;
 | 
					 | 
				
			||||||
		author: string;
 | 
					 | 
				
			||||||
		width: number;
 | 
					 | 
				
			||||||
		color: ColorName;
 | 
					 | 
				
			||||||
		design: (typeof designs)[number];
 | 
					 | 
				
			||||||
		orientation: undefined | "tilted" | "on-display";
 | 
					 | 
				
			||||||
		file: TFile;
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	const { plugin, source }: Props = $props();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	const settings = $derived(BookshelfSettingsSchema.parse(parseYaml(source)));
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	const settingsStore = createSettings(plugin);
 | 
					 | 
				
			||||||
	setSettingsContext(settingsStore);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	let stateFilter: ReadingState = $state(STATUS_TO_BE_READ);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	const metadataStore = createMetadata(plugin, stateFilter);
 | 
					 | 
				
			||||||
	setMetadataContext(metadataStore);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	const designs = [
 | 
					 | 
				
			||||||
		"default",
 | 
					 | 
				
			||||||
		"colored-spine",
 | 
					 | 
				
			||||||
		"dual-top-bands",
 | 
					 | 
				
			||||||
		"split-bands",
 | 
					 | 
				
			||||||
	] as const;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	const randomDesign = () => randomElement(designs);
 | 
					 | 
				
			||||||
	const randomColor = () => randomElement(COLOR_NAMES);
 | 
					 | 
				
			||||||
	function randomOrientation() {
 | 
					 | 
				
			||||||
		const n = randomFloat();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		if (n < 0.55) {
 | 
					 | 
				
			||||||
			return undefined;
 | 
					 | 
				
			||||||
		} else if (n < 0.8) {
 | 
					 | 
				
			||||||
			return "tilted";
 | 
					 | 
				
			||||||
		} else {
 | 
					 | 
				
			||||||
			return "on-display";
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	const randomStackChance = () => randomFloat() > 0.9;
 | 
					 | 
				
			||||||
	function randomStackSize() {
 | 
					 | 
				
			||||||
		const n = randomFloat();
 | 
					 | 
				
			||||||
		if (n < 0.2) {
 | 
					 | 
				
			||||||
			return 5;
 | 
					 | 
				
			||||||
		} else if (n < 0.5) {
 | 
					 | 
				
			||||||
			return 4;
 | 
					 | 
				
			||||||
		} else if (n < 0.8) {
 | 
					 | 
				
			||||||
			return 3;
 | 
					 | 
				
			||||||
		} else if (n < 0.98) {
 | 
					 | 
				
			||||||
			return 2;
 | 
					 | 
				
			||||||
		} else {
 | 
					 | 
				
			||||||
			return 1;
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	function getBookData(metadata: FileMetadata): BookData {
 | 
					 | 
				
			||||||
		return {
 | 
					 | 
				
			||||||
			title: metadata.frontmatter[settings.titleProperty],
 | 
					 | 
				
			||||||
			subtitle: settings.subtitleProperty
 | 
					 | 
				
			||||||
				? metadata.frontmatter[settings.subtitleProperty]
 | 
					 | 
				
			||||||
				: undefined,
 | 
					 | 
				
			||||||
			author: metadata.frontmatter[settings.authorsProperty].join(", "),
 | 
					 | 
				
			||||||
			width: metadata.frontmatter[
 | 
					 | 
				
			||||||
				settingsStore.settings.pageCountProperty
 | 
					 | 
				
			||||||
			],
 | 
					 | 
				
			||||||
			color: randomColor(),
 | 
					 | 
				
			||||||
			design: randomDesign(),
 | 
					 | 
				
			||||||
			orientation: randomOrientation(),
 | 
					 | 
				
			||||||
			file: metadata.file,
 | 
					 | 
				
			||||||
		};
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	const books = $derived.by(() => {
 | 
					 | 
				
			||||||
		let books: (BookData | BookData[])[] = [];
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		for (let i = 0; i < metadataStore.metadata.length; i++) {
 | 
					 | 
				
			||||||
			if (randomStackChance()) {
 | 
					 | 
				
			||||||
				const booksRemaining = metadataStore.metadata.length - i;
 | 
					 | 
				
			||||||
				const stackSize = randomInt(
 | 
					 | 
				
			||||||
					1,
 | 
					 | 
				
			||||||
					Math.min(booksRemaining, randomStackSize()),
 | 
					 | 
				
			||||||
				);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
				books.push(
 | 
					 | 
				
			||||||
					metadataStore.metadata
 | 
					 | 
				
			||||||
						.slice(i, i + stackSize)
 | 
					 | 
				
			||||||
						.map(getBookData),
 | 
					 | 
				
			||||||
				);
 | 
					 | 
				
			||||||
				i += stackSize - 1;
 | 
					 | 
				
			||||||
			} else {
 | 
					 | 
				
			||||||
				books.push(getBookData(metadataStore.metadata[i]));
 | 
					 | 
				
			||||||
			}
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		return books;
 | 
					 | 
				
			||||||
	});
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	onDestroy(() => metadataStore.destroy());
 | 
					 | 
				
			||||||
</script>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
<Bookshelf>
 | 
					 | 
				
			||||||
	{#each books as book}
 | 
					 | 
				
			||||||
		{#if Array.isArray(book)}
 | 
					 | 
				
			||||||
			<BookStack totalChildren={book.length}>
 | 
					 | 
				
			||||||
				{#each book as bookData}
 | 
					 | 
				
			||||||
					<BookStackElement
 | 
					 | 
				
			||||||
						title={bookData.title}
 | 
					 | 
				
			||||||
						subtitle={bookData.subtitle}
 | 
					 | 
				
			||||||
						color={bookData.color}
 | 
					 | 
				
			||||||
						design={bookData.design}
 | 
					 | 
				
			||||||
						onClick={() =>
 | 
					 | 
				
			||||||
							plugin.app.workspace.openLinkText(
 | 
					 | 
				
			||||||
								bookData.file.path,
 | 
					 | 
				
			||||||
								"",
 | 
					 | 
				
			||||||
								true,
 | 
					 | 
				
			||||||
							)}
 | 
					 | 
				
			||||||
					/>
 | 
					 | 
				
			||||||
				{/each}
 | 
					 | 
				
			||||||
			</BookStack>
 | 
					 | 
				
			||||||
		{:else}
 | 
					 | 
				
			||||||
			<Book
 | 
					 | 
				
			||||||
				title={book.title}
 | 
					 | 
				
			||||||
				subtitle={book.subtitle}
 | 
					 | 
				
			||||||
				author={book.author}
 | 
					 | 
				
			||||||
				width={book.width}
 | 
					 | 
				
			||||||
				color={book.color}
 | 
					 | 
				
			||||||
				design={book.design}
 | 
					 | 
				
			||||||
				orientation={book.orientation}
 | 
					 | 
				
			||||||
				onClick={() =>
 | 
					 | 
				
			||||||
					plugin.app.workspace.openLinkText(book.file.path, "", true)}
 | 
					 | 
				
			||||||
			/>
 | 
					 | 
				
			||||||
		{/if}
 | 
					 | 
				
			||||||
	{/each}
 | 
					 | 
				
			||||||
</Bookshelf>
 | 
					 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,46 @@
 | 
				
			||||||
 | 
					import { registerCodeBlockRenderer } from ".";
 | 
				
			||||||
 | 
					import { SvelteCodeBlockRenderer } from "./SvelteCodeBlockRenderer";
 | 
				
			||||||
 | 
					import ShelfCodeBockView from "./ShelfCodeBlockView.svelte";
 | 
				
			||||||
 | 
					import type BookTrackerPlugin from "@src/main";
 | 
				
			||||||
 | 
					import z from "zod/v4";
 | 
				
			||||||
 | 
					import { STATUS_IN_PROGRESS, STATUS_READ, STATUS_TO_BE_READ } from "@src/const";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export function registerShelfCodeBlockProcessor(
 | 
				
			||||||
 | 
						plugin: BookTrackerPlugin
 | 
				
			||||||
 | 
					): void {
 | 
				
			||||||
 | 
						registerCodeBlockRenderer(
 | 
				
			||||||
 | 
							plugin,
 | 
				
			||||||
 | 
							"shelf",
 | 
				
			||||||
 | 
							(source, el) => new ShelfCodeBlockRenderer(plugin, source, el)
 | 
				
			||||||
 | 
						);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const SHELF_VIEWS = ["table", "bookshelf"] as const;
 | 
				
			||||||
 | 
					export type ShelfView = (typeof SHELF_VIEWS)[number];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const ShelfSettingsSchema = z.object({
 | 
				
			||||||
 | 
						statusFilter: z
 | 
				
			||||||
 | 
							.enum([STATUS_TO_BE_READ, STATUS_IN_PROGRESS, STATUS_READ])
 | 
				
			||||||
 | 
							.default(STATUS_TO_BE_READ),
 | 
				
			||||||
 | 
						defaultView: z.enum(SHELF_VIEWS).default("table"),
 | 
				
			||||||
 | 
						coverProperty: z.string(),
 | 
				
			||||||
 | 
						titleProperty: z.string(),
 | 
				
			||||||
 | 
						subtitleProperty: z.optional(z.string()),
 | 
				
			||||||
 | 
						authorsProperty: z.string(),
 | 
				
			||||||
 | 
						seriesTitleProperty: z.optional(z.string()),
 | 
				
			||||||
 | 
						seriesNumberProperty: z.optional(z.string()),
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export class ShelfCodeBlockRenderer extends SvelteCodeBlockRenderer<
 | 
				
			||||||
 | 
						typeof ShelfCodeBockView
 | 
				
			||||||
 | 
					> {
 | 
				
			||||||
 | 
						constructor(
 | 
				
			||||||
 | 
							plugin: BookTrackerPlugin,
 | 
				
			||||||
 | 
							source: string,
 | 
				
			||||||
 | 
							contentEl: HTMLElement
 | 
				
			||||||
 | 
						) {
 | 
				
			||||||
 | 
							super(contentEl, ShelfCodeBockView, { props: { plugin, source } });
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						onunload() {}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,289 @@
 | 
				
			||||||
 | 
					<script lang="ts">
 | 
				
			||||||
 | 
						import { STATUS_IN_PROGRESS, STATUS_READ } from "@src/const";
 | 
				
			||||||
 | 
						import type BookTrackerPlugin from "@src/main";
 | 
				
			||||||
 | 
						import Book from "@ui/components/bookshelf/Book.svelte";
 | 
				
			||||||
 | 
						import Bookshelf from "@ui/components/bookshelf/Bookshelf.svelte";
 | 
				
			||||||
 | 
						import BookStack from "@ui/components/bookshelf/BookStack.svelte";
 | 
				
			||||||
 | 
						import BookStackElement from "@ui/components/bookshelf/BookStackElement.svelte";
 | 
				
			||||||
 | 
						import {
 | 
				
			||||||
 | 
							createMetadata,
 | 
				
			||||||
 | 
							setMetadataContext,
 | 
				
			||||||
 | 
							type FileMetadata,
 | 
				
			||||||
 | 
						} from "@ui/stores/metadata.svelte";
 | 
				
			||||||
 | 
						import {
 | 
				
			||||||
 | 
							createSettings,
 | 
				
			||||||
 | 
							setSettingsContext,
 | 
				
			||||||
 | 
						} from "@ui/stores/settings.svelte";
 | 
				
			||||||
 | 
						import { COLOR_NAMES, type ColorName } from "@utils/color";
 | 
				
			||||||
 | 
						import { randomElement, randomFloat, randomInt } from "@utils/rand";
 | 
				
			||||||
 | 
						import { onDestroy } from "svelte";
 | 
				
			||||||
 | 
						import { ShelfSettingsSchema } from "./ShelfCodeBlock";
 | 
				
			||||||
 | 
						import { parseYaml, TFile } from "obsidian";
 | 
				
			||||||
 | 
						import DateFilter from "@ui/components/DateFilter.svelte";
 | 
				
			||||||
 | 
						import Rating from "@ui/components/Rating.svelte";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						interface Props {
 | 
				
			||||||
 | 
							plugin: BookTrackerPlugin;
 | 
				
			||||||
 | 
							source: string;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						interface BookData {
 | 
				
			||||||
 | 
							title: string;
 | 
				
			||||||
 | 
							subtitle?: string;
 | 
				
			||||||
 | 
							author: string;
 | 
				
			||||||
 | 
							width: number;
 | 
				
			||||||
 | 
							color: ColorName;
 | 
				
			||||||
 | 
							design: (typeof designs)[number];
 | 
				
			||||||
 | 
							orientation: undefined | "tilted" | "on-display";
 | 
				
			||||||
 | 
							file: TFile;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						const { plugin, source }: Props = $props();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						const settings = ShelfSettingsSchema.parse(parseYaml(source));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						const settingsStore = createSettings(plugin);
 | 
				
			||||||
 | 
						setSettingsContext(settingsStore);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						const metadataStore = createMetadata(plugin, settings.statusFilter);
 | 
				
			||||||
 | 
						setMetadataContext(metadataStore);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						const designs = [
 | 
				
			||||||
 | 
							"default",
 | 
				
			||||||
 | 
							"colored-spine",
 | 
				
			||||||
 | 
							"dual-top-bands",
 | 
				
			||||||
 | 
							"split-bands",
 | 
				
			||||||
 | 
						] as const;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						const randomDesign = () => randomElement(designs);
 | 
				
			||||||
 | 
						const randomColor = () => randomElement(COLOR_NAMES);
 | 
				
			||||||
 | 
						function randomOrientation() {
 | 
				
			||||||
 | 
							const n = randomFloat();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							if (n < 0.55) {
 | 
				
			||||||
 | 
								return undefined;
 | 
				
			||||||
 | 
							} else if (n < 0.8) {
 | 
				
			||||||
 | 
								return "tilted";
 | 
				
			||||||
 | 
							} else {
 | 
				
			||||||
 | 
								return "on-display";
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						const randomStackChance = () => randomFloat() > 0.9;
 | 
				
			||||||
 | 
						function randomStackSize() {
 | 
				
			||||||
 | 
							const n = randomFloat();
 | 
				
			||||||
 | 
							if (n < 0.2) {
 | 
				
			||||||
 | 
								return 5;
 | 
				
			||||||
 | 
							} else if (n < 0.5) {
 | 
				
			||||||
 | 
								return 4;
 | 
				
			||||||
 | 
							} else if (n < 0.8) {
 | 
				
			||||||
 | 
								return 3;
 | 
				
			||||||
 | 
							} else if (n < 0.98) {
 | 
				
			||||||
 | 
								return 2;
 | 
				
			||||||
 | 
							} else {
 | 
				
			||||||
 | 
								return 1;
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						function getBookData(metadata: FileMetadata): BookData {
 | 
				
			||||||
 | 
							return {
 | 
				
			||||||
 | 
								title: metadata.frontmatter[settings.titleProperty],
 | 
				
			||||||
 | 
								subtitle: settings.subtitleProperty
 | 
				
			||||||
 | 
									? metadata.frontmatter[settings.subtitleProperty]
 | 
				
			||||||
 | 
									: undefined,
 | 
				
			||||||
 | 
								author: metadata.frontmatter[settings.authorsProperty].join(", "),
 | 
				
			||||||
 | 
								width: metadata.frontmatter[
 | 
				
			||||||
 | 
									settingsStore.settings.pageCountProperty
 | 
				
			||||||
 | 
								],
 | 
				
			||||||
 | 
								color: randomColor(),
 | 
				
			||||||
 | 
								design: randomDesign(),
 | 
				
			||||||
 | 
								orientation: randomOrientation(),
 | 
				
			||||||
 | 
								file: metadata.file,
 | 
				
			||||||
 | 
							};
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						let view = $state(settings.defaultView);
 | 
				
			||||||
 | 
						const books = $derived.by(() => {
 | 
				
			||||||
 | 
							let books: (BookData | BookData[])[] = [];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							for (let i = 0; i < metadataStore.metadata.length; i++) {
 | 
				
			||||||
 | 
								if (randomStackChance()) {
 | 
				
			||||||
 | 
									const booksRemaining = metadataStore.metadata.length - i;
 | 
				
			||||||
 | 
									const stackSize = randomInt(
 | 
				
			||||||
 | 
										1,
 | 
				
			||||||
 | 
										Math.min(booksRemaining, randomStackSize()),
 | 
				
			||||||
 | 
									);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
									books.push(
 | 
				
			||||||
 | 
										metadataStore.metadata
 | 
				
			||||||
 | 
											.slice(i, i + stackSize)
 | 
				
			||||||
 | 
											.map(getBookData),
 | 
				
			||||||
 | 
									);
 | 
				
			||||||
 | 
									i += stackSize - 1;
 | 
				
			||||||
 | 
								} else {
 | 
				
			||||||
 | 
									books.push(getBookData(metadataStore.metadata[i]));
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							return books;
 | 
				
			||||||
 | 
						});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						onDestroy(() => metadataStore.destroy());
 | 
				
			||||||
 | 
					</script>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<div
 | 
				
			||||||
 | 
						class="shelf-code-block"
 | 
				
			||||||
 | 
						class:table-view={view === "table"}
 | 
				
			||||||
 | 
						class:bookshelf-view={view === "bookshelf"}
 | 
				
			||||||
 | 
					>
 | 
				
			||||||
 | 
						<div class="controls">
 | 
				
			||||||
 | 
							<select bind:value={view}>
 | 
				
			||||||
 | 
								<option value="table">Table</option>
 | 
				
			||||||
 | 
								<option value="bookshelf">Bookshelf</option>
 | 
				
			||||||
 | 
							</select>
 | 
				
			||||||
 | 
							{#if settings.statusFilter === STATUS_READ}
 | 
				
			||||||
 | 
								<DateFilter store={metadataStore} />
 | 
				
			||||||
 | 
							{/if}
 | 
				
			||||||
 | 
						</div>
 | 
				
			||||||
 | 
						{#if view === "bookshelf"}
 | 
				
			||||||
 | 
							<Bookshelf>
 | 
				
			||||||
 | 
								{#each books as book}
 | 
				
			||||||
 | 
									{#if Array.isArray(book)}
 | 
				
			||||||
 | 
										<BookStack totalChildren={book.length}>
 | 
				
			||||||
 | 
											{#each book as bookData}
 | 
				
			||||||
 | 
												<BookStackElement
 | 
				
			||||||
 | 
													title={bookData.title}
 | 
				
			||||||
 | 
													subtitle={bookData.subtitle}
 | 
				
			||||||
 | 
													color={bookData.color}
 | 
				
			||||||
 | 
													design={bookData.design}
 | 
				
			||||||
 | 
													onClick={() =>
 | 
				
			||||||
 | 
														plugin.app.workspace.openLinkText(
 | 
				
			||||||
 | 
															bookData.file.path,
 | 
				
			||||||
 | 
															"",
 | 
				
			||||||
 | 
															true,
 | 
				
			||||||
 | 
														)}
 | 
				
			||||||
 | 
												/>
 | 
				
			||||||
 | 
											{/each}
 | 
				
			||||||
 | 
										</BookStack>
 | 
				
			||||||
 | 
									{:else}
 | 
				
			||||||
 | 
										<Book
 | 
				
			||||||
 | 
											title={book.title}
 | 
				
			||||||
 | 
											subtitle={book.subtitle}
 | 
				
			||||||
 | 
											author={book.author}
 | 
				
			||||||
 | 
											width={book.width}
 | 
				
			||||||
 | 
											color={book.color}
 | 
				
			||||||
 | 
											design={book.design}
 | 
				
			||||||
 | 
											orientation={book.orientation}
 | 
				
			||||||
 | 
											onClick={() =>
 | 
				
			||||||
 | 
												plugin.app.workspace.openLinkText(
 | 
				
			||||||
 | 
													book.file.path,
 | 
				
			||||||
 | 
													"",
 | 
				
			||||||
 | 
													true,
 | 
				
			||||||
 | 
												)}
 | 
				
			||||||
 | 
										/>
 | 
				
			||||||
 | 
									{/if}
 | 
				
			||||||
 | 
								{/each}
 | 
				
			||||||
 | 
							</Bookshelf>
 | 
				
			||||||
 | 
						{:else if view === "table"}
 | 
				
			||||||
 | 
							<table>
 | 
				
			||||||
 | 
								<thead>
 | 
				
			||||||
 | 
									<tr>
 | 
				
			||||||
 | 
										<th>Cover</th>
 | 
				
			||||||
 | 
										<th>Title</th>
 | 
				
			||||||
 | 
										<th>Authors</th>
 | 
				
			||||||
 | 
										{#if settings.seriesTitleProperty}
 | 
				
			||||||
 | 
											<th>Series</th>
 | 
				
			||||||
 | 
										{/if}
 | 
				
			||||||
 | 
										{#if settings.seriesNumberProperty}
 | 
				
			||||||
 | 
											<th>#</th>
 | 
				
			||||||
 | 
										{/if}
 | 
				
			||||||
 | 
										{#if settings.statusFilter === STATUS_IN_PROGRESS || settings.statusFilter === STATUS_READ}
 | 
				
			||||||
 | 
											<th>Start Date</th>
 | 
				
			||||||
 | 
										{/if}
 | 
				
			||||||
 | 
										{#if settings.statusFilter === STATUS_READ}
 | 
				
			||||||
 | 
											<th>End Date</th>
 | 
				
			||||||
 | 
											<th>Rating</th>
 | 
				
			||||||
 | 
										{/if}
 | 
				
			||||||
 | 
									</tr>
 | 
				
			||||||
 | 
								</thead>
 | 
				
			||||||
 | 
								<tbody>
 | 
				
			||||||
 | 
									{#each metadataStore.metadata as book}
 | 
				
			||||||
 | 
										<tr>
 | 
				
			||||||
 | 
											<td>
 | 
				
			||||||
 | 
												<img
 | 
				
			||||||
 | 
													src={plugin.app.vault.getResourcePath(
 | 
				
			||||||
 | 
														plugin.app.vault.getFileByPath(
 | 
				
			||||||
 | 
															book.frontmatter[
 | 
				
			||||||
 | 
																settings.coverProperty
 | 
				
			||||||
 | 
															],
 | 
				
			||||||
 | 
														)!,
 | 
				
			||||||
 | 
													)}
 | 
				
			||||||
 | 
													alt={book.frontmatter[settings.titleProperty]}
 | 
				
			||||||
 | 
													width="50"
 | 
				
			||||||
 | 
												/>
 | 
				
			||||||
 | 
											</td>
 | 
				
			||||||
 | 
											<td>{book.frontmatter[settings.titleProperty]}</td>
 | 
				
			||||||
 | 
											<td>
 | 
				
			||||||
 | 
												{book.frontmatter[settings.authorsProperty].join(
 | 
				
			||||||
 | 
													", ",
 | 
				
			||||||
 | 
												)}
 | 
				
			||||||
 | 
											</td>
 | 
				
			||||||
 | 
											{#if settings.seriesTitleProperty}
 | 
				
			||||||
 | 
												<td>
 | 
				
			||||||
 | 
													{book.frontmatter[settings.seriesTitleProperty]}
 | 
				
			||||||
 | 
												</td>
 | 
				
			||||||
 | 
											{/if}
 | 
				
			||||||
 | 
											{#if settings.seriesNumberProperty}
 | 
				
			||||||
 | 
												<td>
 | 
				
			||||||
 | 
													{book.frontmatter[
 | 
				
			||||||
 | 
														settings.seriesNumberProperty
 | 
				
			||||||
 | 
													]}
 | 
				
			||||||
 | 
												</td>
 | 
				
			||||||
 | 
											{/if}
 | 
				
			||||||
 | 
											{#if settings.statusFilter === STATUS_IN_PROGRESS || settings.statusFilter === STATUS_READ}
 | 
				
			||||||
 | 
												<td>
 | 
				
			||||||
 | 
													{book.frontmatter[
 | 
				
			||||||
 | 
														settingsStore.settings.startDateProperty
 | 
				
			||||||
 | 
													]}
 | 
				
			||||||
 | 
												</td>
 | 
				
			||||||
 | 
											{/if}
 | 
				
			||||||
 | 
											{#if settings.statusFilter === STATUS_READ}
 | 
				
			||||||
 | 
												<td>
 | 
				
			||||||
 | 
													{book.frontmatter[
 | 
				
			||||||
 | 
														settingsStore.settings.endDateProperty
 | 
				
			||||||
 | 
													]}
 | 
				
			||||||
 | 
												</td>
 | 
				
			||||||
 | 
												<td>
 | 
				
			||||||
 | 
													<Rating
 | 
				
			||||||
 | 
														rating={book.frontmatter[
 | 
				
			||||||
 | 
															settingsStore.settings.ratingProperty
 | 
				
			||||||
 | 
														]}
 | 
				
			||||||
 | 
													/>
 | 
				
			||||||
 | 
												</td>
 | 
				
			||||||
 | 
											{/if}
 | 
				
			||||||
 | 
										</tr>
 | 
				
			||||||
 | 
									{/each}
 | 
				
			||||||
 | 
								</tbody>
 | 
				
			||||||
 | 
							</table>
 | 
				
			||||||
 | 
						{/if}
 | 
				
			||||||
 | 
					</div>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<style>
 | 
				
			||||||
 | 
						.shelf-code-block {
 | 
				
			||||||
 | 
							.controls {
 | 
				
			||||||
 | 
								margin-bottom: 1rem;
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							&.bookshelf-view {
 | 
				
			||||||
 | 
								.controls {
 | 
				
			||||||
 | 
									margin-left: auto;
 | 
				
			||||||
 | 
									margin-right: auto;
 | 
				
			||||||
 | 
									width: 80%;
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							table {
 | 
				
			||||||
 | 
								border-collapse: collapse;
 | 
				
			||||||
 | 
								width: 100%;
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					</style>
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,46 @@
 | 
				
			||||||
 | 
					<script lang="ts">
 | 
				
			||||||
 | 
						import {
 | 
				
			||||||
 | 
							ALL_TIME,
 | 
				
			||||||
 | 
							type DateFilterStore,
 | 
				
			||||||
 | 
						} from "@ui/stores/date-filter.svelte";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						interface Props {
 | 
				
			||||||
 | 
							store: Pick<
 | 
				
			||||||
 | 
								DateFilterStore,
 | 
				
			||||||
 | 
								"filterYear" | "filterYears" | "filterMonth" | "filterMonths"
 | 
				
			||||||
 | 
							>;
 | 
				
			||||||
 | 
							showAllMonths?: boolean;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						const { store, showAllMonths }: Props = $props();
 | 
				
			||||||
 | 
					</script>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<select class="year-filter" bind:value={store.filterYear}>
 | 
				
			||||||
 | 
						{#each store.filterYears as year}
 | 
				
			||||||
 | 
							<option value={year}>{year}</option>
 | 
				
			||||||
 | 
						{/each}
 | 
				
			||||||
 | 
						<option value={ALL_TIME}>All Time</option>
 | 
				
			||||||
 | 
					</select>
 | 
				
			||||||
 | 
					{#if store.filterYear !== ALL_TIME}
 | 
				
			||||||
 | 
						<select class="month-filter" bind:value={store.filterMonth}>
 | 
				
			||||||
 | 
							<option value={ALL_TIME}>Select Month</option>
 | 
				
			||||||
 | 
							{#if showAllMonths}
 | 
				
			||||||
 | 
								<option value={1}>January</option>
 | 
				
			||||||
 | 
								<option value={2}>February</option>
 | 
				
			||||||
 | 
								<option value={3}>March</option>
 | 
				
			||||||
 | 
								<option value={4}>April</option>
 | 
				
			||||||
 | 
								<option value={5}>May</option>
 | 
				
			||||||
 | 
								<option value={6}>June</option>
 | 
				
			||||||
 | 
								<option value={7}>July</option>
 | 
				
			||||||
 | 
								<option value={8}>August</option>
 | 
				
			||||||
 | 
								<option value={9}>September</option>
 | 
				
			||||||
 | 
								<option value={10}>October</option>
 | 
				
			||||||
 | 
								<option value={11}>November</option>
 | 
				
			||||||
 | 
								<option value={12}>December</option>
 | 
				
			||||||
 | 
							{:else}
 | 
				
			||||||
 | 
								{#each store.filterMonths as month}
 | 
				
			||||||
 | 
									<option value={month.value}>{month.label}</option>
 | 
				
			||||||
 | 
								{/each}
 | 
				
			||||||
 | 
							{/if}
 | 
				
			||||||
 | 
						</select>
 | 
				
			||||||
 | 
					{/if}
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,186 @@
 | 
				
			||||||
 | 
					<script lang="ts">
 | 
				
			||||||
 | 
						interface Props {
 | 
				
			||||||
 | 
							rating: number;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						let { rating }: Props = $props();
 | 
				
			||||||
 | 
					</script>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<span data-star={rating}>{rating}</span>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<style>
 | 
				
			||||||
 | 
						[data-star] {
 | 
				
			||||||
 | 
							text-align: left;
 | 
				
			||||||
 | 
							font-style: normal;
 | 
				
			||||||
 | 
							display: inline-block;
 | 
				
			||||||
 | 
							position: relative;
 | 
				
			||||||
 | 
							unicode-bidi: bidi-override;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						[data-star]::before {
 | 
				
			||||||
 | 
							display: block;
 | 
				
			||||||
 | 
							content: "★★★★★";
 | 
				
			||||||
 | 
							color: #eee;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						[data-star]::after {
 | 
				
			||||||
 | 
							white-space: nowrap;
 | 
				
			||||||
 | 
							position: absolute;
 | 
				
			||||||
 | 
							top: 0;
 | 
				
			||||||
 | 
							left: 0;
 | 
				
			||||||
 | 
							content: "★★★★★";
 | 
				
			||||||
 | 
							width: 0;
 | 
				
			||||||
 | 
							color: #ff8c00;
 | 
				
			||||||
 | 
							overflow: hidden;
 | 
				
			||||||
 | 
							height: 100%;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						[data-star^="0.1"]::after {
 | 
				
			||||||
 | 
							width: 2%;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						[data-star^="0.2"]::after {
 | 
				
			||||||
 | 
							width: 4%;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						[data-star^="0.3"]::after {
 | 
				
			||||||
 | 
							width: 6%;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						[data-star^="0.4"]::after {
 | 
				
			||||||
 | 
							width: 8%;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						[data-star^="0.5"]::after {
 | 
				
			||||||
 | 
							width: 10%;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						[data-star^="0.6"]::after {
 | 
				
			||||||
 | 
							width: 12%;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						[data-star^="0.7"]::after {
 | 
				
			||||||
 | 
							width: 14%;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						[data-star^="0.8"]::after {
 | 
				
			||||||
 | 
							width: 16%;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						[data-star^="0.9"]::after {
 | 
				
			||||||
 | 
							width: 18%;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						[data-star^="1"]::after {
 | 
				
			||||||
 | 
							width: 20%;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						[data-star^="1.1"]::after {
 | 
				
			||||||
 | 
							width: 22%;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						[data-star^="1.2"]::after {
 | 
				
			||||||
 | 
							width: 24%;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						[data-star^="1.3"]::after {
 | 
				
			||||||
 | 
							width: 26%;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						[data-star^="1.4"]::after {
 | 
				
			||||||
 | 
							width: 28%;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						[data-star^="1.5"]::after {
 | 
				
			||||||
 | 
							width: 30%;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						[data-star^="1.6"]::after {
 | 
				
			||||||
 | 
							width: 32%;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						[data-star^="1.7"]::after {
 | 
				
			||||||
 | 
							width: 34%;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						[data-star^="1.8"]::after {
 | 
				
			||||||
 | 
							width: 36%;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						[data-star^="1.9"]::after {
 | 
				
			||||||
 | 
							width: 38%;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						[data-star^="2"]::after {
 | 
				
			||||||
 | 
							width: 40%;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						[data-star^="2.1"]::after {
 | 
				
			||||||
 | 
							width: 42%;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						[data-star^="2.2"]::after {
 | 
				
			||||||
 | 
							width: 44%;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						[data-star^="2.3"]::after {
 | 
				
			||||||
 | 
							width: 46%;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						[data-star^="2.4"]::after {
 | 
				
			||||||
 | 
							width: 48%;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						[data-star^="2.5"]::after {
 | 
				
			||||||
 | 
							width: 50%;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						[data-star^="2.6"]::after {
 | 
				
			||||||
 | 
							width: 52%;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						[data-star^="2.7"]::after {
 | 
				
			||||||
 | 
							width: 54%;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						[data-star^="2.8"]::after {
 | 
				
			||||||
 | 
							width: 56%;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						[data-star^="2.9"]::after {
 | 
				
			||||||
 | 
							width: 58%;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						[data-star^="3"]::after {
 | 
				
			||||||
 | 
							width: 60%;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						[data-star^="3.1"]::after {
 | 
				
			||||||
 | 
							width: 62%;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						[data-star^="3.2"]::after {
 | 
				
			||||||
 | 
							width: 64%;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						[data-star^="3.3"]::after {
 | 
				
			||||||
 | 
							width: 66%;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						[data-star^="3.4"]::after {
 | 
				
			||||||
 | 
							width: 68%;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						[data-star^="3.5"]::after {
 | 
				
			||||||
 | 
							width: 70%;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						[data-star^="3.6"]::after {
 | 
				
			||||||
 | 
							width: 72%;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						[data-star^="3.7"]::after {
 | 
				
			||||||
 | 
							width: 74%;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						[data-star^="3.8"]::after {
 | 
				
			||||||
 | 
							width: 76%;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						[data-star^="3.9"]::after {
 | 
				
			||||||
 | 
							width: 78%;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						[data-star^="4"]::after {
 | 
				
			||||||
 | 
							width: 80%;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						[data-star^="4.1"]::after {
 | 
				
			||||||
 | 
							width: 82%;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						[data-star^="4.2"]::after {
 | 
				
			||||||
 | 
							width: 84%;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						[data-star^="4.3"]::after {
 | 
				
			||||||
 | 
							width: 86%;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						[data-star^="4.4"]::after {
 | 
				
			||||||
 | 
							width: 88%;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						[data-star^="4.5"]::after {
 | 
				
			||||||
 | 
							width: 90%;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						[data-star^="4.6"]::after {
 | 
				
			||||||
 | 
							width: 92%;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						[data-star^="4.7"]::after {
 | 
				
			||||||
 | 
							width: 94%;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						[data-star^="4.8"]::after {
 | 
				
			||||||
 | 
							width: 96%;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						[data-star^="4.9"]::after {
 | 
				
			||||||
 | 
							width: 98%;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						[data-star^="5"]::after {
 | 
				
			||||||
 | 
							width: 100%;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					</style>
 | 
				
			||||||
| 
						 | 
					@ -22,21 +22,23 @@ export function createDateFilter<T>(
 | 
				
			||||||
		initialMonth ? today.getMonth() + 1 : ALL_TIME
 | 
							initialMonth ? today.getMonth() + 1 : ALL_TIME
 | 
				
			||||||
	);
 | 
						);
 | 
				
			||||||
	const filteredData = $derived.by(() => {
 | 
						const filteredData = $derived.by(() => {
 | 
				
			||||||
		return data().filter((item) => {
 | 
							return data()
 | 
				
			||||||
			const date = selector(item);
 | 
								.filter((item) => {
 | 
				
			||||||
			if (filterYear !== ALL_TIME) {
 | 
									const date = selector(item);
 | 
				
			||||||
				if (date.year() !== filterYear) {
 | 
									if (filterYear !== ALL_TIME) {
 | 
				
			||||||
					return false;
 | 
										if (date.year() !== filterYear) {
 | 
				
			||||||
 | 
											return false;
 | 
				
			||||||
 | 
										}
 | 
				
			||||||
				}
 | 
									}
 | 
				
			||||||
			}
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
			if (filterMonth !== ALL_TIME) {
 | 
									if (filterMonth !== ALL_TIME) {
 | 
				
			||||||
				if (date.month() !== filterMonth - 1) {
 | 
										if (date.month() !== filterMonth - 1) {
 | 
				
			||||||
					return false;
 | 
											return false;
 | 
				
			||||||
 | 
										}
 | 
				
			||||||
				}
 | 
									}
 | 
				
			||||||
			}
 | 
									return true;
 | 
				
			||||||
			return true;
 | 
								})
 | 
				
			||||||
		});
 | 
								.sort((a, b) => selector(a).diff(selector(b)));
 | 
				
			||||||
	});
 | 
						});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	const filterYears = $derived.by(() => {
 | 
						const filterYears = $derived.by(() => {
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
		Loading…
	
		Reference in New Issue