feat: pages is_favorite, commandbar improvements, wiki ui

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
beo3000 2026-02-25 08:26:42 +01:00
parent d1286cd717
commit 8bffebfd36
10 changed files with 1213 additions and 30 deletions

View File

@ -50,12 +50,29 @@ Beim Tippen werden **Contexts** (Jour Fixes, Projekte, Firmen, Personen) und **W
## Slash-Commands
Scope-dynamische Commands verwenden den aktuell aktiven Scope (Privat/Firma). Scope-explizite Varianten mit Präfix `p` (Privat) oder `b` (Business/Firma) überschreiben den aktiven Scope.
| Command | Funktion |
|---------|----------|
| `/note [Text]` | Neues Topic im Daily Log erstellen |
| `/todo [Text]` | Topic mit Wiedervorlage (heute) im Daily Log |
| `/note [Text]` | Neues Topic im Daily Log (aktiver Scope) |
| `/pnote [Text]` | Neues Topic im Daily Log (Privat) |
| `/bnote [Text]` | Neues Topic im Daily Log (Firma) |
| `/todo [Text]` | Topic mit Wiedervorlage heute (aktiver Scope) |
| `/ptodo [Text]` | Topic mit Wiedervorlage heute (Privat) |
| `/btodo [Text]` | Topic mit Wiedervorlage heute (Firma) |
| `/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) |
| `/jf [Name]` | Zu passendem Jour-Fix-Kontext springen |
Scope-spezifische Commands erscheinen in der Bar erst ab dem zweiten Zeichen (`/p...` / `/b...`), um Duplikate mit den scope-dynamischen Commands zu vermeiden.
Beispiel: `/note Anruf bei Steffen wegen Angebot` → sofort als Topic in `daily-log` gespeichert, ohne den aktuellen Screen zu verlassen.
---

View File

@ -1 +1 @@
1.1.68
1.1.71

View File

@ -100,7 +100,8 @@
const actions = [];
if ("/note".startsWith(cmd)) {
// Show scope-dynamic /note only when not narrowed to /pnote or /bnote
if ("/note".startsWith(cmd) && !"/pnote".startsWith(cmd) && !"/bnote".startsWith(cmd)) {
actions.push({
id: "cmd-note",
type: "action",
@ -117,7 +118,7 @@
});
}
if ("/pnote".startsWith(cmd)) {
if ("/pnote".startsWith(cmd) && cmd.length > 1) {
actions.push({
id: "cmd-pnote",
type: "action",
@ -133,7 +134,7 @@
});
}
if ("/bnote".startsWith(cmd)) {
if ("/bnote".startsWith(cmd) && cmd.length > 1) {
actions.push({
id: "cmd-bnote",
type: "action",
@ -149,7 +150,8 @@
});
}
if ("/todo".startsWith(cmd)) {
// Show scope-dynamic /todo only when not narrowed to /ptodo or /btodo
if ("/todo".startsWith(cmd) && !"/ptodo".startsWith(cmd) && !"/btodo".startsWith(cmd)) {
actions.push({
id: "cmd-todo",
type: "action",
@ -166,7 +168,7 @@
});
}
if ("/ptodo".startsWith(cmd)) {
if ("/ptodo".startsWith(cmd) && cmd.length > 1) {
actions.push({
id: "cmd-ptodo",
type: "action",
@ -182,7 +184,7 @@
});
}
if ("/btodo".startsWith(cmd)) {
if ("/btodo".startsWith(cmd) && cmd.length > 1) {
actions.push({
id: "cmd-btodo",
type: "action",
@ -198,7 +200,7 @@
});
}
if ("/page".startsWith(cmd)) {
if ("/page".startsWith(cmd) && !"/ppage".startsWith(cmd) && !"/bpage".startsWith(cmd)) {
actions.push({
id: "cmd-page",
type: "action",
@ -217,7 +219,7 @@
});
}
if ("/ppage".startsWith(cmd)) {
if ("/ppage".startsWith(cmd) && cmd.length > 1) {
actions.push({
id: "cmd-ppage",
type: "action",
@ -235,7 +237,7 @@
});
}
if ("/bpage".startsWith(cmd)) {
if ("/bpage".startsWith(cmd) && cmd.length > 1) {
actions.push({
id: "cmd-bpage",
type: "action",

View File

@ -96,9 +96,9 @@
<section class="rounded-lg border border-[#333] bg-[#1a1a1a]">
<!-- Tab bar -->
<div class="flex border-b border-[#333]">
<div class="flex overflow-x-auto border-b border-[#333] scrollbar-none">
<button
class="flex items-center gap-1.5 px-4 py-2.5 text-sm font-semibold transition-colors border-b-2 -mb-px {activeTab === 'notebooks' ? 'border-accent text-accent' : 'border-transparent text-muted hover:text-white'}"
class="flex shrink-0 items-center gap-1.5 px-4 py-2.5 text-sm font-semibold transition-colors border-b-2 -mb-px {activeTab === 'notebooks' ? 'border-accent text-accent' : 'border-transparent text-muted hover:text-white'}"
onclick={() => setTab('notebooks')}
>
<span>📓</span>
@ -108,7 +108,7 @@
{/if}
</button>
<button
class="flex items-center gap-1.5 px-4 py-2.5 text-sm font-semibold transition-colors border-b-2 -mb-px {activeTab === 'favorites' ? 'border-warning text-warning' : 'border-transparent text-muted hover:text-white'}"
class="flex shrink-0 items-center gap-1.5 px-4 py-2.5 text-sm font-semibold transition-colors border-b-2 -mb-px {activeTab === 'favorites' ? 'border-warning text-warning' : 'border-transparent text-muted hover:text-white'}"
onclick={() => setTab('favorites')}
>
<span></span>
@ -118,27 +118,28 @@
{/if}
</button>
<button
class="flex items-center gap-1.5 px-4 py-2.5 text-sm font-semibold transition-colors border-b-2 -mb-px {activeTab === 'recent' ? 'border-[#aaa] text-[#ccc]' : 'border-transparent text-muted hover:text-white'}"
class="flex shrink-0 items-center gap-1.5 px-4 py-2.5 text-sm font-semibold transition-colors border-b-2 -mb-px {activeTab === 'recent' ? 'border-[#aaa] text-[#ccc]' : 'border-transparent text-muted hover:text-white'}"
onclick={() => setTab('recent')}
>
<span></span>
<span>Zuletzt bearbeitet</span>
</button>
<!-- Actions (right side) -->
{#if activeTab === 'notebooks'}
<div class="ml-auto flex items-center gap-2 px-3">
<button
class="rounded bg-accent px-3 py-1.5 text-sm font-medium text-white hover:bg-accent/80"
onclick={addPage}
>+ Seite</button>
<button
class="rounded bg-white/10 px-3 py-1.5 text-sm font-medium text-muted hover:bg-white/20 hover:text-white"
onclick={() => creatingNotebook = true}
>+ Notizbuch</button>
</div>
{/if}
</div>
<!-- Actions bar (Notebooks tab only) -->
{#if activeTab === 'notebooks'}
<div class="flex items-center gap-2 border-b border-[#2a2a2a] px-3 py-2">
<button
class="rounded bg-accent px-3 py-1.5 text-sm font-medium text-white hover:bg-accent/80"
onclick={addPage}
>+ Seite</button>
<button
class="rounded bg-white/10 px-3 py-1.5 text-sm font-medium text-muted hover:bg-white/20 hover:text-white"
onclick={() => creatingNotebook = true}
>+ Notizbuch</button>
</div>
{/if}
<!-- Notebooks tab -->
{#if activeTab === 'notebooks'}
<div class="divide-y divide-[#2a2a2a]">

View File

@ -0,0 +1 @@
-- no-op: is_favorite column added in 0012_chunky_stature.sql

View File

@ -0,0 +1 @@
ALTER TABLE `pages` ADD `is_favorite` integer DEFAULT false NOT NULL;

File diff suppressed because it is too large Load Diff

View File

@ -78,6 +78,20 @@
"when": 1771857652883,
"tag": "0010_sharp_bishop",
"breakpoints": true
},
{
"idx": 11,
"version": "6",
"when": 1771900000000,
"tag": "0011_pages_is_favorite",
"breakpoints": true
},
{
"idx": 12,
"version": "6",
"when": 1772004047537,
"tag": "0012_chunky_stature",
"breakpoints": true
}
]
}

View File

@ -85,6 +85,7 @@ export const pages = sqliteTable('pages', {
title: text('title').notNull(),
body: text('body').notNull().default(''),
isPrivate: integer('is_private', { mode: 'boolean' }).notNull().default(false),
isFavorite: integer('is_favorite', { mode: 'boolean' }).notNull().default(false),
sortOrder: integer('sort_order').notNull().default(0),
updatedAt: text('updated_at').notNull(),
deletedAt: text('deleted_at'),

View File

@ -85,6 +85,7 @@ function mapPage(row: typeof pages.$inferSelect): Page {
title: row.title,
body: row.body,
isPrivate: row.isPrivate,
isFavorite: row.isFavorite,
sortOrder: row.sortOrder,
updatedAt: row.updatedAt,
deletedAt: row.deletedAt,
@ -276,7 +277,7 @@ export async function pushChanges(request: SyncPushRequest, userId: string): Pro
}
for (const pg of pgs) {
const row = { id: pg.id, userId, title: pg.title, body: pg.body, isPrivate: pg.isPrivate, sortOrder: pg.sortOrder, updatedAt: pg.updatedAt, deletedAt: pg.deletedAt, purgedAt: pg.purgedAt ?? null, version: pg.version };
const row = { id: pg.id, userId, title: pg.title, body: pg.body, isPrivate: pg.isPrivate, isFavorite: pg.isFavorite ?? false, sortOrder: pg.sortOrder, updatedAt: pg.updatedAt, deletedAt: pg.deletedAt, purgedAt: pg.purgedAt ?? null, version: pg.version };
if (await upsertEntity(pages, row, conflicts, 'page', userId)) accepted++;
}