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