generated from tpl/obsidian-sample-plugin
Compare commits
11 Commits
Author | SHA1 | Date |
---|---|---|
|
d5e4810d8f | |
|
791fa96d19 | |
|
080074511a | |
|
d42aabc5f2 | |
|
2ba8523a24 | |
|
05daa707d8 | |
|
872fae53da | |
|
92008c0070 | |
|
f8e544d381 | |
|
5164298bcc | |
|
104533e0bb |
|
@ -1,7 +1,7 @@
|
||||||
{
|
{
|
||||||
"id": "obsidian-book-tracker",
|
"id": "obsidian-book-tracker",
|
||||||
"name": "Book Tracker",
|
"name": "Book Tracker",
|
||||||
"version": "1.3.0",
|
"version": "1.4.1",
|
||||||
"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",
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "obsidian-book-tracker",
|
"name": "obsidian-book-tracker",
|
||||||
"version": "1.3.0",
|
"version": "1.4.1",
|
||||||
"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": {
|
||||||
|
|
|
@ -169,8 +169,18 @@ 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: bookData.title,
|
title,
|
||||||
|
subtitle,
|
||||||
description: bookData.description,
|
description: bookData.description,
|
||||||
authors,
|
authors,
|
||||||
series,
|
series,
|
||||||
|
|
35
src/main.ts
35
src/main.ts
|
@ -33,6 +33,7 @@ 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;
|
||||||
|
@ -127,23 +128,35 @@ export default class BookTrackerPlugin extends Plugin {
|
||||||
}
|
}
|
||||||
filePath += fileName + "." + extension;
|
filePath += fileName + "." + extension;
|
||||||
|
|
||||||
const existingFile = this.app.vault.getFileByPath(filePath);
|
let file = this.app.vault.getFileByPath(filePath);
|
||||||
if (existingFile) {
|
if (file) {
|
||||||
if (this.settings.overwriteExistingCovers || overwrite) {
|
if (this.settings.overwriteExistingCovers || overwrite) {
|
||||||
await this.app.vault.modifyBinary(
|
await this.app.vault.modifyBinary(file, response.arrayBuffer);
|
||||||
existingFile,
|
|
||||||
response.arrayBuffer
|
|
||||||
);
|
|
||||||
} else {
|
} else {
|
||||||
new Notice("Cover image already exists: " + filePath);
|
new Notice("Cover image already exists: " + filePath);
|
||||||
|
return file;
|
||||||
}
|
}
|
||||||
return existingFile;
|
} else {
|
||||||
|
file = await this.app.vault.createBinary(
|
||||||
|
filePath,
|
||||||
|
response.arrayBuffer
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return await this.app.vault.createBinary(
|
await compressImage(this.app, file, {
|
||||||
filePath,
|
height: 400,
|
||||||
response.arrayBuffer
|
quality: 0.8,
|
||||||
);
|
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> {
|
||||||
|
|
|
@ -14,6 +14,7 @@ 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;
|
||||||
|
|
|
@ -12,8 +12,10 @@
|
||||||
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);
|
||||||
|
@ -88,7 +90,7 @@
|
||||||
{#if metadata[letter]}
|
{#if metadata[letter]}
|
||||||
{@const meta = metadata[letter]}
|
{@const meta = metadata[letter]}
|
||||||
<OpenFileLink file={meta.file}>
|
<OpenFileLink file={meta.file}>
|
||||||
<BookCover app={plugin.app} book={meta.book} />
|
<BookCover book={meta.book} size="fill" />
|
||||||
</OpenFileLink>
|
</OpenFileLink>
|
||||||
{:else}
|
{:else}
|
||||||
<div class="placeholder">{letter}</div>
|
<div class="placeholder">{letter}</div>
|
||||||
|
@ -139,13 +141,6 @@
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,8 +19,10 @@
|
||||||
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);
|
||||||
|
@ -228,8 +230,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>
|
||||||
|
@ -298,6 +300,7 @@
|
||||||
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);
|
||||||
|
@ -309,6 +312,7 @@
|
||||||
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);
|
||||||
|
@ -316,11 +320,10 @@
|
||||||
height: 100%;
|
height: 100%;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
|
||||||
:global(img) {
|
:global(a) {
|
||||||
border-radius: var(--radius-l);
|
display: block;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
object-fit: cover;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -46,7 +46,6 @@ 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),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -58,7 +57,6 @@ 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),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -70,7 +68,6 @@ 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({
|
||||||
|
|
|
@ -138,15 +138,11 @@
|
||||||
<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
|
<div class="chart pie">
|
||||||
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"}
|
||||||
|
@ -197,7 +193,7 @@
|
||||||
margin: 0 auto 1rem;
|
margin: 0 auto 1rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
&.bar.responsive {
|
&.bar.horizontal {
|
||||||
height: 35rem;
|
height: 35rem;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,6 +17,10 @@
|
||||||
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);
|
||||||
|
@ -32,6 +36,11 @@
|
||||||
});
|
});
|
||||||
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());
|
||||||
|
@ -54,11 +63,11 @@
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
{#if view === "bookshelf"}
|
{#if view === "bookshelf"}
|
||||||
<BookshelfView {plugin} />
|
<BookshelfView />
|
||||||
{:else if view === "table"}
|
{:else if view === "table"}
|
||||||
<TableView {plugin} {settings} />
|
<TableView {settings} />
|
||||||
{:else if view === "details"}
|
{:else if view === "details"}
|
||||||
<DetailsView {plugin} {settings} />
|
<DetailsView {settings} />
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
|
@ -1,14 +1,49 @@
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import type { BookMetadata } from "@src/types";
|
import type { BookMetadata } from "@src/types";
|
||||||
import type { App } from "obsidian";
|
import { getAppContext } from "@ui/stores/app";
|
||||||
|
|
||||||
|
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?: number;
|
size?: "fill" | number | `${number}${NumberUnit}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
const { app, book, size }: Props = $props();
|
const { book, size = 500 }: 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));
|
||||||
|
@ -16,12 +51,54 @@
|
||||||
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>
|
||||||
|
|
||||||
<img src={coverSrc} alt={coverAlt} width={size} />
|
<div
|
||||||
|
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">
|
||||||
img {
|
:global(:root) {
|
||||||
border-radius: var(--radius-l);
|
--book-cover-aspect-ratio: 2 / 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
.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>
|
||||||
|
|
|
@ -12,10 +12,7 @@
|
||||||
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;
|
||||||
|
@ -34,7 +31,7 @@
|
||||||
books: BookData[];
|
books: BookData[];
|
||||||
}
|
}
|
||||||
|
|
||||||
const { plugin }: Props = $props();
|
const app = getAppContext();
|
||||||
|
|
||||||
const metadataStore = getMetadataContext();
|
const metadataStore = getMetadataContext();
|
||||||
|
|
||||||
|
@ -128,7 +125,7 @@
|
||||||
design={bookData.design}
|
design={bookData.design}
|
||||||
orientation="flat"
|
orientation="flat"
|
||||||
onClick={() =>
|
onClick={() =>
|
||||||
plugin.app.workspace
|
app.workspace
|
||||||
.getLeaf(false)
|
.getLeaf(false)
|
||||||
.openFile(bookData.file)}
|
.openFile(bookData.file)}
|
||||||
/>
|
/>
|
||||||
|
@ -143,8 +140,7 @@
|
||||||
color={book.color}
|
color={book.color}
|
||||||
design={book.design}
|
design={book.design}
|
||||||
orientation={book.orientation}
|
orientation={book.orientation}
|
||||||
onClick={() =>
|
onClick={() => app.workspace.getLeaf(false).openFile(book.file)}
|
||||||
plugin.app.workspace.getLeaf(false).openFile(book.file)}
|
|
||||||
/>
|
/>
|
||||||
{/if}
|
{/if}
|
||||||
{/each}
|
{/each}
|
||||||
|
|
|
@ -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 { plugin, settings }: Props = $props();
|
const { 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 app={plugin.app} book={meta.book} />
|
<BookCover 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,11 +59,18 @@
|
||||||
</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: {plugin.readingLog.getLastEntryForBook(
|
Current Page: {logEntry?.pagesReadTotal ?? 0}
|
||||||
meta.file.basename,
|
|
||||||
)?.pagesReadTotal ?? 0}
|
|
||||||
</p>
|
</p>
|
||||||
{/if}
|
{/if}
|
||||||
{#if settings.statusFilter === STATUS_READ}
|
{#if settings.statusFilter === STATUS_READ}
|
||||||
|
@ -144,16 +151,20 @@
|
||||||
background-color: var(--background-secondary);
|
background-color: var(--background-secondary);
|
||||||
border-radius: var(--radius-l);
|
border-radius: var(--radius-l);
|
||||||
|
|
||||||
:global(img) {
|
:global(.book-cover) {
|
||||||
border-radius: var(--radius-l);
|
width: 100%;
|
||||||
|
height: auto;
|
||||||
|
max-height: 100%;
|
||||||
max-width: 30%;
|
max-width: 30%;
|
||||||
}
|
}
|
||||||
|
|
||||||
@container (width < 600px) {
|
@container (width < 700px) {
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
|
||||||
:global(img) {
|
:global(.book-cover) {
|
||||||
|
height: 100%;
|
||||||
|
width: auto;
|
||||||
max-height: 30rem;
|
max-height: 30rem;
|
||||||
max-width: 100%;
|
max-width: 100%;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
<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";
|
||||||
|
@ -8,11 +7,10 @@
|
||||||
import BookCover from "./BookCover.svelte";
|
import BookCover from "./BookCover.svelte";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
plugin: BookTrackerPlugin;
|
|
||||||
settings: ShelfSettings;
|
settings: ShelfSettings;
|
||||||
}
|
}
|
||||||
|
|
||||||
const { plugin, settings }: Props = $props();
|
const { settings }: Props = $props();
|
||||||
|
|
||||||
const metadataStore = getMetadataContext();
|
const metadataStore = getMetadataContext();
|
||||||
</script>
|
</script>
|
||||||
|
@ -38,7 +36,7 @@
|
||||||
{#each metadataStore.metadata as meta}
|
{#each metadataStore.metadata as meta}
|
||||||
<tr>
|
<tr>
|
||||||
<td class="cover">
|
<td class="cover">
|
||||||
<BookCover app={plugin.app} book={meta.book} size={50} />
|
<BookCover book={meta.book} size={50} />
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
<OpenFileLink file={meta.file}>
|
<OpenFileLink file={meta.file}>
|
||||||
|
@ -52,7 +50,9 @@
|
||||||
{meta.book.seriesTitle}
|
{meta.book.seriesTitle}
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
{meta.book.seriesPosition}
|
{#if meta.book.seriesTitle !== ""}
|
||||||
|
#{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,6 +94,10 @@
|
||||||
&.cover {
|
&.cover {
|
||||||
padding: 0;
|
padding: 0;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
|
|
||||||
|
:global(.book-cover) {
|
||||||
|
margin: 0 auto;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -176,6 +176,7 @@
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
color: inherit;
|
||||||
}
|
}
|
||||||
|
|
||||||
.book-title {
|
.book-title {
|
||||||
|
|
|
@ -12,7 +12,6 @@
|
||||||
topN?: number;
|
topN?: number;
|
||||||
unit?: string;
|
unit?: string;
|
||||||
unitPlural?: string;
|
unitPlural?: string;
|
||||||
responsive?: boolean;
|
|
||||||
color?: "rainbow" | ColorName;
|
color?: "rainbow" | ColorName;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -23,7 +22,6 @@
|
||||||
topN,
|
topN,
|
||||||
unit,
|
unit,
|
||||||
unitPlural,
|
unitPlural,
|
||||||
responsive,
|
|
||||||
color,
|
color,
|
||||||
}: Props = $props();
|
}: Props = $props();
|
||||||
|
|
||||||
|
@ -107,8 +105,8 @@
|
||||||
},
|
},
|
||||||
options: {
|
options: {
|
||||||
indexAxis: horizontal ? "y" : "x",
|
indexAxis: horizontal ? "y" : "x",
|
||||||
responsive,
|
responsive: true,
|
||||||
maintainAspectRatio: !responsive,
|
maintainAspectRatio: !horizontal,
|
||||||
plugins: {
|
plugins: {
|
||||||
tooltip: {
|
tooltip: {
|
||||||
yAlign: horizontal ? "center" : "bottom",
|
yAlign: horizontal ? "center" : "bottom",
|
||||||
|
|
|
@ -99,6 +99,7 @@
|
||||||
datasets,
|
datasets,
|
||||||
},
|
},
|
||||||
options: {
|
options: {
|
||||||
|
responsive: true,
|
||||||
scales: {
|
scales: {
|
||||||
y: {
|
y: {
|
||||||
type: "linear",
|
type: "linear",
|
||||||
|
|
|
@ -14,12 +14,10 @@
|
||||||
groups?: PieGrouping[];
|
groups?: PieGrouping[];
|
||||||
unit?: string;
|
unit?: string;
|
||||||
unitPlural?: string;
|
unitPlural?: string;
|
||||||
responsive?: boolean;
|
|
||||||
color?: PieChartColor;
|
color?: PieChartColor;
|
||||||
};
|
};
|
||||||
|
|
||||||
const { property, groups, unit, unitPlural, responsive, color }: Props =
|
const { property, groups, unit, unitPlural, color }: Props = $props();
|
||||||
$props();
|
|
||||||
|
|
||||||
const store = createPropertyStore(property);
|
const store = createPropertyStore(property);
|
||||||
|
|
||||||
|
@ -114,7 +112,7 @@
|
||||||
return {
|
return {
|
||||||
type: "pie",
|
type: "pie",
|
||||||
data: { labels, datasets: [{ data, backgroundColor }] },
|
data: { labels, datasets: [{ data, backgroundColor }] },
|
||||||
options: { responsive },
|
options: { responsive: true },
|
||||||
} as ChartConfiguration;
|
} as ChartConfiguration;
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -1,20 +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, 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, app, id, value = $bindable() }: Props = $props();
|
let { name, description, id, value = $bindable() }: Props = $props();
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<Item {name} {description}>
|
<Item {name} {description}>
|
||||||
{#snippet control()}
|
{#snippet control()}
|
||||||
<FileSuggest {id} {app} asString bind:value />
|
<FileSuggest {id} asString bind:value />
|
||||||
{/snippet}
|
{/snippet}
|
||||||
</Item>
|
</Item>
|
||||||
|
|
|
@ -1,20 +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, 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, app, id, value = $bindable() }: Props = $props();
|
let { name, description, id, value = $bindable() }: Props = $props();
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<Item {name} {description}>
|
<Item {name} {description}>
|
||||||
{#snippet control()}
|
{#snippet control()}
|
||||||
<FolderSuggest {id} {app} asString bind:value />
|
<FolderSuggest {id} asString bind:value />
|
||||||
{/snippet}
|
{/snippet}
|
||||||
</Item>
|
</Item>
|
||||||
|
|
|
@ -1,18 +1,15 @@
|
||||||
<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,
|
||||||
|
@ -23,6 +20,6 @@
|
||||||
|
|
||||||
<Item {name} {description}>
|
<Item {name} {description}>
|
||||||
{#snippet control()}
|
{#snippet control()}
|
||||||
<PropertySuggest {app} {id} asString bind:value {accepts} />
|
<PropertySuggest {id} asString bind:value {accepts} />
|
||||||
{/snippet}
|
{/snippet}
|
||||||
</Item>
|
</Item>
|
||||||
|
|
|
@ -1,10 +1,10 @@
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import type { App, TFile } from "obsidian";
|
import type { 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,7 +15,6 @@
|
||||||
};
|
};
|
||||||
|
|
||||||
let {
|
let {
|
||||||
app,
|
|
||||||
id,
|
id,
|
||||||
asString,
|
asString,
|
||||||
property = "path",
|
property = "path",
|
||||||
|
@ -24,6 +23,7 @@
|
||||||
disabled,
|
disabled,
|
||||||
onSelected,
|
onSelected,
|
||||||
}: Props = $props();
|
}: Props = $props();
|
||||||
|
const app = getAppContext();
|
||||||
|
|
||||||
let items: Item<TFile | string>[] = $state([]);
|
let items: Item<TFile | string>[] = $state([]);
|
||||||
|
|
||||||
|
|
|
@ -1,10 +1,10 @@
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { TFolder, type App } from "obsidian";
|
import { TFolder } 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,7 +15,6 @@
|
||||||
};
|
};
|
||||||
|
|
||||||
let {
|
let {
|
||||||
app,
|
|
||||||
id,
|
id,
|
||||||
asString,
|
asString,
|
||||||
property = "path",
|
property = "path",
|
||||||
|
@ -24,6 +23,7 @@
|
||||||
disabled,
|
disabled,
|
||||||
onSelected,
|
onSelected,
|
||||||
}: Props = $props();
|
}: Props = $props();
|
||||||
|
const app = getAppContext();
|
||||||
|
|
||||||
let items: Item<TFolder | string>[] = $state([]);
|
let items: Item<TFolder | string>[] = $state([]);
|
||||||
|
|
||||||
|
|
|
@ -1,26 +1,25 @@
|
||||||
<script module lang="ts">
|
<script module lang="ts">
|
||||||
export type Field = {
|
export type Property = {
|
||||||
name: string;
|
name: string;
|
||||||
type: string;
|
type: string;
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import type { App } from "obsidian";
|
import { getAppContext } from "@ui/stores/app";
|
||||||
|
|
||||||
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?: Field | string;
|
value?: Property | string;
|
||||||
accepts?: string[];
|
accepts?: string[];
|
||||||
disabled?: boolean;
|
disabled?: boolean;
|
||||||
onSelected?: (fieldOrName: Field | string) => void;
|
onSelected?: (propertyOrName: Property | string) => void;
|
||||||
};
|
};
|
||||||
|
|
||||||
let {
|
let {
|
||||||
app,
|
|
||||||
id,
|
id,
|
||||||
asString,
|
asString,
|
||||||
value = $bindable(),
|
value = $bindable(),
|
||||||
|
@ -28,8 +27,9 @@
|
||||||
disabled,
|
disabled,
|
||||||
onSelected,
|
onSelected,
|
||||||
}: Props = $props();
|
}: Props = $props();
|
||||||
|
const app = getAppContext();
|
||||||
|
|
||||||
let items: Item<Field | string>[] = $state([]);
|
let items: Item<Property | 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(
|
||||||
|
|
|
@ -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, { goodreads, onSearch });
|
super(app, GoodreadsSearchModalView, { app, goodreads, onSearch });
|
||||||
}
|
}
|
||||||
|
|
||||||
static createAndOpen(
|
static createAndOpen(
|
||||||
|
|
|
@ -1,12 +1,16 @@
|
||||||
<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 { goodreads, onSearch }: Props = $props();
|
let { app, goodreads, onSearch }: Props = $props();
|
||||||
|
setAppContext(app);
|
||||||
|
|
||||||
let query = $state("");
|
let query = $state("");
|
||||||
|
|
||||||
|
|
|
@ -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: { searchResult },
|
props: { app: this.app, searchResult },
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,11 +1,15 @@
|
||||||
<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 { searchResult }: Props = $props();
|
let { app, searchResult }: Props = $props();
|
||||||
|
setAppContext(app);
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="obt-goodreads-search-suggestion">
|
<div class="obt-goodreads-search-suggestion">
|
||||||
|
|
|
@ -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, { spiceConfigured, onSubmit });
|
super(app, RatingModalView, { app, spiceConfigured, onSubmit });
|
||||||
}
|
}
|
||||||
|
|
||||||
static createAndOpen(
|
static createAndOpen(
|
||||||
|
|
|
@ -1,12 +1,16 @@
|
||||||
<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 { spiceConfigured = false, onSubmit }: Props = $props();
|
let { app, spiceConfigured = false, onSubmit }: Props = $props();
|
||||||
|
setAppContext(app);
|
||||||
|
|
||||||
let rating = $state(0);
|
let rating = $state(0);
|
||||||
let spice = $state(0);
|
let spice = $state(0);
|
||||||
|
|
|
@ -10,6 +10,7 @@
|
||||||
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";
|
||||||
|
|
||||||
|
@ -20,6 +21,7 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
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);
|
||||||
|
@ -80,7 +82,6 @@
|
||||||
<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}
|
||||||
|
|
|
@ -10,7 +10,7 @@ export class ReadingProgressModal extends SvelteModal<
|
||||||
pageCount: number,
|
pageCount: number,
|
||||||
onSubmit: (pageNumber: number) => void = () => {}
|
onSubmit: (pageNumber: number) => void = () => {}
|
||||||
) {
|
) {
|
||||||
super(app, ReadingProgressModalView, { pageCount, onSubmit });
|
super(app, ReadingProgressModalView, { app, pageCount, onSubmit });
|
||||||
}
|
}
|
||||||
|
|
||||||
static createAndOpen(app: App, pageCount: number): Promise<number> {
|
static createAndOpen(app: App, pageCount: number): Promise<number> {
|
||||||
|
|
|
@ -1,11 +1,14 @@
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { Notice } from "obsidian";
|
import { setAppContext } from "@ui/stores/app";
|
||||||
|
import { App, Notice } from "obsidian";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
|
app: App;
|
||||||
pageCount: number;
|
pageCount: number;
|
||||||
onSubmit: (pageNumber: number) => void;
|
onSubmit: (pageNumber: number) => void;
|
||||||
}
|
}
|
||||||
let { pageCount, onSubmit }: Props = $props();
|
let { app, 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");
|
||||||
|
|
|
@ -9,13 +9,14 @@
|
||||||
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();
|
||||||
const { app } = plugin;
|
setAppContext(plugin.app);
|
||||||
|
|
||||||
const settingsStore = createSettings(plugin);
|
const settingsStore = createSettings(plugin);
|
||||||
|
|
||||||
|
@ -168,21 +169,18 @@
|
||||||
<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."
|
||||||
|
@ -196,7 +194,6 @@
|
||||||
/>
|
/>
|
||||||
<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."
|
||||||
|
@ -218,7 +215,6 @@
|
||||||
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."
|
||||||
|
@ -240,7 +236,6 @@
|
||||||
<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}
|
||||||
|
|
|
@ -32,6 +32,13 @@ 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>(
|
||||||
|
@ -41,6 +48,7 @@ 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[];
|
||||||
|
@ -64,7 +72,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) {
|
if (filterYear !== ALL_TIME && !disableAllFiltering) {
|
||||||
if (date.year() !== filterYear) {
|
if (date.year() !== filterYear) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,6 +16,15 @@ 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) {}
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,72 @@
|
||||||
|
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());
|
||||||
|
}
|
|
@ -2,5 +2,9 @@
|
||||||
"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"
|
||||||
}
|
}
|
Loading…
Reference in New Issue