generated from tpl/obsidian-sample-plugin
			Add actions to reading log viewer
This commit is contained in:
		
							parent
							
								
									a2767f4595
								
							
						
					
					
						commit
						8ebed95fda
					
				| 
						 | 
				
			
			@ -3,6 +3,14 @@ import process from "process";
 | 
			
		|||
import builtins from "builtin-modules";
 | 
			
		||||
import esbuildSvelte from "esbuild-svelte";
 | 
			
		||||
import { sveltePreprocess } from "svelte-preprocess";
 | 
			
		||||
import dotenv from "dotenv";
 | 
			
		||||
import dotenvExpand from "dotenv-expand";
 | 
			
		||||
import manifest from "./manifest.json" with { type: "json" };
 | 
			
		||||
import fs from "fs/promises";
 | 
			
		||||
import path from "path";
 | 
			
		||||
 | 
			
		||||
const env = dotenv.config();
 | 
			
		||||
dotenvExpand.expand(env);
 | 
			
		||||
 | 
			
		||||
const banner = `/*
 | 
			
		||||
THIS IS A GENERATED/BUNDLED FILE BY ESBUILD
 | 
			
		||||
| 
						 | 
				
			
			@ -12,6 +20,21 @@ if you want to view the source, please visit the github repository of this plugi
 | 
			
		|||
 | 
			
		||||
const prod = process.argv[2] === "production";
 | 
			
		||||
 | 
			
		||||
let outDir = "dist";
 | 
			
		||||
if (!prod) {
 | 
			
		||||
	if (!process.env.OBSIDIAN_PLUGIN_DIR) {
 | 
			
		||||
		console.log(
 | 
			
		||||
			"Set OBSIDIAN_PLUGIN_DIR in the .env file to plugin directory to copy files to."
 | 
			
		||||
		);
 | 
			
		||||
	} else {
 | 
			
		||||
		outDir = process.env.OBSIDIAN_PLUGIN_DIR + "/" + manifest.id + "-dev";
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
if (outDir[0] === "~") {
 | 
			
		||||
	outDir = path.join(process.env.HOME, outDir.slice(1));
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const context = await esbuild.context({
 | 
			
		||||
	banner: {
 | 
			
		||||
		js: banner,
 | 
			
		||||
| 
						 | 
				
			
			@ -21,11 +44,32 @@ const context = await esbuild.context({
 | 
			
		|||
	plugins: [
 | 
			
		||||
		esbuildSvelte({
 | 
			
		||||
			preprocess: sveltePreprocess(),
 | 
			
		||||
			compilerOptions: {
 | 
			
		||||
				css: "injected",
 | 
			
		||||
				dev: !prod,
 | 
			
		||||
			},
 | 
			
		||||
			compilerOptions: { dev: !prod },
 | 
			
		||||
		}),
 | 
			
		||||
		{
 | 
			
		||||
			name: 'copy-plugin',
 | 
			
		||||
			setup(build) {
 | 
			
		||||
				build.onEnd(async () => {
 | 
			
		||||
					try {
 | 
			
		||||
						await fs.copyFile(new URL('./manifest.json', import.meta.url), path.resolve(outDir, 'manifest.json'));
 | 
			
		||||
					} catch (e) {
 | 
			
		||||
						console.error('Failed to rename file:', e);
 | 
			
		||||
					}
 | 
			
		||||
				});
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			name: 'rename-plugin',
 | 
			
		||||
			setup(build) {
 | 
			
		||||
				build.onEnd(async () => {
 | 
			
		||||
					try {
 | 
			
		||||
						await fs.rename(path.resolve(outDir, 'main.css'), path.resolve(outDir, 'styles.css'));
 | 
			
		||||
					} catch (e) {
 | 
			
		||||
						console.error('Failed to rename file:', e);
 | 
			
		||||
					}
 | 
			
		||||
				});
 | 
			
		||||
			},
 | 
			
		||||
		}
 | 
			
		||||
	],
 | 
			
		||||
	external: [
 | 
			
		||||
		"obsidian",
 | 
			
		||||
| 
						 | 
				
			
			@ -48,8 +92,8 @@ const context = await esbuild.context({
 | 
			
		|||
	logLevel: "info",
 | 
			
		||||
	sourcemap: prod ? false : "inline",
 | 
			
		||||
	treeShaking: true,
 | 
			
		||||
	outfile: "main.js",
 | 
			
		||||
	minify: prod,
 | 
			
		||||
	outdir: outDir,
 | 
			
		||||
	minify: prod
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
if (prod) {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										11
									
								
								package.json
								
								
								
								
							
							
						
						
									
										11
									
								
								package.json
								
								
								
								
							| 
						 | 
				
			
			@ -4,8 +4,8 @@
 | 
			
		|||
	"description": "This is a sample plugin for Obsidian (https://obsidian.md)",
 | 
			
		||||
	"main": "main.js",
 | 
			
		||||
	"scripts": {
 | 
			
		||||
		"dev": "vite build --watch --mode=development",
 | 
			
		||||
		"build": "vite build",
 | 
			
		||||
		"dev": "node esbuild.config.mjs",
 | 
			
		||||
		"build": "tsc -noEmit -skipLibCheck && node esbuild.config.mjs production",
 | 
			
		||||
		"version": "node version-bump.mjs && git add manifest.json versions.json",
 | 
			
		||||
		"svelte-check": "svelte-check --tsconfig tsconfig.json"
 | 
			
		||||
	},
 | 
			
		||||
| 
						 | 
				
			
			@ -13,13 +13,10 @@
 | 
			
		|||
	"author": "",
 | 
			
		||||
	"license": "MIT",
 | 
			
		||||
	"devDependencies": {
 | 
			
		||||
		"@guanghechen/rollup-plugin-copy": "^6.0.7",
 | 
			
		||||
		"@popperjs/core": "^2.11.8",
 | 
			
		||||
		"@sveltejs/vite-plugin-svelte": "^5.1.0",
 | 
			
		||||
		"@types/node": "^24.0.6",
 | 
			
		||||
		"@typescript-eslint/eslint-plugin": "5.29.0",
 | 
			
		||||
		"@typescript-eslint/parser": "5.29.0",
 | 
			
		||||
		"bits-ui": "^2.8.10",
 | 
			
		||||
		"builtin-modules": "3.3.0",
 | 
			
		||||
		"dotenv": "^17.0.0",
 | 
			
		||||
		"dotenv-expand": "^12.0.2",
 | 
			
		||||
| 
						 | 
				
			
			@ -29,12 +26,12 @@
 | 
			
		|||
		"lucide-svelte": "^0.525.0",
 | 
			
		||||
		"npm-run-all": "^4.1.5",
 | 
			
		||||
		"obsidian": "latest",
 | 
			
		||||
		"runed": "^0.29.1",
 | 
			
		||||
		"sass": "^1.89.2",
 | 
			
		||||
		"svelte": "^5.34.8",
 | 
			
		||||
		"svelte-check": "^4.2.2",
 | 
			
		||||
		"svelte-preprocess": "^6.0.3",
 | 
			
		||||
		"tslib": "2.4.0",
 | 
			
		||||
		"typescript": "5.0.4",
 | 
			
		||||
		"vite": "^6.0.0"
 | 
			
		||||
		"typescript": "5.0.4"
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										738
									
								
								pnpm-lock.yaml
								
								
								
								
							
							
						
						
									
										738
									
								
								pnpm-lock.yaml
								
								
								
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							| 
						 | 
				
			
			@ -1,46 +1,48 @@
 | 
			
		|||
<script lang="ts">
 | 
			
		||||
	import { searchBooks, type SearchResult } from "@data-sources/goodreads";
 | 
			
		||||
 | 
			
		||||
    interface Props {
 | 
			
		||||
        onSearch: (results: SearchResult[]) => void;
 | 
			
		||||
        onError: (error: Error) => void;
 | 
			
		||||
    }
 | 
			
		||||
	interface Props {
 | 
			
		||||
		onSearch: (query: string, results: SearchResult[]) => void;
 | 
			
		||||
		onError: (error: Error) => void;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
    let {
 | 
			
		||||
        onSearch,
 | 
			
		||||
        onError
 | 
			
		||||
    }: Props = $props();
 | 
			
		||||
	let { onSearch, onError }: Props = $props();
 | 
			
		||||
 | 
			
		||||
    let query = $state("");
 | 
			
		||||
	let query = $state("");
 | 
			
		||||
 | 
			
		||||
    async function onkeydown(event: KeyboardEvent) {
 | 
			
		||||
        if (event.key === "Enter") {
 | 
			
		||||
            event.preventDefault();
 | 
			
		||||
            try {
 | 
			
		||||
                const results = await searchBooks(query);
 | 
			
		||||
                if (results.length === 0) {
 | 
			
		||||
                    onError(new Error("No results found."));
 | 
			
		||||
                    return;
 | 
			
		||||
                }
 | 
			
		||||
                onSearch(results);
 | 
			
		||||
            } catch (error) {
 | 
			
		||||
                onError(error as Error);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
	async function onkeydown(event: KeyboardEvent) {
 | 
			
		||||
		if (event.key === "Enter") {
 | 
			
		||||
			event.preventDefault();
 | 
			
		||||
			try {
 | 
			
		||||
				const results = await searchBooks(query);
 | 
			
		||||
				if (results.length === 0) {
 | 
			
		||||
					onError(new Error("No results found."));
 | 
			
		||||
					return;
 | 
			
		||||
				}
 | 
			
		||||
				onSearch(query, results);
 | 
			
		||||
			} catch (error) {
 | 
			
		||||
				onError(error as Error);
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<div class="obt-goodreads-search">
 | 
			
		||||
    <h2>Goodreads Search</h2>
 | 
			
		||||
    <input type="text" placeholder="Search for books..." bind:value={query} {onkeydown} />
 | 
			
		||||
	<h2>Goodreads Search</h2>
 | 
			
		||||
	<input
 | 
			
		||||
		type="text"
 | 
			
		||||
		placeholder="Search for books..."
 | 
			
		||||
		bind:value={query}
 | 
			
		||||
		{onkeydown}
 | 
			
		||||
	/>
 | 
			
		||||
</div>
 | 
			
		||||
 | 
			
		||||
<style lang="scss">
 | 
			
		||||
    .obt-goodreads-search {
 | 
			
		||||
        padding-bottom: var(--size-4-4);
 | 
			
		||||
        
 | 
			
		||||
        input {
 | 
			
		||||
            width: 100%;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
</style>
 | 
			
		||||
	.obt-goodreads-search {
 | 
			
		||||
		padding-bottom: var(--size-4-4);
 | 
			
		||||
 | 
			
		||||
		input {
 | 
			
		||||
			width: 100%;
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
</style>
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,5 +1,4 @@
 | 
			
		|||
<script lang="ts">
 | 
			
		||||
	import { unstable_RatingGroup as RatingGroup } from "bits-ui";
 | 
			
		||||
	import { Star, StarHalf } from "lucide-svelte";
 | 
			
		||||
 | 
			
		||||
	interface Props {
 | 
			
		||||
| 
						 | 
				
			
			@ -7,53 +6,104 @@
 | 
			
		|||
		name?: string;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	const maxV = 5;
 | 
			
		||||
 | 
			
		||||
	let { value = $bindable(), name }: Props = $props();
 | 
			
		||||
 | 
			
		||||
	let ctrl: HTMLElement | null = $state(null);
 | 
			
		||||
	let w = $state(0);
 | 
			
		||||
	let h = $state(0);
 | 
			
		||||
 | 
			
		||||
	$effect(() => {
 | 
			
		||||
		if (ctrl) {
 | 
			
		||||
			ctrl.style.width = `${w}px`;
 | 
			
		||||
			ctrl.style.height = `${h}px`;
 | 
			
		||||
		}
 | 
			
		||||
	});
 | 
			
		||||
 | 
			
		||||
	let hovering = $state(false);
 | 
			
		||||
	let valueHover = $state(0);
 | 
			
		||||
 | 
			
		||||
	let displayVal = $derived(hovering ? valueHover : (value ?? 0));
 | 
			
		||||
	let items = $derived.by(() => {
 | 
			
		||||
		const full = Number.isInteger(displayVal);
 | 
			
		||||
		return Array.from({ length: maxV }, (_, i) => i + 1).map((index) => ({
 | 
			
		||||
			index,
 | 
			
		||||
			state:
 | 
			
		||||
				index <= Math.ceil(displayVal)
 | 
			
		||||
					? full || index != Math.ceil(displayVal)
 | 
			
		||||
						? "active"
 | 
			
		||||
						: "partial"
 | 
			
		||||
					: "inactive",
 | 
			
		||||
		}));
 | 
			
		||||
	});
 | 
			
		||||
 | 
			
		||||
	$effect(() => {
 | 
			
		||||
		console.log(items);
 | 
			
		||||
	});
 | 
			
		||||
 | 
			
		||||
	function calcSliderPos(e: MouseEvent) {
 | 
			
		||||
		return (e.offsetX / (e.target as HTMLElement).clientWidth) * maxV;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	function onclick() {
 | 
			
		||||
		value = valueHover;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	function onmouseout() {
 | 
			
		||||
		hovering = false;
 | 
			
		||||
		valueHover = 0;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	function onmousemove(e: MouseEvent) {
 | 
			
		||||
		hovering = true;
 | 
			
		||||
		valueHover = Math.ceil(calcSliderPos(e) * 2) / 2;
 | 
			
		||||
	}
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<div class="rating-input">
 | 
			
		||||
	<RatingGroup.Root
 | 
			
		||||
		{name}
 | 
			
		||||
		bind:value
 | 
			
		||||
		min={0}
 | 
			
		||||
		max={5}
 | 
			
		||||
		allowHalf
 | 
			
		||||
		class="rating-group"
 | 
			
		||||
	>
 | 
			
		||||
		{#snippet children({ items })}
 | 
			
		||||
			{#each items as item (item.index)}
 | 
			
		||||
				<RatingGroup.Item index={item.index} class="rating-item">
 | 
			
		||||
					{#if item.state === "inactive"}
 | 
			
		||||
						<Star fill="var(--interactive-normal)" />
 | 
			
		||||
					{:else if item.state === "active"}
 | 
			
		||||
						<Star fill="var(--interactive-accent)" />
 | 
			
		||||
					{:else if item.state === "partial"}
 | 
			
		||||
						<Star fill="var(--interactive-normal)" />
 | 
			
		||||
						<StarHalf fill="var(--interactive-accent)" />
 | 
			
		||||
					{/if}
 | 
			
		||||
				</RatingGroup.Item>
 | 
			
		||||
			{/each}
 | 
			
		||||
		{/snippet}
 | 
			
		||||
	</RatingGroup.Root>
 | 
			
		||||
<!-- svelte-ignore a11y_no_static_element_interactions -->
 | 
			
		||||
<!-- svelte-ignore a11y_click_events_have_key_events -->
 | 
			
		||||
<!-- svelte-ignore a11y_no_static_element_interactions -->
 | 
			
		||||
<!-- svelte-ignore a11y_mouse_events_have_key_events -->
 | 
			
		||||
<div class="rating-input" {onclick} {onmouseout}>
 | 
			
		||||
	<!-- svelte-ignore a11y_no_static_element_interactions -->
 | 
			
		||||
	<div class="ctrl" {onmousemove} bind:this={ctrl}></div>
 | 
			
		||||
	<div class="cont m-1" bind:clientWidth={w} bind:clientHeight={h}>
 | 
			
		||||
		{#each items as item (item.index)}
 | 
			
		||||
			<div class="rating-item">
 | 
			
		||||
				{#if item.state === "inactive"}
 | 
			
		||||
					<Star fill="var(--interactive-normal)" />
 | 
			
		||||
				{:else if item.state === "active"}
 | 
			
		||||
					<Star fill="var(--interactive-accent)" />
 | 
			
		||||
				{:else if item.state === "partial"}
 | 
			
		||||
					<Star fill="var(--interactive-normal)" />
 | 
			
		||||
					<StarHalf fill="var(--interactive-accent)" />
 | 
			
		||||
				{/if}
 | 
			
		||||
			</div>
 | 
			
		||||
		{/each}
 | 
			
		||||
	</div>
 | 
			
		||||
</div>
 | 
			
		||||
 | 
			
		||||
<style lang="scss">
 | 
			
		||||
	.rating-input {
 | 
			
		||||
		display: flex;
 | 
			
		||||
		align-items: center;
 | 
			
		||||
		gap: 0.5rem;
 | 
			
		||||
		cursor: pointer;
 | 
			
		||||
 | 
			
		||||
		.ctrl {
 | 
			
		||||
			position: absolute;
 | 
			
		||||
			z-index: 2;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		.cont {
 | 
			
		||||
			display: flex;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		:global(svg) {
 | 
			
		||||
			position: absolute;
 | 
			
		||||
			width: var(--size-4-16);
 | 
			
		||||
			height: var(--size-4-16);
 | 
			
		||||
			width: 100%;
 | 
			
		||||
			height: 100%;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		:global(.rating-group) {
 | 
			
		||||
			display: flex;
 | 
			
		||||
			gap: 0.25rem;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		:global(.rating-item) {
 | 
			
		||||
		.rating-item {
 | 
			
		||||
			position: relative;
 | 
			
		||||
			width: var(--size-4-16);
 | 
			
		||||
			height: var(--size-4-16);
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -0,0 +1,102 @@
 | 
			
		|||
<script lang="ts">
 | 
			
		||||
	import type { ReadingLogEntry } from "@src/types";
 | 
			
		||||
	interface Props {
 | 
			
		||||
		entry: ReadingLogEntry;
 | 
			
		||||
		onSubmit: (entry: ReadingLogEntry) => void;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	let { entry, onSubmit }: Props = $props();
 | 
			
		||||
 | 
			
		||||
	let pagesRead = $state(entry.pagesRead);
 | 
			
		||||
	let pagesReadTotal = $state(entry.pagesReadTotal);
 | 
			
		||||
	let pagesRemaining = $state(entry.pagesRemaining);
 | 
			
		||||
 | 
			
		||||
	// Source: https://github.com/sveltejs/svelte/discussions/14220#discussioncomment-11188219
 | 
			
		||||
	function watch<T>(
 | 
			
		||||
		getter: () => T,
 | 
			
		||||
		effectCallback: (t: T | undefined) => void,
 | 
			
		||||
	) {
 | 
			
		||||
		let previous: T | undefined = undefined;
 | 
			
		||||
 | 
			
		||||
		$effect(() => {
 | 
			
		||||
			const current = getter(); // add $state.snapshot for deep reactivity
 | 
			
		||||
			const cleanup = effectCallback(previous);
 | 
			
		||||
			previous = current;
 | 
			
		||||
 | 
			
		||||
			return cleanup;
 | 
			
		||||
		});
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	watch(
 | 
			
		||||
		() => pagesRead,
 | 
			
		||||
		(prev) => {
 | 
			
		||||
			if (prev !== pagesRead && prev !== undefined) {
 | 
			
		||||
				const diff = pagesRead - prev;
 | 
			
		||||
				pagesReadTotal = pagesReadTotal + diff;
 | 
			
		||||
				pagesRemaining = pagesRemaining - diff;
 | 
			
		||||
			}
 | 
			
		||||
		},
 | 
			
		||||
	);
 | 
			
		||||
 | 
			
		||||
	function onsubmit(ev: SubmitEvent) {
 | 
			
		||||
		ev.preventDefault();
 | 
			
		||||
		onSubmit({
 | 
			
		||||
			...entry,
 | 
			
		||||
			pagesRead,
 | 
			
		||||
			pagesReadTotal,
 | 
			
		||||
			pagesRemaining,
 | 
			
		||||
		});
 | 
			
		||||
	}
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<div class="obt-reading-log-entry-editor">
 | 
			
		||||
	<h2>Edit Reading Log Entry</h2>
 | 
			
		||||
	<form {onsubmit}>
 | 
			
		||||
		<div class="fields">
 | 
			
		||||
			<label for="pagesRead">Pages Read</label>
 | 
			
		||||
			<input
 | 
			
		||||
				type="number"
 | 
			
		||||
				name="pagesRead"
 | 
			
		||||
				id="pagesRead"
 | 
			
		||||
				bind:value={pagesRead}
 | 
			
		||||
			/>
 | 
			
		||||
			<label for="pagesReadTotal">Pages Read Total</label>
 | 
			
		||||
			<input
 | 
			
		||||
				type="number"
 | 
			
		||||
				name="pagesReadTotal"
 | 
			
		||||
				id="pagesReadTotal"
 | 
			
		||||
				bind:value={pagesReadTotal}
 | 
			
		||||
			/>
 | 
			
		||||
			<label for="pagesRemaining">Pages Remaining</label>
 | 
			
		||||
			<input
 | 
			
		||||
				type="number"
 | 
			
		||||
				name="pagesRemaining"
 | 
			
		||||
				id="pagesRemaining"
 | 
			
		||||
				bind:value={pagesRemaining}
 | 
			
		||||
			/>
 | 
			
		||||
		</div>
 | 
			
		||||
		<button type="submit">Submit</button>
 | 
			
		||||
	</form>
 | 
			
		||||
</div>
 | 
			
		||||
 | 
			
		||||
<style lang="scss">
 | 
			
		||||
	.obt-reading-log-entry-editor {
 | 
			
		||||
		margin-bottom: var(--size-4-4);
 | 
			
		||||
 | 
			
		||||
		h2 {
 | 
			
		||||
			margin-bottom: var(--size-4-6);
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		form {
 | 
			
		||||
			display: flex;
 | 
			
		||||
			flex-direction: column;
 | 
			
		||||
			gap: var(--size-4-4);
 | 
			
		||||
 | 
			
		||||
			.fields {
 | 
			
		||||
				display: grid;
 | 
			
		||||
				grid-template-columns: max-content 1fr;
 | 
			
		||||
				gap: var(--size-4-2);
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
</style>
 | 
			
		||||
| 
						 | 
				
			
			@ -1,6 +1,9 @@
 | 
			
		|||
<script lang="ts">
 | 
			
		||||
	import type { ReadingLog } from "@utils/storage";
 | 
			
		||||
	import type { ReadingLogEntry } from "@src/types";
 | 
			
		||||
	import type { App } from "obsidian";
 | 
			
		||||
	import { Edit, Trash } from "lucide-svelte";
 | 
			
		||||
	import { ReadingLogEntryEditModal } from "@views/reading-log-entry-edit-modal";
 | 
			
		||||
 | 
			
		||||
	const ALL_TIME = "ALL_TIME";
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -22,10 +25,23 @@
 | 
			
		|||
		return moment(date).format("YYYY-MM-DD");
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	const entries = readingLog.getEntries();
 | 
			
		||||
	const years = [
 | 
			
		||||
	let entries = $state(
 | 
			
		||||
		readingLog.getEntries().map((entry, id) => ({
 | 
			
		||||
			...entry,
 | 
			
		||||
			id,
 | 
			
		||||
		})),
 | 
			
		||||
	);
 | 
			
		||||
 | 
			
		||||
	function reload() {
 | 
			
		||||
		entries = readingLog.getEntries().map((entry, id) => ({
 | 
			
		||||
			...entry,
 | 
			
		||||
			id,
 | 
			
		||||
		}));
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	const years = $derived([
 | 
			
		||||
		...new Set(entries.map((entry) => entry.createdAt.getFullYear())),
 | 
			
		||||
	];
 | 
			
		||||
	]);
 | 
			
		||||
 | 
			
		||||
	let selectedYear = $state(new Date().getFullYear().toString());
 | 
			
		||||
	const filterYear = $derived(
 | 
			
		||||
| 
						 | 
				
			
			@ -51,6 +67,22 @@
 | 
			
		|||
							entry.createdAt.getMonth() === filterMonth - 1),
 | 
			
		||||
				),
 | 
			
		||||
	);
 | 
			
		||||
 | 
			
		||||
	function editEntry(i: number, entry: ReadingLogEntry) {
 | 
			
		||||
		const modal = new ReadingLogEntryEditModal(app, entry);
 | 
			
		||||
		modal.once("submit", async (event) => {
 | 
			
		||||
			console.log(i, event);
 | 
			
		||||
			modal.close();
 | 
			
		||||
			await readingLog.updateEntry(i, event.entry);
 | 
			
		||||
			reload();
 | 
			
		||||
		});
 | 
			
		||||
		modal.open();
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	async function deleteEntry(i: number) {
 | 
			
		||||
		await readingLog.spliceEntry(i);
 | 
			
		||||
		reload();
 | 
			
		||||
	}
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<div class="obt-reading-log-viewer">
 | 
			
		||||
| 
						 | 
				
			
			@ -85,6 +117,7 @@
 | 
			
		|||
				<th class="book">Book</th>
 | 
			
		||||
				<th class="pages-read">Pages Read</th>
 | 
			
		||||
				<th class="percent-complete">Percent Complete</th>
 | 
			
		||||
				<th class="actions">Actions</th>
 | 
			
		||||
			</tr>
 | 
			
		||||
		</thead>
 | 
			
		||||
		<tbody>
 | 
			
		||||
| 
						 | 
				
			
			@ -102,6 +135,16 @@
 | 
			
		|||
								100,
 | 
			
		||||
						)}%
 | 
			
		||||
					</td>
 | 
			
		||||
					<td class="actions">
 | 
			
		||||
						<button onclick={() => editEntry(entry.id, entry)}>
 | 
			
		||||
							<Edit />
 | 
			
		||||
							<span>Edit</span>
 | 
			
		||||
						</button>
 | 
			
		||||
						<button onclick={() => deleteEntry(entry.id)}>
 | 
			
		||||
							<Trash />
 | 
			
		||||
							<span>Delete</span>
 | 
			
		||||
						</button>
 | 
			
		||||
					</td>
 | 
			
		||||
				</tr>
 | 
			
		||||
			{/each}
 | 
			
		||||
		</tbody>
 | 
			
		||||
| 
						 | 
				
			
			@ -109,6 +152,8 @@
 | 
			
		|||
</div>
 | 
			
		||||
 | 
			
		||||
<style lang="scss">
 | 
			
		||||
	@use "../styles/utils";
 | 
			
		||||
 | 
			
		||||
	.obt-reading-log-viewer {
 | 
			
		||||
		.year-filter:has(> option.all-time:checked) + .month-filter {
 | 
			
		||||
			display: none;
 | 
			
		||||
| 
						 | 
				
			
			@ -130,6 +175,10 @@
 | 
			
		|||
			td.percent-complete {
 | 
			
		||||
				text-align: center;
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			td.actions span {
 | 
			
		||||
				@include utils.visually-hidden;
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
</style>
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -0,0 +1,26 @@
 | 
			
		|||
// Source: https://gist.github.com/ffoodd/000b59f431e3e64e4ce1a24d5bb36034
 | 
			
		||||
 | 
			
		||||
@mixin visually-hidden {
 | 
			
		||||
	border: 0 !important;
 | 
			
		||||
	clip-path: inset(50%) !important; /* 2 */
 | 
			
		||||
	height: 1px !important;
 | 
			
		||||
	margin: -1px !important;
 | 
			
		||||
	overflow: hidden !important;
 | 
			
		||||
	padding: 0 !important;
 | 
			
		||||
	width: 1px !important;
 | 
			
		||||
	white-space: nowrap !important; /* 3 */
 | 
			
		||||
 | 
			
		||||
	&:not(caption) {
 | 
			
		||||
		position: absolute !important;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	* {
 | 
			
		||||
		overflow: hidden !important;
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@mixin visually-hidden-focusable {
 | 
			
		||||
	&:not(:focus, :focus-within) {
 | 
			
		||||
		@include visually-hidden;
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -21,3 +21,11 @@ export interface Book {
 | 
			
		|||
	isbn: string;
 | 
			
		||||
	isbn13: string;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export interface ReadingLogEntry {
 | 
			
		||||
	book: string;
 | 
			
		||||
	pagesRead: number;
 | 
			
		||||
	pagesReadTotal: number;
 | 
			
		||||
	pagesRemaining: number;
 | 
			
		||||
	createdAt: Date;
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -0,0 +1,59 @@
 | 
			
		|||
export abstract class Event {}
 | 
			
		||||
 | 
			
		||||
export type EventHandler<T> = (event: T) => void;
 | 
			
		||||
 | 
			
		||||
export type EventHandlerMap<TEventMap, T extends keyof TEventMap> = Record<
 | 
			
		||||
	T,
 | 
			
		||||
	EventHandler<TEventMap[T]>[]
 | 
			
		||||
>;
 | 
			
		||||
 | 
			
		||||
type Constructor = new (...args: any[]) => {};
 | 
			
		||||
 | 
			
		||||
export function EventEmitter<TEventMap, TBase extends Constructor>(
 | 
			
		||||
	Base: TBase
 | 
			
		||||
) {
 | 
			
		||||
	return class extends Base {
 | 
			
		||||
		private readonly listeners: EventHandlerMap<
 | 
			
		||||
			TEventMap,
 | 
			
		||||
			keyof TEventMap
 | 
			
		||||
		> = {} as EventHandlerMap<TEventMap, keyof TEventMap>;
 | 
			
		||||
 | 
			
		||||
		public on<T extends keyof TEventMap>(
 | 
			
		||||
			type: T,
 | 
			
		||||
			handler: EventHandler<TEventMap[T]>
 | 
			
		||||
		) {
 | 
			
		||||
			if (!this.listeners[type]) {
 | 
			
		||||
				this.listeners[type] = [];
 | 
			
		||||
			}
 | 
			
		||||
			this.listeners[type].push(handler);
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		public off<T extends keyof TEventMap>(
 | 
			
		||||
			type: T,
 | 
			
		||||
			handler: EventHandler<TEventMap[T]>
 | 
			
		||||
		) {
 | 
			
		||||
			if (this.listeners[type]) {
 | 
			
		||||
				this.listeners[type] = this.listeners[type].filter(
 | 
			
		||||
					(h) => h !== handler
 | 
			
		||||
				);
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		public once<T extends keyof TEventMap>(
 | 
			
		||||
			type: T,
 | 
			
		||||
			handler: EventHandler<TEventMap[T]>
 | 
			
		||||
		) {
 | 
			
		||||
			const wrappedHandler = (event: TEventMap[T]) => {
 | 
			
		||||
				handler(event);
 | 
			
		||||
				this.off(type, wrappedHandler);
 | 
			
		||||
			};
 | 
			
		||||
			this.on(type, wrappedHandler);
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		public emit<T extends keyof TEventMap>(type: T, event: TEventMap[T]) {
 | 
			
		||||
			if (this.listeners[type]) {
 | 
			
		||||
				this.listeners[type].forEach((handler) => handler(event));
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	};
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -1,4 +1,5 @@
 | 
			
		|||
import BookTrackerPlugin from "@src/main";
 | 
			
		||||
import type { ReadingLogEntry } from "@src/types";
 | 
			
		||||
import { App } from "obsidian";
 | 
			
		||||
 | 
			
		||||
export class Storage {
 | 
			
		||||
| 
						 | 
				
			
			@ -33,14 +34,6 @@ export class Storage {
 | 
			
		|||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
interface ReadingLogEntry {
 | 
			
		||||
	readonly book: string;
 | 
			
		||||
	readonly pagesRead: number;
 | 
			
		||||
	readonly pagesReadTotal: number;
 | 
			
		||||
	readonly pagesRemaining: number;
 | 
			
		||||
	readonly createdAt: Date;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export class ReadingLog {
 | 
			
		||||
	private entries: ReadingLogEntry[] = [];
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -120,6 +113,17 @@ export class ReadingLog {
 | 
			
		|||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	public async updateEntry(i: number, entry: ReadingLogEntry): Promise<void> {
 | 
			
		||||
		this.entries[i] = entry;
 | 
			
		||||
		await this.storeEntries();
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	public async spliceEntry(i: number): Promise<ReadingLogEntry> {
 | 
			
		||||
		const entry = this.entries.splice(i, 1);
 | 
			
		||||
		await this.storeEntries();
 | 
			
		||||
		return entry[0];
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	public async clearEntries(): Promise<void> {
 | 
			
		||||
		this.entries = [];
 | 
			
		||||
		await this.storeEntries();
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,29 +1,44 @@
 | 
			
		|||
import GoodreadsSearch from "@components/GoodreadsSearch.svelte";
 | 
			
		||||
import { type SearchResult } from "@data-sources/goodreads";
 | 
			
		||||
import { Event, EventEmitter } from "@utils/event";
 | 
			
		||||
import { App, Modal, Notice } from "obsidian";
 | 
			
		||||
import { mount, unmount } from "svelte";
 | 
			
		||||
 | 
			
		||||
export class GoodreadsSearchModal extends Modal {
 | 
			
		||||
	private component: ReturnType<typeof GoodreadsSearch> | undefined;
 | 
			
		||||
 | 
			
		||||
export class SearchEvent extends Event {
 | 
			
		||||
	constructor(
 | 
			
		||||
		app: App,
 | 
			
		||||
		private readonly onSearch: (error: any, results: SearchResult[]) => void
 | 
			
		||||
		public readonly query: string,
 | 
			
		||||
		public readonly results: SearchResult[]
 | 
			
		||||
	) {
 | 
			
		||||
		super(app);
 | 
			
		||||
		super();
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export class ErrorEvent extends Event {
 | 
			
		||||
	constructor(public readonly error: Error) {
 | 
			
		||||
		super();
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
interface GoodreadsSearchModalEventMap {
 | 
			
		||||
	search: SearchEvent;
 | 
			
		||||
	error: ErrorEvent;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export class GoodreadsSearchModal extends EventEmitter<
 | 
			
		||||
	GoodreadsSearchModalEventMap,
 | 
			
		||||
	typeof Modal
 | 
			
		||||
>(Modal) {
 | 
			
		||||
	private component: ReturnType<typeof GoodreadsSearch> | undefined;
 | 
			
		||||
 | 
			
		||||
	onOpen() {
 | 
			
		||||
		this.component = mount(GoodreadsSearch, {
 | 
			
		||||
			target: this.contentEl,
 | 
			
		||||
			props: {
 | 
			
		||||
				onError(error) {
 | 
			
		||||
					this.onSearch(error, []);
 | 
			
		||||
					this.close();
 | 
			
		||||
				onError: (error: Error) => {
 | 
			
		||||
					this.emit("error", new ErrorEvent(error));
 | 
			
		||||
				},
 | 
			
		||||
				onSearch: (results: SearchResult[]) => {
 | 
			
		||||
					this.onSearch(null, results);
 | 
			
		||||
					this.close();
 | 
			
		||||
				onSearch: (query: string, results: SearchResult[]) => {
 | 
			
		||||
					this.emit("search", new SearchEvent(query, results));
 | 
			
		||||
				},
 | 
			
		||||
			},
 | 
			
		||||
		});
 | 
			
		||||
| 
						 | 
				
			
			@ -38,13 +53,14 @@ export class GoodreadsSearchModal extends Modal {
 | 
			
		|||
 | 
			
		||||
	static createAndOpen(app: App): Promise<SearchResult[]> {
 | 
			
		||||
		return new Promise((resolve, reject) => {
 | 
			
		||||
			const modal = new GoodreadsSearchModal(app, (error, results) => {
 | 
			
		||||
				if (error) {
 | 
			
		||||
					new Notice(`Error: ${error.message}`);
 | 
			
		||||
					reject(error);
 | 
			
		||||
				} else {
 | 
			
		||||
					resolve(results);
 | 
			
		||||
				}
 | 
			
		||||
			const modal = new GoodreadsSearchModal(app);
 | 
			
		||||
			modal.once("search", (event: SearchEvent) => {
 | 
			
		||||
				modal.close();
 | 
			
		||||
				resolve(event.results);
 | 
			
		||||
			});
 | 
			
		||||
			modal.once("error", (event: ErrorEvent) => {
 | 
			
		||||
				modal.close();
 | 
			
		||||
				reject(event.error);
 | 
			
		||||
			});
 | 
			
		||||
			modal.open();
 | 
			
		||||
		});
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,22 +1,30 @@
 | 
			
		|||
import Rating from "@components/Rating.svelte";
 | 
			
		||||
import { Event, EventEmitter } from "@utils/event";
 | 
			
		||||
import { App, Modal } from "obsidian";
 | 
			
		||||
import { mount, unmount } from "svelte";
 | 
			
		||||
 | 
			
		||||
export class RatingModal extends Modal {
 | 
			
		||||
	private component: ReturnType<typeof Rating> | undefined;
 | 
			
		||||
 | 
			
		||||
	constructor(app: App, private readonly onSubmit: (rating: number) => void) {
 | 
			
		||||
		super(app);
 | 
			
		||||
class SubmitEvent extends Event {
 | 
			
		||||
	constructor(public readonly rating: number) {
 | 
			
		||||
		super();
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
interface RatingModalEventMap {
 | 
			
		||||
	submit: SubmitEvent;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export class RatingModal extends EventEmitter<
 | 
			
		||||
	RatingModalEventMap,
 | 
			
		||||
	typeof Modal
 | 
			
		||||
>(Modal) {
 | 
			
		||||
	private component: ReturnType<typeof Rating> | undefined;
 | 
			
		||||
 | 
			
		||||
	onOpen(): void {
 | 
			
		||||
		this.component = mount(Rating, {
 | 
			
		||||
			target: this.contentEl,
 | 
			
		||||
			props: {
 | 
			
		||||
				onSubmit: (rating) => {
 | 
			
		||||
					this.onSubmit(rating);
 | 
			
		||||
					this.close();
 | 
			
		||||
				},
 | 
			
		||||
				onSubmit: (rating: number) =>
 | 
			
		||||
					this.emit("submit", new SubmitEvent(rating)),
 | 
			
		||||
			},
 | 
			
		||||
		});
 | 
			
		||||
	}
 | 
			
		||||
| 
						 | 
				
			
			@ -30,8 +38,10 @@ export class RatingModal extends Modal {
 | 
			
		|||
 | 
			
		||||
	static createAndOpen(app: App): Promise<number> {
 | 
			
		||||
		return new Promise((resolve) => {
 | 
			
		||||
			const modal = new RatingModal(app, (rating) => {
 | 
			
		||||
				resolve(rating);
 | 
			
		||||
			const modal = new RatingModal(app);
 | 
			
		||||
			modal.once("submit", (event: SubmitEvent) => {
 | 
			
		||||
				modal.close();
 | 
			
		||||
				resolve(event.rating);
 | 
			
		||||
			});
 | 
			
		||||
			modal.open();
 | 
			
		||||
		});
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -0,0 +1,44 @@
 | 
			
		|||
import ReadingLogEntryEditor from "@components/ReadingLogEntryEditor.svelte";
 | 
			
		||||
import type { ReadingLogEntry } from "@src/types";
 | 
			
		||||
import { Event, EventEmitter } from "@utils/event";
 | 
			
		||||
import { App, Modal } from "obsidian";
 | 
			
		||||
import { mount, unmount } from "svelte";
 | 
			
		||||
 | 
			
		||||
class SubmitEvent extends Event {
 | 
			
		||||
	constructor(public readonly entry: ReadingLogEntry) {
 | 
			
		||||
		super();
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
interface ReadingLogEntryEditModalEventMap {
 | 
			
		||||
	submit: SubmitEvent;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export class ReadingLogEntryEditModal extends EventEmitter<
 | 
			
		||||
	ReadingLogEntryEditModalEventMap,
 | 
			
		||||
	typeof Modal
 | 
			
		||||
>(Modal) {
 | 
			
		||||
	private component: ReturnType<typeof ReadingLogEntryEditor> | undefined;
 | 
			
		||||
 | 
			
		||||
	constructor(app: App, private readonly entry: ReadingLogEntry) {
 | 
			
		||||
		super(app);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	onOpen(): void {
 | 
			
		||||
		this.component = mount(ReadingLogEntryEditor, {
 | 
			
		||||
			target: this.contentEl,
 | 
			
		||||
			props: {
 | 
			
		||||
				entry: this.entry,
 | 
			
		||||
				onSubmit: (entry: ReadingLogEntry) =>
 | 
			
		||||
					this.emit("submit", new SubmitEvent(entry)),
 | 
			
		||||
			},
 | 
			
		||||
		});
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	onClose(): void {
 | 
			
		||||
		if (this.component) {
 | 
			
		||||
			unmount(this.component);
 | 
			
		||||
			this.component = undefined;
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -1,15 +1,25 @@
 | 
			
		|||
import ReadingProgress from "@components/ReadingProgress.svelte";
 | 
			
		||||
import { Event, EventEmitter } from "@utils/event";
 | 
			
		||||
import { App, Modal } from "obsidian";
 | 
			
		||||
import { mount, unmount } from "svelte";
 | 
			
		||||
 | 
			
		||||
export class ReadingProgressModal extends Modal {
 | 
			
		||||
class SubmitEvent extends Event {
 | 
			
		||||
	constructor(public readonly pageNumber: number) {
 | 
			
		||||
		super();
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
interface ReadingProgressModalEventMap {
 | 
			
		||||
	submit: SubmitEvent;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export class ReadingProgressModal extends EventEmitter<
 | 
			
		||||
	ReadingProgressModalEventMap,
 | 
			
		||||
	typeof Modal
 | 
			
		||||
>(Modal) {
 | 
			
		||||
	private component: ReturnType<typeof ReadingProgress> | undefined;
 | 
			
		||||
 | 
			
		||||
	constructor(
 | 
			
		||||
		app: App,
 | 
			
		||||
		private readonly pageLength: number,
 | 
			
		||||
		private readonly onSubmit: (pageNumber: number) => void
 | 
			
		||||
	) {
 | 
			
		||||
	constructor(app: App, private readonly pageLength: number) {
 | 
			
		||||
		super(app);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -18,9 +28,8 @@ export class ReadingProgressModal extends Modal {
 | 
			
		|||
			target: this.contentEl,
 | 
			
		||||
			props: {
 | 
			
		||||
				pageLength: this.pageLength,
 | 
			
		||||
				onSubmit: (pageNumber) => {
 | 
			
		||||
					this.onSubmit(pageNumber);
 | 
			
		||||
					this.close();
 | 
			
		||||
				onSubmit: (pageNumber: number) => {
 | 
			
		||||
					this.emit("submit", new SubmitEvent(pageNumber));
 | 
			
		||||
				},
 | 
			
		||||
			},
 | 
			
		||||
		});
 | 
			
		||||
| 
						 | 
				
			
			@ -35,13 +44,11 @@ export class ReadingProgressModal extends Modal {
 | 
			
		|||
 | 
			
		||||
	static createAndOpen(app: App, pageLength: number): Promise<number> {
 | 
			
		||||
		return new Promise((resolve) => {
 | 
			
		||||
			const modal = new ReadingProgressModal(
 | 
			
		||||
				app,
 | 
			
		||||
				pageLength,
 | 
			
		||||
				(pageNumber) => {
 | 
			
		||||
					resolve(pageNumber);
 | 
			
		||||
				}
 | 
			
		||||
			);
 | 
			
		||||
			const modal = new ReadingProgressModal(app, pageLength);
 | 
			
		||||
			modal.once("submit", (event: SubmitEvent) => {
 | 
			
		||||
				modal.close();
 | 
			
		||||
				resolve(event.pageNumber);
 | 
			
		||||
			});
 | 
			
		||||
			modal.open();
 | 
			
		||||
		});
 | 
			
		||||
	}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,89 +0,0 @@
 | 
			
		|||
import { UserConfig, defineConfig } from "vite";
 | 
			
		||||
import { svelte } from "@sveltejs/vite-plugin-svelte";
 | 
			
		||||
import copy from "@guanghechen/rollup-plugin-copy";
 | 
			
		||||
import manifest from "./manifest.json";
 | 
			
		||||
import path from "path";
 | 
			
		||||
import builtins from "builtin-modules";
 | 
			
		||||
 | 
			
		||||
import dotenv from "dotenv";
 | 
			
		||||
import dotenvExpand from "dotenv-expand";
 | 
			
		||||
const env = dotenv.config();
 | 
			
		||||
dotenvExpand.expand(env);
 | 
			
		||||
 | 
			
		||||
export default defineConfig(async ({ mode }) => {
 | 
			
		||||
	const { resolve } = path;
 | 
			
		||||
	const prod = mode === "production";
 | 
			
		||||
 | 
			
		||||
	let outDir = "dist";
 | 
			
		||||
	if (!prod) {
 | 
			
		||||
		if (!process.env.OBSIDIAN_PLUGIN_DIR) {
 | 
			
		||||
			console.log(
 | 
			
		||||
				"Set OBSIDIAN_PLUGIN_DIR in the .env file to plugin directory to copy files to."
 | 
			
		||||
			);
 | 
			
		||||
		} else {
 | 
			
		||||
			outDir =
 | 
			
		||||
				process.env.OBSIDIAN_PLUGIN_DIR + "/" + manifest.id + "-dev";
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if (outDir[0] === "~") {
 | 
			
		||||
		outDir = path.join(process.env.HOME!, outDir.slice(1));
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return {
 | 
			
		||||
		plugins: [
 | 
			
		||||
			svelte(),
 | 
			
		||||
			copy({
 | 
			
		||||
				targets: [{ src: "manifest.json", dest: outDir }],
 | 
			
		||||
			}),
 | 
			
		||||
		],
 | 
			
		||||
		resolve: {
 | 
			
		||||
			alias: {
 | 
			
		||||
				"@components": resolve(__dirname, "./src/components"),
 | 
			
		||||
				"@data-sources": resolve(__dirname, "./src/data-sources"),
 | 
			
		||||
				"@settings": resolve(__dirname, "./src/settings"),
 | 
			
		||||
				"@utils": resolve(__dirname, "./src/utils"),
 | 
			
		||||
				"@views": resolve(__dirname, "./src/views"),
 | 
			
		||||
				"@src": resolve(__dirname, "./src"),
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
		build: {
 | 
			
		||||
			lib: {
 | 
			
		||||
				entry: resolve(__dirname, "src/main.ts"),
 | 
			
		||||
				name: "main",
 | 
			
		||||
				fileName: () => "main.js",
 | 
			
		||||
				formats: ["cjs"],
 | 
			
		||||
			},
 | 
			
		||||
			minify: prod,
 | 
			
		||||
			sourcemap: prod ? false : "inline",
 | 
			
		||||
			cssCodeSplit: false,
 | 
			
		||||
			emptyOutDir: false,
 | 
			
		||||
			outDir,
 | 
			
		||||
			rollupOptions: {
 | 
			
		||||
				input: {
 | 
			
		||||
					main: resolve(__dirname, "src/main.ts"),
 | 
			
		||||
				},
 | 
			
		||||
				output: {
 | 
			
		||||
					entryFileNames: "main.js",
 | 
			
		||||
					assetFileNames: "styles.css",
 | 
			
		||||
				},
 | 
			
		||||
				external: [
 | 
			
		||||
					"obsidian",
 | 
			
		||||
					"electron",
 | 
			
		||||
					"@codemirror/autocomplete",
 | 
			
		||||
					"@codemirror/collab",
 | 
			
		||||
					"@codemirror/commands",
 | 
			
		||||
					"@codemirror/language",
 | 
			
		||||
					"@codemirror/lint",
 | 
			
		||||
					"@codemirror/search",
 | 
			
		||||
					"@codemirror/state",
 | 
			
		||||
					"@codemirror/view",
 | 
			
		||||
					"@lezer/common",
 | 
			
		||||
					"@lezer/highlight",
 | 
			
		||||
					"@lezer/lr",
 | 
			
		||||
					...builtins,
 | 
			
		||||
				],
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
	} as UserConfig;
 | 
			
		||||
});
 | 
			
		||||
		Loading…
	
		Reference in New Issue