Add entry create button

This commit is contained in:
Evan Fiordeliso 2025-06-29 18:41:54 -04:00
parent 6ffad2b5d1
commit bd21890885
8 changed files with 192 additions and 34 deletions

View File

@ -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"

View File

@ -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

View File

@ -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;
}
} }
} }
} }

View File

@ -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;
} }

View File

@ -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();
}
}

View File

@ -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();
} }

View File

@ -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)),

View File

@ -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)),
},
});
}
}