import { STATUS_READ } from "@src/const"; import type { CachedMetadata, TFile } from "obsidian"; import { getContext, setContext } from "svelte"; import type BookTrackerPlugin from "@src/main"; import { createDateFilter, type DateFilterStore, type DateFilterStoreOptions, } from "./date-filter.svelte"; import type { BookMetadata, ReadingState } from "@src/types"; export type FileMetadata = { file: TFile; book: BookMetadata; }; export type FileProperty = { file: TFile; value: any; }; export interface MetadataStore extends DateFilterStore { get metadata(): FileMetadata[]; destroy(): void; } function getMetadata( plugin: BookTrackerPlugin, state: ReadingState | null ): FileMetadata[] { const metadata: FileMetadata[] = []; for (const file of plugin.app.vault.getMarkdownFiles()) { const book = plugin.getBookMetadata(file); if (!book) { continue; } if (state && book.status !== state) { continue; } metadata.push({ file, book }); } return metadata; } interface MetadataStoreOptions extends DateFilterStoreOptions { /** * The reading state to filter by */ statusFilter?: ReadingState | null; } export function createMetadata( plugin: BookTrackerPlugin, { statusFilter = STATUS_READ, ...dateFilterOpts }: MetadataStoreOptions = {} ): MetadataStore { const initialMetadata = getMetadata(plugin, statusFilter); let metadata: FileMetadata[] = $state(initialMetadata); $effect(() => { metadata = getMetadata(plugin, statusFilter); }); function onChanged(file: TFile, _data: string, cache: CachedMetadata) { metadata = metadata.map((f) => { if (f.file.path === file.path) { return { file: f.file, book: plugin.frontmatterToMetadata(cache.frontmatter), }; } return f; }); } plugin.registerEvent(plugin.app.metadataCache.on("changed", onChanged)); function onDeleted(file: TFile) { metadata = metadata.filter((f) => f.file.path !== file.path); } plugin.registerEvent(plugin.app.metadataCache.on("deleted", onDeleted)); const dateFilter = createDateFilter( () => metadata, (f) => f.book.endDate, dateFilterOpts ); return { get metadata() { return statusFilter === STATUS_READ ? dateFilter.filteredData : metadata; }, get filterYear() { return dateFilter.filterYear; }, set filterYear(value) { dateFilter.filterYear = value; }, get filterMonth() { return dateFilter.filterMonth; }, set filterMonth(value) { dateFilter.filterMonth = value; }, get filterYears() { return dateFilter.filterYears; }, get filterMonths() { return dateFilter.filterMonths; }, destroy() { plugin.app.metadataCache.off("changed", onChanged); plugin.app.metadataCache.off("deleted", onDeleted); }, }; } const METADATA_KEY = Symbol("metadata"); export function setMetadataContext(state: MetadataStore) { setContext(METADATA_KEY, state); } export function getMetadataContext(): MetadataStore { return getContext(METADATA_KEY) as MetadataStore; } function notEmpty(value: any): boolean { return value !== undefined && value !== null && value !== "" && value !== 0; } interface PropertyStore { get propertyData(): FileProperty[]; } export function createPropertyStore( property: T, filter: (value: BookMetadata[T]) => boolean = notEmpty ): PropertyStore { const store = getMetadataContext(); const propertyData = $derived( store.metadata .map((f) => ({ file: f.file, value: f.book[property] })) .filter((f) => (filter ? filter(f.value) : true)) ); return { get propertyData() { return propertyData; }, }; }