generated from tpl/obsidian-sample-plugin
Add entry create button
This commit is contained in:
parent
6ffad2b5d1
commit
bd21890885
|
@ -30,6 +30,7 @@
|
||||||
"sass": "^1.89.2",
|
"sass": "^1.89.2",
|
||||||
"svelte": "^5.34.8",
|
"svelte": "^5.34.8",
|
||||||
"svelte-check": "^4.2.2",
|
"svelte-check": "^4.2.2",
|
||||||
|
"svelte-popperjs": "^1.3.2",
|
||||||
"svelte-preprocess": "^6.0.3",
|
"svelte-preprocess": "^6.0.3",
|
||||||
"tslib": "2.4.0",
|
"tslib": "2.4.0",
|
||||||
"typescript": "5.0.4"
|
"typescript": "5.0.4"
|
||||||
|
|
|
@ -59,6 +59,9 @@ importers:
|
||||||
svelte-check:
|
svelte-check:
|
||||||
specifier: ^4.2.2
|
specifier: ^4.2.2
|
||||||
version: 4.2.2(picomatch@4.0.2)(svelte@5.34.8)(typescript@5.0.4)
|
version: 4.2.2(picomatch@4.0.2)(svelte@5.34.8)(typescript@5.0.4)
|
||||||
|
svelte-popperjs:
|
||||||
|
specifier: ^1.3.2
|
||||||
|
version: 1.3.2(@popperjs/core@2.11.8)(svelte@5.34.8)
|
||||||
svelte-preprocess:
|
svelte-preprocess:
|
||||||
specifier: ^6.0.3
|
specifier: ^6.0.3
|
||||||
version: 6.0.3(postcss@8.5.6)(sass@1.89.2)(svelte@5.34.8)(typescript@5.0.4)
|
version: 6.0.3(postcss@8.5.6)(sass@1.89.2)(svelte@5.34.8)(typescript@5.0.4)
|
||||||
|
@ -1444,6 +1447,12 @@ packages:
|
||||||
svelte: ^4.0.0 || ^5.0.0-next.0
|
svelte: ^4.0.0 || ^5.0.0-next.0
|
||||||
typescript: '>=5.0.0'
|
typescript: '>=5.0.0'
|
||||||
|
|
||||||
|
svelte-popperjs@1.3.2:
|
||||||
|
resolution: {integrity: sha512-fwrErlkvngL876WXRnL3OLlfk/n9YkZwwLxuKRpZOYCJLt1zrwhoKTXS+/sRgDveD/zd6GQ35hV89EOip+NBGA==}
|
||||||
|
peerDependencies:
|
||||||
|
'@popperjs/core': '>=2'
|
||||||
|
svelte: '>=3'
|
||||||
|
|
||||||
svelte-preprocess@6.0.3:
|
svelte-preprocess@6.0.3:
|
||||||
resolution: {integrity: sha512-PLG2k05qHdhmRG7zR/dyo5qKvakhm8IJ+hD2eFRQmMLHp7X3eJnjeupUtvuRpbNiF31RjVw45W+abDwHEmP5OA==}
|
resolution: {integrity: sha512-PLG2k05qHdhmRG7zR/dyo5qKvakhm8IJ+hD2eFRQmMLHp7X3eJnjeupUtvuRpbNiF31RjVw45W+abDwHEmP5OA==}
|
||||||
engines: {node: '>= 18.0.0'}
|
engines: {node: '>= 18.0.0'}
|
||||||
|
@ -3050,6 +3059,11 @@ snapshots:
|
||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- picomatch
|
- picomatch
|
||||||
|
|
||||||
|
svelte-popperjs@1.3.2(@popperjs/core@2.11.8)(svelte@5.34.8):
|
||||||
|
dependencies:
|
||||||
|
'@popperjs/core': 2.11.8
|
||||||
|
svelte: 5.34.8
|
||||||
|
|
||||||
svelte-preprocess@6.0.3(postcss@8.5.6)(sass@1.89.2)(svelte@5.34.8)(typescript@5.0.4):
|
svelte-preprocess@6.0.3(postcss@8.5.6)(sass@1.89.2)(svelte@5.34.8)(typescript@5.0.4):
|
||||||
dependencies:
|
dependencies:
|
||||||
svelte: 5.34.8
|
svelte: 5.34.8
|
||||||
|
|
|
@ -1,15 +1,21 @@
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import type { ReadingLogEntry } from "@src/types";
|
import type { ReadingLogEntry } from "@src/types";
|
||||||
|
import type { App } from "obsidian";
|
||||||
|
import { BookSuggest } from "@settings/suggesters/book";
|
||||||
interface Props {
|
interface Props {
|
||||||
entry: ReadingLogEntry;
|
app: App;
|
||||||
onSubmit: (entry: ReadingLogEntry) => void;
|
entry?: ReadingLogEntry;
|
||||||
|
onSubmit?: (entry: ReadingLogEntry) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
let { entry, onSubmit }: Props = $props();
|
let { app, entry, onSubmit }: Props = $props();
|
||||||
|
|
||||||
let pagesRead = $state(entry.pagesRead);
|
let editMode = $derived(entry !== undefined);
|
||||||
let pagesReadTotal = $state(entry.pagesReadTotal);
|
let book = $state(entry?.book ?? "");
|
||||||
let pagesRemaining = $state(entry.pagesRemaining);
|
let pagesRead = $state(entry?.pagesRead ?? 0);
|
||||||
|
let pagesReadTotal = $state(entry?.pagesReadTotal ?? 0);
|
||||||
|
let pagesRemaining = $state(entry?.pagesRemaining ?? 0);
|
||||||
|
let createdAt = $state(entry?.createdAt ?? new Date());
|
||||||
|
|
||||||
// Source: https://github.com/sveltejs/svelte/discussions/14220#discussioncomment-11188219
|
// Source: https://github.com/sveltejs/svelte/discussions/14220#discussioncomment-11188219
|
||||||
function watch<T>(
|
function watch<T>(
|
||||||
|
@ -40,19 +46,33 @@
|
||||||
|
|
||||||
function onsubmit(ev: SubmitEvent) {
|
function onsubmit(ev: SubmitEvent) {
|
||||||
ev.preventDefault();
|
ev.preventDefault();
|
||||||
onSubmit({
|
onSubmit?.({
|
||||||
...entry,
|
book,
|
||||||
pagesRead,
|
pagesRead,
|
||||||
pagesReadTotal,
|
pagesReadTotal,
|
||||||
pagesRemaining,
|
pagesRemaining,
|
||||||
|
createdAt: new Date(createdAt),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function bookSuggest(el: HTMLInputElement) {
|
||||||
|
new BookSuggest(app, el);
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="obt-reading-log-entry-editor">
|
<div class="obt-reading-log-entry-editor">
|
||||||
<h2>Edit Reading Log Entry</h2>
|
<h2>{editMode ? "Edit" : "Create"} Reading Log Entry</h2>
|
||||||
<form {onsubmit}>
|
<form {onsubmit}>
|
||||||
<div class="fields">
|
<div class="fields">
|
||||||
|
<label for="book">Book</label>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
name="book"
|
||||||
|
id="book"
|
||||||
|
bind:value={book}
|
||||||
|
disabled={editMode}
|
||||||
|
use:bookSuggest
|
||||||
|
/>
|
||||||
<label for="pagesRead">Pages Read</label>
|
<label for="pagesRead">Pages Read</label>
|
||||||
<input
|
<input
|
||||||
type="number"
|
type="number"
|
||||||
|
@ -74,6 +94,14 @@
|
||||||
id="pagesRemaining"
|
id="pagesRemaining"
|
||||||
bind:value={pagesRemaining}
|
bind:value={pagesRemaining}
|
||||||
/>
|
/>
|
||||||
|
<label for="createdAt">Created At</label>
|
||||||
|
<input
|
||||||
|
type="datetime-local"
|
||||||
|
name="createdAt"
|
||||||
|
id="createdAt"
|
||||||
|
bind:value={createdAt}
|
||||||
|
disabled={editMode}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
<button type="submit">Submit</button>
|
<button type="submit">Submit</button>
|
||||||
</form>
|
</form>
|
||||||
|
@ -95,7 +123,13 @@
|
||||||
.fields {
|
.fields {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: max-content 1fr;
|
grid-template-columns: max-content 1fr;
|
||||||
gap: var(--size-4-2);
|
gap: var(--size-4-4);
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
input:disabled {
|
||||||
|
color: var(--text-muted);
|
||||||
|
cursor: not-allowed;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,8 +2,9 @@
|
||||||
import type { ReadingLog } from "@utils/storage";
|
import type { ReadingLog } from "@utils/storage";
|
||||||
import type { ReadingLogEntry } from "@src/types";
|
import type { ReadingLogEntry } from "@src/types";
|
||||||
import type { App } from "obsidian";
|
import type { App } from "obsidian";
|
||||||
import { Edit, Trash } from "lucide-svelte";
|
import { Edit, Trash, Plus } from "lucide-svelte";
|
||||||
import { ReadingLogEntryEditModal } from "@views/reading-log-entry-edit-modal";
|
import { ReadingLogEntryEditModal } from "@views/reading-log-entry-edit-modal";
|
||||||
|
import { ReadingLogNewEntryModal } from "@views/reading-log-new-entry-modal";
|
||||||
|
|
||||||
const ALL_TIME = "ALL_TIME";
|
const ALL_TIME = "ALL_TIME";
|
||||||
|
|
||||||
|
@ -68,6 +69,16 @@
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
function createEntry() {
|
||||||
|
const modal = new ReadingLogNewEntryModal(app);
|
||||||
|
modal.once("submit", async (event) => {
|
||||||
|
modal.close();
|
||||||
|
await readingLog.addRawEntry(event.entry);
|
||||||
|
reload();
|
||||||
|
});
|
||||||
|
modal.open();
|
||||||
|
}
|
||||||
|
|
||||||
function editEntry(i: number, entry: ReadingLogEntry) {
|
function editEntry(i: number, entry: ReadingLogEntry) {
|
||||||
const modal = new ReadingLogEntryEditModal(app, entry);
|
const modal = new ReadingLogEntryEditModal(app, entry);
|
||||||
modal.once("submit", async (event) => {
|
modal.once("submit", async (event) => {
|
||||||
|
@ -86,7 +97,8 @@
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="obt-reading-log-viewer">
|
<div class="obt-reading-log-viewer">
|
||||||
<div class="filters">
|
<div class="controls">
|
||||||
|
<div class="left">
|
||||||
<select class="year-filter" bind:value={selectedYear}>
|
<select class="year-filter" bind:value={selectedYear}>
|
||||||
{#each years as year}
|
{#each years as year}
|
||||||
<option value={year.toString()}>{year}</option>
|
<option value={year.toString()}>{year}</option>
|
||||||
|
@ -109,6 +121,13 @@
|
||||||
<option value="12">December</option>
|
<option value="12">December</option>
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="right">
|
||||||
|
<button onclick={createEntry} class="create-entry" type="button">
|
||||||
|
<Plus />
|
||||||
|
Create
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<table>
|
<table>
|
||||||
<thead>
|
<thead>
|
||||||
|
@ -155,6 +174,27 @@
|
||||||
@use "../styles/utils";
|
@use "../styles/utils";
|
||||||
|
|
||||||
.obt-reading-log-viewer {
|
.obt-reading-log-viewer {
|
||||||
|
.controls {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 1fr 1fr;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
.right {
|
||||||
|
text-align: right;
|
||||||
|
|
||||||
|
button.create-entry {
|
||||||
|
display: inline-flex;
|
||||||
|
gap: var(--size-2-2);
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
:global(svg) {
|
||||||
|
width: var(--icon-s);
|
||||||
|
height: var(--icon-s);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.year-filter:has(> option.all-time:checked) + .month-filter {
|
.year-filter:has(> option.all-time:checked) + .month-filter {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,34 @@
|
||||||
|
// Credits go to Liam's Periodic Notes Plugin: https://github.com/liamcain/obsidian-periodic-notes
|
||||||
|
|
||||||
|
import { TAbstractFile, TFile } from "obsidian";
|
||||||
|
import { TextInputSuggest } from "./core";
|
||||||
|
|
||||||
|
export class BookSuggest extends TextInputSuggest<TFile> {
|
||||||
|
getSuggestions(inputStr: string): TFile[] {
|
||||||
|
const abstractFiles = this.app.vault.getAllLoadedFiles();
|
||||||
|
const files: TFile[] = [];
|
||||||
|
const lowerCaseInputStr = inputStr.toLowerCase();
|
||||||
|
|
||||||
|
abstractFiles.forEach((file: TAbstractFile) => {
|
||||||
|
if (
|
||||||
|
file instanceof TFile &&
|
||||||
|
file.extension === "md" &&
|
||||||
|
file.basename.toLowerCase().contains(lowerCaseInputStr)
|
||||||
|
) {
|
||||||
|
files.push(file);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return files;
|
||||||
|
}
|
||||||
|
|
||||||
|
renderSuggestion(file: TFile, el: HTMLElement): void {
|
||||||
|
el.setText(file.basename);
|
||||||
|
}
|
||||||
|
|
||||||
|
selectSuggestion(file: TFile): void {
|
||||||
|
this.inputEl.value = file.basename;
|
||||||
|
this.inputEl.trigger("input");
|
||||||
|
this.close();
|
||||||
|
}
|
||||||
|
}
|
|
@ -93,7 +93,11 @@ export class ReadingLog {
|
||||||
createdAt: new Date(),
|
createdAt: new Date(),
|
||||||
};
|
};
|
||||||
|
|
||||||
this.entries.push(newEntry);
|
await this.addRawEntry(newEntry);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async addRawEntry(entry: ReadingLogEntry) {
|
||||||
|
this.entries.push(entry);
|
||||||
await this.storeEntries();
|
await this.storeEntries();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -21,6 +21,7 @@ export class ReadingLogEntryEditModal extends EventEmitter<
|
||||||
constructor(app: App, entry: ReadingLogEntry) {
|
constructor(app: App, entry: ReadingLogEntry) {
|
||||||
super(app, ReadingLogEntryEditor, {
|
super(app, ReadingLogEntryEditor, {
|
||||||
props: {
|
props: {
|
||||||
|
app,
|
||||||
entry,
|
entry,
|
||||||
onSubmit: (entry: ReadingLogEntry) =>
|
onSubmit: (entry: ReadingLogEntry) =>
|
||||||
this.emit("submit", new SubmitEvent(entry)),
|
this.emit("submit", new SubmitEvent(entry)),
|
||||||
|
|
|
@ -0,0 +1,30 @@
|
||||||
|
import ReadingLogEntryEditor from "@components/ReadingLogEntryEditor.svelte";
|
||||||
|
import type { ReadingLogEntry } from "@src/types";
|
||||||
|
import { Event, EventEmitter } from "@utils/event";
|
||||||
|
import { App } from "obsidian";
|
||||||
|
import { SvelteModal } from "./svelte-modal";
|
||||||
|
|
||||||
|
export class SubmitEvent extends Event {
|
||||||
|
constructor(public readonly entry: ReadingLogEntry) {
|
||||||
|
super();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ReadingLogNewEntryModalEventMap {
|
||||||
|
submit: SubmitEvent;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class ReadingLogNewEntryModal extends EventEmitter<
|
||||||
|
ReadingLogNewEntryModalEventMap,
|
||||||
|
typeof SvelteModal<typeof ReadingLogEntryEditor>
|
||||||
|
>(SvelteModal) {
|
||||||
|
constructor(app: App) {
|
||||||
|
super(app, ReadingLogEntryEditor, {
|
||||||
|
props: {
|
||||||
|
app,
|
||||||
|
onSubmit: (entry: ReadingLogEntry) =>
|
||||||
|
this.emit("submit", new SubmitEvent(entry)),
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue