generated from tpl/obsidian-sample-plugin
			Switch to vite for building and add rating modal
This commit is contained in:
		
							parent
							
								
									db676f93f2
								
							
						
					
					
						commit
						1ecf93e5da
					
				
							
								
								
									
										12
									
								
								package.json
								
								
								
								
							
							
						
						
									
										12
									
								
								package.json
								
								
								
								
							| 
						 | 
				
			
			@ -4,8 +4,8 @@
 | 
			
		|||
	"description": "This is a sample plugin for Obsidian (https://obsidian.md)",
 | 
			
		||||
	"main": "main.js",
 | 
			
		||||
	"scripts": {
 | 
			
		||||
		"dev": "node esbuild.config.mjs",
 | 
			
		||||
		"build": "tsc -noEmit -skipLibCheck && node esbuild.config.mjs production",
 | 
			
		||||
		"dev": "vite build --watch --mode=development",
 | 
			
		||||
		"build": "vite build",
 | 
			
		||||
		"version": "node version-bump.mjs && git add manifest.json versions.json",
 | 
			
		||||
		"svelte-check": "svelte-check --tsconfig tsconfig.json"
 | 
			
		||||
	},
 | 
			
		||||
| 
						 | 
				
			
			@ -14,13 +14,16 @@
 | 
			
		|||
	"license": "MIT",
 | 
			
		||||
	"devDependencies": {
 | 
			
		||||
		"@popperjs/core": "^2.11.8",
 | 
			
		||||
		"@types/node": "^16.11.6",
 | 
			
		||||
		"@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",
 | 
			
		||||
		"esbuild": "0.17.3",
 | 
			
		||||
		"esbuild-svelte": "^0.9.3",
 | 
			
		||||
		"handlebars": "^4.7.8",
 | 
			
		||||
		"lucide-svelte": "^0.525.0",
 | 
			
		||||
		"npm-run-all": "^4.1.5",
 | 
			
		||||
		"obsidian": "latest",
 | 
			
		||||
		"sass": "^1.89.2",
 | 
			
		||||
| 
						 | 
				
			
			@ -28,6 +31,7 @@
 | 
			
		|||
		"svelte-check": "^4.2.2",
 | 
			
		||||
		"svelte-preprocess": "^6.0.3",
 | 
			
		||||
		"tslib": "2.4.0",
 | 
			
		||||
		"typescript": "5.0.4"
 | 
			
		||||
		"typescript": "5.0.4",
 | 
			
		||||
		"vite": "^6.0.0"
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										753
									
								
								pnpm-lock.yaml
								
								
								
								
							
							
						
						
									
										753
									
								
								pnpm-lock.yaml
								
								
								
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							| 
						 | 
				
			
			@ -1,2 +0,0 @@
 | 
			
		|||
onlyBuiltDependencies:
 | 
			
		||||
  - esbuild
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,57 @@
 | 
			
		|||
<script lang="ts">
 | 
			
		||||
	import RatingInput from "./RatingInput.svelte";
 | 
			
		||||
 | 
			
		||||
	interface Props {
 | 
			
		||||
		onSubmit: (value: number) => void;
 | 
			
		||||
	}
 | 
			
		||||
	let { onSubmit }: Props = $props();
 | 
			
		||||
 | 
			
		||||
	let value = $state(0);
 | 
			
		||||
 | 
			
		||||
	function onsubmit(ev: SubmitEvent) {
 | 
			
		||||
		ev.preventDefault();
 | 
			
		||||
 | 
			
		||||
		onSubmit(value);
 | 
			
		||||
	}
 | 
			
		||||
</script>
 | 
			
		||||
 | 
			
		||||
<div class="obt-rating">
 | 
			
		||||
	<h2>Rating</h2>
 | 
			
		||||
	<form {onsubmit}>
 | 
			
		||||
		<div class="value-field">
 | 
			
		||||
			<label for="value">Rating</label>
 | 
			
		||||
			<RatingInput bind:value />
 | 
			
		||||
		</div>
 | 
			
		||||
		<button type="submit">Submit</button>
 | 
			
		||||
	</form>
 | 
			
		||||
</div>
 | 
			
		||||
 | 
			
		||||
<style lang="scss">
 | 
			
		||||
	.obt-rating {
 | 
			
		||||
		padding-bottom: var(--size-4-4);
 | 
			
		||||
 | 
			
		||||
		h2 {
 | 
			
		||||
			text-align: center;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		form {
 | 
			
		||||
			display: flex;
 | 
			
		||||
			flex-direction: column;
 | 
			
		||||
			gap: var(--size-4-4);
 | 
			
		||||
 | 
			
		||||
			.value-field {
 | 
			
		||||
				display: flex;
 | 
			
		||||
				flex-direction: column;
 | 
			
		||||
				align-items: center;
 | 
			
		||||
 | 
			
		||||
				label {
 | 
			
		||||
					display: none;
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			button {
 | 
			
		||||
				align-self: stretch;
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
</style>
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,62 @@
 | 
			
		|||
<script lang="ts">
 | 
			
		||||
	import { unstable_RatingGroup as RatingGroup } from "bits-ui";
 | 
			
		||||
	import { Star, StarHalf } from "lucide-svelte";
 | 
			
		||||
 | 
			
		||||
	interface Props {
 | 
			
		||||
		value?: number;
 | 
			
		||||
		name?: string;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	let { value = $bindable(), name }: Props = $props();
 | 
			
		||||
</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>
 | 
			
		||||
</div>
 | 
			
		||||
 | 
			
		||||
<style lang="scss">
 | 
			
		||||
	.rating-input {
 | 
			
		||||
		display: flex;
 | 
			
		||||
		align-items: center;
 | 
			
		||||
		gap: 0.5rem;
 | 
			
		||||
 | 
			
		||||
		:global(svg) {
 | 
			
		||||
			position: absolute;
 | 
			
		||||
			width: var(--size-4-16);
 | 
			
		||||
			height: var(--size-4-16);
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		:global(.rating-group) {
 | 
			
		||||
			display: flex;
 | 
			
		||||
			gap: 0.25rem;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		:global(.rating-item) {
 | 
			
		||||
			position: relative;
 | 
			
		||||
			width: var(--size-4-16);
 | 
			
		||||
			height: var(--size-4-16);
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
</style>
 | 
			
		||||
| 
						 | 
				
			
			@ -1,5 +1,9 @@
 | 
			
		|||
import { requestUrl } from "obsidian";
 | 
			
		||||
import { Author, Book as OutputBook, Series as OutputSeries } from "../types";
 | 
			
		||||
import type {
 | 
			
		||||
	Author,
 | 
			
		||||
	Book as OutputBook,
 | 
			
		||||
	Series as OutputSeries,
 | 
			
		||||
} from "../types";
 | 
			
		||||
 | 
			
		||||
interface Ref {
 | 
			
		||||
	__ref: string;
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -16,6 +16,7 @@ import {
 | 
			
		|||
} from "./const";
 | 
			
		||||
import { ReadingLog, Storage } from "@utils/storage";
 | 
			
		||||
import { ReadingProgressModal } from "@views/reading-progress-modal";
 | 
			
		||||
import { RatingModal } from "@views/rating-modal";
 | 
			
		||||
 | 
			
		||||
export default class BookTrackerPlugin extends Plugin {
 | 
			
		||||
	settings: BookTrackerPluginSettings;
 | 
			
		||||
| 
						 | 
				
			
			@ -279,6 +280,11 @@ export default class BookTrackerPlugin extends Plugin {
 | 
			
		|||
			return;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if (!this.settings.ratingProperty) {
 | 
			
		||||
			new Notice("Rating property is not set in settings.");
 | 
			
		||||
			return;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		const pageLength =
 | 
			
		||||
			(this.app.metadataCache.getFileCache(activeFile)?.frontmatter?.[
 | 
			
		||||
				this.settings.pageLengthProperty
 | 
			
		||||
| 
						 | 
				
			
			@ -291,6 +297,8 @@ export default class BookTrackerPlugin extends Plugin {
 | 
			
		|||
			return;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		const rating = await RatingModal.createAndOpen(this.app);
 | 
			
		||||
 | 
			
		||||
		await this.readingLog.addEntry(activeFile.basename, pageLength);
 | 
			
		||||
 | 
			
		||||
		// @ts-expect-error Moment is provided by Obsidian
 | 
			
		||||
| 
						 | 
				
			
			@ -299,6 +307,7 @@ export default class BookTrackerPlugin extends Plugin {
 | 
			
		|||
		this.app.fileManager.processFrontMatter(activeFile, (frontMatter) => {
 | 
			
		||||
			frontMatter[this.settings.statusProperty] = READ_STATE;
 | 
			
		||||
			frontMatter[this.settings.endDateProperty] = endDate;
 | 
			
		||||
			frontMatter[this.settings.ratingProperty] = rating;
 | 
			
		||||
		});
 | 
			
		||||
 | 
			
		||||
		new Notice("Reading finished for " + activeFile.name);
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,7 +1,7 @@
 | 
			
		|||
// Credits go to Liam's Periodic Notes Plugin: https://github.com/liamcain/obsidian-periodic-notes
 | 
			
		||||
 | 
			
		||||
import { App, ISuggestOwner, Scope } from "obsidian";
 | 
			
		||||
import { createPopper, Instance as PopperInstance } from "@popperjs/core";
 | 
			
		||||
import { App, type ISuggestOwner, Scope } from "obsidian";
 | 
			
		||||
import { createPopper, type Instance as PopperInstance } from "@popperjs/core";
 | 
			
		||||
 | 
			
		||||
const wrapAround = (value: number, size: number): number => {
 | 
			
		||||
	return ((value % size) + size) % size;
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -0,0 +1,39 @@
 | 
			
		|||
import Rating from "@components/Rating.svelte";
 | 
			
		||||
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);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	onOpen(): void {
 | 
			
		||||
		this.component = mount(Rating, {
 | 
			
		||||
			target: this.contentEl,
 | 
			
		||||
			props: {
 | 
			
		||||
				onSubmit: (rating) => {
 | 
			
		||||
					this.onSubmit(rating);
 | 
			
		||||
					this.close();
 | 
			
		||||
				},
 | 
			
		||||
			},
 | 
			
		||||
		});
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	onClose(): void {
 | 
			
		||||
		if (this.component) {
 | 
			
		||||
			unmount(this.component);
 | 
			
		||||
			this.component = undefined;
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	static createAndOpen(app: App): Promise<number> {
 | 
			
		||||
		return new Promise((resolve) => {
 | 
			
		||||
			const modal = new RatingModal(app, (rating) => {
 | 
			
		||||
				resolve(rating);
 | 
			
		||||
			});
 | 
			
		||||
			modal.open();
 | 
			
		||||
		});
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -1,3 +1 @@
 | 
			
		|||
.obt-settings .search-input-container {
 | 
			
		||||
  width: 100%;
 | 
			
		||||
}
 | 
			
		||||
.obt-goodreads-search.svelte-11kqgr4{padding-bottom:var(--size-4-4)}.obt-goodreads-search.svelte-11kqgr4 input:where(.svelte-11kqgr4){width:100%}.obt-goodreads-search-suggestion.svelte-1kq4sbn{display:flex;align-items:center}.obt-goodreads-search-suggestion.svelte-1kq4sbn img.cover:where(.svelte-1kq4sbn){max-width:100px;max-height:100px;margin-right:var(--size-4-2);object-fit:cover;border-radius:var(--radius-s)}.obt-goodreads-search-suggestion.svelte-1kq4sbn .details:where(.svelte-1kq4sbn){flex-grow:1}.obt-goodreads-search-suggestion.svelte-1kq4sbn .details:where(.svelte-1kq4sbn) .title:where(.svelte-1kq4sbn){color:var(--text-normal);font-size:var(--font-ui-medium)}.obt-goodreads-search-suggestion.svelte-1kq4sbn .details:where(.svelte-1kq4sbn) .extra-details:where(.svelte-1kq4sbn){color:var(--text-muted);font-size:var(--font-ui-small);display:flex;gap:var(--size-4-1)}.obt-reading-progress.svelte-paogvq{padding-bottom:var(--size-4-4)}.obt-reading-progress.svelte-paogvq h2:where(.svelte-paogvq){margin-bottom:var(--size-4-6)}.obt-reading-progress.svelte-paogvq form:where(.svelte-paogvq){display:flex;flex-direction:column;gap:var(--size-4-4)}.obt-reading-progress.svelte-paogvq form:where(.svelte-paogvq) .value-field:where(.svelte-paogvq){display:flex;flex-direction:column;align-items:stretch;gap:var(--size-4-2);width:100%}.obt-reading-progress.svelte-paogvq form:where(.svelte-paogvq) .mode-field:where(.svelte-paogvq){width:100%;display:grid;grid-template-columns:1fr 1fr}.obt-reading-progress.svelte-paogvq form:where(.svelte-paogvq) .mode-field:where(.svelte-paogvq) .mode-field-option:where(.svelte-paogvq){text-align:center;padding:var(--size-4-2);background-color:var(--interactive-normal);border:var(--border-width) solid var(--background-modifier-border);border-radius:var(--radius-m)}.obt-reading-progress.svelte-paogvq form:where(.svelte-paogvq) .mode-field:where(.svelte-paogvq) .mode-field-option:where(.svelte-paogvq):has(input:where(.svelte-paogvq):checked){background-color:var(--interactive-accent)}.obt-reading-progress.svelte-paogvq form:where(.svelte-paogvq) .mode-field:where(.svelte-paogvq) .mode-field-option:where(.svelte-paogvq):hover{background-color:var(--interactive-hover)}.obt-reading-progress.svelte-paogvq form:where(.svelte-paogvq) .mode-field:where(.svelte-paogvq) .mode-field-option:where(.svelte-paogvq) input:where(.svelte-paogvq){display:none}.obt-reading-progress.svelte-paogvq form:where(.svelte-paogvq) .mode-field:where(.svelte-paogvq) .mode-field-option:where(.svelte-paogvq).page-number{border-top-right-radius:0;border-bottom-right-radius:0}.obt-reading-progress.svelte-paogvq form:where(.svelte-paogvq) .mode-field:where(.svelte-paogvq) .mode-field-option:where(.svelte-paogvq).percentage{border-top-left-radius:0;border-bottom-left-radius:0}.rating-input.svelte-19sa8ca{display:flex;align-items:center;gap:.5rem}.rating-input.svelte-19sa8ca svg{position:absolute;width:var(--size-4-16);height:var(--size-4-16)}.rating-input.svelte-19sa8ca .rating-group{display:flex;gap:.25rem}.rating-input.svelte-19sa8ca .rating-item{position:relative;width:var(--size-4-16);height:var(--size-4-16)}.obt-rating.svelte-badw3i{padding-bottom:var(--size-4-4)}.obt-rating.svelte-badw3i h2:where(.svelte-badw3i){text-align:center}.obt-rating.svelte-badw3i form:where(.svelte-badw3i){display:flex;flex-direction:column;gap:var(--size-4-4)}.obt-rating.svelte-badw3i form:where(.svelte-badw3i) .value-field:where(.svelte-badw3i){display:flex;flex-direction:column;align-items:center}.obt-rating.svelte-badw3i form:where(.svelte-badw3i) .value-field:where(.svelte-badw3i) label:where(.svelte-badw3i){display:none}.obt-rating.svelte-badw3i form:where(.svelte-badw3i) button:where(.svelte-badw3i){align-self:stretch}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -0,0 +1,61 @@
 | 
			
		|||
import { UserConfig, defineConfig } from "vite";
 | 
			
		||||
import { svelte } from "@sveltejs/vite-plugin-svelte";
 | 
			
		||||
import path from "path";
 | 
			
		||||
import builtins from "builtin-modules";
 | 
			
		||||
 | 
			
		||||
export default defineConfig(async ({ mode }) => {
 | 
			
		||||
	const { resolve } = path;
 | 
			
		||||
	const prod = mode === "production";
 | 
			
		||||
 | 
			
		||||
	return {
 | 
			
		||||
		plugins: [svelte()],
 | 
			
		||||
		resolve: {
 | 
			
		||||
			alias: {
 | 
			
		||||
				"@components": path.resolve(__dirname, "./src/components"),
 | 
			
		||||
				"@data-sources": path.resolve(__dirname, "./src/data-sources"),
 | 
			
		||||
				"@settings": path.resolve(__dirname, "./src/settings"),
 | 
			
		||||
				"@utils": path.resolve(__dirname, "./src/utils"),
 | 
			
		||||
				"@views": path.resolve(__dirname, "./src/views"),
 | 
			
		||||
				"@src": path.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