ui optimierungen
This commit is contained in:
parent
b24915783d
commit
53b20cf492
|
|
@ -1 +1 @@
|
||||||
1.1.49
|
1.1.52
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { page } from "$app/stores";
|
import { page } from "$app/state";
|
||||||
import { goto } from "$app/navigation";
|
import { goto } from "$app/navigation";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
|
|
@ -7,7 +7,7 @@
|
||||||
}
|
}
|
||||||
let { onsidebaropen }: Props = $props();
|
let { onsidebaropen }: Props = $props();
|
||||||
|
|
||||||
const currentPath = $derived($page.url.pathname);
|
const currentPath = $derived(page.url.pathname);
|
||||||
|
|
||||||
interface Tab {
|
interface Tab {
|
||||||
id: string;
|
id: string;
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { page } from '$app/stores';
|
import { page } from '$app/state';
|
||||||
import { liveQuery } from 'dexie';
|
import { liveQuery } from 'dexie';
|
||||||
import { db } from '$lib/db/schema';
|
import { db } from '$lib/db/schema';
|
||||||
import { collapsedTopicIds } from '$lib/stores/agenda';
|
import { collapsedTopicIds } from '$lib/stores/agenda';
|
||||||
|
|
@ -34,7 +34,7 @@
|
||||||
journalScope = s;
|
journalScope = s;
|
||||||
localStorage.setItem(SCOPE_KEY, s);
|
localStorage.setItem(SCOPE_KEY, s);
|
||||||
}
|
}
|
||||||
const initialDate = $derived($page.url.searchParams.get('date') ?? undefined);
|
const initialDate = $derived(page.url.searchParams.get('date') ?? undefined);
|
||||||
let compact = $state(false);
|
let compact = $state(false);
|
||||||
|
|
||||||
// Rating modal state
|
// Rating modal state
|
||||||
|
|
|
||||||
|
|
@ -12,7 +12,7 @@
|
||||||
import LinkTitle from './LinkTitle.svelte';
|
import LinkTitle from './LinkTitle.svelte';
|
||||||
import { get } from 'svelte/store';
|
import { get } from 'svelte/store';
|
||||||
import { currentScope, scopeSettings } from '$lib/stores/scopeContext';
|
import { currentScope, scopeSettings } from '$lib/stores/scopeContext';
|
||||||
import { normalizeTitleAndBody } from '$lib/utils/titleUtils';
|
import { normalizeTitleAndBody, extractTitleAndBody } from '$lib/utils/titleUtils';
|
||||||
import { useUnsavedGuard } from '$lib/utils/unsavedGuard.svelte';
|
import { useUnsavedGuard } from '$lib/utils/unsavedGuard.svelte';
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
|
|
@ -27,7 +27,6 @@
|
||||||
const isDailyLog = $derived(contextId === 'daily-log');
|
const isDailyLog = $derived(contextId === 'daily-log');
|
||||||
|
|
||||||
// --- Daily-log journal mode ---
|
// --- Daily-log journal mode ---
|
||||||
let entryTitle = $state('');
|
|
||||||
let entryText = $state('');
|
let entryText = $state('');
|
||||||
let entryEditor: MarkdownEditor;
|
let entryEditor: MarkdownEditor;
|
||||||
let selectedDate = $state(initialDate ?? today());
|
let selectedDate = $state(initialDate ?? today());
|
||||||
|
|
@ -35,7 +34,7 @@
|
||||||
let wiedervorlageChecked = $state(true);
|
let wiedervorlageChecked = $state(true);
|
||||||
let meetingPickerOpen = $state(false);
|
let meetingPickerOpen = $state(false);
|
||||||
|
|
||||||
useUnsavedGuard(() => entryTitle.trim() !== '' || entryText.trim() !== '' || editingId !== null);
|
useUnsavedGuard(() => entryText.trim() !== '' || editingId !== null);
|
||||||
|
|
||||||
// All meeting contexts for the link dropdown
|
// All meeting contexts for the link dropdown
|
||||||
const meetingContexts = liveQuery(() =>
|
const meetingContexts = liveQuery(() =>
|
||||||
|
|
@ -83,9 +82,12 @@
|
||||||
|
|
||||||
|
|
||||||
async function handleAddEntry() {
|
async function handleAddEntry() {
|
||||||
let title = entryTitle.trim();
|
const raw = entryText.trim();
|
||||||
let body = entryText.trim();
|
if (!raw) return;
|
||||||
if (!title) return;
|
const { maxTitleLength } = get(scopeSettings);
|
||||||
|
const extracted = extractTitleAndBody(raw, maxTitleLength);
|
||||||
|
let title = extracted.title;
|
||||||
|
let body = extracted.body;
|
||||||
|
|
||||||
// If title is a URL, try to fetch its page title
|
// If title is a URL, try to fetch its page title
|
||||||
if (/^https?:\/\//i.test(title)) {
|
if (/^https?:\/\//i.test(title)) {
|
||||||
|
|
@ -103,14 +105,11 @@
|
||||||
await createHistoryEntry(topic.id, selectedDate, body, null, false, isPrivate);
|
await createHistoryEntry(topic.id, selectedDate, body, null, false, isPrivate);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
const { maxTitleLength } = get(scopeSettings);
|
const text = body ? `${title}\n\n${body}` : title;
|
||||||
const { title: shortTitle, body: fullBody } = normalizeTitleAndBody(title, body, maxTitleLength);
|
|
||||||
const text = fullBody ? `${shortTitle}\n\n${fullBody}` : shortTitle;
|
|
||||||
await getOrCreateJournalTopic();
|
await getOrCreateJournalTopic();
|
||||||
await createHistoryEntry(JOURNAL_TOPIC_ID, selectedDate, text, null, wiedervorlageChecked, isPrivate);
|
await createHistoryEntry(JOURNAL_TOPIC_ID, selectedDate, text, null, wiedervorlageChecked, isPrivate);
|
||||||
}
|
}
|
||||||
|
|
||||||
entryTitle = '';
|
|
||||||
entryText = '';
|
entryText = '';
|
||||||
entryEditor?.clear();
|
entryEditor?.clear();
|
||||||
selectedLinkedContextId = '';
|
selectedLinkedContextId = '';
|
||||||
|
|
@ -118,10 +117,6 @@
|
||||||
wiedervorlageChecked = true;
|
wiedervorlageChecked = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleTitleKeypress(e: KeyboardEvent) {
|
|
||||||
if (e.key === 'Enter') handleAddEntry();
|
|
||||||
}
|
|
||||||
|
|
||||||
let confirmDeleteId = $state<string | null>(null);
|
let confirmDeleteId = $state<string | null>(null);
|
||||||
|
|
||||||
// --- Edit mode ---
|
// --- Edit mode ---
|
||||||
|
|
@ -160,9 +155,13 @@
|
||||||
confirmDeleteId = null;
|
confirmDeleteId = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
function formatTime(isoString: string): string {
|
function formatTime(entry: { sortOrder: number; updatedAt: string }): string {
|
||||||
try {
|
try {
|
||||||
return new Date(isoString).toLocaleTimeString('de-DE', { hour: '2-digit', minute: '2-digit' });
|
// sortOrder >= 1e12 means it was set as Date.now() (ms timestamp) → use as creation time
|
||||||
|
const ts = entry.sortOrder >= 1_000_000_000_000
|
||||||
|
? new Date(entry.sortOrder)
|
||||||
|
: new Date(entry.updatedAt);
|
||||||
|
return ts.toLocaleTimeString('de-DE', { hour: '2-digit', minute: '2-digit' });
|
||||||
} catch {
|
} catch {
|
||||||
return '';
|
return '';
|
||||||
}
|
}
|
||||||
|
|
@ -238,52 +237,15 @@
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
<div class="mb-8 flex flex-col gap-2.5 rounded-lg border border-border bg-sidebar p-4">
|
<div class="mb-8 flex flex-col gap-2.5 rounded-lg border border-border bg-sidebar p-4">
|
||||||
<div class="relative flex items-center gap-2">
|
|
||||||
<input
|
|
||||||
type="text"
|
|
||||||
class="flex-1 rounded border border-[#444] bg-bg px-2.5 py-2 font-mono text-white"
|
|
||||||
placeholder="Titel / Was ist passiert?"
|
|
||||||
bind:value={entryTitle}
|
|
||||||
onkeypress={handleTitleKeypress}
|
|
||||||
use:mention
|
|
||||||
/>
|
|
||||||
<!-- Meeting picker icon button -->
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
class="flex items-center gap-1 rounded border px-2 py-2 text-sm transition-colors {journalScope === 'private' ? 'hidden' : selectedLinkedContextId ? 'border-accent bg-accent/20 text-accent' : 'border-[#444] bg-bg text-muted hover:text-white'}"
|
|
||||||
onclick={() => meetingPickerOpen = !meetingPickerOpen}
|
|
||||||
title="Jour-fix verknüpfen"
|
|
||||||
>
|
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
||||||
<rect x="3" y="4" width="18" height="18" rx="2" ry="2"/>
|
|
||||||
<line x1="16" y1="2" x2="16" y2="6"/>
|
|
||||||
<line x1="8" y1="2" x2="8" y2="6"/>
|
|
||||||
<line x1="3" y1="10" x2="21" y2="10"/>
|
|
||||||
</svg>
|
|
||||||
</button>
|
|
||||||
{#if meetingPickerOpen}
|
|
||||||
<div class="absolute right-0 top-full z-50 mt-1 min-w-48 rounded border border-[#444] bg-[#1e1e1e] py-1 shadow-lg">
|
|
||||||
<button
|
|
||||||
class="w-full px-3 py-1.5 text-left text-sm text-muted hover:bg-[#2a2a2a] {!selectedLinkedContextId ? 'text-white' : ''}"
|
|
||||||
onclick={() => { selectedLinkedContextId = ''; meetingPickerOpen = false; }}
|
|
||||||
>— kein Jour-fix —</button>
|
|
||||||
{#each $meetingContexts ?? [] as ctx}
|
|
||||||
<button
|
|
||||||
class="w-full px-3 py-1.5 text-left text-sm hover:bg-[#2a2a2a] {selectedLinkedContextId === ctx.id ? 'text-accent' : 'text-white'}"
|
|
||||||
onclick={() => { selectedLinkedContextId = ctx.id; meetingPickerOpen = false; }}
|
|
||||||
>{ctx.name}</button>
|
|
||||||
{/each}
|
|
||||||
</div>
|
|
||||||
{/if}
|
|
||||||
</div>
|
|
||||||
<MarkdownEditor
|
<MarkdownEditor
|
||||||
bind:this={entryEditor}
|
bind:this={entryEditor}
|
||||||
placeholder="Details (optional)"
|
placeholder="Was ist passiert? (1. Zeile = Titel)"
|
||||||
minHeight="60px"
|
minHeight="60px"
|
||||||
wikiScope={journalScope === 'private'}
|
wikiScope={journalScope === 'private'}
|
||||||
onchange={(md) => entryText = md}
|
onchange={(md) => entryText = md}
|
||||||
|
onsave={handleAddEntry}
|
||||||
/>
|
/>
|
||||||
<div class="flex items-center gap-4">
|
<div class="flex items-center gap-2">
|
||||||
<button
|
<button
|
||||||
class="rounded px-4 py-2 font-bold text-white hover:brightness-110"
|
class="rounded px-4 py-2 font-bold text-white hover:brightness-110"
|
||||||
style="background-color: {journalScope === 'private' ? $scopeSettings.privateColor : $scopeSettings.businessColor}"
|
style="background-color: {journalScope === 'private' ? $scopeSettings.privateColor : $scopeSettings.businessColor}"
|
||||||
|
|
@ -294,13 +256,41 @@
|
||||||
{#if !selectedLinkedContextId}
|
{#if !selectedLinkedContextId}
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
class="flex items-center gap-1.5 rounded-full border px-3 py-1 text-xs font-medium transition-colors {wiedervorlageChecked ? 'border-amber-500 bg-amber-500/20 text-amber-400' : 'border-[#444] text-muted hover:border-[#666] hover:text-white'}"
|
class="rounded border p-1.5 text-base leading-none transition-colors {wiedervorlageChecked ? 'border-amber-500 bg-amber-500/20 text-amber-400' : 'border-[#444] text-muted hover:border-[#666] hover:text-white'}"
|
||||||
onclick={() => wiedervorlageChecked = !wiedervorlageChecked}
|
onclick={() => wiedervorlageChecked = !wiedervorlageChecked}
|
||||||
>
|
title="Wiedervorlage"
|
||||||
<span class="text-base leading-none">{wiedervorlageChecked ? '⏰' : '○'}</span>
|
>{wiedervorlageChecked ? '⏰' : '○'}</button>
|
||||||
Wiedervorlage
|
|
||||||
</button>
|
|
||||||
{/if}
|
{/if}
|
||||||
|
<!-- Meeting picker button -->
|
||||||
|
<div class="relative {journalScope === 'private' ? 'hidden' : ''}">
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
class="flex items-center gap-1 rounded border px-2 py-1.5 text-sm transition-colors {selectedLinkedContextId ? 'border-accent bg-accent/20 text-accent' : 'border-[#444] bg-bg text-muted hover:text-white'}"
|
||||||
|
onclick={() => meetingPickerOpen = !meetingPickerOpen}
|
||||||
|
title="Jour-fix verknüpfen"
|
||||||
|
>
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||||
|
<rect x="3" y="4" width="18" height="18" rx="2" ry="2"/>
|
||||||
|
<line x1="16" y1="2" x2="16" y2="6"/>
|
||||||
|
<line x1="8" y1="2" x2="8" y2="6"/>
|
||||||
|
<line x1="3" y1="10" x2="21" y2="10"/>
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
{#if meetingPickerOpen}
|
||||||
|
<div class="absolute left-0 top-full z-50 mt-1 min-w-48 rounded border border-[#444] bg-[#1e1e1e] py-1 shadow-lg">
|
||||||
|
<button
|
||||||
|
class="w-full px-3 py-1.5 text-left text-sm text-muted hover:bg-[#2a2a2a] {!selectedLinkedContextId ? 'text-white' : ''}"
|
||||||
|
onclick={() => { selectedLinkedContextId = ''; meetingPickerOpen = false; }}
|
||||||
|
>— kein Jour-fix —</button>
|
||||||
|
{#each $meetingContexts ?? [] as ctx}
|
||||||
|
<button
|
||||||
|
class="w-full px-3 py-1.5 text-left text-sm hover:bg-[#2a2a2a] {selectedLinkedContextId === ctx.id ? 'text-accent' : 'text-white'}"
|
||||||
|
onclick={() => { selectedLinkedContextId = ctx.id; meetingPickerOpen = false; }}
|
||||||
|
>{ctx.name}</button>
|
||||||
|
{/each}
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
@ -358,7 +348,7 @@
|
||||||
{@const title = lines[0].replace(/^#{1,6}\s+/, '')}
|
{@const title = lines[0].replace(/^#{1,6}\s+/, '')}
|
||||||
{@const body = lines.slice(1).join('\n').trim()}
|
{@const body = lines.slice(1).join('\n').trim()}
|
||||||
<div class="group mb-3 flex items-start gap-2 rounded bg-card-bg p-2.5">
|
<div class="group mb-3 flex items-start gap-2 rounded bg-card-bg p-2.5">
|
||||||
<span class="mt-0.5 text-xs text-muted whitespace-nowrap">{formatTime(entry.updatedAt)}</span>
|
<span class="mt-0.5 text-xs text-muted whitespace-nowrap">{formatTime(entry)}</span>
|
||||||
<div class="flex-1">
|
<div class="flex-1">
|
||||||
<div class="flex flex-wrap items-center gap-1.5 font-bold">
|
<div class="flex flex-wrap items-center gap-1.5 font-bold">
|
||||||
<LinkTitle text={title} />
|
<LinkTitle text={title} />
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { page } from '$app/stores';
|
import { page } from '$app/state';
|
||||||
import { goto } from '$app/navigation';
|
import { goto } from '$app/navigation';
|
||||||
|
|
||||||
import { liveQuery } from 'dexie';
|
import { liveQuery } from 'dexie';
|
||||||
|
|
@ -26,7 +26,7 @@
|
||||||
}
|
}
|
||||||
let { onnavigate, hideLogo = false }: Props = $props();
|
let { onnavigate, hideLogo = false }: Props = $props();
|
||||||
|
|
||||||
const currentId = $derived($page.params.id ?? '');
|
const currentId = $derived(page.params.id ?? '');
|
||||||
|
|
||||||
const contexts = liveQuery(() => db.contexts.filter(c => !c.deletedAt && !c.archivedAt).sortBy('sortOrder'));
|
const contexts = liveQuery(() => db.contexts.filter(c => !c.deletedAt && !c.archivedAt).sortBy('sortOrder'));
|
||||||
|
|
||||||
|
|
@ -108,12 +108,12 @@
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
<!-- General -->
|
<!-- General -->
|
||||||
<div class="mb-1 mt-4 pl-1 text-xs uppercase text-muted">General</div>
|
<div class="mb-1 mt-6 pl-1 text-xs uppercase text-muted">General</div>
|
||||||
<ul class="m-0 list-none p-0">
|
<ul class="m-0 list-none p-0">
|
||||||
{#if dailyLog}
|
{#if dailyLog}
|
||||||
<li>
|
<li>
|
||||||
<button
|
<button
|
||||||
class="mb-1 w-full rounded px-2.5 py-2 text-left text-sm transition-colors {currentId === 'daily-log' ? 'bg-accent font-bold text-white' : 'text-[#ccc] hover:bg-white/5 hover:text-white'}"
|
class="mb-1 w-full rounded px-2.5 py-2.5 text-left text-sm transition-colors {currentId === 'daily-log' ? 'bg-accent font-bold text-white' : 'text-[#ccc] hover:bg-white/5 hover:text-white'}"
|
||||||
onclick={() => navigate('daily-log')}
|
onclick={() => navigate('daily-log')}
|
||||||
>
|
>
|
||||||
{dailyLog.name}
|
{dailyLog.name}
|
||||||
|
|
@ -126,7 +126,7 @@
|
||||||
<ul class="m-0 list-none p-0">
|
<ul class="m-0 list-none p-0">
|
||||||
<li>
|
<li>
|
||||||
<button
|
<button
|
||||||
class="mb-1 w-full rounded px-2.5 py-2 text-left text-sm transition-colors {$page.url.pathname.startsWith('/journal/archive') ? 'bg-accent font-bold text-white' : 'text-[#ccc] hover:bg-white/5 hover:text-white'}"
|
class="mb-1 w-full rounded px-2.5 py-2.5 text-left text-sm transition-colors {page.url.pathname.startsWith('/journal/archive') ? 'bg-accent font-bold text-white' : 'text-[#ccc] hover:bg-white/5 hover:text-white'}"
|
||||||
onclick={() => { goto('/journal/archive'); onnavigate?.(); }}
|
onclick={() => { goto('/journal/archive'); onnavigate?.(); }}
|
||||||
>
|
>
|
||||||
📅 Archiv
|
📅 Archiv
|
||||||
|
|
@ -135,7 +135,7 @@
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
<!-- Jour Fixes -->
|
<!-- Jour Fixes -->
|
||||||
<div class="mb-1 mt-4 flex items-center justify-between pl-1 pr-1">
|
<div class="mb-1 mt-6 flex items-center justify-between pl-1 pr-1">
|
||||||
<button class="text-xs uppercase text-muted hover:text-white flex items-center gap-1" onclick={() => toggleSection('meetings')}>
|
<button class="text-xs uppercase text-muted hover:text-white flex items-center gap-1" onclick={() => toggleSection('meetings')}>
|
||||||
<span class="inline-block text-[10px] transition-transform {collapsed['meetings'] ? '-rotate-90' : ''}">{@html '▼'}</span>
|
<span class="inline-block text-[10px] transition-transform {collapsed['meetings'] ? '-rotate-90' : ''}">{@html '▼'}</span>
|
||||||
Jour Fixes
|
Jour Fixes
|
||||||
|
|
@ -147,7 +147,7 @@
|
||||||
{#each meetings as ctx}
|
{#each meetings as ctx}
|
||||||
<li>
|
<li>
|
||||||
<button
|
<button
|
||||||
class="mb-1 w-full rounded px-2.5 py-2 text-left text-sm transition-colors {currentId === ctx.id ? 'bg-accent font-bold text-white' : 'text-[#ccc] hover:bg-white/5 hover:text-white'}"
|
class="mb-1 w-full rounded px-2.5 py-2.5 text-left text-sm transition-colors {currentId === ctx.id ? 'bg-accent font-bold text-white' : 'text-[#ccc] hover:bg-white/5 hover:text-white'}"
|
||||||
onclick={() => navigate(ctx.id)}
|
onclick={() => navigate(ctx.id)}
|
||||||
>
|
>
|
||||||
{ctx.name}
|
{ctx.name}
|
||||||
|
|
@ -168,7 +168,7 @@
|
||||||
{/if}
|
{/if}
|
||||||
<li>
|
<li>
|
||||||
<button
|
<button
|
||||||
class="mb-1 w-full rounded px-2.5 py-2 text-left text-xs text-[#666] transition-colors hover:bg-white/5 hover:text-[#aaa]"
|
class="mb-1 w-full rounded px-2.5 py-2.5 text-left text-xs text-[#666] transition-colors hover:bg-white/5 hover:text-[#aaa]"
|
||||||
onclick={() => { goto('/meetings'); onnavigate?.(); }}
|
onclick={() => { goto('/meetings'); onnavigate?.(); }}
|
||||||
>
|
>
|
||||||
Alle Jour Fixes →
|
Alle Jour Fixes →
|
||||||
|
|
@ -178,7 +178,7 @@
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
<!-- Projects -->
|
<!-- Projects -->
|
||||||
<div class="mb-1 mt-4 flex items-center justify-between pl-1 pr-1">
|
<div class="mb-1 mt-6 flex items-center justify-between pl-1 pr-1">
|
||||||
<button class="text-xs uppercase text-muted hover:text-white flex items-center gap-1" onclick={() => toggleSection('projects')}>
|
<button class="text-xs uppercase text-muted hover:text-white flex items-center gap-1" onclick={() => toggleSection('projects')}>
|
||||||
<span class="inline-block text-[10px] transition-transform {collapsed['projects'] ? '-rotate-90' : ''}">{@html '▼'}</span>
|
<span class="inline-block text-[10px] transition-transform {collapsed['projects'] ? '-rotate-90' : ''}">{@html '▼'}</span>
|
||||||
Projects
|
Projects
|
||||||
|
|
@ -190,7 +190,7 @@
|
||||||
{#each projects as ctx}
|
{#each projects as ctx}
|
||||||
<li>
|
<li>
|
||||||
<button
|
<button
|
||||||
class="mb-1 w-full rounded px-2.5 py-2 text-left text-sm transition-colors {currentId === ctx.id ? 'bg-accent font-bold text-white' : 'text-[#ccc] hover:bg-white/5 hover:text-white'}"
|
class="mb-1 w-full rounded px-2.5 py-2.5 text-left text-sm transition-colors {currentId === ctx.id ? 'bg-accent font-bold text-white' : 'text-[#ccc] hover:bg-white/5 hover:text-white'}"
|
||||||
onclick={() => navigate(ctx.id)}
|
onclick={() => navigate(ctx.id)}
|
||||||
>
|
>
|
||||||
{displayName(ctx)}
|
{displayName(ctx)}
|
||||||
|
|
@ -211,7 +211,7 @@
|
||||||
{/if}
|
{/if}
|
||||||
<li>
|
<li>
|
||||||
<button
|
<button
|
||||||
class="mb-1 w-full rounded px-2.5 py-2 text-left text-xs text-[#666] transition-colors hover:bg-white/5 hover:text-[#aaa]"
|
class="mb-1 w-full rounded px-2.5 py-2.5 text-left text-xs text-[#666] transition-colors hover:bg-white/5 hover:text-[#aaa]"
|
||||||
onclick={() => { goto('/projects'); onnavigate?.(); }}
|
onclick={() => { goto('/projects'); onnavigate?.(); }}
|
||||||
>
|
>
|
||||||
Alle Projekte →
|
Alle Projekte →
|
||||||
|
|
@ -221,7 +221,7 @@
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
<!-- Companies -->
|
<!-- Companies -->
|
||||||
<div class="mb-1 mt-4 flex items-center justify-between pl-1 pr-1">
|
<div class="mb-1 mt-6 flex items-center justify-between pl-1 pr-1">
|
||||||
<button class="text-xs uppercase text-muted hover:text-white flex items-center gap-1" onclick={() => toggleSection('companies')}>
|
<button class="text-xs uppercase text-muted hover:text-white flex items-center gap-1" onclick={() => toggleSection('companies')}>
|
||||||
<span class="inline-block text-[10px] transition-transform {collapsed['companies'] ? '-rotate-90' : ''}">{@html '▼'}</span>
|
<span class="inline-block text-[10px] transition-transform {collapsed['companies'] ? '-rotate-90' : ''}">{@html '▼'}</span>
|
||||||
Firmen
|
Firmen
|
||||||
|
|
@ -233,7 +233,7 @@
|
||||||
{#each companies as ctx}
|
{#each companies as ctx}
|
||||||
<li>
|
<li>
|
||||||
<button
|
<button
|
||||||
class="mb-1 w-full rounded px-2.5 py-2 text-left text-sm transition-colors {currentId === ctx.id ? 'bg-accent font-bold text-white' : 'text-[#ccc] hover:bg-white/5 hover:text-white'}"
|
class="mb-1 w-full rounded px-2.5 py-2.5 text-left text-sm transition-colors {currentId === ctx.id ? 'bg-accent font-bold text-white' : 'text-[#ccc] hover:bg-white/5 hover:text-white'}"
|
||||||
onclick={() => navigate(ctx.id)}
|
onclick={() => navigate(ctx.id)}
|
||||||
>
|
>
|
||||||
{displayName(ctx)}
|
{displayName(ctx)}
|
||||||
|
|
@ -254,7 +254,7 @@
|
||||||
{/if}
|
{/if}
|
||||||
<li>
|
<li>
|
||||||
<button
|
<button
|
||||||
class="mb-1 w-full rounded px-2.5 py-2 text-left text-xs text-[#666] transition-colors hover:bg-white/5 hover:text-[#aaa]"
|
class="mb-1 w-full rounded px-2.5 py-2.5 text-left text-xs text-[#666] transition-colors hover:bg-white/5 hover:text-[#aaa]"
|
||||||
onclick={() => { goto('/companies'); onnavigate?.(); }}
|
onclick={() => { goto('/companies'); onnavigate?.(); }}
|
||||||
>
|
>
|
||||||
Alle Firmen →
|
Alle Firmen →
|
||||||
|
|
@ -264,7 +264,7 @@
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
<!-- People -->
|
<!-- People -->
|
||||||
<div class="mb-1 mt-4 flex items-center justify-between pl-1 pr-1">
|
<div class="mb-1 mt-6 flex items-center justify-between pl-1 pr-1">
|
||||||
<button class="text-xs uppercase text-muted hover:text-white flex items-center gap-1" onclick={() => toggleSection('people')}>
|
<button class="text-xs uppercase text-muted hover:text-white flex items-center gap-1" onclick={() => toggleSection('people')}>
|
||||||
<span class="inline-block text-[10px] transition-transform {collapsed['people'] ? '-rotate-90' : ''}">{@html '▼'}</span>
|
<span class="inline-block text-[10px] transition-transform {collapsed['people'] ? '-rotate-90' : ''}">{@html '▼'}</span>
|
||||||
People
|
People
|
||||||
|
|
@ -276,7 +276,7 @@
|
||||||
{#each people as ctx}
|
{#each people as ctx}
|
||||||
<li>
|
<li>
|
||||||
<button
|
<button
|
||||||
class="mb-1 w-full rounded px-2.5 py-2 text-left text-sm transition-colors {currentId === ctx.id ? 'bg-accent font-bold text-white' : 'text-[#ccc] hover:bg-white/5 hover:text-white'}"
|
class="mb-1 w-full rounded px-2.5 py-2.5 text-left text-sm transition-colors {currentId === ctx.id ? 'bg-accent font-bold text-white' : 'text-[#ccc] hover:bg-white/5 hover:text-white'}"
|
||||||
onclick={() => navigate(ctx.id)}
|
onclick={() => navigate(ctx.id)}
|
||||||
>
|
>
|
||||||
{displayName(ctx)}
|
{displayName(ctx)}
|
||||||
|
|
@ -297,7 +297,7 @@
|
||||||
{/if}
|
{/if}
|
||||||
<li>
|
<li>
|
||||||
<button
|
<button
|
||||||
class="mb-1 w-full rounded px-2.5 py-2 text-left text-xs text-[#666] transition-colors hover:bg-white/5 hover:text-[#aaa]"
|
class="mb-1 w-full rounded px-2.5 py-2.5 text-left text-xs text-[#666] transition-colors hover:bg-white/5 hover:text-[#aaa]"
|
||||||
onclick={() => { goto('/persons'); onnavigate?.(); }}
|
onclick={() => { goto('/persons'); onnavigate?.(); }}
|
||||||
>
|
>
|
||||||
Alle Personen →
|
Alle Personen →
|
||||||
|
|
@ -307,7 +307,7 @@
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
<!-- Wiki -->
|
<!-- Wiki -->
|
||||||
<div class="mb-1 mt-4 flex items-center justify-between pl-1 pr-1">
|
<div class="mb-1 mt-6 flex items-center justify-between pl-1 pr-1">
|
||||||
<button class="text-xs uppercase text-muted hover:text-white flex items-center gap-1" onclick={() => toggleSection('wiki')}>
|
<button class="text-xs uppercase text-muted hover:text-white flex items-center gap-1" onclick={() => toggleSection('wiki')}>
|
||||||
<span class="inline-block text-[10px] transition-transform {collapsed['wiki'] ? '-rotate-90' : ''}">{@html '▼'}</span>
|
<span class="inline-block text-[10px] transition-transform {collapsed['wiki'] ? '-rotate-90' : ''}">{@html '▼'}</span>
|
||||||
Wiki
|
Wiki
|
||||||
|
|
@ -319,7 +319,7 @@
|
||||||
{#each ($wikiNotebooks$ ?? []) as nb}
|
{#each ($wikiNotebooks$ ?? []) as nb}
|
||||||
<li>
|
<li>
|
||||||
<button
|
<button
|
||||||
class="mb-1 w-full rounded px-2.5 py-2 text-left text-sm transition-colors text-[#ccc] hover:bg-white/5 hover:text-white"
|
class="mb-1 w-full rounded px-2.5 py-2.5 text-left text-sm transition-colors text-[#ccc] hover:bg-white/5 hover:text-white"
|
||||||
onclick={() => { goto(`/wiki/notebook/${nb.id}`); onnavigate?.(); }}
|
onclick={() => { goto(`/wiki/notebook/${nb.id}`); onnavigate?.(); }}
|
||||||
>
|
>
|
||||||
{nb.name}
|
{nb.name}
|
||||||
|
|
@ -328,7 +328,7 @@
|
||||||
{/each}
|
{/each}
|
||||||
<li>
|
<li>
|
||||||
<button
|
<button
|
||||||
class="mb-1 w-full rounded px-2.5 py-2 text-left text-xs text-[#666] transition-colors hover:bg-white/5 hover:text-[#aaa]"
|
class="mb-1 w-full rounded px-2.5 py-2.5 text-left text-xs text-[#666] transition-colors hover:bg-white/5 hover:text-[#aaa]"
|
||||||
onclick={() => { goto('/wiki'); onnavigate?.(); }}
|
onclick={() => { goto('/wiki'); onnavigate?.(); }}
|
||||||
>
|
>
|
||||||
Alle Notizbücher →
|
Alle Notizbücher →
|
||||||
|
|
@ -339,7 +339,7 @@
|
||||||
|
|
||||||
<div class="mt-4">
|
<div class="mt-4">
|
||||||
<button
|
<button
|
||||||
class="mb-1 w-full rounded px-2.5 py-2 text-left text-sm transition-colors flex items-center gap-2 text-[#ccc] hover:bg-white/5 hover:text-white"
|
class="mb-1 w-full rounded px-2.5 py-2.5 text-left text-sm transition-colors flex items-center gap-2 text-[#ccc] hover:bg-white/5 hover:text-white"
|
||||||
onclick={() => { goto('/trash'); onnavigate?.(); }}
|
onclick={() => { goto('/trash'); onnavigate?.(); }}
|
||||||
>
|
>
|
||||||
<span>🗑</span>
|
<span>🗑</span>
|
||||||
|
|
|
||||||
|
|
@ -245,7 +245,7 @@ export async function createHistoryEntry(topicId: string, date: string, text: st
|
||||||
topicId,
|
topicId,
|
||||||
date,
|
date,
|
||||||
text,
|
text,
|
||||||
sortOrder: existing.length,
|
sortOrder: Date.now(),
|
||||||
linkedContextId,
|
linkedContextId,
|
||||||
doneAt: null,
|
doneAt: null,
|
||||||
wiedervorlageDate: autoWiedervorlage ? date : null,
|
wiedervorlageDate: autoWiedervorlage ? date : null,
|
||||||
|
|
|
||||||
|
|
@ -13,3 +13,19 @@ export function normalizeTitleAndBody(
|
||||||
const newBody = body.trim() ? `${title}\n\n${body.trim()}` : title;
|
const newBody = body.trim() ? `${title}\n\n${body.trim()}` : title;
|
||||||
return { title: short, body: newBody };
|
return { title: short, body: newBody };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Splits raw combined input into title + body.
|
||||||
|
* First line (stripped of leading markdown heading markers) becomes the title.
|
||||||
|
* Remaining lines become the body.
|
||||||
|
* Then normalizeTitleAndBody is applied to enforce maxLen.
|
||||||
|
*/
|
||||||
|
export function extractTitleAndBody(
|
||||||
|
raw: string,
|
||||||
|
maxLen = DEFAULT_MAX_TITLE_LENGTH
|
||||||
|
): { title: string; body: string } {
|
||||||
|
const lines = raw.split('\n');
|
||||||
|
const firstLine = lines[0].replace(/^#{1,6}\s*/, '').trim();
|
||||||
|
const rest = lines.slice(1).join('\n').trim();
|
||||||
|
return normalizeTitleAndBody(firstLine, rest, maxLen);
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,8 @@
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { page } from '$app/stores';
|
import { page } from '$app/state';
|
||||||
import ContextPage from '$lib/components/ContextPage.svelte';
|
import ContextPage from '$lib/components/ContextPage.svelte';
|
||||||
|
|
||||||
const contextId = $derived($page.params.id);
|
const contextId = $derived(page.params.id);
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
{#key contextId}
|
{#key contextId}
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { page } from '$app/stores';
|
import { page } from '$app/state';
|
||||||
import { goto } from '$app/navigation';
|
import { goto } from '$app/navigation';
|
||||||
import { db } from '$lib/db/schema';
|
import { db } from '$lib/db/schema';
|
||||||
import RenderedMarkdown from '$lib/components/RenderedMarkdown.svelte';
|
import RenderedMarkdown from '$lib/components/RenderedMarkdown.svelte';
|
||||||
|
|
@ -18,8 +18,8 @@
|
||||||
});
|
});
|
||||||
|
|
||||||
// URL params
|
// URL params
|
||||||
const month = $derived($page.url.searchParams.get('month') ?? ''); // YYYY-MM
|
const month = $derived(page.url.searchParams.get('month') ?? ''); // YYYY-MM
|
||||||
const day = $derived($page.url.searchParams.get('day') ?? ''); // DD
|
const day = $derived(page.url.searchParams.get('day') ?? ''); // DD
|
||||||
|
|
||||||
// View: 'index' | 'month' | 'day'
|
// View: 'index' | 'month' | 'day'
|
||||||
const view = $derived(day ? 'day' : month ? 'month' : 'index');
|
const view = $derived(day ? 'day' : month ? 'month' : 'index');
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { page } from '$app/stores';
|
import { page } from '$app/state';
|
||||||
import { goto } from '$app/navigation';
|
import { goto } from '$app/navigation';
|
||||||
import { db } from '$lib/db/schema';
|
import { db } from '$lib/db/schema';
|
||||||
import { upsertPage, softDeletePage, togglePageFavorite, getNotebooksForPage, getBacklinksForPage, assignPageToNotebook, removePageFromNotebook, getAllNotebooks } from '$lib/db/repositories';
|
import { upsertPage, softDeletePage, togglePageFavorite, getNotebooksForPage, getBacklinksForPage, assignPageToNotebook, removePageFromNotebook, getAllNotebooks } from '$lib/db/repositories';
|
||||||
|
|
@ -9,7 +9,7 @@
|
||||||
import type { Page, Notebook } from '@ka-note/shared';
|
import type { Page, Notebook } from '@ka-note/shared';
|
||||||
import { currentScope, scopeSettings } from '$lib/stores/scopeContext';
|
import { currentScope, scopeSettings } from '$lib/stores/scopeContext';
|
||||||
import { useUnsavedGuard } from '$lib/utils/unsavedGuard.svelte';
|
import { useUnsavedGuard } from '$lib/utils/unsavedGuard.svelte';
|
||||||
const pageId = $derived($page.params.id);
|
const pageId = $derived(page.params.id);
|
||||||
let currentPage = $state<Page | undefined>(undefined);
|
let currentPage = $state<Page | undefined>(undefined);
|
||||||
|
|
||||||
const SCOPE_KEY = 'wiki-scope';
|
const SCOPE_KEY = 'wiki-scope';
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { page } from '$app/stores';
|
import { page } from '$app/state';
|
||||||
import { goto } from '$app/navigation';
|
import { goto } from '$app/navigation';
|
||||||
import { liveQuery } from 'dexie';
|
import { liveQuery } from 'dexie';
|
||||||
import { db } from '$lib/db/schema';
|
import { db } from '$lib/db/schema';
|
||||||
|
|
@ -12,7 +12,7 @@
|
||||||
if ($notebook$) currentScope.set($notebook$.isPrivate ? 'private' : 'business');
|
if ($notebook$) currentScope.set($notebook$.isPrivate ? 'private' : 'business');
|
||||||
});
|
});
|
||||||
|
|
||||||
const notebookId = $derived($page.params.id);
|
const notebookId = $derived(page.params.id);
|
||||||
const notebook$ = liveQuery(() => db.notebooks.get(notebookId));
|
const notebook$ = liveQuery(() => db.notebooks.get(notebookId));
|
||||||
const pages$ = pagesForNotebook(notebookId);
|
const pages$ = pagesForNotebook(notebookId);
|
||||||
|
|
||||||
|
|
|
||||||
Binary file not shown.
Binary file not shown.
Loading…
Reference in New Issue