A few small changes

This commit is contained in:
Evan Fiordeliso 2025-07-14 21:16:32 -04:00
parent 891041c965
commit 19d56652eb
6 changed files with 117 additions and 86 deletions

View File

@ -43,7 +43,7 @@ export class ResetReadingStatusCommand extends EditorCheckCommand {
frontMatter[this.settings.endDateProperty] = "";
});
this.readingLog.removeEntriesForBook(file.basename);
this.readingLog.deleteEntriesForBook(file.basename);
new Notice("Reading status reset for " + file.basename);
}

View File

@ -1,5 +1,5 @@
<script lang="ts">
import { parseYaml } from "obsidian";
import { parseYaml, TFile } from "obsidian";
import { ReadingCalendarSettingsSchema } from "./ReadingCalendarCodeBlock";
import type { SvelteCodeBlockProps } from "./SvelteCodeBlockRenderer";
import {
@ -15,7 +15,8 @@
setReadingLogContext,
} from "@ui/stores/reading-log.svelte";
import { ArrowLeft, ArrowRight } from "lucide-svelte";
import { onMount } from "svelte";
import { onDestroy, onMount } from "svelte";
import OpenFileLink from "@ui/components/OpenFileLink.svelte";
const { plugin, source }: SvelteCodeBlockProps = $props();
@ -85,6 +86,11 @@
updateToday();
});
onDestroy(() => {
metadataStore.destroy();
readingLog.destroy();
});
const weeks = $derived.by(() => {
// @ts-expect-error Moment is provided by Obsidian
const firstDay = moment()
@ -114,40 +120,53 @@
return weeks;
});
interface CoverData {
src: string;
alt: string;
interface BookData {
coverSrc: string;
coverAlt: string;
file: TFile;
}
interface BookMapItem {
totalPagesRead: number;
covers: CoverData[];
books: BookData[];
}
const bookMap = $derived.by(() => {
const bookMap = new Map<number, BookMapItem>();
for (const item of readingLog.entries) {
const key = item.createdAt.date();
const bookMap = $derived(
readingLog.entries.reduce(
(acc, entry) => {
const key = entry.createdAt.date();
let coverPath = metadataStore.metadata.find(
(entry) => entry.file.basename === item.book,
)?.frontmatter?.[settings.coverProperty];
const metadata = metadataStore.metadata.find(
(other) => other.file.basename === entry.book,
);
coverPath = plugin.app.vault.getFileByPath(coverPath);
if (!metadata) {
return acc;
}
if (!coverPath) {
continue;
}
const coverPath = metadata.frontmatter?.[
settings.coverProperty
] as string;
const coverFile = plugin.app.vault.getFileByPath(coverPath);
let coverSrc = "";
if (coverFile) {
coverSrc = plugin.app.vault.getResourcePath(coverFile);
}
coverPath = plugin.app.vault.getResourcePath(coverPath);
const value = acc[key] ?? { totalPagesRead: 0, books: [] };
value.totalPagesRead += entry.pagesRead;
value.books.push({
coverSrc,
coverAlt: entry.book,
file: metadata.file,
});
acc[key] = value;
const value = bookMap.get(key) ?? { totalPagesRead: 0, covers: [] };
value.totalPagesRead += item.pagesRead;
value.covers.push({ src: coverPath, alt: item.book });
bookMap.set(key, value);
}
return bookMap;
});
return acc;
},
{} as Record<number, BookMapItem>,
),
);
</script>
<div class="reading-calendar">
@ -207,6 +226,7 @@
<tr>
{#each week as day}
{@const isThisMonth = day.month() === month}
{@const date = day.date()}
<td
class:is-today={day.isSame(today, "day")}
class:is-weekend={day.day() === 0 ||
@ -218,23 +238,25 @@
>
<div class="header">
<span>{day.date()}</span>
{#if isThisMonth && bookMap.has(day.date())}
{@const data = bookMap.get(day.date())!}
{#if isThisMonth && date in bookMap}
{@const data = bookMap[date]}
<span class="total-pages-read">
Pages: {data.totalPagesRead}
</span>
{/if}
</div>
<div class="covers">
{#if isThisMonth && bookMap.has(day.date())}
{@const data = bookMap.get(day.date())!}
{#each data.covers as cover}
{#if cover}
<img
src={cover.src}
alt={cover.alt}
/>
{/if}
{#if isThisMonth && date in bookMap}
{@const data = bookMap[date]}
{#each data.books as book}
<div class="cover">
<OpenFileLink file={book.file}>
<img
src={book.coverSrc}
alt={book.coverAlt}
/>
</OpenFileLink>
</div>
{/each}
{/if}
</div>
@ -312,59 +334,46 @@
width: 100%;
aspect-ratio: 2 / 3;
img {
.cover {
border-radius: var(--radius-l);
}
position: absolute;
height: 100%;
width: 100%;
&:has(img:first-child:nth-last-child(1)) {
img {
position: absolute;
height: 100%;
border-radius: var(--radius-l);
width: 100%;
height: 100%;
object-fit: cover;
}
}
&:has(img:first-child:nth-last-child(2)) {
img:first-child {
position: absolute;
height: 100%;
width: 100%;
&:has(.cover:first-child:nth-last-child(2)) {
.cover:first-child {
clip-path: polygon(0 0, 100% 0, 0 100%);
}
img:last-child {
position: absolute;
height: 100%;
width: 100%;
.cover:last-child {
clip-path: polygon(100% 100%, 100% 0, 0 100%);
}
}
&:has(img:first-child:nth-last-child(3)) {
img:first-child {
position: absolute;
height: 100%;
width: 100%;
&:has(.cover:first-child:nth-last-child(3)) {
.cover:first-child {
clip-path: polygon(0 0, 100% 0, 100% 20%, 0 50%);
}
img:nth-child(2) {
position: absolute;
height: 100%;
width: 100%;
.cover:nth-child(2) {
clip-path: polygon(100% 80%, 100% 20%, 0 50%);
}
img:last-child {
position: absolute;
height: 100%;
width: 100%;
.cover:last-child {
clip-path: polygon(0 50%, 100% 80%, 100% 100%, 0% 100%);
}
}
&:has(img:first-child:nth-last-child(4)) {
img:first-child {
&:has(.cover:first-child:nth-last-child(4)) {
.cover:first-child {
position: absolute;
height: 50%;
width: 50%;
@ -372,7 +381,7 @@
left: 0;
}
img:nth-child(2) {
.cover:nth-child(2) {
position: absolute;
height: 50%;
width: 50%;
@ -380,7 +389,7 @@
right: 0;
}
img:nth-child(3) {
.cover:nth-child(3) {
position: absolute;
height: 50%;
width: 50%;
@ -388,7 +397,7 @@
left: 0;
}
img:last-child {
.cover:last-child {
position: absolute;
height: 50%;
width: 50%;

View File

@ -16,8 +16,14 @@
const href = $derived(
makeUri(file instanceof TFile ? file.path : getLinkpath(file)),
);
const title = $derived(
file instanceof TFile
? file.basename
: file.split("/").pop()?.replace(".md", ""),
);
</script>
<a {href}>
<a {href} {title}>
{@render children?.()}
</a>

View File

@ -111,12 +111,13 @@
type: "linear",
display: true,
position: "left",
ticks: { beginAtZero: true },
beginAtZero: true,
},
y1: {
type: "linear",
display: !isMonthly,
position: "right",
beginAtZero: true,
grid: { drawOnChartArea: false },
},
},

View File

@ -1,6 +1,10 @@
import type { ReadingLog, ReadingLogEntry } from "@utils/ReadingLog";
import { getContext, setContext } from "svelte";
import { createDateFilter, type DateFilterStore } from "./date-filter.svelte";
import {
createDateFilter,
type DateFilterStore,
type DateFilterStoreOptions,
} from "./date-filter.svelte";
export interface ReadingLogStore extends DateFilterStore {
get entries(): ReadingLogEntry[];
@ -12,13 +16,16 @@ export interface ReadingLogStore extends DateFilterStore {
destroy(): void;
}
export function createReadingLog(readingLog: ReadingLog): ReadingLogStore {
export function createReadingLog(
readingLog: ReadingLog,
{ initialMonth = true, ...otherOpts }: DateFilterStoreOptions = {}
): ReadingLogStore {
let entries: ReadingLogEntry[] = $state(readingLog.getEntries());
const dateFilter = createDateFilter(
() => entries,
(entry) => entry.createdAt,
{ initialMonth: true }
{ initialMonth, ...otherOpts }
);
async function addEntry(entry: ReadingLogEntry): Promise<void> {
@ -34,18 +41,24 @@ export function createReadingLog(readingLog: ReadingLog): ReadingLogStore {
}
async function removeEntry(entry: ReadingLogEntry): Promise<void> {
await readingLog.removeEntry(entry);
await readingLog.deleteEntry(entry);
}
const loadHandler = readingLog.on("load", (payload) => {
console.info("Reading log loaded");
console.debug("Reading log entries:", payload.entries);
entries = payload.entries;
});
const createdHandler = readingLog.on("created", (payload) => {
const createdHandler = readingLog.on("create", (payload) => {
console.info("Reading log entry created");
console.debug("Reading log entry:", payload.entry);
entries.push(payload.entry);
});
const updatedHandler = readingLog.on("updated", (payload) => {
const updatedHandler = readingLog.on("update", (payload) => {
console.info("Reading log entry updated");
console.debug("Reading log entry:", payload.entry);
const index = entries.findIndex(
(entry) => entry.id === payload.entry.id
);
@ -54,7 +67,9 @@ export function createReadingLog(readingLog: ReadingLog): ReadingLogStore {
}
});
const removedHandler = readingLog.on("removed", (payload) => {
const removedHandler = readingLog.on("delete", (payload) => {
console.info("Reading log entry deleted");
console.debug("Reading log entry:", payload.entry);
const index = entries.findIndex(
(entry) => entry.id === payload.entry.id
);

View File

@ -14,9 +14,9 @@ export interface ReadingLogEntry {
interface ReadingLogEventMap {
load: { entries: ReadingLogEntry[] };
created: { entry: ReadingLogEntry };
updated: { entry: ReadingLogEntry };
removed: { entry: ReadingLogEntry };
create: { entry: ReadingLogEntry };
update: { entry: ReadingLogEntry };
delete: { entry: ReadingLogEntry };
}
const DEFAULT_FILENAME = "reading-log.json";
@ -126,7 +126,7 @@ export class ReadingLog extends EventEmitter<ReadingLogEventMap> {
public async addRawEntry(entry: ReadingLogEntry) {
this.entries.push(entry);
await this.save();
this.emit("created", { entry });
this.emit("create", { entry });
}
public async updateEntry(entry: ReadingLogEntry): Promise<void> {
@ -138,20 +138,20 @@ export class ReadingLog extends EventEmitter<ReadingLogEventMap> {
this.entries[index] = entry;
await this.save();
this.emit("updated", { entry });
this.emit("update", { entry });
}
public async removeEntry(entry: ReadingLogEntry): Promise<void> {
public async deleteEntry(entry: ReadingLogEntry): Promise<void> {
const index = this.entries.findIndex((other) => other.id === entry.id);
if (index !== -1) {
const removed = this.entries.splice(index, 1);
await this.save();
this.emit("removed", { entry: removed[0] });
this.emit("delete", { entry: removed[0] });
}
}
public async removeEntriesForBook(book: string): Promise<void> {
public async deleteEntriesForBook(book: string): Promise<void> {
this.entries = this.entries.filter((entry) => entry.book !== book);
await this.save();
}