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",
|
||||
"name": "Book Tracker",
|
||||
"version": "1.0.0",
|
||||
"version": "1.1.0",
|
||||
"minAppVersion": "0.15.0",
|
||||
"description": "Simplifies tracking your reading progress and managing your book collection in Obsidian.",
|
||||
"author": "FiFiTiDo",
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"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.",
|
||||
"main": "main.js",
|
||||
"scripts": {
|
||||
|
|
|
@ -24,6 +24,7 @@ import { Goodreads } from "@data-sources/Goodreads";
|
|||
import { CreateBookFromGoodreadsUrlCommand } from "@commands/CreateBookFromGoodreadsUrlCommand";
|
||||
import { registerShelfCodeBlockProcessor } from "@ui/code-blocks/ShelfCodeBlock";
|
||||
import { ReloadReadingLogCommand } from "@commands/ReloadReadingLogCommand";
|
||||
import { registerReadingCalendarCodeBlockProcessor } from "@ui/code-blocks/ReadingCalendarCodeBlock";
|
||||
|
||||
export default class BookTrackerPlugin extends Plugin {
|
||||
public settings: BookTrackerPluginSettings;
|
||||
|
@ -89,6 +90,7 @@ export default class BookTrackerPlugin extends Plugin {
|
|||
registerReadingLogCodeBlockProcessor(this);
|
||||
registerReadingStatsCodeBlockProcessor(this);
|
||||
registerShelfCodeBlockProcessor(this);
|
||||
registerReadingCalendarCodeBlockProcessor(this);
|
||||
}
|
||||
|
||||
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(
|
||||
plugin,
|
||||
"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 { Edit, Trash, Plus } from "lucide-svelte";
|
||||
import { ReadingLogEntryEditModal } from "@ui/modals";
|
||||
import type BookTrackerPlugin from "@src/main";
|
||||
import { createReadingLog } from "@ui/stores/reading-log.svelte";
|
||||
import { ALL_TIME } from "@ui/stores/date-filter.svelte";
|
||||
import { onDestroy, onMount } from "svelte";
|
||||
import OpenFileLink from "@ui/components/OpenFileLink.svelte";
|
||||
import { setAppContext } from "@ui/stores/app";
|
||||
import type { SvelteCodeBlockProps } from "./SvelteCodeBlockRenderer";
|
||||
|
||||
interface Props {
|
||||
plugin: BookTrackerPlugin;
|
||||
}
|
||||
|
||||
const { plugin }: Props = $props();
|
||||
const { plugin }: SvelteCodeBlockProps = $props();
|
||||
setAppContext(plugin.app);
|
||||
|
||||
const store = createReadingLog(plugin.readingLog);
|
||||
|
@ -32,7 +28,6 @@
|
|||
const modal = new ReadingLogEntryEditModal(
|
||||
plugin,
|
||||
async (entry) => {
|
||||
modal.close();
|
||||
await store.updateEntry(entry);
|
||||
},
|
||||
entry,
|
||||
|
|
|
@ -11,7 +11,13 @@ export function registerReadingStatsCodeBlockProcessor(
|
|||
registerCodeBlockRenderer(
|
||||
plugin,
|
||||
"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 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,
|
||||
setSettingsContext,
|
||||
} from "@ui/stores/settings.svelte";
|
||||
import type BookTrackerPlugin from "@src/main";
|
||||
import BookCountStat from "@ui/components/stats/BookCountStat.svelte";
|
||||
import { ALL_TIME } from "@ui/stores/date-filter.svelte";
|
||||
import {
|
||||
|
@ -28,13 +27,9 @@
|
|||
setReadingLogContext,
|
||||
} from "@ui/stores/reading-log.svelte";
|
||||
import { setAppContext } from "@ui/stores/app";
|
||||
import type { SvelteCodeBlockProps } from "./SvelteCodeBlockRenderer";
|
||||
|
||||
interface Props {
|
||||
plugin: BookTrackerPlugin;
|
||||
source: string;
|
||||
}
|
||||
|
||||
const { plugin, source }: Props = $props();
|
||||
const { plugin, source }: SvelteCodeBlockProps = $props();
|
||||
setAppContext(plugin.app);
|
||||
|
||||
const settingsStore = createSettings(plugin);
|
||||
|
|
|
@ -11,7 +11,8 @@ export function registerShelfCodeBlockProcessor(
|
|||
registerCodeBlockRenderer(
|
||||
plugin,
|
||||
"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 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">
|
||||
import { STATUS_READ } from "@src/const";
|
||||
import type BookTrackerPlugin from "@src/main";
|
||||
import {
|
||||
createMetadata,
|
||||
setMetadataContext,
|
||||
|
@ -17,13 +16,9 @@
|
|||
import TableView from "@ui/components/TableView.svelte";
|
||||
import DetailsView from "@ui/components/DetailsView.svelte";
|
||||
import { setAppContext } from "@ui/stores/app";
|
||||
import type { SvelteCodeBlockProps } from "./SvelteCodeBlockRenderer";
|
||||
|
||||
interface Props {
|
||||
plugin: BookTrackerPlugin;
|
||||
source: string;
|
||||
}
|
||||
|
||||
const { plugin, source }: Props = $props();
|
||||
const { plugin, source }: SvelteCodeBlockProps = $props();
|
||||
setAppContext(plugin.app);
|
||||
|
||||
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 type BookTrackerPlugin from "@src/main";
|
||||
|
||||
export interface SvelteCodeBlockProps {
|
||||
plugin: BookTrackerPlugin;
|
||||
source: string;
|
||||
}
|
||||
|
||||
export class SvelteCodeBlockRenderer<
|
||||
TComponent extends Component<TProps, TExports, TBindings>,
|
||||
TProps extends Record<string, any> = {},
|
||||
TExports extends Record<string, any> = {},
|
||||
TBindings extends keyof TProps | "" = string
|
||||
TComponent extends Component<SvelteCodeBlockProps, TExports>,
|
||||
TExports extends Record<string, any> = {}
|
||||
> extends MarkdownRenderChild {
|
||||
protected component: TExports | undefined;
|
||||
|
||||
constructor(
|
||||
private readonly contentEl: HTMLElement,
|
||||
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);
|
||||
}
|
||||
|
||||
onload(): void {
|
||||
this.component = mount(this.componentCtor, {
|
||||
...this.mountOpts,
|
||||
target: this.contentEl,
|
||||
props: {
|
||||
plugin: this.plugin,
|
||||
source: this.source,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -11,9 +11,7 @@ export class GoodreadsSearchModal extends SvelteModal<
|
|||
goodreads: Goodreads,
|
||||
onSearch: (error: any, results: SearchResult[]) => void = () => {}
|
||||
) {
|
||||
super(app, GoodreadsSearchModalView, {
|
||||
props: { goodreads, onSearch },
|
||||
});
|
||||
super(app, GoodreadsSearchModalView, { goodreads, onSearch });
|
||||
}
|
||||
|
||||
static createAndOpen(
|
||||
|
|
|
@ -8,7 +8,7 @@ export class RatingModal extends SvelteModal<typeof RatingModalView> {
|
|||
spiceConfigured: boolean,
|
||||
onSubmit: (rating: number, spice: number) => void = () => {}
|
||||
) {
|
||||
super(app, RatingModalView, { props: { spiceConfigured, onSubmit } });
|
||||
super(app, RatingModalView, { spiceConfigured, onSubmit });
|
||||
}
|
||||
|
||||
static createAndOpen(
|
||||
|
|
|
@ -8,11 +8,16 @@ export class ReadingLogEntryEditModal extends SvelteModal<
|
|||
> {
|
||||
constructor(
|
||||
plugin: BookTrackerPlugin,
|
||||
onSubmit?: (entry: ReadingLogEntry) => void,
|
||||
onSubmit: (entry: ReadingLogEntry) => void,
|
||||
entry?: ReadingLogEntry
|
||||
) {
|
||||
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,
|
||||
onSubmit: (pageNumber: number) => void = () => {}
|
||||
) {
|
||||
super(app, ReadingProgressModalView, {
|
||||
props: { pageCount, onSubmit },
|
||||
});
|
||||
super(app, ReadingProgressModalView, { pageCount, onSubmit });
|
||||
}
|
||||
|
||||
static createAndOpen(app: App, pageCount: number): Promise<number> {
|
||||
|
|
|
@ -1,26 +1,23 @@
|
|||
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<
|
||||
TComponent extends Component<TProps, TExports, TBindings>,
|
||||
TProps extends Record<string, any> = {},
|
||||
TExports extends Record<string, any> = {},
|
||||
TBindings extends keyof TProps | "" = string
|
||||
TComponent extends Component<Record<string, any>, any, any>
|
||||
> extends Modal {
|
||||
protected component: TExports | undefined;
|
||||
protected component: TComponent | undefined;
|
||||
|
||||
constructor(
|
||||
app: App,
|
||||
private readonly componentCtor: TComponent,
|
||||
private readonly mountOpts: Omit<MountOptions<TProps>, "target">
|
||||
private readonly props: ComponentProps<TComponent>
|
||||
) {
|
||||
super(app);
|
||||
}
|
||||
|
||||
onOpen(): void {
|
||||
this.component = mount(this.componentCtor, {
|
||||
...this.mountOpts,
|
||||
target: this.contentEl,
|
||||
props: this.props,
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -130,8 +130,13 @@ export class ReadingLog extends EventEmitter<ReadingLogEventMap> {
|
|||
}
|
||||
|
||||
public async updateEntry(entry: ReadingLogEntry): Promise<void> {
|
||||
this.entries[this.entries.findIndex((other) => other.id === entry.id)] =
|
||||
entry;
|
||||
const index = this.entries.findIndex((other) => other.id === entry.id);
|
||||
|
||||
if (index === -1) {
|
||||
throw new Error("Entry not found");
|
||||
}
|
||||
|
||||
this.entries[index] = entry;
|
||||
await this.save();
|
||||
this.emit("updated", { entry });
|
||||
}
|
||||
|
|
|
@ -23,7 +23,7 @@ export class EventEmitter<TEventMap> {
|
|||
this.listeners[type].push(handler);
|
||||
|
||||
return {
|
||||
off() {
|
||||
off: () => {
|
||||
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