This commit is contained in:
beo3000 2026-02-25 16:53:11 +01:00
parent 22e14def5f
commit 22d811716d
15 changed files with 153 additions and 146 deletions

View File

@ -1 +1 @@
1.1.73
1.1.74

View File

@ -10,9 +10,11 @@
getOrCreateJournalTopic,
createHistoryEntry,
createPage,
createTopic,
upsertContext,
contextNameExists,
pageNameExists,
} from "$lib/db/repositories";
import { today } from "$lib/db/helpers";
import { newId, today } from "$lib/db/helpers";
const contextsQuery = allActiveContexts();
const pagesQuery = allPages();
@ -212,6 +214,10 @@
badge: "BEFEHL",
action: async () => {
if (!text) return;
if (await pageNameExists(text)) {
alert(`Wiki-Seite "${text}" existiert bereits.`);
return;
}
const isPrivateScope = $currentScope === "private";
const page = await createPage(text, isPrivateScope);
closeBar();
@ -231,6 +237,10 @@
badge: "BEFEHL",
action: async () => {
if (!text) return;
if (await pageNameExists(text)) {
alert(`Wiki-Seite "${text}" existiert bereits.`);
return;
}
const page = await createPage(text, true);
closeBar();
goto(`/wiki/${page.id}`);
@ -249,6 +259,10 @@
badge: "BEFEHL",
action: async () => {
if (!text) return;
if (await pageNameExists(text)) {
alert(`Wiki-Seite "${text}" existiert bereits.`);
return;
}
const page = await createPage(text, false);
closeBar();
goto(`/wiki/${page.id}`);
@ -261,56 +275,19 @@
id: "cmd-person",
type: "action",
icon: "👤",
label: text
? `Person (${$currentScope === "private" ? "Privat" : "Firma"}): "${text}"`
: `Neu: Person (${$currentScope === "private" ? "Privat" : "Firma"})`,
label: text ? `Person: "${text}"` : `Neu: Person`,
badge: "BEFEHL",
action: async () => {
if (!text) return;
const isPrivateScope = $currentScope === "private";
const topic = await createTopic(
text,
"person",
isPrivateScope,
);
const fullName = `Person ${text}`;
if (await contextNameExists(fullName, "person")) {
alert(`Person "${text}" existiert bereits.`);
return;
}
const id = newId();
await upsertContext({ id, name: fullName, type: "person" });
closeBar();
goto(`/context/${topic.id}`);
},
});
}
if ("/pperson".startsWith(cmd)) {
actions.push({
id: "cmd-pperson",
type: "action",
icon: "👤",
label: text
? `Person (Privat): "${text}"`
: `Neu: Person (Privat)`,
badge: "BEFEHL",
action: async () => {
if (!text) return;
const topic = await createTopic(text, "person", true);
closeBar();
goto(`/context/${topic.id}`);
},
});
}
if ("/bperson".startsWith(cmd)) {
actions.push({
id: "cmd-bperson",
type: "action",
icon: "👤",
label: text
? `Person (Firma): "${text}"`
: `Neu: Person (Firma)`,
badge: "BEFEHL",
action: async () => {
if (!text) return;
const topic = await createTopic(text, "person", false);
closeBar();
goto(`/context/${topic.id}`);
goto(`/context/${id}`);
},
});
}
@ -320,60 +297,22 @@
id: "cmd-firma",
type: "action",
icon: "🏢",
label: text
? `Firma (${$currentScope === "private" ? "Privat" : "Firma"}): "${text}"`
: `Neu: Firma (${$currentScope === "private" ? "Privat" : "Firma"})`,
label: text ? `Firma: "${text}"` : `Neu: Firma`,
badge: "BEFEHL",
action: async () => {
if (!text) return;
const isPrivateScope = $currentScope === "private";
const topic = await createTopic(
text,
"company",
isPrivateScope,
);
const fullName = `Firma ${text}`;
if (await contextNameExists(fullName, "company")) {
alert(`Firma "${text}" existiert bereits.`);
return;
}
const id = newId();
await upsertContext({ id, name: fullName, type: "company" });
closeBar();
goto(`/context/${topic.id}`);
goto(`/context/${id}`);
},
});
}
if ("/pfirma".startsWith(cmd)) {
actions.push({
id: "cmd-pfirma",
type: "action",
icon: "🏢",
label: text
? `Firma (Privat): "${text}"`
: `Neu: Firma (Privat)`,
badge: "BEFEHL",
action: async () => {
if (!text) return;
const topic = await createTopic(text, "company", true);
closeBar();
goto(`/context/${topic.id}`);
},
});
}
if ("/bfirma".startsWith(cmd)) {
actions.push({
id: "cmd-bfirma",
type: "action",
icon: "🏢",
label: text
? `Firma (Firma): "${text}"`
: `Neu: Firma (Firma)`,
badge: "BEFEHL",
action: async () => {
if (!text) return;
const topic = await createTopic(text, "company", false);
closeBar();
goto(`/context/${topic.id}`);
},
});
}
if ("/sidebar".startsWith(cmd)) {
actions.push({
id: "cmd-sidebar",

View File

@ -87,6 +87,26 @@ export async function reorderContext(id: string, direction: 'up' | 'down'): Prom
}
}
export async function contextNameExists(name: string, type: ContextType): Promise<boolean> {
const q = name.toLowerCase();
const count = await db.contexts
.filter(c => !c.deletedAt && c.type === type && c.name.toLowerCase() === q)
.count();
return count > 0;
}
export async function pageNameExists(title: string): Promise<boolean> {
const q = title.toLowerCase();
const count = await db.pages.filter(p => !p.deletedAt && p.title.toLowerCase() === q).count();
return count > 0;
}
export async function notebookNameExists(name: string): Promise<boolean> {
const q = name.toLowerCase();
const count = await db.notebooks.filter(n => !n.deletedAt && n.name.toLowerCase() === q).count();
return count > 0;
}
export async function findContextByMentionName(name: string, type: 'person' | 'project' | 'company'): Promise<AgendaContext | undefined> {
const q = name.toLowerCase();
return db.contexts

View File

@ -2,26 +2,33 @@
import { goto } from '$app/navigation';
import { liveQuery } from 'dexie';
import { db } from '$lib/db/schema';
import { softDeleteContext, toggleFavorite, upsertContext, reorderContext } from '$lib/db/repositories';
import { softDeleteContext, toggleFavorite, upsertContext, reorderContext, contextNameExists } from '$lib/db/repositories';
import { newId } from '$lib/db/helpers';
import ConfirmDialog from '$lib/components/ConfirmDialog.svelte';
let creating = $state(false);
let newName = $state('');
let createError = $state('');
async function create() {
const name = newName.trim();
if (!name) return;
const fullName = `Firma ${name}`;
if (await contextNameExists(fullName, 'company')) {
createError = `"${name}" existiert bereits`;
return;
}
const id = newId();
await upsertContext({ id, name: `Firma ${name}`, type: 'company' });
await upsertContext({ id, name: fullName, type: 'company' });
newName = '';
creating = false;
createError = '';
goto(`/context/${id}`);
}
function onKeydown(e: KeyboardEvent) {
if (e.key === 'Enter') { e.preventDefault(); create(); }
else if (e.key === 'Escape') { creating = false; newName = ''; }
else if (e.key === 'Escape') { creating = false; newName = ''; createError = ''; }
}
const allCompanies = liveQuery(() =>
@ -60,9 +67,11 @@
placeholder="Firmenname..."
bind:value={newName}
onkeydown={onKeydown}
onblur={() => setTimeout(() => { creating = false; newName = ''; }, 150)}
oninput={() => createError = ''}
onblur={() => setTimeout(() => { creating = false; newName = ''; createError = ''; }, 150)}
autofocus
/>
{#if createError}<p class="mb-3 -mt-2 text-xs text-red-400">{createError}</p>{/if}
{/if}
{#if companies.length > 0}

View File

@ -2,26 +2,33 @@
import { goto } from '$app/navigation';
import { liveQuery } from 'dexie';
import { db } from '$lib/db/schema';
import { softDeleteContext, toggleFavorite, upsertContext, reorderContext } from '$lib/db/repositories';
import { softDeleteContext, toggleFavorite, upsertContext, reorderContext, contextNameExists } from '$lib/db/repositories';
import { newId } from '$lib/db/helpers';
import ConfirmDialog from '$lib/components/ConfirmDialog.svelte';
let creating = $state(false);
let newName = $state('');
let createWarning = $state('');
async function create() {
const name = newName.trim();
if (!name) return;
if (await contextNameExists(name, 'meeting')) {
createWarning = `"${name}" existiert bereits`;
} else {
createWarning = '';
}
const id = newId();
await upsertContext({ id, name, type: 'meeting' });
newName = '';
creating = false;
createWarning = '';
goto(`/context/${id}`);
}
function onKeydown(e: KeyboardEvent) {
if (e.key === 'Enter') { e.preventDefault(); create(); }
else if (e.key === 'Escape') { creating = false; newName = ''; }
else if (e.key === 'Escape') { creating = false; newName = ''; createWarning = ''; }
}
const allMeetings = liveQuery(() =>
@ -56,9 +63,11 @@
placeholder="Name des Jour Fix..."
bind:value={newName}
onkeydown={onKeydown}
onblur={() => setTimeout(() => { creating = false; newName = ''; }, 150)}
oninput={() => createWarning = ''}
onblur={() => setTimeout(() => { creating = false; newName = ''; createWarning = ''; }, 150)}
autofocus
/>
{#if createWarning}<p class="mb-3 -mt-2 text-xs text-yellow-400">{createWarning}</p>{/if}
{/if}
{#if meetings.length > 0}

View File

@ -2,7 +2,7 @@
import { goto } from '$app/navigation';
import { liveQuery } from 'dexie';
import { db } from '$lib/db/schema';
import { upsertContext } from '$lib/db/repositories';
import { upsertContext, contextNameExists } from '$lib/db/repositories';
import { newId } from '$lib/db/helpers';
import PersonList from '$lib/components/PersonList.svelte';
@ -14,20 +14,27 @@
let creating = $state(false);
let newName = $state('');
let createError = $state('');
async function create() {
const name = newName.trim();
if (!name) return;
const fullName = `Person ${name}`;
if (await contextNameExists(fullName, 'person')) {
createError = `"${name}" existiert bereits`;
return;
}
const id = newId();
await upsertContext({ id, name: `Person ${name}`, type: 'person' });
await upsertContext({ id, name: fullName, type: 'person', meta: { fullName: name, email: '', phone: '', duSince: '' } });
newName = '';
creating = false;
createError = '';
goto(`/context/${id}`);
}
function onKeydown(e: KeyboardEvent) {
if (e.key === 'Enter') { e.preventDefault(); create(); }
else if (e.key === 'Escape') { creating = false; newName = ''; }
else if (e.key === 'Escape') { creating = false; newName = ''; createError = ''; }
}
</script>
@ -43,9 +50,11 @@
placeholder="Name der Person..."
bind:value={newName}
onkeydown={onKeydown}
onblur={() => setTimeout(() => { creating = false; newName = ''; }, 150)}
oninput={() => createError = ''}
onblur={() => setTimeout(() => { creating = false; newName = ''; createError = ''; }, 150)}
autofocus
/>
{#if createError}<p class="mb-3 -mt-2 text-xs text-red-400">{createError}</p>{/if}
{/if}
<PersonList {persons} />

View File

@ -2,27 +2,34 @@
import { goto } from '$app/navigation';
import { liveQuery } from 'dexie';
import { db } from '$lib/db/schema';
import { softDeleteContext, toggleFavorite, upsertContext, reorderContext } from '$lib/db/repositories';
import { softDeleteContext, toggleFavorite, upsertContext, reorderContext, contextNameExists } from '$lib/db/repositories';
import { newId } from '$lib/db/helpers';
import ConfirmDialog from '$lib/components/ConfirmDialog.svelte';
import type { AgendaContext, ProjectMeta } from '@ka-note/shared';
let creating = $state(false);
let newName = $state('');
let createError = $state('');
async function create() {
const name = newName.trim();
if (!name) return;
const fullName = `Project ${name}`;
if (await contextNameExists(fullName, 'project')) {
createError = `"${name}" existiert bereits`;
return;
}
const id = newId();
await upsertContext({ id, name: `Project ${name}`, type: 'project' });
await upsertContext({ id, name: fullName, type: 'project' });
newName = '';
creating = false;
createError = '';
goto(`/context/${id}`);
}
function onKeydown(e: KeyboardEvent) {
if (e.key === 'Enter') { e.preventDefault(); create(); }
else if (e.key === 'Escape') { creating = false; newName = ''; }
else if (e.key === 'Escape') { creating = false; newName = ''; createError = ''; }
}
const allProjects = liveQuery(() =>
@ -66,9 +73,11 @@
placeholder="Projektname..."
bind:value={newName}
onkeydown={onKeydown}
onblur={() => setTimeout(() => { creating = false; newName = ''; }, 150)}
oninput={() => createError = ''}
onblur={() => setTimeout(() => { creating = false; newName = ''; createError = ''; }, 150)}
autofocus
/>
{#if createError}<p class="mb-3 -mt-2 text-xs text-red-400">{createError}</p>{/if}
{/if}
{#if activeProjects.length > 0}

View File

@ -1,7 +1,7 @@
<script lang="ts">
import { goto } from '$app/navigation';
import { allNotebooks, unassignedPages, favoritePages, recentlyEditedPages, type PageWithNotebooks } from '$lib/stores/wiki';
import { createNotebook, createPage, toggleNotebookFavorite, upsertNotebook, reorderNotebook } from '$lib/db/repositories';
import { createNotebook, createPage, toggleNotebookFavorite, upsertNotebook, reorderNotebook, notebookNameExists } from '$lib/db/repositories';
import { currentScope, scopeSettings } from '$lib/stores/scopeContext';
const notebooks$ = allNotebooks();
@ -45,6 +45,7 @@
let creatingNotebook = $state(false);
let newNotebookName = $state('');
let notebookError = $state('');
function setScope(s: 'business' | 'private') {
scope = s;
@ -58,9 +59,14 @@
async function addNotebook() {
const name = newNotebookName.trim();
if (!name) return;
if (await notebookNameExists(name)) {
notebookError = `"${name}" existiert bereits`;
return;
}
await createNotebook(name, null, scope === 'private');
newNotebookName = '';
creatingNotebook = false;
notebookError = '';
}
async function addPage() {
@ -70,7 +76,7 @@
function handleInputKeydown(e: KeyboardEvent) {
if (e.key === 'Enter') { e.preventDefault(); addNotebook(); }
else if (e.key === 'Escape') { creatingNotebook = false; newNotebookName = ''; }
else if (e.key === 'Escape') { creatingNotebook = false; newNotebookName = ''; notebookError = ''; }
}
function autofocus(node: HTMLElement) { node.focus(); }
@ -164,13 +170,15 @@
{/each}
{#if creatingNotebook}
<input
class="w-full rounded-b-lg bg-white/10 px-4 py-2.5 text-sm text-white outline-none placeholder:text-muted"
class="w-full bg-white/10 px-4 py-2.5 text-sm text-white outline-none placeholder:text-muted"
placeholder="Notizbuchname..."
bind:value={newNotebookName}
onkeydown={handleInputKeydown}
onblur={() => setTimeout(() => { creatingNotebook = false; newNotebookName = ''; }, 150)}
oninput={() => notebookError = ''}
onblur={() => setTimeout(() => { creatingNotebook = false; newNotebookName = ''; notebookError = ''; }, 150)}
use:autofocus
/>
{#if notebookError}<p class="rounded-b-lg bg-white/5 px-4 py-1.5 text-xs text-red-400">{notebookError}</p>{/if}
{/if}
</div>
<!-- Unassigned pages (shown below notebooks) -->

View File

@ -1,4 +1,10 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32">
<rect width="32" height="32" rx="4" fill="#007acc"/>
<text x="16" y="23" font-family="Segoe UI, sans-serif" font-size="18" font-weight="bold" fill="white" text-anchor="middle">K</text>
<defs>
<linearGradient id="bg" x1="0" y1="0" x2="1" y2="1">
<stop offset="0%" stop-color="#3366ff"/>
<stop offset="100%" stop-color="#8b5cf6"/>
</linearGradient>
</defs>
<rect width="32" height="32" rx="6" fill="url(#bg)"/>
<text x="16" y="23.5" font-family="'Inter', 'Segoe UI', sans-serif" font-size="17" font-weight="800" fill="white" text-anchor="middle" letter-spacing="-1">KA</text>
</svg>

Before

Width:  |  Height:  |  Size: 257 B

After

Width:  |  Height:  |  Size: 485 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.0 KiB

After

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 16 KiB

After

Width:  |  Height:  |  Size: 71 KiB

View File

@ -1,32 +1,30 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512" width="512" height="512">
<!-- Dark background -->
<rect width="512" height="512" fill="#3a3a3a" rx="80"/>
<defs>
<!-- Vibrant blue-purple gradient background -->
<linearGradient id="bg" x1="0" y1="0" x2="1" y2="1">
<stop offset="0%" stop-color="#3366ff"/>
<stop offset="100%" stop-color="#8b5cf6"/>
</linearGradient>
<!-- Subtle inner glow -->
<radialGradient id="glow" cx="50%" cy="40%" r="60%">
<stop offset="0%" stop-color="#ffffff" stop-opacity="0.12"/>
<stop offset="100%" stop-color="#ffffff" stop-opacity="0"/>
</radialGradient>
</defs>
<!-- White circle -->
<circle cx="256" cy="256" r="170" fill="#ffffff"/>
<!-- Rounded background -->
<rect width="512" height="512" rx="108" fill="url(#bg)"/>
<!-- Glow overlay -->
<rect width="512" height="512" rx="108" fill="url(#glow)"/>
<!-- Open book shape -->
<!-- Book spine (center line) -->
<!-- Left page -->
<path d="M130 195 Q130 185 140 183 L248 183 L248 330 Q200 325 152 335 Q130 338 130 320 Z" fill="#f0ede8"/>
<!-- Right page -->
<path d="M264 183 L372 183 Q382 185 382 195 L382 320 Q382 338 360 335 Q312 325 264 330 Z" fill="#f0ede8"/>
<!-- Book spine shadow -->
<rect x="248" y="183" width="16" height="147" fill="#e0dbd2" rx="2"/>
<!-- Left page: orange/amber checkmark -->
<polyline points="160,255 190,285 230,220" fill="none" stroke="#f59e0b" stroke-width="16" stroke-linecap="round" stroke-linejoin="round"/>
<!-- Right page: teal horizontal lines -->
<line x1="280" y1="220" x2="360" y2="220" stroke="#0d9488" stroke-width="8" stroke-linecap="round"/>
<line x1="280" y1="245" x2="355" y2="245" stroke="#0d9488" stroke-width="8" stroke-linecap="round"/>
<line x1="280" y1="270" x2="350" y2="270" stroke="#0d9488" stroke-width="8" stroke-linecap="round"/>
<line x1="280" y1="295" x2="345" y2="295" stroke="#0d9488" stroke-width="8" stroke-linecap="round"/>
<!-- Right page: teal clock icon (top-right corner of right page) -->
<!-- Clock circle -->
<circle cx="358" cy="200" r="18" fill="none" stroke="#0d9488" stroke-width="5"/>
<!-- Clock hands -->
<line x1="358" y1="200" x2="358" y2="191" stroke="#0d9488" stroke-width="4" stroke-linecap="round"/>
<line x1="358" y1="200" x2="365" y2="204" stroke="#0d9488" stroke-width="4" stroke-linecap="round"/>
<!-- "KA" lettering — bold, modern, white -->
<text
x="256" y="330"
font-family="'Inter', 'Segoe UI', 'Helvetica Neue', Arial, sans-serif"
font-size="260"
font-weight="800"
fill="#ffffff"
text-anchor="middle"
letter-spacing="-12"
>KA</text>
</svg>

Before

Width:  |  Height:  |  Size: 1.7 KiB

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

Binary file not shown.

View File

@ -155,7 +155,7 @@ async function upsertEntity(
} catch (err) {
const msg = err instanceof Error ? err.message : String(err);
console.error(`[sync] insert failed [${entityType}] id=${id}`, msg);
throw err;
return false; // skip bad entity, don't abort entire push
}
return true;
}