obsidian-book-tracker/src/ui/stores/metadata.svelte.ts

157 lines
3.5 KiB
TypeScript

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<T extends keyof BookMetadata>(
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;
},
};
}