Compare commits

..

No commits in common. "main" and "v1.3.0" have entirely different histories.
main ... v1.3.0

38 changed files with 121 additions and 328 deletions

View File

@ -1,7 +1,7 @@
{ {
"id": "obsidian-book-tracker", "id": "obsidian-book-tracker",
"name": "Book Tracker", "name": "Book Tracker",
"version": "1.4.1", "version": "1.3.0",
"minAppVersion": "0.15.0", "minAppVersion": "0.15.0",
"description": "Simplifies tracking your reading progress and managing your book collection in Obsidian.", "description": "Simplifies tracking your reading progress and managing your book collection in Obsidian.",
"author": "FiFiTiDo", "author": "FiFiTiDo",

View File

@ -1,6 +1,6 @@
{ {
"name": "obsidian-book-tracker", "name": "obsidian-book-tracker",
"version": "1.4.1", "version": "1.3.0",
"description": "Simplifies tracking your reading progress and managing your book collection in Obsidian.", "description": "Simplifies tracking your reading progress and managing your book collection in Obsidian.",
"main": "main.js", "main": "main.js",
"scripts": { "scripts": {

View File

@ -169,18 +169,8 @@ export class Goodreads {
}; };
} }
let title = bookData.title;
let subtitle = "";
if (title.includes(": ")) {
const parts = title.split(": ");
subtitle = parts.pop()!;
title = parts.join(": ");
}
return { return {
title, title: bookData.title,
subtitle,
description: bookData.description, description: bookData.description,
authors, authors,
series, series,

View File

@ -33,7 +33,6 @@ import { ReloadReadingLogCommand } from "@commands/ReloadReadingLogCommand";
import { registerReadingCalendarCodeBlockProcessor } from "@ui/code-blocks/ReadingCalendarCodeBlock"; import { registerReadingCalendarCodeBlockProcessor } from "@ui/code-blocks/ReadingCalendarCodeBlock";
import { registerAToZChallengeCodeBlockProcessor } from "@ui/code-blocks/AToZChallengeCodeBlock"; import { registerAToZChallengeCodeBlockProcessor } from "@ui/code-blocks/AToZChallengeCodeBlock";
import moment from "@external/moment"; import moment from "@external/moment";
import { compressImage } from "@utils/image";
export default class BookTrackerPlugin extends Plugin { export default class BookTrackerPlugin extends Plugin {
public settings: BookTrackerPluginSettings; public settings: BookTrackerPluginSettings;
@ -128,35 +127,23 @@ export default class BookTrackerPlugin extends Plugin {
} }
filePath += fileName + "." + extension; filePath += fileName + "." + extension;
let file = this.app.vault.getFileByPath(filePath); const existingFile = this.app.vault.getFileByPath(filePath);
if (file) { if (existingFile) {
if (this.settings.overwriteExistingCovers || overwrite) { if (this.settings.overwriteExistingCovers || overwrite) {
await this.app.vault.modifyBinary(file, response.arrayBuffer); await this.app.vault.modifyBinary(
existingFile,
response.arrayBuffer
);
} else { } else {
new Notice("Cover image already exists: " + filePath); new Notice("Cover image already exists: " + filePath);
return file;
} }
} else { return existingFile;
file = await this.app.vault.createBinary(
filePath,
response.arrayBuffer
);
} }
await compressImage(this.app, file, { return await this.app.vault.createBinary(
height: 400, filePath,
quality: 0.8, response.arrayBuffer
maintainAspectRatio: true, );
});
if (file.extension !== "jpg") {
await this.app.fileManager.renameFile(
file,
file.path.replace(/\.[^.]+$/, ".jpg")
);
}
return file;
} }
async createEntry(book: Book): Promise<void> { async createEntry(book: Book): Promise<void> {

View File

@ -14,7 +14,6 @@ export interface Series {
export interface Book { export interface Book {
title: string; title: string;
subtitle: string;
description: string; description: string;
authors: Author[]; authors: Author[];
series: Series | null; series: Series | null;

View File

@ -12,10 +12,8 @@
import OpenFileLink from "@ui/components/OpenFileLink.svelte"; import OpenFileLink from "@ui/components/OpenFileLink.svelte";
import DateFilter from "@ui/components/DateFilter.svelte"; import DateFilter from "@ui/components/DateFilter.svelte";
import BookCover from "@ui/components/BookCover.svelte"; import BookCover from "@ui/components/BookCover.svelte";
import { setAppContext } from "@ui/stores/app";
const { plugin }: SvelteCodeBlockProps = $props(); const { plugin }: SvelteCodeBlockProps = $props();
setAppContext(plugin.app);
const settingsStore = createSettings(plugin); const settingsStore = createSettings(plugin);
setSettingsContext(settingsStore); setSettingsContext(settingsStore);
@ -90,7 +88,7 @@
{#if metadata[letter]} {#if metadata[letter]}
{@const meta = metadata[letter]} {@const meta = metadata[letter]}
<OpenFileLink file={meta.file}> <OpenFileLink file={meta.file}>
<BookCover book={meta.book} size="fill" /> <BookCover app={plugin.app} book={meta.book} />
</OpenFileLink> </OpenFileLink>
{:else} {:else}
<div class="placeholder">{letter}</div> <div class="placeholder">{letter}</div>
@ -141,6 +139,13 @@
font-weight: bold; font-weight: bold;
font-style: italic; font-style: italic;
} }
:global(img) {
width: 100%;
height: 100%;
object-fit: cover;
border-radius: var(--radius-l);
}
} }
} }
} }

View File

@ -19,10 +19,8 @@
import OpenFileLink from "@ui/components/OpenFileLink.svelte"; import OpenFileLink from "@ui/components/OpenFileLink.svelte";
import moment from "@external/moment"; import moment from "@external/moment";
import BookCover from "@ui/components/BookCover.svelte"; import BookCover from "@ui/components/BookCover.svelte";
import { setAppContext } from "@ui/stores/app";
const { plugin }: SvelteCodeBlockProps = $props(); const { plugin }: SvelteCodeBlockProps = $props();
setAppContext(plugin.app);
const settingsStore = createSettings(plugin); const settingsStore = createSettings(plugin);
setSettingsContext(settingsStore); setSettingsContext(settingsStore);
@ -230,8 +228,8 @@
<div class="cover"> <div class="cover">
<OpenFileLink file={meta.file}> <OpenFileLink file={meta.file}>
<BookCover <BookCover
app={plugin.app}
book={meta.book} book={meta.book}
size="fill"
/> />
</OpenFileLink> </OpenFileLink>
</div> </div>
@ -300,7 +298,6 @@
display: flex; display: flex;
justify-content: space-between; justify-content: space-between;
align-items: center; align-items: center;
margin-bottom: -12px;
.total-pages-read { .total-pages-read {
font-size: var(--font-smallest); font-size: var(--font-smallest);
@ -312,7 +309,6 @@
position: relative; position: relative;
width: 100%; width: 100%;
aspect-ratio: 2 / 3; aspect-ratio: 2 / 3;
max-height: 100%;
.cover { .cover {
border-radius: var(--radius-l); border-radius: var(--radius-l);
@ -320,10 +316,11 @@
height: 100%; height: 100%;
width: 100%; width: 100%;
:global(a) { :global(img) {
display: block; border-radius: var(--radius-l);
width: 100%; width: 100%;
height: 100%; height: 100%;
object-fit: cover;
} }
} }

View File

@ -46,6 +46,7 @@ const PieChart = z.object({
unit: z.optional(z.string()), unit: z.optional(z.string()),
unitPlural: z.optional(z.string()), unitPlural: z.optional(z.string()),
groups: z.optional(z.array(PieGroupingSchema)), groups: z.optional(z.array(PieGroupingSchema)),
responsive: z.optional(z.boolean()),
color: z.optional(PieChartColorSchema), color: z.optional(PieChartColorSchema),
}); });
@ -57,6 +58,7 @@ const BarChart = z.object({
topN: z.optional(z.number()), topN: z.optional(z.number()),
unit: z.optional(z.string()), unit: z.optional(z.string()),
unitPlural: z.optional(z.string()), unitPlural: z.optional(z.string()),
responsive: z.optional(z.boolean()),
color: z.optional(color), color: z.optional(color),
}); });
@ -68,6 +70,7 @@ const LineChart = z.object({
secondProperty: z.optional(z.keyof(BookMetadataSchema)), secondProperty: z.optional(z.keyof(BookMetadataSchema)),
secondUnit: z.optional(z.string()), secondUnit: z.optional(z.string()),
secondUnitPlural: z.optional(z.string()), secondUnitPlural: z.optional(z.string()),
responsive: z.optional(z.boolean()),
}); });
const BooksAndPagesChart = z.object({ const BooksAndPagesChart = z.object({

View File

@ -138,11 +138,15 @@
<div <div
class="chart bar" class="chart bar"
class:horizontal={chart.horizontal} class:horizontal={chart.horizontal}
class:responsive={chart.responsive}
> >
<Bar {...chart} /> <Bar {...chart} />
</div> </div>
{:else if chart.type === "pie"} {:else if chart.type === "pie"}
<div class="chart pie"> <div
class="chart pie"
class:responsive={chart.responsive}
>
<Pie {...chart} /> <Pie {...chart} />
</div> </div>
{:else if chart.type === "books-and-pages"} {:else if chart.type === "books-and-pages"}
@ -193,7 +197,7 @@
margin: 0 auto 1rem; margin: 0 auto 1rem;
} }
&.bar.horizontal { &.bar.responsive {
height: 35rem; height: 35rem;
} }
} }

