diff --git a/Journal.App/package-lock.json b/Journal.App/package-lock.json index afee08e..cd7af70 100644 --- a/Journal.App/package-lock.json +++ b/Journal.App/package-lock.json @@ -10,6 +10,7 @@ "license": "MIT", "dependencies": { "@tauri-apps/api": "^2", + "@tauri-apps/plugin-dialog": "^2.6.0", "@tauri-apps/plugin-opener": "^2" }, "devDependencies": { @@ -1208,6 +1209,15 @@ "node": ">= 10" } }, + "node_modules/@tauri-apps/plugin-dialog": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/@tauri-apps/plugin-dialog/-/plugin-dialog-2.6.0.tgz", + "integrity": "sha512-q4Uq3eY87TdcYzXACiYSPhmpBA76shgmQswGkSVio4C82Sz2W4iehe9TnKYwbq7weHiL88Yw19XZm7v28+Micg==", + "license": "MIT OR Apache-2.0", + "dependencies": { + "@tauri-apps/api": "^2.8.0" + } + }, "node_modules/@tauri-apps/plugin-opener": { "version": "2.5.3", "resolved": "https://registry.npmjs.org/@tauri-apps/plugin-opener/-/plugin-opener-2.5.3.tgz", diff --git a/Journal.App/package.json b/Journal.App/package.json index c741d40..bdca238 100644 --- a/Journal.App/package.json +++ b/Journal.App/package.json @@ -14,16 +14,17 @@ "license": "MIT", "dependencies": { "@tauri-apps/api": "^2", + "@tauri-apps/plugin-dialog": "^2.6.0", "@tauri-apps/plugin-opener": "^2" }, "devDependencies": { "@sveltejs/adapter-static": "^3.0.6", "@sveltejs/kit": "^2.9.0", "@sveltejs/vite-plugin-svelte": "^5.0.0", + "@tauri-apps/cli": "^2", "svelte": "^5.0.0", "svelte-check": "^4.0.0", "typescript": "~5.6.2", - "vite": "^6.0.3", - "@tauri-apps/cli": "^2" + "vite": "^6.0.3" } } diff --git a/Journal.App/src-tauri/Cargo.lock b/Journal.App/src-tauri/Cargo.lock index e48c277..2191152 100644 --- a/Journal.App/src-tauri/Cargo.lock +++ b/Journal.App/src-tauri/Cargo.lock @@ -698,6 +698,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "89a09f22a6c6069a18470eb92d2298acf25463f14256d24778e1230d789a2aec" dependencies = [ "bitflags 2.11.0", + "block2", + "libc", "objc2", ] @@ -1776,6 +1778,7 @@ dependencies = [ "serde_json", "tauri", "tauri-build", + "tauri-plugin-dialog", "tauri-plugin-opener", "tokio", ] @@ -2940,6 +2943,30 @@ dependencies = [ "web-sys", ] +[[package]] +name = "rfd" +version = "0.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a15ad77d9e70a92437d8f74c35d99b4e4691128df018833e99f90bcd36152672" +dependencies = [ + "block2", + "dispatch2", + "glib-sys", + "gobject-sys", + "gtk-sys", + "js-sys", + "log", + "objc2", + "objc2-app-kit", + "objc2-core-foundation", + "objc2-foundation", + "raw-window-handle", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", + "windows-sys 0.60.2", +] + [[package]] name = "rustc_version" version = "0.4.1" @@ -3626,6 +3653,46 @@ dependencies = [ "walkdir", ] +[[package]] +name = "tauri-plugin-dialog" +version = "2.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9204b425d9be8d12aa60c2a83a289cf7d1caae40f57f336ed1155b3a5c0e359b" +dependencies = [ + "log", + "raw-window-handle", + "rfd", + "serde", + "serde_json", + "tauri", + "tauri-plugin", + "tauri-plugin-fs", + "thiserror 2.0.18", + "url", +] + +[[package]] +name = "tauri-plugin-fs" +version = "2.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed390cc669f937afeb8b28032ce837bac8ea023d975a2e207375ec05afaf1804" +dependencies = [ + "anyhow", + "dunce", + "glob", + "percent-encoding", + "schemars 0.8.22", + "serde", + "serde_json", + "serde_repr", + "tauri", + "tauri-plugin", + "tauri-utils", + "thiserror 2.0.18", + "toml 0.9.12+spec-1.1.0", + "url", +] + [[package]] name = "tauri-plugin-opener" version = "2.5.3" diff --git a/Journal.App/src-tauri/Cargo.toml b/Journal.App/src-tauri/Cargo.toml index e39f75d..ccc2244 100644 --- a/Journal.App/src-tauri/Cargo.toml +++ b/Journal.App/src-tauri/Cargo.toml @@ -19,6 +19,7 @@ tauri-build = { version = "2", features = [] } [dependencies] tauri = { version = "2", features = [] } +tauri-plugin-dialog = "2" tauri-plugin-opener = "2" serde = { version = "1", features = ["derive"] } serde_json = "1" diff --git a/Journal.App/src-tauri/capabilities/default.json b/Journal.App/src-tauri/capabilities/default.json index 4cdbf49..01a4101 100644 --- a/Journal.App/src-tauri/capabilities/default.json +++ b/Journal.App/src-tauri/capabilities/default.json @@ -5,6 +5,7 @@ "windows": ["main"], "permissions": [ "core:default", + "dialog:default", "opener:default" ] } diff --git a/Journal.App/src-tauri/src/lib.rs b/Journal.App/src-tauri/src/lib.rs index 14f72a7..49dfa3a 100644 --- a/Journal.App/src-tauri/src/lib.rs +++ b/Journal.App/src-tauri/src/lib.rs @@ -28,6 +28,7 @@ struct CommandEnvelope { const DEFAULT_SETTINGS_TAGS: &[&str] = &["Personal", "Work", "Ideas", "Journal"]; const DEFAULT_FRAGMENT_TYPES: &[&str] = &["Quote", "Snippet", "Reference"]; +const DEFAULT_STARTUP_VIEW: &str = "entries"; #[derive(Deserialize, Serialize)] struct AppSettings { @@ -37,6 +38,8 @@ struct AppSettings { tags: Vec, #[serde(default = "default_fragment_types")] fragment_types: Vec, + #[serde(default = "default_startup_view")] + default_startup_view: String, } impl Default for AppSettings { @@ -45,6 +48,7 @@ impl Default for AppSettings { sidecar_root: None, tags: default_settings_tags(), fragment_types: default_fragment_types(), + default_startup_view: default_startup_view(), } } } @@ -63,6 +67,10 @@ fn default_fragment_types() -> Vec { .collect() } +fn default_startup_view() -> String { + DEFAULT_STARTUP_VIEW.to_string() +} + fn normalize_items(values: Vec, fallback: &[&str]) -> Vec { let mut seen = HashSet::new(); let mut normalized = Vec::new(); @@ -84,6 +92,17 @@ fn normalize_items(values: Vec, fallback: &[&str]) -> Vec { normalized } +fn normalize_startup_view(value: Option) -> String { + let normalized = value + .unwrap_or_else(default_startup_view) + .trim() + .to_lowercase(); + match normalized.as_str() { + "entries" | "calendar" | "fragments" | "todos" | "lists" => normalized, + _ => default_startup_view(), + } +} + struct ManagedSidecar { child: Child, stdin: ChildStdin, @@ -363,10 +382,12 @@ async fn get_ui_settings(state: tauri::State<'_, SidecarState>) -> Result, tags: Vec, fragment_types: Vec, + default_startup_view: Option, ) -> Result { let mut settings = load_settings(&state.config_path); settings.tags = normalize_items(tags, DEFAULT_SETTINGS_TAGS); settings.fragment_types = normalize_items(fragment_types, DEFAULT_FRAGMENT_TYPES); + settings.default_startup_view = normalize_startup_view(default_startup_view); save_settings(&state.config_path, &settings)?; Ok(serde_json::json!({ "tags": settings.tags, - "fragmentTypes": settings.fragment_types + "fragmentTypes": settings.fragment_types, + "defaultStartupView": settings.default_startup_view })) } @@ -416,6 +440,7 @@ async fn sidecar_command( #[cfg_attr(mobile, tauri::mobile_entry_point)] pub fn run() { let app = tauri::Builder::default() + .plugin(tauri_plugin_dialog::init()) .plugin(tauri_plugin_opener::init()) .invoke_handler(tauri::generate_handler![ sidecar_command, diff --git a/Journal.App/src/lib/components/EditorPanel.svelte b/Journal.App/src/lib/components/EditorPanel.svelte index b6b536e..f1c918e 100644 --- a/Journal.App/src/lib/components/EditorPanel.svelte +++ b/Journal.App/src/lib/components/EditorPanel.svelte @@ -1,5 +1,6 @@ @@ -195,7 +201,10 @@ {@html renderMarkdown(openDocumentContent)} {:else} -
+

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

