This commit is contained in:
beo3000 2026-02-23 08:49:19 +01:00
parent 7886947a65
commit d63c75e551
16 changed files with 174 additions and 33 deletions

View File

@ -1 +1 @@
1.1.7 1.1.11

View File

@ -49,12 +49,19 @@
editing = false; editing = false;
} }
} }
const headerColor = $derived(
showScopeSwitch
? (journalScope === 'private' ? $scopeSettings.privateColor : $scopeSettings.businessColor)
: $scopeSettings.businessColor
);
</script> </script>
<h1 class="flex items-center justify-between border-b-2 border-accent pb-2.5 mb-5"> <h1 class="flex items-center justify-between border-b-2 pb-2.5 mb-5" style="border-color: {headerColor}">
{#if editing} {#if editing}
<input <input
class="text-2xl font-bold bg-transparent border-b-2 border-accent text-inherit outline-none w-full mr-4" class="text-2xl font-bold bg-transparent border-b-2 text-inherit outline-none w-full mr-4"
style="border-color: {headerColor}"
bind:value={nameInput} bind:value={nameInput}
onkeydown={onKeydown} onkeydown={onKeydown}
onblur={saveEdit} onblur={saveEdit}

View File

@ -346,6 +346,15 @@
<input class="rounded border border-[#555] bg-[#111] px-2.5 py-1.5 text-[#ddd]" <input class="rounded border border-[#555] bg-[#111] px-2.5 py-1.5 text-[#ddd]"
value={meta.links} onchange={(e) => updateMeta('links', e.currentTarget.value)} /> value={meta.links} onchange={(e) => updateMeta('links', e.currentTarget.value)} />
</div> </div>
<div class="mb-2.5 flex flex-col">
<label class="mb-1 text-sm text-[#aaa]">Notizen:</label>
<EditableMarkdown
content={(context.meta as ProjectMeta)?.notes ?? ''}
placeholder="Notizen..."
minHeight="60px"
onchange={(md) => updateMeta('notes', md)}
/>
</div>
{:else if isCompany} {:else if isCompany}
{@const meta = (context.meta ?? { website: '', address: '' }) as CompanyMeta} {@const meta = (context.meta ?? { website: '', address: '' }) as CompanyMeta}
<div class="mb-2.5 flex flex-col"> <div class="mb-2.5 flex flex-col">

View File

@ -10,7 +10,7 @@
import ConfirmDialog from './ConfirmDialog.svelte'; import ConfirmDialog from './ConfirmDialog.svelte';
import WiedervorlageSection from './WiedervorlageSection.svelte'; import WiedervorlageSection from './WiedervorlageSection.svelte';
import LinkTitle from './LinkTitle.svelte'; import LinkTitle from './LinkTitle.svelte';
import { currentScope } from '$lib/stores/scopeContext'; import { currentScope, scopeSettings } from '$lib/stores/scopeContext';
interface Props { interface Props {
contextId: string; contextId: string;
@ -120,12 +120,14 @@
let editingId = $state<string | null>(null); let editingId = $state<string | null>(null);
let editTitle = $state(''); let editTitle = $state('');
let editBody = $state(''); let editBody = $state('');
let editIsPrivate = $state(false);
function startEdit(entry: { id: string; text: string }) { function startEdit(entry: { id: string; text: string; isPrivate?: boolean }) {
const lines = entry.text.split('\n'); const lines = entry.text.split('\n');
editingId = entry.id; editingId = entry.id;
editTitle = lines[0]; editTitle = lines[0];
editBody = lines.slice(1).join('\n').trim(); editBody = lines.slice(1).join('\n').trim();
editIsPrivate = !!entry.isPrivate;
} }
function cancelEdit() { function cancelEdit() {
@ -135,7 +137,7 @@
async function saveEdit() { async function saveEdit() {
if (!editingId || !editTitle.trim()) return; if (!editingId || !editTitle.trim()) return;
const text = editBody.trim() ? `${editTitle.trim()}\n${editBody.trim()}` : editTitle.trim(); const text = editBody.trim() ? `${editTitle.trim()}\n${editBody.trim()}` : editTitle.trim();
await updateHistoryEntry(editingId, text); await updateHistoryEntry(editingId, text, editIsPrivate);
editingId = null; editingId = null;
} }
@ -273,7 +275,8 @@
/> />
<div class="flex items-center gap-4"> <div class="flex items-center gap-4">
<button <button
class="rounded bg-accent 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}"
onclick={handleAddEntry} onclick={handleAddEntry}
> >
+ {selectedLinkedContextId ? 'Thema hinzufügen' : 'Notiz hinzufügen'} + {selectedLinkedContextId ? 'Thema hinzufügen' : 'Notiz hinzufügen'}
@ -312,7 +315,7 @@
wikiScope={journalScope === 'private'} wikiScope={journalScope === 'private'}
onchange={(md) => editBody = md} onchange={(md) => editBody = md}
/> />
<div class="flex gap-2"> <div class="flex items-center gap-2 flex-wrap">
<button <button
class="rounded bg-accent px-3 py-1 text-sm font-bold text-white hover:brightness-110" class="rounded bg-accent px-3 py-1 text-sm font-bold text-white hover:brightness-110"
onclick={saveEdit} onclick={saveEdit}
@ -321,6 +324,17 @@
class="rounded bg-[#444] px-3 py-1 text-sm text-white hover:bg-[#555]" class="rounded bg-[#444] px-3 py-1 text-sm text-white hover:bg-[#555]"
onclick={cancelEdit} onclick={cancelEdit}
>Abbrechen</button> >Abbrechen</button>
<div class="ml-auto flex gap-1 rounded-full bg-[#333] p-0.5">
<button
class="rounded-full px-2.5 py-1 text-xs font-bold transition-all {!editIsPrivate ? 'bg-accent text-white shadow' : 'text-[#aaa] hover:text-white'}"
onclick={() => editIsPrivate = false}
>Firma</button>
<button
class="rounded-full px-2.5 py-1 text-xs font-bold transition-all {editIsPrivate ? 'text-white shadow' : 'text-[#aaa] hover:text-white'}"
style={editIsPrivate ? `background-color: ${$scopeSettings.privateColor}` : ''}
onclick={() => editIsPrivate = true}
>Privat</button>
</div>
</div> </div>
</div> </div>
{:else} {:else}
@ -330,10 +344,13 @@
<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.updatedAt)}</span>
<div class="flex-1"> <div class="flex-1">
<div class="font-bold"> <div class="flex flex-wrap items-center gap-1.5 font-bold">
<LinkTitle text={title} /> <LinkTitle text={title} />
{#if entry.wiedervorlageDate && !entry.wiedervorlageResolvedAt}
<span class="text-base leading-none text-amber-400" title="In Wiedervorlage bis {entry.wiedervorlageDate}"></span>
{/if}
{#if entry.linkedContextId} {#if entry.linkedContextId}
<span class="ml-1 inline-block rounded bg-accent/20 px-1.5 py-0.5 text-xs font-normal text-accent"> <span class="inline-block rounded bg-accent/20 px-1.5 py-0.5 text-xs font-normal text-accent">
{contextNameMap().get(entry.linkedContextId) ?? entry.linkedContextId} {contextNameMap().get(entry.linkedContextId) ?? entry.linkedContextId}
</span> </span>
{/if} {/if}

View File

@ -1,6 +1,6 @@
<script lang="ts"> <script lang="ts">
import { goto } from '$app/navigation'; import { goto } from '$app/navigation';
import { softDeleteContext, toggleFavorite } from '$lib/db/repositories'; import { softDeleteContext, toggleFavorite, reorderContext } from '$lib/db/repositories';
import ConfirmDialog from '$lib/components/ConfirmDialog.svelte'; import ConfirmDialog from '$lib/components/ConfirmDialog.svelte';
import type { AgendaContext, PersonMeta, PersonSubType } from '@ka-note/shared'; import type { AgendaContext, PersonMeta, PersonSubType } from '@ka-note/shared';
@ -41,9 +41,13 @@
{#if persons.length > 0} {#if persons.length > 0}
<div class="flex flex-col gap-2"> <div class="flex flex-col gap-2">
{#each persons as ctx (ctx.id)} {#each persons as ctx, i (ctx.id)}
{@const subType = ((ctx.meta as PersonMeta | null)?.personSubType ?? 'contact') as PersonSubType} {@const subType = ((ctx.meta as PersonMeta | null)?.personSubType ?? 'contact') as PersonSubType}
<div class="flex items-center gap-2"> <div class="flex items-center gap-2">
<div class="flex flex-col">
<button class="text-[#555] hover:text-[#aaa] disabled:opacity-20 leading-none px-1" onclick={() => reorderContext(ctx.id, 'up')} disabled={i === 0} title="Nach oben"></button>
<button class="text-[#555] hover:text-[#aaa] disabled:opacity-20 leading-none px-1" onclick={() => reorderContext(ctx.id, 'down')} disabled={i === persons.length - 1} title="Nach unten"></button>
</div>
<button <button
class="rounded border border-[#444] bg-sidebar px-2.5 py-2.5 transition-colors hover:border-[#666] {ctx.isFavorite ? 'text-yellow-400' : 'text-[#555] hover:text-yellow-400/60'}" class="rounded border border-[#444] bg-sidebar px-2.5 py-2.5 transition-colors hover:border-[#666] {ctx.isFavorite ? 'text-yellow-400' : 'text-[#555] hover:text-yellow-400/60'}"
onclick={() => toggleFavorite(ctx.id)} onclick={() => toggleFavorite(ctx.id)}

View File

@ -98,23 +98,27 @@
>Abbrechen</button> >Abbrechen</button>
</div> </div>
{:else} {:else}
<div class="flex gap-2"> <div class="flex gap-1.5">
<button <button
class="rounded bg-green-700 px-3 py-1 text-sm font-bold text-white hover:brightness-110" class="rounded bg-green-700 px-2.5 py-1.5 text-base leading-none text-white hover:brightness-110 active:brightness-90"
title="Erledigt"
onclick={handleOk} onclick={handleOk}
>Ok</button> ></button>
<button <button
class="rounded bg-amber-700 px-3 py-1 text-sm text-white hover:brightness-110" class="rounded bg-amber-700 px-2.5 py-1.5 text-base leading-none text-white hover:brightness-110 active:brightness-90"
title="Verschieben"
onclick={() => showVerschieben = true} onclick={() => showVerschieben = true}
>Verschieben</button> >📅</button>
<button <button
class="rounded bg-[#444] px-3 py-1 text-sm text-white hover:bg-[#555]" class="rounded bg-[#444] px-2.5 py-1.5 text-base leading-none text-white hover:bg-[#555] active:bg-[#555]"
title="In Thema wandeln"
onclick={() => showConvert = true} onclick={() => showConvert = true}
>In Thema wandeln</button> ></button>
<button <button
class="ml-auto rounded bg-red-900/60 px-3 py-1 text-sm text-red-300 hover:bg-red-800" class="ml-auto rounded bg-red-900/60 px-2.5 py-1.5 text-base leading-none text-red-300 hover:bg-red-800 active:bg-red-800"
title="Löschen"
onclick={handleDelete} onclick={handleDelete}
>Löschen</button> >🗑</button>
</div> </div>
{/if} {/if}
</div> </div>

View File

@ -66,6 +66,21 @@ export async function toggleFavorite(id: string): Promise<void> {
} }
} }
export async function reorderContext(id: string, direction: 'up' | 'down'): Promise<void> {
const ctx = await db.contexts.get(id);
if (!ctx) return;
const siblings = await db.contexts
.filter(c => !c.deletedAt && c.type === ctx.type)
.sortBy('sortOrder');
const idx = siblings.findIndex(c => c.id === id);
const swapIdx = direction === 'up' ? idx - 1 : idx + 1;
if (swapIdx < 0 || swapIdx >= siblings.length) return;
const other = siblings[swapIdx];
const ts = now();
await db.contexts.update(id, { sortOrder: other.sortOrder, updatedAt: ts, version: ctx.version + 1 });
await db.contexts.update(other.id, { sortOrder: ctx.sortOrder, updatedAt: ts, version: other.version + 1 });
}
export async function findContextByMentionName(name: string, type: 'person' | 'project' | 'company'): Promise<AgendaContext | undefined> { export async function findContextByMentionName(name: string, type: 'person' | 'project' | 'company'): Promise<AgendaContext | undefined> {
const q = name.toLowerCase(); const q = name.toLowerCase();
return db.contexts return db.contexts
@ -257,10 +272,12 @@ export async function convertToTopic(entryId: string, contextId: string): Promis
return topic; return topic;
} }
export async function updateHistoryEntry(id: string, text: string): Promise<void> { export async function updateHistoryEntry(id: string, text: string, isPrivate?: boolean): Promise<void> {
const entry = await db.historyEntries.get(id); const entry = await db.historyEntries.get(id);
if (entry) { if (entry) {
await db.historyEntries.update(id, { text, updatedAt: now(), version: entry.version + 1 }); const patch: Record<string, unknown> = { text, updatedAt: now(), version: entry.version + 1 };
if (isPrivate !== undefined) patch.isPrivate = isPrivate;
await db.historyEntries.update(id, patch);
} }
} }
@ -381,6 +398,13 @@ export async function upsertPage(page: Partial<Page> & { id: string }): Promise<
} }
} }
export async function togglePageFavorite(id: string): Promise<void> {
const p = await db.pages.get(id);
if (p) {
await db.pages.update(id, { isFavorite: !p.isFavorite, updatedAt: now(), version: p.version + 1 });
}
}
export async function softDeletePage(id: string): Promise<void> { export async function softDeletePage(id: string): Promise<void> {
const page = await db.pages.get(id); const page = await db.pages.get(id);
if (page) { if (page) {
@ -469,6 +493,21 @@ export async function toggleNotebookFavorite(id: string): Promise<void> {
} }
} }
export async function reorderNotebook(id: string, direction: 'up' | 'down'): Promise<void> {
const nb = await db.notebooks.get(id);
if (!nb) return;
const siblings = await db.notebooks
.filter(n => !n.deletedAt)
.sortBy('sortOrder');
const idx = siblings.findIndex(n => n.id === id);
const swapIdx = direction === 'up' ? idx - 1 : idx + 1;
if (swapIdx < 0 || swapIdx >= siblings.length) return;
const other = siblings[swapIdx];
const ts = now();
await db.notebooks.update(id, { sortOrder: other.sortOrder, updatedAt: ts, version: nb.version + 1 });
await db.notebooks.update(other.id, { sortOrder: nb.sortOrder, updatedAt: ts, version: other.version + 1 });
}
export async function softDeleteNotebook(id: string): Promise<void> { export async function softDeleteNotebook(id: string): Promise<void> {
const nb = await db.notebooks.get(id); const nb = await db.notebooks.get(id);
if (nb) { if (nb) {

View File

@ -118,6 +118,14 @@ export class KaNoteDB extends Dexie {
if (nb.isFavorite === undefined) nb.isFavorite = false; if (nb.isFavorite === undefined) nb.isFavorite = false;
}); });
}); });
this.version(12).stores({
pages: '&id, deletedAt, isPrivate, isFavorite',
}).upgrade(tx => {
return tx.table('pages').toCollection().modify((p: Record<string, unknown>) => {
if (p.isFavorite === undefined) p.isFavorite = false;
});
});
} }
} }