View File

@ -17,10 +17,6 @@
import DetailsView from "@ui/components/DetailsView.svelte"; import DetailsView from "@ui/components/DetailsView.svelte";
import { setAppContext } from "@ui/stores/app"; import { setAppContext } from "@ui/stores/app";
import type { SvelteCodeBlockProps } from "./SvelteCodeBlockRenderer"; import type { SvelteCodeBlockProps } from "./SvelteCodeBlockRenderer";
import {
createReadingLog,
setReadingLogContext,
} from "@ui/stores/reading-log.svelte";
const { plugin, source }: SvelteCodeBlockProps = $props(); const { plugin, source }: SvelteCodeBlockProps = $props();
setAppContext(plugin.app); setAppContext(plugin.app);
@ -36,11 +32,6 @@
}); });
setMetadataContext(metadataStore); setMetadataContext(metadataStore);
const readingLog = createReadingLog(plugin.readingLog, {
disableAllFiltering: true,
});
setReadingLogContext(readingLog);
let view = $state(settings.defaultView); let view = $state(settings.defaultView);
onDestroy(() => metadataStore.destroy()); onDestroy(() => metadataStore.destroy());
@ -63,11 +54,11 @@
{/if} {/if}
</div> </div>
{#if view === "bookshelf"} {#if view === "bookshelf"}
<BookshelfView /> <BookshelfView {plugin} />
{:else if view === "table"} {:else if view === "table"}
<TableView {settings} /> <TableView {plugin} {settings} />
{:else if view === "details"} {:else if view === "details"}
<DetailsView {settings} /> <DetailsView {plugin} {settings} />
{/if} {/if}
</div> </div>

View File

@ -1,49 +1,14 @@
<script lang="ts"> <script lang="ts">
import type { BookMetadata } from "@src/types"; import type { BookMetadata } from "@src/types";
import { getAppContext } from "@ui/stores/app"; import type { App } from "obsidian";
type NumberUnit =
| "cap"
| "ch"
| "em"
| "ex"
| "ic"
| "lh"
| "rcap"
| "rch"
| "rem"
| "rex"
| "ric"
| "rlh"
| "vh"
| "vw"
| "vmax"
| "vmin"
| "vb"
| "vi"
| "cqw"
| "cqh"
| "cqi"
| "cqb"
| "cqmin"
| "cqmax"
| "px"
| "cm"
| "mm"
| "Q"
| "in"
| "pc"
| "pt"
| "%";
interface Props { interface Props {
app: App;
book: BookMetadata; book: BookMetadata;
size?: "fill" | number | `${number}${NumberUnit}`; size?: number;
} }
const { book, size = 500 }: Props = $props(); const { app, book, size }: Props = $props();
const app = getAppContext();
const coverPath = $derived(book.localCoverPath); const coverPath = $derived(book.localCoverPath);
const coverFile = $derived(app.vault.getFileByPath(coverPath)); const coverFile = $derived(app.vault.getFileByPath(coverPath));
@ -51,54 +16,12 @@
coverFile ? app.vault.getResourcePath(coverFile) : "", coverFile ? app.vault.getResourcePath(coverFile) : "",
); );
const coverAlt = $derived(book.title); const coverAlt = $derived(book.title);
const coverWidth = $derived.by(() => {
if (size === "fill") {
return "100%";
} else if (typeof size === "string") {
return size;
} else if (typeof size === "number") {
return `${size}px`;
} else {
return undefined;
}
});
const coverHeight = $derived.by(() => {
if (size === "fill") {
return "100%";
} else {
return undefined;
}
});
</script> </script>
<div <img src={coverSrc} alt={coverAlt} width={size} />
class="book-cover"
style:--book-cover-width={coverWidth}
style:--book-cover-height={coverHeight}
>
<img src={coverSrc} alt={coverAlt} />
</div>
<style lang="scss"> <style lang="scss">
:global(:root) { img {
--book-cover-aspect-ratio: 2 / 3; border-radius: var(--radius-l);
}
.book-cover {
width: var(--book-cover-width);
height: var(
--book-cover-height,
calc(var(--book-cover-width) / (var(--book-cover-aspect-ratio)))
);
img {
border-radius: var(--radius-l);
height: 100%;
width: 100%;
object-fit: cover;
object-position: center;
}
} }
</style> </style>

