upd commandbar

This commit is contained in:
beo3000 2026-02-25 17:08:54 +01:00
parent 22d811716d
commit 2cb4c24e40
5 changed files with 278 additions and 40 deletions

View File

@ -63,16 +63,17 @@ Scope-dynamische Commands verwenden den aktuell aktiven Scope (Privat/Firma). Sc
| `/page [Titel]` | Neue Wiki-Seite erstellen (aktiver Scope) |
| `/ppage [Titel]` | Neue Wiki-Seite erstellen (Privat) |
| `/bpage [Titel]` | Neue Wiki-Seite erstellen (Firma) |
| `/person [Name]` | Neuen Personen-Kontext erstellen (aktiver Scope) |
| `/pperson [Name]` | Neuen Personen-Kontext erstellen (Privat) |
| `/bperson [Name]` | Neuen Personen-Kontext erstellen (Firma) |
| `/firma [Name]` | Neuen Firmen-Kontext erstellen (aktiver Scope) |
| `/pfirma [Name]` | Neuen Firmen-Kontext erstellen (Privat) |
| `/bfirma [Name]` | Neuen Firmen-Kontext erstellen (Firma) |
| `/per [Name]` | Neuen Personen-Kontext erstellen; Name → Title Case, setzt `meta.fullName` |
| `/firma [Name]` | Neuen Firmen-Kontext erstellen |
| `/jf [Name]` | Zu passendem Jour-Fix-Kontext springen |
| `/sidebar` | Sidebar ein-/ausblenden |
| `/home` | Zurück zur Journal-Startseite (`/`) |
| `/help` | Hilfeseite öffnen (`/help`) |
Scope-spezifische Commands erscheinen in der Bar erst ab dem zweiten Zeichen (`/p...` / `/b...`), um Duplikate mit den scope-dynamischen Commands zu vermeiden.
Wenn kein Text angegeben wird, erscheint ein Browser-`prompt()` zur Eingabe — `Enter` auf einem Befehl ohne Text löst also direkt eine Aktion aus.
Beispiel: `/note Anruf bei Steffen wegen Angebot` → sofort als Topic in `daily-log` gespeichert, ohne den aktuellen Screen zu verlassen.
---
@ -134,12 +135,13 @@ In-Memory: `label.toLowerCase().includes(query)` — reicht für typische Datenm
## Scope
**In Scope:**
**Implementiert:**
- Navigation zu Contexts und Wiki-Seiten
- Slash-Commands: `/note`, `/todo`, `/jf`
- Slash-Commands: `/note`, `/pnote`, `/bnote`, `/todo`, `/ptodo`, `/btodo`, `/page`, `/ppage`, `/bpage`, `/per`, `/firma`, `/jf`, `/sidebar`, `/home`, `/help`
- Recent-Items (sessionStorage)
- Tastatursteuerung
- Tastatursteuerung inkl. Enter ohne Text (Browser-Prompt)
- Responsive (Modal auf Mobile funktioniert)
- `/per` setzt automatisch Title Case und `meta.fullName`
**Out of Scope (v1):**
- Fuzzy-Suche / Treffergewichtung
@ -147,6 +149,7 @@ In-Memory: `label.toLowerCase().includes(query)` — reicht für typische Datenm
- Server-seitige Volltextsuche
- Persistente "Favourites" in der Bar
- Konfigurierbarer Shortcut
- Scope-Varianten für `/per` und `/firma` (`/pper`, `/bper`, ...)
---

View File