View File

@ -29,6 +29,12 @@ export function notebooksForContext(contextId: string) {
); );
} }
export function favoritePages() {
return liveQuery(() =>
db.pages.filter(p => !p.deletedAt && !!p.isFavorite).sortBy('title')
);
}
export function allPages() { export function allPages() {
return liveQuery(() => return liveQuery(() =>
db.pages.filter(p => !p.deletedAt).sortBy('title') db.pages.filter(p => !p.deletedAt).sortBy('title')

View File

@ -2,7 +2,7 @@
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';
import { softDeleteContext, toggleFavorite, upsertContext } from '$lib/db/repositories'; import { softDeleteContext, toggleFavorite, upsertContext, reorderContext } from '$lib/db/repositories';
import { newId } from '$lib/db/helpers'; import { newId } from '$lib/db/helpers';
import ConfirmDialog from '$lib/components/ConfirmDialog.svelte'; import ConfirmDialog from '$lib/components/ConfirmDialog.svelte';
@ -67,8 +67,12 @@
{#if companies.length > 0} {#if companies.length > 0}
<div class="flex flex-col gap-2"> <div class="flex flex-col gap-2">
{#each companies as ctx (ctx.id)} {#each companies as ctx, i (ctx.id)}
<div class="flex items-center gap-2"> <div class="flex items-center gap-2">
<div class="flex flex-col">
<button class="text-[#555] hover:text-[#aaa] disabled:opacity-20 leading-none px-1" onclick={() => reorderContext(ctx.id, 'up')} disabled={i === 0} title="Nach oben"></button>
<button class="text-[#555] hover:text-[#aaa] disabled:opacity-20 leading-none px-1" onclick={() => reorderContext(ctx.id, 'down')} disabled={i === companies.length - 1} title="Nach unten"></button>
</div>
<button <button
class="rounded border border-[#444] bg-sidebar px-2.5 py-2.5 transition-colors hover:border-[#666] {ctx.isFavorite ? 'text-yellow-400' : 'text-[#555] hover:text-yellow-400/60'}" class="rounded border border-[#444] bg-sidebar px-2.5 py-2.5 transition-colors hover:border-[#666] {ctx.isFavorite ? 'text-yellow-400' : 'text-[#555] hover:text-yellow-400/60'}"
onclick={() => toggleFavorite(ctx.id)} onclick={() => toggleFavorite(ctx.id)}

View File

@ -2,7 +2,7 @@
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';
import { softDeleteContext, toggleFavorite, upsertContext } from '$lib/db/repositories'; import { softDeleteContext, toggleFavorite, upsertContext, reorderContext } from '$lib/db/repositories';
import { newId } from '$lib/db/helpers'; import { newId } from '$lib/db/helpers';
import ConfirmDialog from '$lib/components/ConfirmDialog.svelte'; import ConfirmDialog from '$lib/components/ConfirmDialog.svelte';
@ -63,8 +63,12 @@
{#if meetings.length > 0} {#if meetings.length > 0}
<div class="flex flex-col gap-2"> <div class="flex flex-col gap-2">
{#each meetings as ctx (ctx.id)} {#each meetings as ctx, i (ctx.id)}
<div class="flex items-center gap-2"> <div class="flex items-center gap-2">
<div class="flex flex-col">
<button class="text-[#555] hover:text-[#aaa] disabled:opacity-20 leading-none px-1" onclick={() => reorderContext(ctx.id, 'up')} disabled={i === 0} title="Nach oben"></button>
<button class="text-[#555] hover:text-[#aaa] disabled:opacity-20 leading-none px-1" onclick={() => reorderContext(ctx.id, 'down')} disabled={i === meetings.length - 1} title="Nach unten"></button>
</div>
<button <button
class="rounded border border-[#444] bg-sidebar px-2.5 py-2.5 transition-colors hover:border-[#666] {ctx.isFavorite ? 'text-yellow-400' : 'text-[#555] hover:text-yellow-400/60'}" class="rounded border border-[#444] bg-sidebar px-2.5 py-2.5 transition-colors hover:border-[#666] {ctx.isFavorite ? 'text-yellow-400' : 'text-[#555] hover:text-yellow-400/60'}"
onclick={() => toggleFavorite(ctx.id)} onclick={() => toggleFavorite(ctx.id)}

View File

@ -2,7 +2,7 @@
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';
import { softDeleteContext, toggleFavorite, upsertContext } from '$lib/db/repositories'; import { softDeleteContext, toggleFavorite, upsertContext, reorderContext } from '$lib/db/repositories';
import { newId } from '$lib/db/helpers'; import { newId } from '$lib/db/helpers';
import ConfirmDialog from '$lib/components/ConfirmDialog.svelte'; import ConfirmDialog from '$lib/components/ConfirmDialog.svelte';
import type { AgendaContext, ProjectMeta } from '@ka-note/shared'; import type { AgendaContext, ProjectMeta } from '@ka-note/shared';
@ -74,8 +74,12 @@
{#if activeProjects.length > 0} {#if activeProjects.length > 0}
<h2 class="mb-3 text-sm font-bold uppercase text-[#aaa]">Aktiv</h2> <h2 class="mb-3 text-sm font-bold uppercase text-[#aaa]">Aktiv</h2>
<div class="mb-8 flex flex-col gap-2"> <div class="mb-8 flex flex-col gap-2">
{#each activeProjects as ctx (ctx.id)} {#each activeProjects as ctx, i (ctx.id)}
<div class="flex items-center gap-2"> <div class="flex items-center gap-2">
<div class="flex flex-col">
<button class="text-[#555] hover:text-[#aaa] disabled:opacity-20 leading-none px-1" onclick={() => reorderContext(ctx.id, 'up')} disabled={i === 0} title="Nach oben"></button>
<button class="text-[#555] hover:text-[#aaa] disabled:opacity-20 leading-none px-1" onclick={() => reorderContext(ctx.id, 'down')} disabled={i === activeProjects.length - 1} title="Nach unten"></button>
</div>
<button <button
class="rounded border border-[#444] bg-sidebar px-2.5 py-2.5 transition-colors hover:border-[#666] {ctx.isFavorite ? 'text-yellow-400' : 'text-[#555] hover:text-yellow-400/60'}" class="rounded border border-[#444] bg-sidebar px-2.5 py-2.5 transition-colors hover:border-[#666] {ctx.isFavorite ? 'text-yellow-400' : 'text-[#555] hover:text-yellow-400/60'}"
onclick={() => toggleFavorite(ctx.id)} onclick={() => toggleFavorite(ctx.id)}

View File

@ -1,11 +1,12 @@
<script lang="ts"> <script lang="ts">
import { goto } from '$app/navigation'; import { goto } from '$app/navigation';
import { allNotebooks, unassignedPages } from '$lib/stores/wiki'; import { allNotebooks, unassignedPages, favoritePages } from '$lib/stores/wiki';
import { createNotebook, createPage, toggleNotebookFavorite, upsertNotebook } from '$lib/db/repositories'; import { createNotebook, createPage, toggleNotebookFavorite, upsertNotebook, reorderNotebook } from '$lib/db/repositories';
import { currentScope, scopeSettings } from '$lib/stores/scopeContext'; import { currentScope, scopeSettings } from '$lib/stores/scopeContext';
const notebooks$ = allNotebooks(); const notebooks$ = allNotebooks();
const unassigned$ = unassignedPages(); const unassigned$ = unassignedPages();
const favoritePages$ = favoritePages();
const SCOPE_KEY = 'wiki-scope'; const SCOPE_KEY = 'wiki-scope';
let scope = $state<'business' | 'private'>( let scope = $state<'business' | 'private'>(
@ -20,6 +21,10 @@
($unassigned$ ?? []).filter(p => p.isPrivate === (scope === 'private')) ($unassigned$ ?? []).filter(p => p.isPrivate === (scope === 'private'))
); );
const filteredFavoritePages = $derived(
($favoritePages$ ?? []).filter(p => p.isPrivate === (scope === 'private'))
);
let creatingNotebook = $state(false); let creatingNotebook = $state(false);
let newNotebookName = $state(''); let newNotebookName = $state('');
@ -71,6 +76,24 @@
</div> </div>
</h1> </h1>
<!-- Favorite pages -->
{#if filteredFavoritePages.length > 0}
<section>
<h2 class="mb-2 text-sm font-semibold uppercase text-muted">Favoriten</h2>
<div class="flex flex-wrap gap-2">
{#each filteredFavoritePages as page (page.id)}
<button
class="flex items-center gap-1.5 rounded-lg border border-warning/40 bg-warning/10 px-3 py-1.5 text-sm text-warning hover:bg-warning/20 transition-colors"
onclick={() => goto(`/wiki/${page.id}`)}
>
<span class="text-xs"></span>
<span>{page.title}</span>
</button>
{/each}
</div>
</section>
{/if}
<div class="flex items-center justify-between"> <div class="flex items-center justify-between">
<h2 class="text-sm font-semibold uppercase text-muted">Notizbücher</h2> <h2 class="text-sm font-semibold uppercase text-muted">Notizbücher</h2>
<div class="flex items-center gap-3"> <div class="flex items-center gap-3">
@ -83,8 +106,12 @@
</div> </div>
<div class="space-y-1"> <div class="space-y-1">
{#each filteredNotebooks as nb (nb.id)} {#each filteredNotebooks as nb, i (nb.id)}
<div class="flex items-center gap-1 rounded hover:bg-white/5 group"> <div class="flex items-center gap-1 rounded hover:bg-white/5 group">
<div class="flex flex-col pl-1">
<button class="text-[#555] hover:text-[#aaa] disabled:opacity-20 leading-none text-[10px] px-0.5" onclick={() => reorderNotebook(nb.id, 'up')} disabled={i === 0} title="Nach oben"></button>
<button class="text-[#555] hover:text-[#aaa] disabled:opacity-20 leading-none text-[10px] px-0.5" onclick={() => reorderNotebook(nb.id, 'down')} disabled={i === filteredNotebooks.length - 1} title="Nach unten"></button>
</div>
<button <button
class="flex flex-1 items-center gap-2 px-3 py-2 text-left text-sm text-[#ccc] hover:text-white" class="flex flex-1 items-center gap-2 px-3 py-2 text-left text-sm text-[#ccc] hover:text-white"
onclick={() => goto(`/wiki/notebook/${nb.id}`)} onclick={() => goto(`/wiki/notebook/${nb.id}`)}

View File

@ -2,7 +2,7 @@
import { page } from '$app/stores'; import { page } from '$app/stores';
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, getNotebooksForPage, getBacklinksForPage, assignPageToNotebook, removePageFromNotebook, getAllNotebooks } from '$lib/db/repositories'; import { upsertPage, softDeletePage, togglePageFavorite, getNotebooksForPage, getBacklinksForPage, assignPageToNotebook, removePageFromNotebook, getAllNotebooks } from '$lib/db/repositories';
import MarkdownEditor from '$lib/components/MarkdownEditor.svelte'; import MarkdownEditor from '$lib/components/MarkdownEditor.svelte';
import RenderedMarkdown from '$lib/components/RenderedMarkdown.svelte'; import RenderedMarkdown from '$lib/components/RenderedMarkdown.svelte';
import ConfirmDialog from '$lib/components/ConfirmDialog.svelte'; import ConfirmDialog from '$lib/components/ConfirmDialog.svelte';
@ -59,6 +59,7 @@
async function saveBody() { async function saveBody() {
const body = editorRef?.getMarkdown() ?? ''; const body = editorRef?.getMarkdown() ?? '';
await upsertPage({ id: pageId, body }); await upsertPage({ id: pageId, body });
if (currentPage) currentPage = { ...currentPage, body };
} }
async function switchToRead() { async function switchToRead() {
@ -137,6 +138,11 @@
> >
{editing ? '✓ Fertig' : '✎ Bearbeiten'} {editing ? '✓ Fertig' : '✎ Bearbeiten'}
</button> </button>
<button
class="rounded px-2 py-1.5 text-base transition-colors {currentPage.isFavorite ? 'text-warning' : 'text-muted hover:text-warning'}"
onclick={async () => { await togglePageFavorite(pageId); currentPage = { ...currentPage!, isFavorite: !currentPage!.isFavorite }; }}
title={currentPage.isFavorite ? 'Favorit entfernen' : 'Als Favorit markieren'}
>★</button>
<button <button
class="rounded px-2 py-1.5 text-xs text-muted hover:bg-danger/20 hover:text-danger ml-auto" class="rounded px-2 py-1.5 text-xs text-muted hover:bg-danger/20 hover:text-danger ml-auto"
onclick={() => confirmDelete = true} onclick={() => confirmDelete = true}

Binary file not shown.

View File

@ -23,6 +23,7 @@ export interface ProjectMeta {
status: string; status: string;
owner: string; owner: string;
links: string; links: string;
notes?: string;
} }
export interface PersonMeta { export interface PersonMeta {
@ -91,6 +92,7 @@ export interface Page extends SyncEntity {
body: string; body: string;
isPrivate: boolean; isPrivate: boolean;
sortOrder: number; sortOrder: number;
isFavorite?: boolean;
} }
export interface Notebook extends SyncEntity { export interface Notebook extends SyncEntity {