From c2a94ba6f41faa832f8eae95b4bb7fce55be6b1f Mon Sep 17 00:00:00 2001 From: Jacob Schmidt Date: Sat, 28 Feb 2026 13:00:04 -0600 Subject: [PATCH] Improve mobile layout and configure Prettier for Svelte --- Journal.App/.prettierignore | 8 + Journal.App/README.md | 42 +- Journal.App/package-lock.json | 36 +- Journal.App/package.json | 6 +- Journal.App/prettier.config.cjs | 11 + .../src-tauri/capabilities/default.json | 6 +- Journal.App/src-tauri/tauri.conf.json | 2 +- Journal.App/src/app.html | 8 +- Journal.App/src/lib/backend/auth.ts | 37 +- Journal.App/src/lib/backend/client.ts | 9 +- Journal.App/src/lib/backend/entries.ts | 138 +++-- Journal.App/src/lib/backend/fragments.ts | 21 +- Journal.App/src/lib/backend/lists.ts | 21 +- Journal.App/src/lib/backend/normalize.ts | 6 +- Journal.App/src/lib/backend/templates.ts | 30 +- Journal.App/src/lib/backend/todos.ts | 38 +- Journal.App/src/lib/backend/types.ts | 1 - .../src/lib/components/AppModal.svelte | 11 +- .../src/lib/components/CalendarWidget.svelte | 99 +++- .../src/lib/components/EditorPanel.svelte | 48 +- Journal.App/src/lib/components/Navbar.svelte | 18 +- .../src/lib/components/SidePanel.svelte | 514 +++++++++++++----- .../components/editor/FragmentEditor.svelte | 105 +++- .../lib/components/editor/ListEditor.svelte | 33 +- .../components/editor/MarkdownEditor.svelte | 137 ++++- .../components/editor/MarkdownToolbar.svelte | 139 ++++- .../lib/components/editor/TodoEditor.svelte | 49 +- Journal.App/src/lib/runtime/invoke.ts | 71 ++- Journal.App/src/lib/stores/entries.ts | 43 +- Journal.App/src/lib/stores/fragments.ts | 75 ++- Journal.App/src/lib/stores/lists.ts | 24 +- Journal.App/src/lib/stores/settings.ts | 47 +- Journal.App/src/lib/stores/todos.ts | 74 ++- Journal.App/src/lib/utils/markdown.ts | 23 +- Journal.App/src/lib/utils/metadata.ts | 24 +- Journal.App/src/routes/+layout.svelte | 7 +- Journal.App/src/routes/+page.svelte | 150 +++-- Journal.App/src/routes/settings/+page.svelte | 424 ++++++++++----- Journal.App/static/style.css | 10 +- 39 files changed, 1845 insertions(+), 700 deletions(-) create mode 100644 Journal.App/.prettierignore create mode 100644 Journal.App/prettier.config.cjs diff --git a/Journal.App/.prettierignore b/Journal.App/.prettierignore new file mode 100644 index 0000000..ca17681 --- /dev/null +++ b/Journal.App/.prettierignore @@ -0,0 +1,8 @@ +node_modules/ +build/ +.svelte-kit/ +.vscode/ +dist/ +coverage/ +target/ +src-tauri/target/ diff --git a/Journal.App/README.md b/Journal.App/README.md index d5f55d0..aa3ddaa 100644 --- a/Journal.App/README.md +++ b/Journal.App/README.md @@ -18,12 +18,12 @@ npm run tauri dev # Tauri desktop window (connects to dev server) ## Build Targets -| Command | Output | Use case | -|---------|--------|----------| -| `npm run build` | `Journal.App/build/` | Web bundle for `Journal.WebGateway` | -| `.\scripts\publish-app.ps1 -Target web` | `Journal.App/build/` | Same, via script | -| `.\scripts\publish-app.ps1 -Target tauri -TauriBundles none` | `src-tauri/target/release/journalapp.exe` | Raw desktop exe | -| `.\scripts\publish-app.ps1 -Target tauri -TauriBundles nsis` | NSIS installer | Packaged installer | +| Command | Output | Use case | +| ------------------------------------------------------------ | ----------------------------------------- | ----------------------------------- | +| `npm run build` | `Journal.App/build/` | Web bundle for `Journal.WebGateway` | +| `.\scripts\publish-app.ps1 -Target web` | `Journal.App/build/` | Same, via script | +| `.\scripts\publish-app.ps1 -Target tauri -TauriBundles none` | `src-tauri/target/release/journalapp.exe` | Raw desktop exe | +| `.\scripts\publish-app.ps1 -Target tauri -TauriBundles nsis` | NSIS installer | Packaged installer | ## Frontend State Management @@ -31,13 +31,13 @@ Svelte stores are the source of truth for all feature state. ### Current Stores -| Store file | State exports | Notes | -|-----------|---------------|-------| -| `src/lib/stores/entries.ts` | `entriesStore` | Entry list, `getDefaultEntry`, `createEntryDraft` | -| `src/lib/stores/fragments.ts` | `fragmentsStore` | Fragment CRUD + parse/serialize helpers | -| `src/lib/stores/todos.ts` | `todoListsStore`, `todosStore` | Todo list and item CRUD | -| `src/lib/stores/lists.ts` | `listsStore` | Generic list CRUD, `createListDraft` | -| `src/lib/stores/settings.ts` | `settingsTags`, `settingsFragmentTypes` | Tag/type config | +| Store file | State exports | Notes | +| ----------------------------- | --------------------------------------- | ------------------------------------------------- | +| `src/lib/stores/entries.ts` | `entriesStore` | Entry list, `getDefaultEntry`, `createEntryDraft` | +| `src/lib/stores/fragments.ts` | `fragmentsStore` | Fragment CRUD + parse/serialize helpers | +| `src/lib/stores/todos.ts` | `todoListsStore`, `todosStore` | Todo list and item CRUD | +| `src/lib/stores/lists.ts` | `listsStore` | Generic list CRUD, `createListDraft` | +| `src/lib/stores/settings.ts` | `settingsTags`, `settingsFragmentTypes` | Tag/type config | ### Store-First Rule @@ -47,14 +47,14 @@ Svelte stores are the source of truth for all feature state. ## Tauri Commands (Rust → Frontend) -| Command | Description | -|---------|-------------| -| `sidecar_command` | Forward a `CommandEnvelope` to `Journal.Sidecar` stdin/stdout and return parsed JSON | -| `get_sidecar_root` | Get currently resolved sidecar root path | -| `set_sidecar_root` | Override root path (saved to `settings.json`, restarts sidecar) | -| `get_ui_settings` | Load tag/fragment-type settings | -| `set_ui_settings` | Persist tag/fragment-type settings | -| `shutdown` | Stop sidecar, exit app | +| Command | Description | +| ------------------ | ------------------------------------------------------------------------------------ | +| `sidecar_command` | Forward a `CommandEnvelope` to `Journal.Sidecar` stdin/stdout and return parsed JSON | +| `get_sidecar_root` | Get currently resolved sidecar root path | +| `set_sidecar_root` | Override root path (saved to `settings.json`, restarts sidecar) | +| `get_ui_settings` | Load tag/fragment-type settings | +| `set_ui_settings` | Persist tag/fragment-type settings | +| `shutdown` | Stop sidecar, exit app | ## Sidecar Path Resolution diff --git a/Journal.App/package-lock.json b/Journal.App/package-lock.json index c9c5ff0..47db5f0 100644 --- a/Journal.App/package-lock.json +++ b/Journal.App/package-lock.json @@ -18,6 +18,8 @@ "@sveltejs/kit": "^2.9.0", "@sveltejs/vite-plugin-svelte": "^5.0.0", "@tauri-apps/cli": "^2", + "prettier": "^3.8.1", + "prettier-plugin-svelte": "^3.5.0", "svelte": "^5.0.0", "svelte-check": "^4.0.0", "typescript": "~5.6.2", @@ -906,7 +908,6 @@ "integrity": "sha512-NXsZLvalgI3HrHG6ogoEVzjyV7bSFQNqQeekfU7nNufQFrRyV3EBDfQKEwxx50peu7spZR42JuC1PFhwxuvBrg==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@standard-schema/spec": "^1.0.0", "@sveltejs/acorn-typescript": "^1.0.5", @@ -949,7 +950,6 @@ "integrity": "sha512-Y1Cs7hhTc+a5E9Va/xwKlAJoariQyHY+5zBgCZg4PFWNYQ1nMN9sjK1zhw1gK69DuqVP++sht/1GZg1aRwmAXQ==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@sveltejs/vite-plugin-svelte-inspector": "^4.0.1", "debug": "^4.4.1", @@ -1256,7 +1256,6 @@ "integrity": "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==", "dev": true, "license": "MIT", - "peer": true, "bin": { "acorn": "bin/acorn" }, @@ -1543,7 +1542,6 @@ "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=12" }, @@ -1580,6 +1578,33 @@ "node": "^10 || ^12 || >=14" } }, + "node_modules/prettier": { + "version": "3.8.1", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.8.1.tgz", + "integrity": "sha512-UOnG6LftzbdaHZcKoPFtOcCKztrQ57WkHDeRD9t/PTQtmT0NHSeWWepj6pS0z/N7+08BHFDQVUrfmfMRcZwbMg==", + "dev": true, + "license": "MIT", + "bin": { + "prettier": "bin/prettier.cjs" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/prettier/prettier?sponsor=1" + } + }, + "node_modules/prettier-plugin-svelte": { + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/prettier-plugin-svelte/-/prettier-plugin-svelte-3.5.0.tgz", + "integrity": "sha512-2lLO/7EupnjO/95t+XZesXs8Bf3nYLIDfCo270h5QWbj/vjLqmrQ1LiRk9LPggxSDsnVYfehamZNf+rgQYApZg==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "prettier": "^3.0.0", + "svelte": "^3.2.0 || ^4.0.0-next.0 || ^5.0.0-next.0" + } + }, "node_modules/readdirp": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz", @@ -1690,7 +1715,6 @@ "integrity": "sha512-pRUBr6j6uQDgBi208gHnGRMykw0Rf2Yr1HmLyRucsvcaYgIUxswJkT93WZJflsmezu5s8Lq+q78EoyLv2yaFCg==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@jridgewell/remapping": "^2.3.4", "@jridgewell/sourcemap-codec": "^1.5.0", @@ -1770,7 +1794,6 @@ "integrity": "sha512-hjcS1mhfuyi4WW8IWtjP7brDrG2cuDZukyrYrSauoXGNgx0S7zceP07adYkJycEr56BOUTNPzbInooiN3fn1qw==", "dev": true, "license": "Apache-2.0", - "peer": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -1785,7 +1808,6 @@ "integrity": "sha512-+Oxm7q9hDoLMyJOYfUYBuHQo+dkAloi33apOPP56pzj+vsdJDzr+j1NISE5pyaAuKL4A3UD34qd0lx5+kfKp2g==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "esbuild": "^0.25.0", "fdir": "^6.4.4", diff --git a/Journal.App/package.json b/Journal.App/package.json index bdca238..bcb8b0a 100644 --- a/Journal.App/package.json +++ b/Journal.App/package.json @@ -9,7 +9,9 @@ "preview": "vite preview", "check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json", "check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch", - "tauri": "tauri" + "tauri": "tauri", + "format": "prettier --write .", + "format:check": "prettier --check ." }, "license": "MIT", "dependencies": { @@ -22,6 +24,8 @@ "@sveltejs/kit": "^2.9.0", "@sveltejs/vite-plugin-svelte": "^5.0.0", "@tauri-apps/cli": "^2", + "prettier": "^3.8.1", + "prettier-plugin-svelte": "^3.5.0", "svelte": "^5.0.0", "svelte-check": "^4.0.0", "typescript": "~5.6.2", diff --git a/Journal.App/prettier.config.cjs b/Journal.App/prettier.config.cjs new file mode 100644 index 0000000..282b589 --- /dev/null +++ b/Journal.App/prettier.config.cjs @@ -0,0 +1,11 @@ +module.exports = { + plugins: ["prettier-plugin-svelte"], + overrides: [ + { + files: "*.svelte", + options: { + parser: "svelte", + }, + }, + ], +}; diff --git a/Journal.App/src-tauri/capabilities/default.json b/Journal.App/src-tauri/capabilities/default.json index 01a4101..cce481c 100644 --- a/Journal.App/src-tauri/capabilities/default.json +++ b/Journal.App/src-tauri/capabilities/default.json @@ -3,9 +3,5 @@ "identifier": "default", "description": "Capability for the main window", "windows": ["main"], - "permissions": [ - "core:default", - "dialog:default", - "opener:default" - ] + "permissions": ["core:default", "dialog:default", "opener:default"] } diff --git a/Journal.App/src-tauri/tauri.conf.json b/Journal.App/src-tauri/tauri.conf.json index ffc904b..2b445fc 100644 --- a/Journal.App/src-tauri/tauri.conf.json +++ b/Journal.App/src-tauri/tauri.conf.json @@ -32,4 +32,4 @@ "icons/icon.ico" ] } -} \ No newline at end of file +} diff --git a/Journal.App/src/app.html b/Journal.App/src/app.html index ef938e9..a3a83c3 100644 --- a/Journal.App/src/app.html +++ b/Journal.App/src/app.html @@ -3,12 +3,16 @@ - - + + Journal %sveltekit.head% +
%sveltekit.body%
diff --git a/Journal.App/src/lib/backend/auth.ts b/Journal.App/src/lib/backend/auth.ts index b3f8697..f1c3639 100644 --- a/Journal.App/src/lib/backend/auth.ts +++ b/Journal.App/src/lib/backend/auth.ts @@ -4,7 +4,7 @@ import { pickCase } from "./normalize"; export function hydrateWorkspace(password: string): Promise { return sendCommand({ action: "db.hydrate_workspace", - payload: { password } + payload: { password }, }); } @@ -24,17 +24,19 @@ type PersistOptions = { keepalive?: boolean; }; -async function getRuntimeConfig(options: PersistOptions = {}): Promise { +async function getRuntimeConfig( + options: PersistOptions = {}, +): Promise { const data = await sendCommand( { - action: "config.get" + action: "config.get", }, - options + options, ); return { dataDirectory: pickCase(data, "dataDirectory", "DataDirectory", ""), - vaultDirectory: pickCase(data, "vaultDirectory", "VaultDirectory", "") + vaultDirectory: pickCase(data, "vaultDirectory", "VaultDirectory", ""), }; } @@ -45,8 +47,8 @@ export async function unlockVaultWorkspace(password: string): Promise { payload: { password, vaultDirectory: config.vaultDirectory, - dataDirectory: config.dataDirectory - } + dataDirectory: config.dataDirectory, + }, }); if (!loaded) { @@ -57,12 +59,15 @@ export async function unlockVaultWorkspace(password: string): Promise { action: "db.hydrate_workspace", payload: { password, - dataDirectory: config.dataDirectory - } + dataDirectory: config.dataDirectory, + }, }); } -export async function persistAndClearVault(password: string, options: PersistOptions = {}): Promise { +export async function persistAndClearVault( + password: string, + options: PersistOptions = {}, +): Promise { const config = await getRuntimeConfig(options); await sendCommand( @@ -71,19 +76,19 @@ export async function persistAndClearVault(password: string, options: PersistOpt payload: { password, vaultDirectory: config.vaultDirectory, - dataDirectory: config.dataDirectory - } + dataDirectory: config.dataDirectory, + }, }, - options + options, ); await sendCommand( { action: "vault.clear_data_directory", payload: { - dataDirectory: config.dataDirectory - } + dataDirectory: config.dataDirectory, + }, }, - options + options, ); } diff --git a/Journal.App/src/lib/backend/client.ts b/Journal.App/src/lib/backend/client.ts index 1ab887b..81b5dc2 100644 --- a/Journal.App/src/lib/backend/client.ts +++ b/Journal.App/src/lib/backend/client.ts @@ -9,14 +9,17 @@ type SendCommandOptions = { keepalive?: boolean; }; -export async function sendCommand(command: BackendCommand, options: SendCommandOptions = {}): Promise { +export async function sendCommand( + command: BackendCommand, + options: SendCommandOptions = {}, +): Promise { const envelope: BackendCommand = { ...command, - correlationId: command.correlationId ?? newCorrelationId() + correlationId: command.correlationId ?? newCorrelationId(), }; const response = await invoke>("sidecar_command", { command: envelope, - keepalive: options.keepalive === true + keepalive: options.keepalive === true, }); if (!response.ok) { diff --git a/Journal.App/src/lib/backend/entries.ts b/Journal.App/src/lib/backend/entries.ts index a4dd4c3..9b6fdd0 100644 --- a/Journal.App/src/lib/backend/entries.ts +++ b/Journal.App/src/lib/backend/entries.ts @@ -1,5 +1,9 @@ import { sendCommand } from "./client"; -import { normalizeFragment, type FragmentDto, type FragmentDtoRaw } from "./fragments"; +import { + normalizeFragment, + type FragmentDto, + type FragmentDtoRaw, +} from "./fragments"; import { pickCase } from "./normalize"; export type ParsedSectionDto = { @@ -107,80 +111,126 @@ function normalizeSection(raw: ParsedSectionDtoRaw): ParsedSectionDto { return { title: pickCase(raw, "title", "Title", ""), content: pickCase(raw, "content", "Content", [] as string[]), - checkboxes: pickCase(raw, "checkboxes", "Checkboxes", {} as Record) + checkboxes: pickCase( + raw, + "checkboxes", + "Checkboxes", + {} as Record, + ), }; } -function normalizeJournalEntry(raw: JournalEntryDtoRaw | undefined): JournalEntryDto { - const fragments = pickCase(raw, "fragments", "Fragments", [] as FragmentDtoRaw[]); - const sections = pickCase(raw, "sections", "Sections", {} as Record); +function normalizeJournalEntry( + raw: JournalEntryDtoRaw | undefined, +): JournalEntryDto { + const fragments = pickCase( + raw, + "fragments", + "Fragments", + [] as FragmentDtoRaw[], + ); + const sections = pickCase( + raw, + "sections", + "Sections", + {} as Record, + ); return { date: pickCase(raw, "date", "Date", ""), fragments: fragments.map(normalizeFragment), rawContent: pickCase(raw, "rawContent", "RawContent", ""), sections: Object.fromEntries( - Object.entries(sections).map(([key, value]) => [key, normalizeSection(value)]) - ) + Object.entries(sections).map(([key, value]) => [ + key, + normalizeSection(value), + ]), + ), }; } function normalizeEntryListItem(raw: EntryListItemDtoRaw): EntryListItemDto { return { fileName: pickCase(raw, "fileName", "FileName", ""), - filePath: pickCase(raw, "filePath", "FilePath", "") + filePath: pickCase(raw, "filePath", "FilePath", ""), }; } -function normalizeEntryLoadResult(raw: EntryLoadResultDtoRaw): EntryLoadResultDto { - const nestedEntry = pickCase(raw, "entry", "Entry", undefined as JournalEntryDtoRaw | undefined); - const entry = - nestedEntry - ? normalizeJournalEntry(nestedEntry) - : normalizeJournalEntry({ - date: pickCase(raw, "date", "Date", undefined as string | undefined), - rawContent: pickCase(raw, "rawContent", "RawContent", undefined as string | undefined), - fragments: [], - sections: {} - }); +function normalizeEntryLoadResult( + raw: EntryLoadResultDtoRaw, +): EntryLoadResultDto { + const nestedEntry = pickCase( + raw, + "entry", + "Entry", + undefined as JournalEntryDtoRaw | undefined, + ); + const entry = nestedEntry + ? normalizeJournalEntry(nestedEntry) + : normalizeJournalEntry({ + date: pickCase(raw, "date", "Date", undefined as string | undefined), + rawContent: pickCase( + raw, + "rawContent", + "RawContent", + undefined as string | undefined, + ), + fragments: [], + sections: {}, + }); return { fileName: pickCase(raw, "fileName", "FileName", ""), filePath: pickCase(raw, "filePath", "FilePath", ""), - entry + entry, }; } -function normalizeEntrySearchResult(raw: EntrySearchResultDtoRaw): EntrySearchResultDto { - const nestedEntry = pickCase(raw, "entry", "Entry", undefined as JournalEntryDtoRaw | undefined); - const entry = - nestedEntry - ? normalizeJournalEntry(nestedEntry) - : normalizeJournalEntry({ - date: pickCase(raw, "date", "Date", undefined as string | undefined), - rawContent: pickCase(raw, "rawContent", "RawContent", undefined as string | undefined), - fragments: [], - sections: {} - }); +function normalizeEntrySearchResult( + raw: EntrySearchResultDtoRaw, +): EntrySearchResultDto { + const nestedEntry = pickCase( + raw, + "entry", + "Entry", + undefined as JournalEntryDtoRaw | undefined, + ); + const entry = nestedEntry + ? normalizeJournalEntry(nestedEntry) + : normalizeJournalEntry({ + date: pickCase(raw, "date", "Date", undefined as string | undefined), + rawContent: pickCase( + raw, + "rawContent", + "RawContent", + undefined as string | undefined, + ), + fragments: [], + sections: {}, + }); return { fileName: pickCase(raw, "fileName", "FileName", ""), - entry + entry, }; } -export async function listEntries(dataDirectory?: string): Promise { +export async function listEntries( + dataDirectory?: string, +): Promise { const data = await sendCommand({ action: "entries.list", - payload: { dataDirectory } + payload: { dataDirectory }, }); - return data.map(normalizeEntryListItem).filter((item) => Boolean(item.filePath)); + return data + .map(normalizeEntryListItem) + .filter((item) => Boolean(item.filePath)); } export async function loadEntry(filePath: string): Promise { const data = await sendCommand({ action: "entries.load", - payload: { filePath } + payload: { filePath }, }); return normalizeEntryLoadResult(data); @@ -194,26 +244,30 @@ export async function saveEntry(payload: { }): Promise { const data = await sendCommand({ action: "entries.save", - payload + payload, }); return { - filePath: pickCase(data, "filePath", "FilePath", "") + filePath: pickCase(data, "filePath", "FilePath", ""), }; } export async function deleteEntry(filePath: string): Promise { return sendCommand({ action: "entries.delete", - payload: { filePath } + payload: { filePath }, }); } -export async function searchEntries(payload: EntrySearchRequestDto): Promise { +export async function searchEntries( + payload: EntrySearchRequestDto, +): Promise { const data = await sendCommand({ action: "search.entries", - payload + payload, }); - return data.map(normalizeEntrySearchResult).filter((item) => Boolean(item.fileName)); + return data + .map(normalizeEntrySearchResult) + .filter((item) => Boolean(item.fileName)); } diff --git a/Journal.App/src/lib/backend/fragments.ts b/Journal.App/src/lib/backend/fragments.ts index dc0f26f..fdd03a2 100644 --- a/Journal.App/src/lib/backend/fragments.ts +++ b/Journal.App/src/lib/backend/fragments.ts @@ -41,13 +41,13 @@ export function normalizeFragment(raw: FragmentDtoRaw): FragmentDto { type: pickCase(raw, "type", "Type", ""), description: pickCase(raw, "description", "Description", ""), time: pickCase(raw, "time", "Time", ""), - tags: pickCase(raw, "tags", "Tags", [] as string[]) + tags: pickCase(raw, "tags", "Tags", [] as string[]), }; } export async function listFragments(): Promise { const data = await sendCommand({ - action: "fragments.list" + action: "fragments.list", }); return data.map(normalizeFragment).filter((item) => Boolean(item.id)); } @@ -55,32 +55,37 @@ export async function listFragments(): Promise { export async function getFragment(id: string): Promise { const data = await sendCommand({ action: "fragments.get", - id + id, }); if (!data) return null; const normalized = normalizeFragment(data); return normalized.id ? normalized : null; } -export async function createFragment(payload: CreateFragmentPayload): Promise { +export async function createFragment( + payload: CreateFragmentPayload, +): Promise { const data = await sendCommand({ action: "fragments.create", - payload + payload, }); return normalizeFragment(data); } -export function updateFragment(id: string, payload: UpdateFragmentPayload): Promise { +export function updateFragment( + id: string, + payload: UpdateFragmentPayload, +): Promise { return sendCommand({ action: "fragments.update", id, - payload + payload, }); } export function deleteFragment(id: string): Promise { return sendCommand({ action: "fragments.delete", - id + id, }); } diff --git a/Journal.App/src/lib/backend/lists.ts b/Journal.App/src/lib/backend/lists.ts index 74ec4d8..037c3f4 100644 --- a/Journal.App/src/lib/backend/lists.ts +++ b/Journal.App/src/lib/backend/lists.ts @@ -38,13 +38,13 @@ export function normalizeList(raw: ListDocumentDtoRaw): ListDocumentDto { label: pickCase(raw, "label", "Label", ""), content: pickCase(raw, "content", "Content", ""), createdAt: pickCase(raw, "createdAt", "CreatedAt", ""), - updatedAt: pickCase(raw, "updatedAt", "UpdatedAt", "") + updatedAt: pickCase(raw, "updatedAt", "UpdatedAt", ""), }; } export async function listLists(): Promise { const data = await sendCommand({ - action: "lists.list" + action: "lists.list", }); return data.map(normalizeList).filter((item) => Boolean(item.id)); } @@ -52,32 +52,37 @@ export async function listLists(): Promise { export async function getList(id: string): Promise { const data = await sendCommand({ action: "lists.get", - id + id, }); if (!data) return null; const normalized = normalizeList(data); return normalized.id ? normalized : null; } -export async function createList(payload: CreateListPayload): Promise { +export async function createList( + payload: CreateListPayload, +): Promise { const data = await sendCommand({ action: "lists.create", - payload + payload, }); return normalizeList(data); } -export function updateList(id: string, payload: UpdateListPayload): Promise { +export function updateList( + id: string, + payload: UpdateListPayload, +): Promise { return sendCommand({ action: "lists.update", id, - payload + payload, }); } export function deleteList(id: string): Promise { return sendCommand({ action: "lists.delete", - id + id, }); } diff --git a/Journal.App/src/lib/backend/normalize.ts b/Journal.App/src/lib/backend/normalize.ts index 6dded60..40a6413 100644 --- a/Journal.App/src/lib/backend/normalize.ts +++ b/Journal.App/src/lib/backend/normalize.ts @@ -1,14 +1,16 @@ type UnknownObject = Record; function asObject(value: unknown): UnknownObject | undefined { - return value && typeof value === "object" ? (value as UnknownObject) : undefined; + return value && typeof value === "object" + ? (value as UnknownObject) + : undefined; } export function pickCase( source: unknown, camelKey: string, pascalKey: string, - fallback: T + fallback: T, ): T { const obj = asObject(source); if (!obj) return fallback; diff --git a/Journal.App/src/lib/backend/templates.ts b/Journal.App/src/lib/backend/templates.ts index 79bf828..0b6b8ef 100644 --- a/Journal.App/src/lib/backend/templates.ts +++ b/Journal.App/src/lib/backend/templates.ts @@ -37,32 +37,40 @@ type EntryTemplateSaveResultDtoRaw = { FilePath?: string; }; -function normalizeTemplateItem(raw: EntryTemplateItemDtoRaw): EntryTemplateItemDto { +function normalizeTemplateItem( + raw: EntryTemplateItemDtoRaw, +): EntryTemplateItemDto { return { fileName: pickCase(raw, "fileName", "FileName", ""), - filePath: pickCase(raw, "filePath", "FilePath", "") + filePath: pickCase(raw, "filePath", "FilePath", ""), }; } -export async function listEntryTemplates(dataDirectory?: string): Promise { +export async function listEntryTemplates( + dataDirectory?: string, +): Promise { const data = await sendCommand({ action: "templates.list", - payload: { dataDirectory } + payload: { dataDirectory }, }); - return data.map(normalizeTemplateItem).filter((item) => Boolean(item.filePath)); + return data + .map(normalizeTemplateItem) + .filter((item) => Boolean(item.filePath)); } -export async function loadEntryTemplate(filePath: string): Promise { +export async function loadEntryTemplate( + filePath: string, +): Promise { const data = await sendCommand({ action: "templates.load", - payload: { filePath } + payload: { filePath }, }); return { fileName: pickCase(data, "fileName", "FileName", ""), filePath: pickCase(data, "filePath", "FilePath", ""), - content: pickCase(data, "content", "Content", "") + content: pickCase(data, "content", "Content", ""), }; } @@ -74,17 +82,17 @@ export async function saveEntryTemplate(payload: { }): Promise { const data = await sendCommand({ action: "templates.save", - payload + payload, }); return { - filePath: pickCase(data, "filePath", "FilePath", "") + filePath: pickCase(data, "filePath", "FilePath", ""), }; } export async function deleteEntryTemplate(filePath: string): Promise { return sendCommand({ action: "templates.delete", - payload: { filePath } + payload: { filePath }, }); } diff --git a/Journal.App/src/lib/backend/todos.ts b/Journal.App/src/lib/backend/todos.ts index 1802e9e..73c5150 100644 --- a/Journal.App/src/lib/backend/todos.ts +++ b/Journal.App/src/lib/backend/todos.ts @@ -66,7 +66,7 @@ function normalizeItem(raw: TodoItemDtoRaw): TodoItemDto { listId: pickCase(raw, "listId", "ListId", ""), text: pickCase(raw, "text", "Text", ""), done: pickCase(raw, "done", "Done", false), - sortOrder: pickCase(raw, "sortOrder", "SortOrder", 0) + sortOrder: pickCase(raw, "sortOrder", "SortOrder", 0), }; } @@ -76,13 +76,13 @@ function normalizeList(raw: TodoListDtoRaw): TodoListDto { id: pickCase(raw, "id", "Id", ""), label: pickCase(raw, "label", "Label", ""), createdAt: pickCase(raw, "createdAt", "CreatedAt", ""), - items: rawItems.map(normalizeItem) + items: rawItems.map(normalizeItem), }; } export async function listTodoLists(): Promise { const data = await sendCommand({ - action: "todos.list" + action: "todos.list", }); return data.map(normalizeList).filter((item) => Boolean(item.id)); } @@ -90,55 +90,65 @@ export async function listTodoLists(): Promise { export async function getTodoList(id: string): Promise { const data = await sendCommand({ action: "todos.get", - id + id, }); if (!data) return null; const normalized = normalizeList(data); return normalized.id ? normalized : null; } -export async function createTodoList(payload: CreateTodoListPayload): Promise { +export async function createTodoList( + payload: CreateTodoListPayload, +): Promise { const data = await sendCommand({ action: "todos.create", - payload + payload, }); return normalizeList(data); } -export function updateTodoList(id: string, payload: UpdateTodoListPayload): Promise { +export function updateTodoList( + id: string, + payload: UpdateTodoListPayload, +): Promise { return sendCommand({ action: "todos.update", id, - payload + payload, }); } export function deleteTodoList(id: string): Promise { return sendCommand({ action: "todos.delete", - id + id, }); } -export async function createTodoItem(payload: CreateTodoItemPayload): Promise { +export async function createTodoItem( + payload: CreateTodoItemPayload, +): Promise { const data = await sendCommand({ action: "todos.items.create", - payload + payload, }); return normalizeItem(data); } -export function updateTodoItem(id: string, payload: UpdateTodoItemPayload): Promise { +export function updateTodoItem( + id: string, + payload: UpdateTodoItemPayload, +): Promise { return sendCommand({ action: "todos.items.update", id, - payload + payload, }); } export function deleteTodoItem(id: string): Promise { return sendCommand({ action: "todos.items.delete", - id + id, }); } diff --git a/Journal.App/src/lib/backend/types.ts b/Journal.App/src/lib/backend/types.ts index 87644cf..1be56ad 100644 --- a/Journal.App/src/lib/backend/types.ts +++ b/Journal.App/src/lib/backend/types.ts @@ -10,4 +10,3 @@ export type BackendCommand = { export type BackendOk = { ok: true; data: T }; export type BackendErr = { ok: false; error: string }; export type BackendResponse = BackendOk | BackendErr; - diff --git a/Journal.App/src/lib/components/AppModal.svelte b/Journal.App/src/lib/components/AppModal.svelte index 2ced0ef..240d830 100644 --- a/Journal.App/src/lib/components/AppModal.svelte +++ b/Journal.App/src/lib/components/AppModal.svelte @@ -1,3 +1,4 @@ +
- @@ -125,7 +179,12 @@ {currentYear} -
diff --git a/Journal.App/src/lib/components/EditorPanel.svelte b/Journal.App/src/lib/components/EditorPanel.svelte index ad4dc61..e890f27 100644 --- a/Journal.App/src/lib/components/EditorPanel.svelte +++ b/Journal.App/src/lib/components/EditorPanel.svelte @@ -1,3 +1,4 @@ + @@ -736,19 +882,43 @@

{panelTitle}

{#if activeSection === "calendar"} - + {/if} {#if activeSection === "entries"} - - + + {/if} -
@@ -799,12 +969,24 @@
- - + +
- +
@@ -812,9 +994,17 @@

Saved Views

{#each builtInViews as view} - + {/each} - +
{#if showSaveViewInput}
@@ -837,8 +1027,17 @@
    {#each calendarSavedViews as view}
  • - - +
  • @@ -856,12 +1055,17 @@

    {calendarMonthLabel} {calendarYear} Timeline

    -

    Last refreshed: {calendarLastRefreshedAt || "Not yet"}

    +

    + Last refreshed: {calendarLastRefreshedAt || "Not yet"} +

    {:else} {#if showNewItemInput} @@ -900,10 +1104,20 @@ {item.label}
    - -
    @@ -932,10 +1146,20 @@ {item.label}
    - -
    @@ -963,10 +1187,20 @@ {#if showItemActions}
    - -
    @@ -980,7 +1214,11 @@ + @media (max-width: 980px) { + .side-panel { + padding: 12px 10px; + } + + .panel-list .item-actions { + display: flex; + } + + .panel-list .item-action { + width: 28px; + height: 28px; + } + } + + @media (max-width: 720px) { + .calendar-control-row { + grid-template-columns: 1fr; + } + } + diff --git a/Journal.App/src/lib/components/editor/FragmentEditor.svelte b/Journal.App/src/lib/components/editor/FragmentEditor.svelte index 2a69059..aa3b3c3 100644 --- a/Journal.App/src/lib/components/editor/FragmentEditor.svelte +++ b/Journal.App/src/lib/components/editor/FragmentEditor.svelte @@ -1,3 +1,4 @@ + @@ -203,10 +236,17 @@ {:else}

    {fragmentMode === "create" ? "New Fragment" : "Edit Fragment"}

    - +
    + {/if}
    @@ -252,8 +297,16 @@ aria-label="Fragment body" >
    - - + +
    {/if} @@ -292,7 +345,9 @@ color: var(--text-primary); font-size: 0.92rem; line-height: 1.65; - font-family: "Segoe UI Variable", "Segoe UI", "Segoe UI Emoji", "Apple Color Emoji", "Noto Color Emoji", Inter, Roboto, Helvetica, Arial, sans-serif; + font-family: + "Segoe UI Variable", "Segoe UI", "Segoe UI Emoji", "Apple Color Emoji", + "Noto Color Emoji", Inter, Roboto, Helvetica, Arial, sans-serif; } .fragment-view :global(h1), @@ -334,11 +389,17 @@ width: 100%; border: 1px solid var(--border-soft); border-radius: 8px; - background-color: color-mix(in srgb, var(--surface-1) 88%, var(--bg-editor) 12%); + background-color: color-mix( + in srgb, + var(--surface-1) 88%, + var(--bg-editor) 12% + ); color: var(--text-primary); padding: 10px 11px; font-size: 0.88rem; - font-family: "Segoe UI Variable", "Segoe UI", "Segoe UI Emoji", "Apple Color Emoji", "Noto Color Emoji", Inter, Roboto, Helvetica, Arial, sans-serif; + font-family: + "Segoe UI Variable", "Segoe UI", "Segoe UI Emoji", "Apple Color Emoji", + "Noto Color Emoji", Inter, Roboto, Helvetica, Arial, sans-serif; } .fragment-form textarea { diff --git a/Journal.App/src/lib/components/editor/ListEditor.svelte b/Journal.App/src/lib/components/editor/ListEditor.svelte index 5f34b21..d7ea716 100644 --- a/Journal.App/src/lib/components/editor/ListEditor.svelte +++ b/Journal.App/src/lib/components/editor/ListEditor.svelte @@ -1,3 +1,4 @@ +
    @@ -246,156 +258,225 @@

    Settings

    Configure app behavior and interface options.

    -
    -
    -
    -

    - - Startup -

    -
    - -
    +
    +
    +

    + + Startup +

    +
    + +
    -
    -
    -

    - - Tags -

    -

    Add and manage tags used for notes and entries.

    -
    +
    +
    +

    + + Tags +

    +

    + Add and manage tags used for notes and entries. +

    +
    -
    - event.key === "Enter" && addTag()} - /> - -
    +
    + event.key === "Enter" && addTag()} + /> + +
    -
      - {#each $settingsTags as tag, index} -
    • - {#if editingTagIndex === index} - { - if (event.key === "Enter") saveEditTag(); - if (event.key === "Escape") cancelEditTag(); - }} - /> -
      - - -
      - {:else} - {tag} -
      - - -
      - {/if} -
    • - {/each} -
    -
    +
      + {#each $settingsTags as tag, index} +
    • + {#if editingTagIndex === index} + { + if (event.key === "Enter") saveEditTag(); + if (event.key === "Escape") cancelEditTag(); + }} + /> +
      + + +
      + {:else} + {tag} +
      + + +
      + {/if} +
    • + {/each} +
    +
    -
    -
    -

    - - Fragment Types -

    -

    Configure custom fragment types for the Fragments section.

    -
    +
    +
    +

    + + Fragment Types +

    +

    + Configure custom fragment types for the Fragments section. +

    +
    -
    - event.key === "Enter" && addFragmentTypeLocal()} - /> - -
    +
    + + event.key === "Enter" && addFragmentTypeLocal()} + /> + +
    -
      - {#each $settingsFragmentTypes as type, index} -
    • - {#if editingFragmentTypeIndex === index} - { - if (event.key === "Enter") saveEditFragmentType(); - if (event.key === "Escape") cancelEditFragmentType(); - }} - /> -
      - - -
      - {:else} - {type} -
      - - -
      - {/if} -
    • - {/each} -
    -
    +
      + {#each $settingsFragmentTypes as type, index} +
    • + {#if editingFragmentTypeIndex === index} + { + if (event.key === "Enter") saveEditFragmentType(); + if (event.key === "Escape") cancelEditFragmentType(); + }} + /> +
      + + +
      + {:else} + {type} +
      + + +
      + {/if} +
    • + {/each} +
    +
    -
    -
    -

    - - Sidecar -

    -

    Root directory containing the Journal.Sidecar project.

    -
    +
    +
    +

    + + Sidecar +

    +

    + Root directory containing the Journal.Sidecar project. +

    +
    -
    - event.key === "Enter" && saveSidecarRoot()} - /> - - {#if sidecarRootIsCustom} - +
    + event.key === "Enter" && saveSidecarRoot()} + /> + + {#if sidecarRootIsCustom} + + {/if} +
    + + {#if sidecarRootError} +

    {sidecarRootError}

    {/if} -
    - - {#if sidecarRootError} -

    {sidecarRootError}

    - {/if} -
    +
    @@ -414,11 +495,14 @@ - diff --git a/Journal.App/static/style.css b/Journal.App/static/style.css index 53a432c..873272b 100644 --- a/Journal.App/static/style.css +++ b/Journal.App/static/style.css @@ -1,5 +1,6 @@ :root { - font-family: "Segoe UI Variable", "Segoe UI", Inter, Roboto, Helvetica, Arial, sans-serif; + font-family: + "Segoe UI Variable", "Segoe UI", Inter, Roboto, Helvetica, Arial, sans-serif; font-size: 15px; line-height: 1.45; font-weight: 400; @@ -51,7 +52,11 @@ body { } body { - background: radial-gradient(circle at 15% -10%, var(--zinc-800) 0%, var(--bg-app) 42%); + background: radial-gradient( + circle at 15% -10%, + var(--zinc-800) 0%, + var(--bg-app) 42% + ); color: var(--text-primary); } @@ -84,6 +89,7 @@ select { .app-shell { min-height: 100vh; + min-height: 100dvh; display: grid; grid-template-columns: 72px 300px minmax(0, 1fr); }