View File

@ -12,7 +12,10 @@
import { v4 as uuidv4 } from "uuid"; import { v4 as uuidv4 } from "uuid";
import memoize from "just-memoize"; import memoize from "just-memoize";
import type { TFile } from "obsidian"; import type { TFile } from "obsidian";
import { getAppContext } from "@ui/stores/app";
interface Props {
plugin: BookTrackerPlugin;
}
interface BookData { interface BookData {
id: string; id: string;
@ -31,7 +34,7 @@
books: BookData[]; books: BookData[];
} }
const app = getAppContext(); const { plugin }: Props = $props();
const metadataStore = getMetadataContext(); const metadataStore = getMetadataContext();
@ -125,7 +128,7 @@
design={bookData.design} design={bookData.design}
orientation="flat" orientation="flat"
onClick={() => onClick={() =>
app.workspace plugin.app.workspace
.getLeaf(false) .getLeaf(false)
.openFile(bookData.file)} .openFile(bookData.file)}
/> />
@ -140,7 +143,8 @@
color={book.color} color={book.color}
design={book.design} design={book.design}
orientation={book.orientation} orientation={book.orientation}
onClick={() => app.workspace.getLeaf(false).openFile(book.file)} onClick={() =>
plugin.app.workspace.getLeaf(false).openFile(book.file)}
/> />
{/if} {/if}
{/each} {/each}

View File