@ -92,6 +92,11 @@
}
}
function promptIfEmpty(current: string, label: string): string {
if (current) return current;
return window.prompt(`${label}:`) ?? "";
}
const results = $derived.by(() => {
const q = query.trim().toLowerCase();
@ -114,9 +119,10 @@
: `Neu: Notiz (${$currentScope === "private" ? "Privat" : "Firma"})`,
badge: "BEFEHL",
action: async () => {
if (!text) return;
const t = promptIfEmpty(text, "Titel der Notiz");
if (!t) return;
const isPrivateScope = $currentScope === "private";
await executeNoteCommand(text, isPrivateScope);
await executeNoteCommand(t, isPrivateScope);
},
});
}
@ -131,8 +137,9 @@
: "Neu: Notiz (Privat)",
badge: "BEFEHL",
action: async () => {
if (!text) return;
await executeNoteCommand(text, true);
const t = promptIfEmpty(text, "Titel der Notiz");
if (!t) return;
await executeNoteCommand(t, true);
},
});
}
@ -147,8 +154,9 @@
: "Neu: Notiz (Firma)",
badge: "BEFEHL",
action: async () => {
if (!text) return;
await executeNoteCommand(text, false);
const t = promptIfEmpty(text, "Titel der Notiz");
if (!t) return;
await executeNoteCommand(t, false);
},
});
}
@ -164,9 +172,10 @@
: `Neu: Todo (${$currentScope === "private" ? "Privat" : "Firma"})`,
badge: "BEFEHL",
action: async () => {
if (!text) return;
const t = promptIfEmpty(text, "Titel des Todos");
if (!t) return;
const isPrivateScope = $currentScope === "private";
await executeTodoCommand(text, isPrivateScope);
await executeTodoCommand(t, isPrivateScope);
},
});
}
@ -181,8 +190,9 @@
: "Neu: Todo (Privat)",
badge: "BEFEHL",
action: async () => {
if (!text) return;
await executeTodoCommand(text, true);
const t = promptIfEmpty(text, "Titel des Todos");
if (!t) return;
await executeTodoCommand(t, true);
},
});
}
@ -197,8 +207,9 @@
: "Neu: Todo (Firma)",
badge: "BEFEHL",
action: async () => {
if (!text) return;
await executeTodoCommand(text, false);
const t = promptIfEmpty(text, "Titel des Todos");
if (!t) return;
await executeTodoCommand(t, false);
},
});
}
@ -213,13 +224,14 @@
: `Neu: Wiki-Seite (${$currentScope === "private" ? "Privat" : "Firma"})`,
badge: "BEFEHL",
action: async () => {
if (!text) return;
if (await pageNameExists(text)) {
alert(`Wiki-Seite "${text}" existiert bereits.`);
const t = promptIfEmpty(text, "Titel der Wiki-Seite");
if (!t) return;
if (await pageNameExists(t)) {
alert(`Wiki-Seite "${t}" existiert bereits.`);
return;
}
const isPrivateScope = $currentScope === "private";
const page = await createPage(text, isPrivateScope);
const page = await createPage(t, isPrivateScope);
closeBar();
goto(`/wiki/${page.id}`);
},
@ -236,12 +248,13 @@
: `Neu: Wiki-Seite (Privat)`,
badge: "BEFEHL",
action: async () => {
if (!text) return;
if (await pageNameExists(text)) {
alert(`Wiki-Seite "${text}" existiert bereits.`);
const t = promptIfEmpty(text, "Titel der Wiki-Seite");
if (!t) return;
if (await pageNameExists(t)) {
alert(`Wiki-Seite "${t}" existiert bereits.`);
return;
}
const page = await createPage(text, true);
const page = await createPage(t, true);
closeBar();
goto(`/wiki/${page.id}`);
},
@ -258,12 +271,13 @@
: `Neu: Wiki-Seite (Firma)`,
badge: "BEFEHL",
action: async () => {
if (!text) return;
if (await pageNameExists(text)) {
alert(`Wiki-Seite "${text}" existiert bereits.`);
const t = promptIfEmpty(text, "Titel der Wiki-Seite");
if (!t) return;
if (await pageNameExists(t)) {
alert(`Wiki-Seite "${t}" existiert bereits.`);
return;
}
const page = await createPage(text, false);
const page = await createPage(t, false);
closeBar();
goto(`/wiki/${page.id}`);
},
@ -278,14 +292,16 @@
label: text ? `Person: "${text}"` : `Neu: Person`,
badge: "BEFEHL",
action: async () => {
if (!text) return;
const fullName = `Person ${text}`;
const raw = promptIfEmpty(text, "Name der Person");
if (!raw) return;
const displayName = raw.replace(/\b\w/g, (c) => c.toUpperCase());
const fullName = `Person ${displayName}`;
if (await contextNameExists(fullName, "person")) {
alert(`Person "${text}" existiert bereits.`);
alert(`Person "${displayName}" existiert bereits.`);
return;
}
const id = newId();
await upsertContext({ id, name: fullName, type: "person" });
await upsertContext({ id, name: fullName, type: "person", meta: { fullName: displayName, email: "", phone: "", duSince: "", personSubType: "colleague" } });
closeBar();
goto(`/context/${id}`);
},
@ -300,10 +316,11 @@
label: text ? `Firma: "${text}"` : `Neu: Firma`,
badge: "BEFEHL",
action: async () => {
if (!text) return;
const fullName = `Firma ${text}`;
const t = promptIfEmpty(text, "Name der Firma");
if (!t) return;
const fullName = `Firma ${t}`;
if (await contextNameExists(fullName, "company")) {
alert(`Firma "${text}" existiert bereits.`);
alert(`Firma "${t}" existiert bereits.`);
return;
}
const id = newId();
@ -327,6 +344,34 @@
});
}
if ("/home".startsWith(cmd)) {
actions.push({
id: "cmd-home",
type: "action",
icon: "🏠",
label: "Journal (Startseite)",
badge: "BEFEHL",
action: () => {
closeBar();
goto("/");
},
});
}
if ("/help".startsWith(cmd)) {
actions.push({
id: "cmd-help",
type: "action",
icon: "❓",
label: "Hilfe & Befehle anzeigen",
badge: "BEFEHL",
action: () => {
closeBar();
goto("/help");
},
});
}
if ("/jf".startsWith(cmd)) {
actions.push({
id: "cmd-jf",

View File

@ -0,0 +1,190 @@
<script lang="ts">
import { goto } from '$app/navigation';
</script>
<div class="mx-auto max-w-3xl p-6">
<div class="mb-6 flex items-center gap-3">
<h1 class="flex-1 text-2xl font-bold text-white">Hilfe & Befehle</h1>
<button
class="rounded bg-card-bg px-3 py-1.5 text-sm text-muted hover:text-white"
onclick={() => history.back()}
>
← Zurück
</button>
</div>
<!-- Command Bar -->
<section class="mb-8">
<h2 class="mb-3 text-lg font-semibold text-white">Command Bar</h2>
<p class="mb-4 text-sm text-muted">
Öffnen mit <kbd class="rounded bg-card-bg px-1.5 py-0.5 font-mono text-xs text-white">Ctrl+K</kbd>.
Tippe einen Befehl mit <code class="text-accent">/</code> oder suche direkt nach Kontexten und Wiki-Seiten.
</p>
<div class="rounded border border-border bg-card-bg overflow-hidden">
<table class="w-full text-sm">
<thead>
<tr class="border-b border-border text-left text-xs text-muted">
<th class="px-4 py-2 font-medium">Befehl</th>
<th class="px-4 py-2 font-medium">Beschreibung</th>
</tr>
</thead>
<tbody class="divide-y divide-border">
<!-- Neuanlage -->
<tr class="bg-bg/30">
<td colspan="2" class="px-4 py-1.5 text-xs font-semibold uppercase tracking-wide text-muted">Neuanlage</td>
</tr>
<tr class="hover:bg-bg/40">
<td class="px-4 py-2 font-mono text-accent">/note [Titel]</td>
<td class="px-4 py-2 text-white">Neue Notiz (aktueller Scope)</td>
</tr>
<tr class="hover:bg-bg/40">
<td class="px-4 py-2 font-mono text-accent">/pnote [Titel]</td>
<td class="px-4 py-2 text-white">Neue Notiz (Privat)</td>
</tr>
<tr class="hover:bg-bg/40">
<td class="px-4 py-2 font-mono text-accent">/bnote [Titel]</td>
<td class="px-4 py-2 text-white">Neue Notiz (Firma)</td>
</tr>
<tr class="hover:bg-bg/40">
<td class="px-4 py-2 font-mono text-accent">/todo [Titel]</td>
<td class="px-4 py-2 text-white">Neues Todo (aktueller Scope)</td>
</tr>
<tr class="hover:bg-bg/40">
<td class="px-4 py-2 font-mono text-accent">/ptodo [Titel]</td>
<td class="px-4 py-2 text-white">Neues Todo (Privat)</td>
</tr>
<tr class="hover:bg-bg/40">
<td class="px-4 py-2 font-mono text-accent">/btodo [Titel]</td>
<td class="px-4 py-2 text-white">Neues Todo (Firma)</td>
</tr>
<tr class="hover:bg-bg/40">
<td class="px-4 py-2 font-mono text-accent">/page [Titel]</td>
<td class="px-4 py-2 text-white">Neue Wiki-Seite (aktueller Scope)</td>
</tr>
<tr class="hover:bg-bg/40">
<td class="px-4 py-2 font-mono text-accent">/ppage [Titel]</td>
<td class="px-4 py-2 text-white">Neue Wiki-Seite (Privat)</td>
</tr>
<tr class="hover:bg-bg/40">
<td class="px-4 py-2 font-mono text-accent">/bpage [Titel]</td>
<td class="px-4 py-2 text-white">Neue Wiki-Seite (Firma)</td>
</tr>
<tr class="hover:bg-bg/40">
<td class="px-4 py-2 font-mono text-accent">/per [Name]</td>
<td class="px-4 py-2 text-white">Neue Person anlegen</td>
</tr>
<tr class="hover:bg-bg/40">
<td class="px-4 py-2 font-mono text-accent">/firma [Name]</td>
<td class="px-4 py-2 text-white">Neue Firma anlegen</td>
</tr>
<!-- Navigation -->
<tr class="bg-bg/30">
<td colspan="2" class="px-4 py-1.5 text-xs font-semibold uppercase tracking-wide text-muted">Navigation</td>
</tr>
<tr class="hover:bg-bg/40">
<td class="px-4 py-2 font-mono text-accent">/jf [Suche]</td>
<td class="px-4 py-2 text-white">Zu einem Jour-Fix-Meeting springen</td>
</tr>
<tr class="hover:bg-bg/40">
<td class="px-4 py-2 font-mono text-accent">/home</td>
<td class="px-4 py-2 text-white">Zurück zur Journal-Startseite</td>
</tr>
<tr class="hover:bg-bg/40">
<td class="px-4 py-2 font-mono text-accent">/help</td>
<td class="px-4 py-2 text-white">Diese Hilfeseite öffnen</td>
</tr>
<!-- UI -->
<tr class="bg-bg/30">
<td colspan="2" class="px-4 py-1.5 text-xs font-semibold uppercase tracking-wide text-muted">UI</td>
</tr>
<tr class="hover:bg-bg/40">
<td class="px-4 py-2 font-mono text-accent">/sidebar</td>
<td class="px-4 py-2 text-white">Sidebar ein-/ausblenden</td>
</tr>
<!-- Suche -->
<tr class="bg-bg/30">
<td colspan="2" class="px-4 py-1.5 text-xs font-semibold uppercase tracking-wide text-muted">Suche (ohne /)</td>
</tr>
<tr class="hover:bg-bg/40">
<td class="px-4 py-2 font-mono text-accent">[Suchbegriff]</td>
<td class="px-4 py-2 text-white">Kontexte und Wiki-Seiten durchsuchen</td>
</tr>
<tr class="hover:bg-bg/40">
<td class="px-4 py-2 font-mono text-accent">(leer)</td>
<td class="px-4 py-2 text-white">Zuletzt besuchte Kontexte anzeigen</td>
</tr>
</tbody>
</table>
</div>
</section>
<!-- Keyboard Shortcuts -->
<section class="mb-8">
<h2 class="mb-3 text-lg font-semibold text-white">Tastenkürzel</h2>
<div class="rounded border border-border bg-card-bg overflow-hidden">
<table class="w-full text-sm">
<thead>
<tr class="border-b border-border text-left text-xs text-muted">
<th class="px-4 py-2 font-medium">Kürzel</th>
<th class="px-4 py-2 font-medium">Funktion</th>
</tr>
</thead>
<tbody class="divide-y divide-border">
<tr class="hover:bg-bg/40">
<td class="px-4 py-2"><kbd class="rounded bg-bg px-1.5 py-0.5 font-mono text-xs text-white">Ctrl+K</kbd></td>
<td class="px-4 py-2 text-white">Command Bar öffnen/schließen</td>
</tr>
<tr class="hover:bg-bg/40">
<td class="px-4 py-2"><kbd class="rounded bg-bg px-1.5 py-0.5 font-mono text-xs text-white">Ctrl+B</kbd></td>
<td class="px-4 py-2 text-white">Sidebar ein-/ausblenden</td>
</tr>
<tr class="hover:bg-bg/40">
<td class="px-4 py-2"><kbd class="rounded bg-bg px-1.5 py-0.5 font-mono text-xs text-white">↑ ↓</kbd></td>
<td class="px-4 py-2 text-white">In Command Bar navigieren</td>
</tr>
<tr class="hover:bg-bg/40">
<td class="px-4 py-2"><kbd class="rounded bg-bg px-1.5 py-0.5 font-mono text-xs text-white">Enter</kbd></td>
<td class="px-4 py-2 text-white">Ausgewählten Befehl ausführen</td>
</tr>
<tr class="hover:bg-bg/40">
<td class="px-4 py-2"><kbd class="rounded bg-bg px-1.5 py-0.5 font-mono text-xs text-white">Esc</kbd></td>
<td class="px-4 py-2 text-white">Command Bar schließen</td>
</tr>
</tbody>
</table>
</div>
</section>
<!-- Inline Tags -->
<section class="mb-8">
<h2 class="mb-3 text-lg font-semibold text-white">Inline-Tags im Text</h2>
<div class="rounded border border-border bg-card-bg overflow-hidden">
<table class="w-full text-sm">
<thead>
<tr class="border-b border-border text-left text-xs text-muted">
<th class="px-4 py-2 font-medium">Syntax</th>
<th class="px-4 py-2 font-medium">Bedeutung</th>
</tr>
</thead>
<tbody class="divide-y divide-border">
<tr class="hover:bg-bg/40">
<td class="px-4 py-2 font-mono text-accent">-&gt; NAME</td>
<td class="px-4 py-2 text-white">Zuweisung an Person</td>
</tr>
<tr class="hover:bg-bg/40">
<td class="px-4 py-2 font-mono text-accent">@P:PROJEKT</td>
<td class="px-4 py-2 text-white">Projektreferenz</td>
</tr>
<tr class="hover:bg-bg/40">
<td class="px-4 py-2 font-mono text-accent">@NAME</td>
<td class="px-4 py-2 text-white">Erwähnung einer Person</td>
</tr>
</tbody>
</table>
</div>
</section>
</div>

Binary file not shown.

Binary file not shown.