@@ -254,34 +263,35 @@ .fragment-surface { min-height: 0; flex: 1; - padding: 8px; - display: grid; - place-items: center; + overflow: auto; + padding: 0 14px 14px; } .fragment-form { - width: min(760px, 100%); - border: 1px solid var(--border-soft); - border-radius: 12px; - background: var(--surface-1); - box-shadow: 0 14px 34px rgba(0, 0, 0, 0.25); - padding: 14px; + width: min(100%, 920px); + margin: 0 auto; + border: none; + border-radius: 0; + background: transparent; + padding: 28px 36px; display: flex; flex-direction: column; - gap: 10px; + gap: 12px; } .fragment-view { - width: min(760px, 100%); - border: 1px solid var(--border-soft); - border-radius: 12px; - background: var(--surface-1); - box-shadow: 0 14px 34px rgba(0, 0, 0, 0.25); - padding: 14px; + width: min(100%, 920px); + margin: 0 auto; + border: none; + border-radius: 0; + background: transparent; + padding: 28px 36px; display: flex; flex-direction: column; gap: 14px; color: var(--text-primary); + font-size: 0.92rem; + line-height: 1.65; } .fragment-view :global(h1), @@ -311,9 +321,10 @@ } .fragment-form h2 { - font-size: 0.94rem; + font-size: 1rem; font-weight: 600; color: var(--text-primary); + margin-bottom: 2px; } .fragment-form input, @@ -322,15 +333,16 @@ width: 100%; border: 1px solid var(--border-soft); border-radius: 8px; - background: var(--bg-app); + background: color-mix(in srgb, var(--surface-1) 88%, var(--bg-editor) 12%); color: var(--text-primary); - padding: 9px 10px; - font-size: 0.86rem; + padding: 10px 11px; + font-size: 0.88rem; } .fragment-form textarea { resize: vertical; - min-height: 160px; + min-height: 220px; + line-height: 1.55; } .fragment-form-row { @@ -343,7 +355,7 @@ width: fit-content; border-radius: 8px; border: 1px solid var(--border-strong); - background: var(--surface-3); + background: color-mix(in srgb, var(--surface-2) 84%, var(--bg-hover) 16%); color: var(--text-primary); padding: 8px 12px; font-size: 0.82rem; @@ -363,7 +375,7 @@ .fragment-secondary { border-radius: 8px; border: 1px solid var(--border-soft); - background: var(--surface-1); + background: color-mix(in srgb, var(--surface-1) 90%, var(--bg-editor) 10%); color: var(--text-muted); padding: 8px 12px; font-size: 0.82rem; @@ -374,4 +386,21 @@ background: var(--bg-hover); color: var(--text-primary); } + + @media (max-width: 980px) { + .fragment-surface { + padding: 4px 8px 10px; + } + + .fragment-form, + .fragment-view { + width: 100%; + padding: 18px 16px; + font-size: 0.89rem; + } + + .fragment-form-row { + grid-template-columns: 1fr; + } + } diff --git a/Journal.App/src/lib/components/editor/ListEditor.svelte b/Journal.App/src/lib/components/editor/ListEditor.svelte new file mode 100644 index 0000000..5f34b21 --- /dev/null +++ b/Journal.App/src/lib/components/editor/ListEditor.svelte @@ -0,0 +1,278 @@ + + +
+
+ + + + + +
    + {#each items as item} +
  • + {#if editingItemId === item.id} + { + if (event.key === "Enter") saveEditItem(); + if (event.key === "Escape") cancelEditItem(); + }} + /> +
    + + +
    + {:else} + {item.text} +
    + + +
    + {/if} +
  • + {/each} +
+
+
+ + diff --git a/Journal.App/src/lib/components/editor/MarkdownEditor.svelte b/Journal.App/src/lib/components/editor/MarkdownEditor.svelte index ccb5774..55d9717 100644 --- a/Journal.App/src/lib/components/editor/MarkdownEditor.svelte +++ b/Journal.App/src/lib/components/editor/MarkdownEditor.svelte @@ -1,5 +1,6 @@ + +
+
+ + {#if isEntryDocument} + + {/if} +
+ +
+ + + + + + +
+
+ + diff --git a/Journal.App/src/lib/components/editor/TodoEditor.svelte b/Journal.App/src/lib/components/editor/TodoEditor.svelte index 2ac5c45..47c00f7 100644 --- a/Journal.App/src/lib/components/editor/TodoEditor.svelte +++ b/Journal.App/src/lib/components/editor/TodoEditor.svelte @@ -172,23 +172,22 @@ .todo-surface { min-height: 0; flex: 1; - padding: 8px; - display: grid; - place-items: center; + overflow: auto; + padding: 0 14px 14px; } .todo-card { - width: min(760px, 100%); - border: 1px solid var(--border-soft); - border-radius: 12px; - background: var(--surface-1); - box-shadow: 0 14px 34px rgba(0, 0, 0, 0.25); - padding: 14px; + width: min(100%, 920px); + margin: 0 auto; + border: none; + border-radius: 0; + background: transparent; + padding: 28px 36px; display: flex; flex-direction: column; - gap: 10px; + gap: 12px; max-height: 100%; - overflow: auto; + overflow: visible; } .todo-create { @@ -201,19 +200,20 @@ width: 100%; border: 1px solid var(--border-soft); border-radius: 8px; - background: var(--bg-app); + background: color-mix(in srgb, var(--surface-1) 88%, var(--bg-editor) 12%); color: var(--text-primary); - padding: 8px 10px; - font-size: 0.86rem; + padding: 10px 11px; + font-size: 0.88rem; } .todo-add-btn { border-radius: 8px; border: 1px solid var(--border-strong); - background: var(--surface-3); + background: color-mix(in srgb, var(--surface-2) 84%, var(--bg-hover) 16%); color: var(--text-primary); - padding: 8px 12px; + padding: 9px 14px; font-size: 0.82rem; + font-weight: 600; cursor: pointer; } @@ -225,7 +225,7 @@ list-style: none; display: flex; flex-direction: column; - gap: 8px; + gap: 10px; } .todo-item { @@ -235,8 +235,8 @@ gap: 10px; border: 1px solid var(--border-soft); border-radius: 8px; - padding: 8px 10px; - background: var(--bg-app); + padding: 10px 12px; + background: color-mix(in srgb, var(--surface-1) 90%, var(--bg-editor) 10%); } .todo-check { @@ -245,8 +245,9 @@ } .todo-text { - font-size: 0.86rem; + font-size: 0.9rem; color: var(--text-primary); + line-height: 1.45; } .todo-text.is-done { @@ -262,7 +263,7 @@ .todo-btn { border-radius: 7px; border: 1px solid var(--border-soft); - background: var(--surface-1); + background: color-mix(in srgb, var(--surface-1) 90%, var(--bg-editor) 10%); color: var(--text-muted); padding: 6px 10px; font-size: 0.78rem; @@ -271,7 +272,7 @@ .todo-btn.save { border-color: var(--border-strong); - background: var(--surface-3); + background: color-mix(in srgb, var(--surface-2) 84%, var(--bg-hover) 16%); color: var(--text-primary); } @@ -281,4 +282,25 @@ background: var(--bg-hover); color: var(--text-primary); } + + @media (max-width: 980px) { + .todo-surface { + padding: 4px 8px 10px; + } + + .todo-card { + width: 100%; + padding: 18px 16px; + } + + .todo-item { + grid-template-columns: auto minmax(0, 1fr); + row-gap: 8px; + } + + .todo-actions { + grid-column: 1 / -1; + justify-content: flex-end; + } + } diff --git a/Journal.App/src/lib/runtime/invoke.ts b/Journal.App/src/lib/runtime/invoke.ts index b665d7a..200bb40 100644 --- a/Journal.App/src/lib/runtime/invoke.ts +++ b/Journal.App/src/lib/runtime/invoke.ts @@ -9,6 +9,7 @@ type WindowWithTauri = Window & { type UiSettingsPayload = { tags?: string[]; fragmentTypes?: string[]; + defaultStartupView?: string; }; type FetchJsonOptions = { @@ -48,7 +49,8 @@ function readUiSettingsFromLocalStorage(): UiSettingsPayload { const parsed = JSON.parse(raw) as UiSettingsPayload; return { tags: Array.isArray(parsed.tags) ? parsed.tags : undefined, - fragmentTypes: Array.isArray(parsed.fragmentTypes) ? parsed.fragmentTypes : undefined + fragmentTypes: Array.isArray(parsed.fragmentTypes) ? parsed.fragmentTypes : undefined, + defaultStartupView: typeof parsed.defaultStartupView === "string" ? parsed.defaultStartupView : undefined }; } catch { return {}; @@ -62,7 +64,8 @@ function writeUiSettingsToLocalStorage(payload: UiSettingsPayload): void { const safePayload: UiSettingsPayload = { tags: Array.isArray(payload.tags) ? payload.tags : undefined, - fragmentTypes: Array.isArray(payload.fragmentTypes) ? payload.fragmentTypes : undefined + fragmentTypes: Array.isArray(payload.fragmentTypes) ? payload.fragmentTypes : undefined, + defaultStartupView: typeof payload.defaultStartupView === "string" ? payload.defaultStartupView : undefined }; window.localStorage.setItem(UI_SETTINGS_KEY, JSON.stringify(safePayload)); @@ -131,8 +134,12 @@ export async function invoke(command: string, args?: InvokeArgs): Promise Array.isArray(args?.fragmentTypes) ? (args?.fragmentTypes as string[]) : Array.isArray(args?.fragment_types) ? (args?.fragment_types as string[]) : undefined; + const defaultStartupView = + typeof args?.defaultStartupView === "string" ? args.defaultStartupView : + typeof args?.default_startup_view === "string" ? args.default_startup_view : + undefined; - writeUiSettingsToLocalStorage({ tags, fragmentTypes }); + writeUiSettingsToLocalStorage({ tags, fragmentTypes, defaultStartupView }); return undefined as T; } case "shutdown": diff --git a/Journal.App/src/lib/stores/settings.ts b/Journal.App/src/lib/stores/settings.ts index 81bb23f..b9b705b 100644 --- a/Journal.App/src/lib/stores/settings.ts +++ b/Journal.App/src/lib/stores/settings.ts @@ -4,9 +4,13 @@ import { invoke } from "$lib/runtime/invoke"; const defaultTags = ["Personal", "Work", "Ideas", "Journal"]; const defaultFragmentTypes = ["Quote", "Snippet", "Reference"]; +const startupViews = ["entries", "calendar", "fragments", "todos", "lists"] as const; +const defaultStartupView = "entries"; +export type StartupView = typeof startupViews[number]; export const settingsTags = writable([...defaultTags]); export const settingsFragmentTypes = writable([...defaultFragmentTypes]); +export const settingsDefaultStartupView = writable(defaultStartupView); let hydrationComplete = false; let hydrating = false; @@ -14,6 +18,7 @@ let hydrating = false; type UiSettingsPayload = { tags?: string[]; fragmentTypes?: string[]; + defaultStartupView?: string; }; function normalize(value: string): string { @@ -39,6 +44,14 @@ function normalizeValues(values: string[], fallback: string[]): string[] { return result.length ? result : [...fallback]; } +function normalizeStartupView(value: string | undefined): StartupView { + const normalized = (value ?? "").trim().toLowerCase(); + if (startupViews.includes(normalized as StartupView)) { + return normalized as StartupView; + } + return defaultStartupView; +} + export async function hydrateUiSettings(): Promise { if (hydrating || hydrationComplete) return; hydrating = true; @@ -46,6 +59,7 @@ export async function hydrateUiSettings(): Promise { const payload = await invoke("get_ui_settings"); settingsTags.set(normalizeValues(payload.tags ?? [], defaultTags)); settingsFragmentTypes.set(normalizeValues(payload.fragmentTypes ?? [], defaultFragmentTypes)); + settingsDefaultStartupView.set(normalizeStartupView(payload.defaultStartupView)); } catch (error) { console.error("[settings] hydrate failed", error); } finally { @@ -57,13 +71,17 @@ export async function hydrateUiSettings(): Promise { export async function persistUiSettings(): Promise { const tags = normalizeValues(get(settingsTags), defaultTags); const fragmentTypes = normalizeValues(get(settingsFragmentTypes), defaultFragmentTypes); + const startupView = normalizeStartupView(get(settingsDefaultStartupView)); settingsTags.set(tags); settingsFragmentTypes.set(fragmentTypes); + settingsDefaultStartupView.set(startupView); await invoke("set_ui_settings", { tags, fragmentTypes, - fragment_types: fragmentTypes + fragment_types: fragmentTypes, + defaultStartupView: startupView, + default_startup_view: startupView }); } @@ -132,3 +150,11 @@ export function removeFragmentType(index: number): boolean { return true; } +export function setDefaultStartupView(value: string): boolean { + const next = normalizeStartupView(value); + if (get(settingsDefaultStartupView) === next) return false; + settingsDefaultStartupView.set(next); + queuePersist(); + return true; +} + diff --git a/Journal.App/src/routes/+page.svelte b/Journal.App/src/routes/+page.svelte index eeb5987..6851838 100644 --- a/Journal.App/src/routes/+page.svelte +++ b/Journal.App/src/routes/+page.svelte @@ -6,6 +6,7 @@ import { deleteEntryByStoreId, entriesStore, getDefaultEntry, hydrateEntries, loadEntryByStoreId, saveEntryFromStore } from "$lib/stores/entries"; import { deleteFragmentByStoreId, hydrateFragments } from "$lib/stores/fragments"; import { deleteListByStoreId, hydrateLists, updateListByStoreId } from "$lib/stores/lists"; + import { hydrateUiSettings, settingsDefaultStartupView, type StartupView } from "$lib/stores/settings"; import { isVaultReady, setFlushCallback, setVaultSession } from "$lib/stores/session"; import { deleteTodoListByStoreId, hydrateTodos } from "$lib/stores/todos"; import Navbar from "$lib/components/Navbar.svelte"; @@ -48,6 +49,43 @@ let pendingDeleteItemId = ""; let templateRefreshToken = 0; + function resolveStartupSection(value: string): StartupView { + switch (value) { + case "calendar": + case "fragments": + case "todos": + case "lists": + case "entries": + return value; + default: + return "entries"; + } + } + + function parseSectionQuery(value: string | null): StartupView | null { + if (!value) return null; + const normalized = value.trim().toLowerCase(); + switch (normalized) { + case "entries": + case "calendar": + case "fragments": + case "todos": + case "lists": + return normalized; + default: + return null; + } + } + + function applyStartupSection(section: StartupView) { + selectedSection = section; + editMode = false; + if (section !== "entries") { + activeDocumentId = ""; + activeDocumentLabel = ""; + } + } + function toTemplatePath(id: string): string | null { const prefix = "entries/template-file/"; if (!id.startsWith(prefix)) return null; @@ -274,8 +312,13 @@ } async function handleSelect(id: string) { - if (id === "account" || id === "settings") { - goto(`/${id}`); + if (id === "account") { + goto("/account"); + return; + } + + if (id === "settings") { + goto(`/settings?return=${encodeURIComponent(selectedSection)}`); return; } @@ -415,7 +458,13 @@ onMount(() => { setFlushCallback(saveCurrentDocument); - bootstrapFragmentsWithUnlock(); + void (async () => { + await hydrateUiSettings(); + const startupSection = resolveStartupSection(get(settingsDefaultStartupView)); + const sectionFromQuery = parseSectionQuery(new URLSearchParams(window.location.search).get("section")); + applyStartupSection(sectionFromQuery ?? startupSection); + await bootstrapFragmentsWithUnlock(); + })(); }); diff --git a/Journal.App/src/routes/settings/+page.svelte b/Journal.App/src/routes/settings/+page.svelte index b52f378..38490d7 100644 --- a/Journal.App/src/routes/settings/+page.svelte +++ b/Journal.App/src/routes/settings/+page.svelte @@ -7,12 +7,14 @@ addSettingsTag, removeFragmentType, removeSettingsTag, + setDefaultStartupView, + settingsDefaultStartupView, settingsFragmentTypes, settingsTags, updateFragmentType, updateSettingsTag } from "$lib/stores/settings"; - import { invoke } from "$lib/runtime/invoke"; + import { invoke, isTauriRuntime } from "$lib/runtime/invoke"; import { onMount } from "svelte"; const activeSection = "settings"; @@ -34,8 +36,15 @@ let sidecarRoot = ""; let sidecarRootIsCustom = false; let sidecarRootError = ""; + let sidecarBrowseBusy = false; + let returnSection = "entries"; onMount(async () => { + const queryReturn = new URLSearchParams(window.location.search).get("return")?.trim().toLowerCase() ?? ""; + if (["entries", "calendar", "fragments", "todos", "lists"].includes(queryReturn)) { + returnSection = queryReturn; + } + try { const result: any = await invoke("get_sidecar_root"); sidecarRoot = result.root; @@ -67,6 +76,31 @@ } } + async function browseSidecarRoot() { + sidecarRootError = ""; + if (!isTauriRuntime()) { + sidecarRootError = "Folder picker is only available in desktop app."; + return; + } + sidecarBrowseBusy = true; + try { + const { open } = await import("@tauri-apps/plugin-dialog"); + const picked = await open({ + directory: true, + multiple: false, + title: "Select Sidecar Root Directory" + }); + if (typeof picked === "string" && picked.trim()) { + sidecarRoot = picked; + await saveSidecarRoot(); + } + } catch (e) { + sidecarRootError = String(e); + } finally { + sidecarBrowseBusy = false; + } + } + function showModal(options: { action: "logout-confirm" | "logout-info"; title: string; @@ -128,7 +162,11 @@ return; } - goto("/"); + goto(`/?section=${encodeURIComponent(id)}`); + } + + function closeSettings() { + goto(`/?section=${encodeURIComponent(returnSection)}`); } function addTag() { @@ -193,6 +231,10 @@ } } + function updateDefaultStartupView(value: string) { + setDefaultStartupView(value); + } +
@@ -200,25 +242,46 @@
-

Settings

-

Configure app behavior and interface options.

+
+

Settings

+

Configure app behavior and interface options.

+
+
+
+

+ + Startup +

+
-

Tags

-

Add and manage tags used for notes and entries.

+
+

+ + Tags +

+

Add and manage tags used for notes and entries.

+
-

Fragment Types

-

Configure custom fragment types for the Fragments section.

+
+

+ + Fragment Types +

+

Configure custom fragment types for the Fragments section.

+
-

Sidecar

-

Root directory containing the Journal.Sidecar project.

+
+

+ + Sidecar +

+

Root directory containing the Journal.Sidecar project.

+
event.key === "Enter" && saveSidecarRoot()} /> - + {#if sidecarRootIsCustom} {/if} @@ -343,126 +418,196 @@ padding: 20px; display: flex; flex-direction: column; - gap: 16px; - background: linear-gradient(180deg, var(--surface-2) 0%, var(--bg-editor) 100%); + gap: 18px; + background: var(--bg-editor); color: var(--text-primary); } + .route-header { + display: flex; + align-items: flex-start; + justify-content: space-between; + gap: 10px; + padding: 2px 2px 0; + } + + .route-header-main { + display: flex; + flex-direction: column; + gap: 4px; + } + .route-header h1 { - font-size: 1.1rem; - margin-bottom: 4px; + font-size: 1.2rem; + font-weight: 700; + letter-spacing: 0.01em; } .route-header p { color: var(--text-muted); - font-size: 0.9rem; + font-size: 0.88rem; + } + + .header-close-btn { + width: 32px; + height: 32px; + flex-shrink: 0; + border-radius: 8px; + border: 1px solid var(--border-soft); + background: color-mix(in srgb, var(--surface-1) 92%, var(--bg-editor) 8%); + color: var(--text-muted); + display: grid; + place-items: center; + cursor: pointer; + transition: background-color 120ms ease, color 120ms ease, border-color 120ms ease, transform 120ms ease; + } + + .header-close-btn .material-symbols-outlined { + font-size: 1rem; + } + + .header-close-btn:hover { + background: var(--bg-hover); + color: var(--text-primary); + border-color: var(--border-strong); + transform: translateY(-1px); } .settings-grid { columns: 2; - column-gap: 16px; + column-gap: 14px; } .route-card { break-inside: avoid; border: 1px solid var(--border-soft); - background: var(--surface-1); - border-radius: 10px; - padding: 14px; + background: color-mix(in srgb, var(--surface-1) 92%, var(--bg-editor) 8%); + border-radius: 12px; + padding: 14px 14px 13px; display: flex; flex-direction: column; gap: 12px; - margin-bottom: 16px; + box-shadow: + inset 0 1px 0 color-mix(in srgb, var(--zinc-300) 8%, transparent 92%), + 0 8px 24px color-mix(in srgb, var(--bg-app) 32%, transparent 68%); + margin-bottom: 14px; + } + + .card-head { + display: flex; + flex-direction: column; + gap: 5px; + } + + .card-title { + display: inline-flex; + align-items: center; + gap: 8px; + font-size: 0.96rem; + font-weight: 650; + color: var(--text-primary); + letter-spacing: 0.01em; + } + + .card-title .material-symbols-outlined { + font-size: 1rem; + color: var(--text-dim); } .route-card label { display: flex; flex-direction: column; - gap: 5px; + gap: 6px; color: var(--text-muted); - font-size: 0.82rem; + font-size: 0.8rem; + font-weight: 600; + letter-spacing: 0.01em; } .route-card select { border: 1px solid var(--border-soft); - border-radius: 7px; - background: var(--bg-app); - color: var(--text-primary); - padding: 8px 10px; - } - - .route-card h2 { - font-size: 0.95rem; + border-radius: 8px; + background: color-mix(in srgb, var(--surface-2) 88%, var(--bg-editor) 12%); color: var(--text-primary); + padding: 9px 10px; + font-size: 0.84rem; } .section-copy { - font-size: 0.82rem; + font-size: 0.8rem; color: var(--text-muted); + line-height: 1.4; } .create-row { display: flex; gap: 8px; + align-items: center; } .create-row input { flex: 1; + min-width: 0; } .item-list { list-style: none; display: flex; flex-direction: column; - gap: 8px; + gap: 7px; } .item-row { border: 1px solid var(--border-soft); border-radius: 8px; - padding: 8px 10px; + padding: 9px 10px; display: flex; align-items: center; justify-content: space-between; gap: 10px; - font-size: 0.84rem; + font-size: 0.83rem; color: var(--text-primary); - background: var(--bg-app); + background: color-mix(in srgb, var(--surface-1) 90%, var(--bg-editor) 10%); } .item-row input, .route-card input { border: 1px solid var(--border-soft); - border-radius: 7px; - background: var(--bg-app); + border-radius: 8px; + background: color-mix(in srgb, var(--surface-2) 88%, var(--bg-editor) 12%); color: var(--text-primary); - padding: 6px 9px; + padding: 8px 10px; + font-size: 0.84rem; min-width: 200px; } .row-actions { display: flex; gap: 6px; + flex-wrap: wrap; } .secondary-btn, .ghost-btn, .danger-btn { border: 1px solid var(--border-soft); - border-radius: 7px; - padding: 6px 10px; - font-size: 0.78rem; + border-radius: 8px; + padding: 7px 11px; + font-size: 0.76rem; + font-weight: 600; + letter-spacing: 0.01em; cursor: pointer; color: var(--text-primary); + transition: background-color 120ms ease, color 120ms ease, border-color 120ms ease, transform 120ms ease; } .secondary-btn { - background: var(--surface-3); + background: color-mix(in srgb, var(--surface-2) 84%, var(--bg-hover) 16%); border-color: var(--border-strong); } .ghost-btn { - background: var(--surface-1); + background: color-mix(in srgb, var(--surface-1) 92%, var(--bg-editor) 8%); color: var(--text-muted); } @@ -476,11 +621,55 @@ .danger-btn:hover { background: var(--bg-hover); color: var(--text-primary); + border-color: var(--border-strong); + transform: translateY(-1px); } .error-text { color: #e74c3c; font-size: 0.82rem; } + + @media (max-width: 1100px) { + .settings-grid { + columns: 1; + } + } + + @media (max-width: 720px) { + .route-view { + padding: 14px 12px; + gap: 14px; + } + + .route-card { + border-radius: 10px; + padding: 12px; + gap: 10px; + } + + .route-header { + align-items: center; + } + + .create-row { + flex-wrap: wrap; + } + + .create-row > * { + flex: 1 1 auto; + } + + .item-row { + align-items: flex-start; + flex-direction: column; + gap: 8px; + } + + .row-actions { + width: 100%; + justify-content: flex-end; + } + } diff --git a/Journal.App/static/style.css b/Journal.App/static/style.css index 1516702..6ee04e3 100644 --- a/Journal.App/static/style.css +++ b/Journal.App/static/style.css @@ -18,7 +18,7 @@ --bg-app: var(--zinc-950); --bg-navbar: var(--zinc-900); - --bg-panel: var(--zinc-900); + --bg-panel: var(--zinc-800); --bg-editor: var(--zinc-900); --bg-hover: var(--zinc-800); --bg-active: var(--zinc-700);