@ -1,27 +1,27 @@
<script lang="ts"> <script lang="ts">
import { STATUS_IN_PROGRESS, STATUS_READ } from "@src/const"; import { STATUS_IN_PROGRESS, STATUS_READ } from "@src/const";
import type BookTrackerPlugin from "@src/main";
import { getMetadataContext } from "@ui/stores/metadata.svelte"; import { getMetadataContext } from "@ui/stores/metadata.svelte";
import type { ShelfSettings } from "@ui/code-blocks/ShelfCodeBlock"; import type { ShelfSettings } from "@ui/code-blocks/ShelfCodeBlock";
import { Dot, Flame, Star, StarHalf } from "lucide-svelte"; import { Dot, Flame, Star, StarHalf } from "lucide-svelte";
import RatingInput from "./RatingInput.svelte"; import RatingInput from "./RatingInput.svelte";
import OpenFileLink from "./OpenFileLink.svelte"; import OpenFileLink from "./OpenFileLink.svelte";
import BookCover from "./BookCover.svelte"; import BookCover from "./BookCover.svelte";
import { getReadingLogContext } from "@ui/stores/reading-log.svelte";
interface Props { interface Props {
plugin: BookTrackerPlugin;
settings: ShelfSettings; settings: ShelfSettings;
} }
const { settings }: Props = $props(); const { plugin, settings }: Props = $props();
const metadataStore = getMetadataContext(); const metadataStore = getMetadataContext();
const readingLog = getReadingLogContext();
</script> </script>
<div class="book-details-list"> <div class="book-details-list">
{#each metadataStore.metadata as meta} {#each metadataStore.metadata as meta}
<div class="book-details"> <div class="book-details">
<BookCover book={meta.book} /> <BookCover app={plugin.app} book={meta.book} />
<div class="book-info"> <div class="book-info">
<OpenFileLink file={meta.file}> <OpenFileLink file={meta.file}>
<h2 class="book-title"> <h2 class="book-title">
@ -59,18 +59,11 @@
</p> </p>
{/if} {/if}
{#if settings.statusFilter === STATUS_IN_PROGRESS} {#if settings.statusFilter === STATUS_IN_PROGRESS}
{@const logEntryIndex =
readingLog.entries.findLastIndex(
(e) => e.book === meta.file.basename,
)}
{@const logEntry =
logEntryIndex !== -1
? readingLog.entries[logEntryIndex]
: null}
<Dot color="var(--text-muted)" /> <Dot color="var(--text-muted)" />
<p class="current-page"> <p class="current-page">
Current Page: {logEntry?.pagesReadTotal ?? 0} Current Page: {plugin.readingLog.getLastEntryForBook(
meta.file.basename,
)?.pagesReadTotal ?? 0}
</p> </p>
{/if} {/if}
{#if settings.statusFilter === STATUS_READ} {#if settings.statusFilter === STATUS_READ}
@ -151,20 +144,16 @@
background-color: var(--background-secondary); background-color: var(--background-secondary);
border-radius: var(--radius-l); border-radius: var(--radius-l);
:global(.book-cover) { :global(img) {
width: 100%; border-radius: var(--radius-l);
height: auto;
max-height: 100%;
max-width: 30%; max-width: 30%;
} }
@container (width < 700px) { @container (width < 600px) {
flex-direction: column; flex-direction: column;
align-items: center; align-items: center;
:global(.book-cover) { :global(img) {
height: 100%;
width: auto;
max-height: 30rem; max-height: 30rem;
max-width: 100%; max-width: 100%;
} }

View File

@ -1,5 +1,6 @@
<script lang="ts"> <script lang="ts">
import { STATUS_IN_PROGRESS, STATUS_READ } from "@src/const"; import { STATUS_IN_PROGRESS, STATUS_READ } from "@src/const";
import type BookTrackerPlugin from "@src/main";
import { getMetadataContext } from "@ui/stores/metadata.svelte"; import { getMetadataContext } from "@ui/stores/metadata.svelte";
import Rating from "@ui/components/Rating.svelte"; import Rating from "@ui/components/Rating.svelte";
import type { ShelfSettings } from "@ui/code-blocks/ShelfCodeBlock"; import type { ShelfSettings } from "@ui/code-blocks/ShelfCodeBlock";
@ -7,10 +8,11 @@
import BookCover from "./BookCover.svelte"; import BookCover from "./BookCover.svelte";
interface Props { interface Props {
plugin: BookTrackerPlugin;
settings: ShelfSettings; settings: ShelfSettings;
} }
const { settings }: Props = $props(); const { plugin, settings }: Props = $props();
const metadataStore = getMetadataContext(); const metadataStore = getMetadataContext();
</script> </script>
@ -36,7 +38,7 @@
{#each metadataStore.metadata as meta} {#each metadataStore.metadata as meta}
<tr> <tr>
<td class="cover"> <td class="cover">
<BookCover book={meta.book} size={50} /> <BookCover app={plugin.app} book={meta.book} size={50} />
</td> </td>
<td> <td>
<OpenFileLink file={meta.file}> <OpenFileLink file={meta.file}>
@ -50,9 +52,7 @@
{meta.book.seriesTitle} {meta.book.seriesTitle}
</td> </td>
<td> <td>
{#if meta.book.seriesTitle !== ""} {meta.book.seriesPosition}
#{meta.book.seriesPosition}
{/if}
</td> </td>
{#if settings.statusFilter === STATUS_IN_PROGRESS || settings.statusFilter === STATUS_READ} {#if settings.statusFilter === STATUS_IN_PROGRESS || settings.statusFilter === STATUS_READ}
<td> <td>
@ -94,10 +94,6 @@
&.cover { &.cover {
padding: 0; padding: 0;
margin: 0; margin: 0;
:global(.book-cover) {
margin: 0 auto;
}
} }
} }
} }

View File

@ -176,7 +176,6 @@
display: flex; display: flex;
justify-content: center; justify-content: center;
align-items: center; align-items: center;
color: inherit;
} }
.book-title { .book-title {

View File

@ -12,6 +12,7 @@
topN?: number; topN?: number;
unit?: string; unit?: string;
unitPlural?: string; unitPlural?: string;
responsive?: boolean;
color?: "rainbow" | ColorName; color?: "rainbow" | ColorName;
}; };
@ -22,6 +23,7 @@
topN, topN,
unit, unit,
unitPlural, unitPlural,
responsive,
color, color,
}: Props = $props(); }: Props = $props();
@ -105,8 +107,8 @@
}, },
options: { options: {
indexAxis: horizontal ? "y" : "x", indexAxis: horizontal ? "y" : "x",
responsive: true, responsive,
maintainAspectRatio: !horizontal, maintainAspectRatio: !responsive,
plugins: { plugins: {
tooltip: { tooltip: {
yAlign: horizontal ? "center" : "bottom", yAlign: horizontal ? "center" : "bottom",

View File

@ -99,7 +99,6 @@
datasets, datasets,
}, },
options: { options: {
responsive: true,
scales: { scales: {
y: { y: {
type: "linear", type: "linear",

View File

@ -14,10 +14,12 @@
groups?: PieGrouping[]; groups?: PieGrouping[];
unit?: string; unit?: string;
unitPlural?: string; unitPlural?: string;
responsive?: boolean;
color?: PieChartColor; color?: PieChartColor;
}; };
const { property, groups, unit, unitPlural, color }: Props = $props(); const { property, groups, unit, unitPlural, responsive, color }: Props =
$props();
const store = createPropertyStore(property); const store = createPropertyStore(property);
@ -112,7 +114,7 @@
return { return {
type: "pie", type: "pie",
data: { labels, datasets: [{ data, backgroundColor }] }, data: { labels, datasets: [{ data, backgroundColor }] },
options: { responsive: true }, options: { responsive },
} as ChartConfiguration; } as ChartConfiguration;
}); });
</script> </script>

View File

@ -1,18 +1,20 @@
<script lang="ts"> <script lang="ts">
import type { ComponentProps } from "svelte"; import type { ComponentProps } from "svelte";
import Item from "./Item.svelte"; import Item from "./Item.svelte";
import type { App, TFile } from "obsidian";
import FileSuggest from "../suggesters/FileSuggest.svelte"; import FileSuggest from "../suggesters/FileSuggest.svelte";
type Props = Omit<ComponentProps<typeof Item>, "control"> & { type Props = Omit<ComponentProps<typeof Item>, "control"> & {
app: App;
id: string; id: string;
value?: string; value?: string;
}; };
let { name, description, id, value = $bindable() }: Props = $props(); let { name, description, app, id, value = $bindable() }: Props = $props();
</script> </script>
<Item {name} {description}> <Item {name} {description}>
{#snippet control()} {#snippet control()}
<FileSuggest {id} asString bind:value /> <FileSuggest {id} {app} asString bind:value />
{/snippet} {/snippet}
</Item> </Item>

View File

@ -1,18 +1,20 @@
<script lang="ts"> <script lang="ts">
import type { ComponentProps } from "svelte"; import type { ComponentProps } from "svelte";
import Item from "./Item.svelte"; import Item from "./Item.svelte";
import type { App, TFolder } from "obsidian";
import FolderSuggest from "../suggesters/FolderSuggest.svelte"; import FolderSuggest from "../suggesters/FolderSuggest.svelte";
type Props = Omit<ComponentProps<typeof Item>, "control"> & { type Props = Omit<ComponentProps<typeof Item>, "control"> & {
app: App;
id: string; id: string;
value?: string; value?: string;
}; };
let { name, description, id, value = $bindable() }: Props = $props(); let { name, description, app, id, value = $bindable() }: Props = $props();
</script> </script>
<Item {name} {description}> <Item {name} {description}>
{#snippet control()} {#snippet control()}
<FolderSuggest {id} asString bind:value /> <FolderSuggest {id} {app} asString bind:value />
{/snippet} {/snippet}
</Item> </Item>

View File

@ -1,15 +1,18 @@
<script lang="ts"> <script lang="ts">
import type { ComponentProps } from "svelte"; import type { ComponentProps } from "svelte";
import Item from "./Item.svelte"; import Item from "./Item.svelte";
import type { App } from "obsidian";
import PropertySuggest from "../suggesters/PropertySuggest.svelte"; import PropertySuggest from "../suggesters/PropertySuggest.svelte";
type Props = Omit<ComponentProps<typeof Item>, "control"> & { type Props = Omit<ComponentProps<typeof Item>, "control"> & {
app: App;
id: string; id: string;
value?: string; value?: string;
accepts?: string[]; accepts?: string[];
}; };
let { let {
app,
name, name,
description, description,
id, id,
@ -20,6 +23,6 @@
<Item {name} {description}> <Item {name} {description}>
{#snippet control()} {#snippet control()}
<PropertySuggest {id} asString bind:value {accepts} /> <PropertySuggest {app} {id} asString bind:value {accepts} />
{/snippet} {/snippet}
</Item> </Item>

View File

@ -1,10 +1,10 @@
<script lang="ts"> <script lang="ts">
import type { TFile } from "obsidian"; import type { App, TFile } from "obsidian";
import TextInputSuggest, { type Item } from "./TextInputSuggest.svelte"; import TextInputSuggest, { type Item } from "./TextInputSuggest.svelte";
import type { StringKeys } from "@utils/types"; import type { StringKeys } from "@utils/types";
import { getAppContext } from "@ui/stores/app";
type Props = { type Props = {
app: App;
id: string; id: string;
asString?: boolean; asString?: boolean;
property?: StringKeys<TFile>; property?: StringKeys<TFile>;
@ -15,6 +15,7 @@
}; };
let { let {
app,
id, id,
asString, asString,
property = "path", property = "path",
@ -23,7 +24,6 @@
disabled, disabled,
onSelected, onSelected,
}: Props = $props(); }: Props = $props();
const app = getAppContext();
let items: Item<TFile | string>[] = $state([]); let items: Item<TFile | string>[] = $state([]);

View File

@ -1,10 +1,10 @@
<script lang="ts"> <script lang="ts">
import { TFolder } from "obsidian"; import { TFolder, type App } from "obsidian";
import TextInputSuggest, { type Item } from "./TextInputSuggest.svelte"; import TextInputSuggest, { type Item } from "./TextInputSuggest.svelte";
import type { StringKeys } from "@utils/types"; import type { StringKeys } from "@utils/types";
import { getAppContext } from "@ui/stores/app";
type Props = { type Props = {
app: App;
id: string; id: string;
asString?: boolean; asString?: boolean;
property?: StringKeys<TFolder>; property?: StringKeys<TFolder>;
@ -15,6 +15,7 @@
}; };
let { let {
app,
id, id,
asString, asString,
property = "path", property = "path",
@ -23,7 +24,6 @@
disabled, disabled,
onSelected, onSelected,
}: Props = $props(); }: Props = $props();
const app = getAppContext();
let items: Item<TFolder | string>[] = $state([]); let items: Item<TFolder | string>[] = $state([]);

View File

@ -1,25 +1,26 @@
<script module lang="ts"> <script module lang="ts">
export type Property = { export type Field = {
name: string; name: string;
type: string; type: string;
}; };
</script> </script>
<script lang="ts"> <script lang="ts">
import { getAppContext } from "@ui/stores/app"; import type { App } from "obsidian";
import TextInputSuggest, { type Item } from "./TextInputSuggest.svelte"; import TextInputSuggest, { type Item } from "./TextInputSuggest.svelte";
type Props = { type Props = {
app: App;
id: string; id: string;
asString?: boolean; asString?: boolean;
value?: Property | string; value?: Field | string;
accepts?: string[]; accepts?: string[];
disabled?: boolean; disabled?: boolean;
onSelected?: (propertyOrName: Property | string) => void; onSelected?: (fieldOrName: Field | string) => void;
}; };
let { let {
app,
id, id,
asString, asString,
value = $bindable(), value = $bindable(),
@ -27,9 +28,8 @@
disabled, disabled,
onSelected, onSelected,
}: Props = $props(); }: Props = $props();
const app = getAppContext();
let items: Item<Property | string>[] = $state([]); let items: Item<Field | string>[] = $state([]);
async function handleChange(query: string) { async function handleChange(query: string) {
const typesContent = await app.vault.adapter.read( const typesContent = await app.vault.adapter.read(

View File

@ -11,7 +11,7 @@ export class GoodreadsSearchModal extends SvelteModal<
goodreads: Goodreads, goodreads: Goodreads,
onSearch: (error: any, results: SearchResult[]) => void = () => {} onSearch: (error: any, results: SearchResult[]) => void = () => {}
) { ) {
super(app, GoodreadsSearchModalView, { app, goodreads, onSearch }); super(app, GoodreadsSearchModalView, { goodreads, onSearch });
} }
static createAndOpen( static createAndOpen(

View File

@ -1,16 +1,12 @@
<script lang="ts"> <script lang="ts">
import { Goodreads, type SearchResult } from "@data-sources/Goodreads"; import { Goodreads, type SearchResult } from "@data-sources/Goodreads";
import { setAppContext } from "@ui/stores/app";
import type { App } from "obsidian";
interface Props { interface Props {
app: App;
goodreads: Goodreads; goodreads: Goodreads;
onSearch: (error: any, results?: SearchResult[]) => void; onSearch: (error: any, results?: SearchResult[]) => void;
} }
let { app, goodreads, onSearch }: Props = $props(); let { goodreads, onSearch }: Props = $props();
setAppContext(app);
let query = $state(""); let query = $state("");

View File

@ -19,7 +19,7 @@ export class GoodreadsSearchSuggestModal extends SuggestModal<SearchResult> {
renderSuggestion(searchResult: SearchResult, el: HTMLElement): void { renderSuggestion(searchResult: SearchResult, el: HTMLElement): void {
mount(GoodreadsSearchSuggestion, { mount(GoodreadsSearchSuggestion, {
target: el, target: el,
props: { app: this.app, searchResult }, props: { searchResult },
}); });
} }

View File

@ -1,15 +1,11 @@
<script lang="ts"> <script lang="ts">
import type { SearchResult } from "@data-sources/Goodreads"; import type { SearchResult } from "@data-sources/Goodreads";
import { setAppContext } from "@ui/stores/app";
import type { App } from "obsidian";
interface Props { interface Props {
app: App;
searchResult: SearchResult; searchResult: SearchResult;
} }
let { app, searchResult }: Props = $props(); let { searchResult }: Props = $props();
setAppContext(app);
</script> </script>
<div class="obt-goodreads-search-suggestion"> <div class="obt-goodreads-search-suggestion">

View File

@ -8,7 +8,7 @@ export class RatingModal extends SvelteModal<typeof RatingModalView> {
spiceConfigured: boolean, spiceConfigured: boolean,
onSubmit: (rating: number, spice: number) => void = () => {} onSubmit: (rating: number, spice: number) => void = () => {}
) { ) {
super(app, RatingModalView, { app, spiceConfigured, onSubmit }); super(app, RatingModalView, { spiceConfigured, onSubmit });
} }
static createAndOpen( static createAndOpen(

View File

@ -1,16 +1,12 @@
<script lang="ts"> <script lang="ts">
import RatingInput from "@ui/components/RatingInput.svelte"; import RatingInput from "@ui/components/RatingInput.svelte";
import { setAppContext } from "@ui/stores/app";
import { Flame } from "lucide-svelte"; import { Flame } from "lucide-svelte";
import type { App } from "obsidian";
interface Props { interface Props {
app: App;
spiceConfigured?: boolean; spiceConfigured?: boolean;
onSubmit: (rating: number, spice: number) => void; onSubmit: (rating: number, spice: number) => void;
} }
let { app, spiceConfigured = false, onSubmit }: Props = $props(); let { spiceConfigured = false, onSubmit }: Props = $props();
setAppContext(app);
let rating = $state(0); let rating = $state(0);
let spice = $state(0); let spice = $state(0);

View File

@ -10,7 +10,6 @@
setSettingsContext, setSettingsContext,
} from "@ui/stores/settings.svelte"; } from "@ui/stores/settings.svelte";
import moment from "@external/moment"; import moment from "@external/moment";
import { setAppContext } from "@ui/stores/app";
const INPUT_DATETIME_FORMAT = "YYYY-MM-DDTHH:mm"; const INPUT_DATETIME_FORMAT = "YYYY-MM-DDTHH:mm";
@ -21,7 +20,6 @@
} }
let { plugin, entry, onSubmit }: Props = $props(); let { plugin, entry, onSubmit }: Props = $props();
setAppContext(plugin.app);
const settingsStore = createSettings(plugin); const settingsStore = createSettings(plugin);
setSettingsContext(settingsStore); setSettingsContext(settingsStore);
@ -82,6 +80,7 @@
<label for="book">Book</label> <label for="book">Book</label>
<FileSuggest <FileSuggest
id="book" id="book"
app={plugin.app}
asString asString
property="basename" property="basename"
inFolder={plugin.settings.bookFolder} inFolder={plugin.settings.bookFolder}

View File

@ -10,7 +10,7 @@ export class ReadingProgressModal extends SvelteModal<
pageCount: number, pageCount: number,
onSubmit: (pageNumber: number) => void = () => {} onSubmit: (pageNumber: number) => void = () => {}
) { ) {
super(app, ReadingProgressModalView, { app, pageCount, onSubmit }); super(app, ReadingProgressModalView, { pageCount, onSubmit });
} }
static createAndOpen(app: App, pageCount: number): Promise<number> { static createAndOpen(app: App, pageCount: number): Promise<number> {

View File

@ -1,14 +1,11 @@
<script lang="ts"> <script lang="ts">
import { setAppContext } from "@ui/stores/app"; import { Notice } from "obsidian";
import { App, Notice } from "obsidian";
interface Props { interface Props {
app: App;
pageCount: number; pageCount: number;
onSubmit: (pageNumber: number) => void; onSubmit: (pageNumber: number) => void;
} }
let { app, pageCount, onSubmit }: Props = $props(); let { pageCount, onSubmit }: Props = $props();
setAppContext(app);
let value = $state(0); let value = $state(0);
let mode: "page-number" | "percentage" = $state("page-number"); let mode: "page-number" | "percentage" = $state("page-number");

View File

@ -9,14 +9,13 @@
import { createSettings } from "@ui/stores/settings.svelte"; import { createSettings } from "@ui/stores/settings.svelte";
import { onMount } from "svelte"; import { onMount } from "svelte";
import type { BookTrackerSettings } from "./types"; import type { BookTrackerSettings } from "./types";
import { setAppContext } from "@ui/stores/app";
type Props = { type Props = {
plugin: BookTrackerPlugin; plugin: BookTrackerPlugin;
}; };
const { plugin }: Props = $props(); const { plugin }: Props = $props();
setAppContext(plugin.app); const { app } = plugin;
const settingsStore = createSettings(plugin); const settingsStore = createSettings(plugin);
@ -169,18 +168,21 @@
<div class="obt-settings"> <div class="obt-settings">
<Header title="Folders" /> <Header title="Folders" />
<FolderSuggestItem <FolderSuggestItem
{app}
id="book-folder" id="book-folder"
name="Book Folder" name="Book Folder"
description="Select the folder where book entries are stored." description="Select the folder where book entries are stored."
bind:value={settingsStore.settings.bookFolder} bind:value={settingsStore.settings.bookFolder}
/> />
<FolderSuggestItem <FolderSuggestItem
{app}
id="tbr-folder" id="tbr-folder"
name="To Be Read Folder" name="To Be Read Folder"
description="Select the folder to use for To Be Read entries" description="Select the folder to use for To Be Read entries"
bind:value={settingsStore.settings.tbrFolder} bind:value={settingsStore.settings.tbrFolder}
/> />
<FolderSuggestItem <FolderSuggestItem
{app}
id="read-folder" id="read-folder"
name="Read Books Folder" name="Read Books Folder"
description="Select the folder to use for Read entries." description="Select the folder to use for Read entries."
@ -194,6 +196,7 @@
/> />
<Header title="Book Creation" /> <Header title="Book Creation" />
<FileSuggestItem <FileSuggestItem
{app}
id="template-file" id="template-file"
name="Template File" name="Template File"
description="Select the template file to use for new book entries." description="Select the template file to use for new book entries."
@ -215,6 +218,7 @@
bind:checked={settingsStore.settings.downloadCovers} bind:checked={settingsStore.settings.downloadCovers}
/> />
<FolderSuggestItem <FolderSuggestItem
{app}
id="cover-folder" id="cover-folder"
name="Cover Folder" name="Cover Folder"
description="Select the folder to download covers to." description="Select the folder to download covers to."
@ -236,6 +240,7 @@
<Header title="Book Properties" /> <Header title="Book Properties" />
{#each properties as property} {#each properties as property}
<PropertySuggestItem <PropertySuggestItem
{app}
id={property.key} id={property.key}
name={`${property.label} Property`} name={`${property.label} Property`}
description={property.description} description={property.description}

View File

@ -32,13 +32,6 @@ export interface DateFilterStoreOptions {
* @default false * @default false
*/ */
disableMonthFilter?: boolean; disableMonthFilter?: boolean;
/**
* If true, the year filter will be disabled
*
* @default false
*/
disableAllFiltering?: boolean;
} }
export function createDateFilter<T>( export function createDateFilter<T>(
@ -48,7 +41,6 @@ export function createDateFilter<T>(
initialMonth = false, initialMonth = false,
initialYear = true, initialYear = true,
disableMonthFilter = false, disableMonthFilter = false,
disableAllFiltering = false,
}: DateFilterStoreOptions }: DateFilterStoreOptions
): DateFilterStore & { ): DateFilterStore & {
filteredData: T[]; filteredData: T[];
@ -72,7 +64,7 @@ export function createDateFilter<T>(
return data() return data()
.filter((item) => { .filter((item) => {
const date = selector(item); const date = selector(item);
if (filterYear !== ALL_TIME && !disableAllFiltering) { if (filterYear !== ALL_TIME) {
if (date.year() !== filterYear) { if (date.year() !== filterYear) {
return false; return false;
} }

View File

@ -16,15 +16,6 @@ Handlebars.registerHelper("normalizeDesc", (desc: string) => {
return desc.replace(/(\r\n|\n|\r)/gm, " ").trim(); return desc.replace(/(\r\n|\n|\r)/gm, " ").trim();
}); });
Handlebars.registerHelper("indent", (text: string, indent = " ") => {
return new Handlebars.SafeString(
text
.split("\n")
.map((line) => indent + line)
.join("\n")
);
});
export class Templater { export class Templater {
public constructor(private readonly app: App) {} public constructor(private readonly app: App) {}

View File

@ -1,72 +0,0 @@
import { type App, type TFile } from "obsidian";
interface CompressOptions {
width?: number;
height?: number;
maintainAspectRatio?: boolean;
quality?: number;
}
export async function compressImage(
app: App,
file: TFile,
options: CompressOptions
) {
const img = await new Promise<HTMLImageElement>((resolve) => {
const img = new Image();
img.src = app.vault.getResourcePath(file);
img.onload = () => {
resolve(img);
};
});
const canvas = document.createElement("canvas");
const ctx = canvas.getContext("2d")!;
const quality = options.quality ?? 1;
let height = options.height;
let width = options.width;
if (!width && !height) {
width = img.width;
height = img.height;
} else if (!width && height) {
width = height * (img.width / img.height);
} else if (!height && width) {
height = width * (img.height / img.width);
}
if (options.maintainAspectRatio) {
const aspectRatio = img.width / img.height;
if (options.height)
if (width! > height!) {
height = width! / aspectRatio;
} else {
width = height! * aspectRatio;
}
}
console.log(width, height);
canvas.width = width!;
canvas.height = height!;
ctx.drawImage(img, 0, 0, width!, height!);
const blob = await new Promise<Blob>((resolve, reject) => {
canvas.toBlob(
(blob) => {
if (blob) {
resolve(blob);
} else {
reject(new Error("Failed to compress image"));
}
},
"image/jpeg",
quality
);
});
return app.vault.modifyBinary(file, await blob.arrayBuffer());
}

View File

@ -2,9 +2,5 @@
"1.0.0": "0.15.0", "1.0.0": "0.15.0",
"1.1.0": "0.15.0", "1.1.0": "0.15.0",
"1.2.0": "0.15.0", "1.2.0": "0.15.0",
"1.3.0": "0.15.0", "1.3.0": "0.15.0"
"1.3.1": "0.15.0",
"1.3.2": "0.15.0",
"1.4.0": "0.15.0",
"1.4.1": "0.15.0"
} }