refactoring
This commit is contained in:
parent
957c1da44a
commit
9670ae3c9f
|
|
@ -130,3 +130,62 @@ Konfiguration: `Table.configure({ resizable: false })`
|
||||||
|
|
||||||
- `/code` (inline) — nach dem Löschen des Slash-Texts gibt es keine Auswahl; das Code-Mark wird als "stored mark" für den nächsten Tipp-Vorgang aktiviert (Tiptap-Standardverhalten)
|
- `/code` (inline) — nach dem Löschen des Slash-Texts gibt es keine Auswahl; das Code-Mark wird als "stored mark" für den nächsten Tipp-Vorgang aktiviert (Tiptap-Standardverhalten)
|
||||||
- Bubble Menu Link-Button nutzt `window.prompt` — blockiert kurz den Browser
|
- Bubble Menu Link-Button nutzt `window.prompt` — blockiert kurz den Browser
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Read-Mode: RenderedMarkdown-Pipeline
|
||||||
|
|
||||||
|
### Komponenten
|
||||||
|
|
||||||
|
| Komponente | Datei | Zweck |
|
||||||
|
|-----------|-------|-------|
|
||||||
|
| `RenderedMarkdown` | `components/RenderedMarkdown.svelte` | Generisches Render-Primitiv (marked + DOMPurify). Props: `text`, `historyEntryId?`, `topicId?`, `class?` |
|
||||||
|
| `HistoryEntryText` | `components/HistoryEntryText.svelte` | Wrapper für HistoryEntry-Text. Erzwingt `entry: EntryLike` als Pflicht-Prop, leitet `id`/`topicId` an `RenderedMarkdown` weiter |
|
||||||
|
| `refClick` | `actions/refClick.ts` | Svelte Action — Event-Delegation für @mentions, Assignments, Task-Chips, Wiki-Links |
|
||||||
|
|
||||||
|
### Wann `HistoryEntryText` vs. `RenderedMarkdown`
|
||||||
|
|
||||||
|
**`HistoryEntryText`** — immer wenn der Text einem HistoryEntry gehört (Chip-Klick soll Text aktualisieren können):
|
||||||
|
```svelte
|
||||||
|
<HistoryEntryText {entry} />
|
||||||
|
<HistoryEntryText {entry} textOverride={body} class="text-sm" />
|
||||||
|
```
|
||||||
|
|
||||||
|
**`RenderedMarkdown`** — für echte read-only-Kontexte ohne HistoryEntry-Bezug:
|
||||||
|
```svelte
|
||||||
|
<RenderedMarkdown text={page.body} /> <!-- Wiki-Seite -->
|
||||||
|
<RenderedMarkdown text={content} /> <!-- EditableMarkdown-Preview -->
|
||||||
|
```
|
||||||
|
|
||||||
|
`RenderedMarkdown` direkt mit expliziten IDs — wenn kein vollständiges Entry-Objekt vorhanden (projizierte Typen mit abweichenden Feldnamen):
|
||||||
|
```svelte
|
||||||
|
<RenderedMarkdown text={entry.text} historyEntryId={entry.historyEntryId} topicId={entry.topicId} />
|
||||||
|
```
|
||||||
|
|
||||||
|
### `EntryLike`-Interface
|
||||||
|
|
||||||
|
`HistoryEntryText` akzeptiert ein strukturelles Minimum — kompatibel mit vollständigen `HistoryEntry`-Objekten und schmalen Projektionen:
|
||||||
|
```typescript
|
||||||
|
interface EntryLike { id: string; topicId: string; text: string; }
|
||||||
|
```
|
||||||
|
TypeScript erzwingt alle drei Felder. Vergessene IDs → Compile-Fehler (nicht silent failure wie vorher).
|
||||||
|
|
||||||
|
### `refClick`-Action: Param-Übergabe
|
||||||
|
|
||||||
|
Die Action empfängt `historyEntryId`/`topicId` explizit über `use:refClick`. `RenderedMarkdown` setzt dies intern — Aufrufer müssen die Action nie direkt verwenden.
|
||||||
|
|
||||||
|
Bei Task-Chip-Klick dispatcht `refClick` ein `ka-open-task-create`-Event mit `historyEntryId` im Detail. `ContextPage` fängt es und löst daraus `contextId` auf:
|
||||||
|
```
|
||||||
|
historyEntryId → db.historyEntries.get() → entry.topicId → db.topics.get() → topic.contextId
|
||||||
|
```
|
||||||
|
|
||||||
|
### Vollständige Render-Chain (Task-Flow)
|
||||||
|
|
||||||
|
```
|
||||||
|
[] Aufgabe → renderMarkdown.ts (marked extension)
|
||||||
|
→ <span data-task-new data-task-line="base64">[ ]</span>
|
||||||
|
→ RenderedMarkdown (use:refClick={{ historyEntryId, topicId }})
|
||||||
|
→ Klick → handleTaskNewClick(historyEntryId, topicId)
|
||||||
|
→ ka-open-task-create event (bubbles up to ContextPage)
|
||||||
|
→ createTask() + updateHistoryEntry([] → [T:id])
|
||||||
|
```
|
||||||
|
|
|
||||||
|
|
@ -76,29 +76,83 @@ Das `source`-Feld und `externalId`/`externalUrl` bereiten künftige Integratione
|
||||||
|
|
||||||
## Implementierungshinweise (für Wartung)
|
## Implementierungshinweise (für Wartung)
|
||||||
|
|
||||||
|
### Architektur: Wo HistoryEntries auftauchen
|
||||||
|
|
||||||
|
HistoryEntries sind **topic-scoped**, nicht context-scoped. Ein HistoryEntry gehört zu einem Topic (`entry.topicId`), das Topic gehört zu einem Kontext (`topic.contextId`). Daraus folgt:
|
||||||
|
|
||||||
|
```
|
||||||
|
HistoryEntry.text
|
||||||
|
→ gerendert in RenderedMarkdown
|
||||||
|
→ Task-Chip-Klick braucht entry.id + entry.topicId
|
||||||
|
→ ContextPage löst daraus contextId auf
|
||||||
|
```
|
||||||
|
|
||||||
|
**Der nicht-offensichtliche Fall: `EventCard`**
|
||||||
|
|
||||||
|
`EventCard` zeigt einen *Termin* (Kontext vom Typ `meeting`), nicht direkt einen HistoryEntry. Die Meeting-Notizen sind aber intern als HistoryEntry gespeichert — unter dem speziellen Topic `notesTopicId(event.id)`. Dieser Mechanismus ist im EventCard-Code versteckt (`liveQuery` auf `notesTopicId`). Wer `EventCard` nur von außen sieht ("das ist eine Meeting-Karte"), erwartet dort keinen HistoryEntry-Kontext — und vergisst deshalb, `historyEntryId` zu übergeben.
|
||||||
|
|
||||||
|
Merksatz: **Überall wo `entry.text` oder ein abgeleiteter Text (title/body-Split) gerendert wird, muss `entry.id` als `historyEntryId` mitgegeben werden — egal wie die Komponente heißt.**
|
||||||
|
|
||||||
### `historyEntryId` muss überall explizit übergeben werden
|
### `historyEntryId` muss überall explizit übergeben werden
|
||||||
|
|
||||||
`RenderedMarkdown` wird an ~10 Stellen instanziiert. Ohne `historyEntryId`-Prop öffnet ein Task-Chip-Klick das Modal mit `historyEntryId: null` — der Task wird angelegt, aber der Quelltext (`[] …` → `[T:id]`) wird **nicht** aktualisiert.
|
`RenderedMarkdown` wird an ~10 Stellen instanziiert. Ohne `historyEntryId`-Prop öffnet ein Task-Chip-Klick das Modal mit `historyEntryId: null` — der Task wird angelegt, aber der Quelltext (`[] …` → `[T:id]`) wird **nicht** aktualisiert. Der Fehler ist stumm: kein Fehler, kein visuelles Feedback, nur der Chip bleibt `[ ]`.
|
||||||
|
|
||||||
**Alle Stellen müssen `historyEntryId={entry.id} topicId={entry.topicId}` übergeben**, wenn der gerenderte Text einem HistoryEntry gehört:
|
**Vollständige Instanz-Übersicht:**
|
||||||
|
|
||||||
| Datei | Status |
|
| Datei | Quelle | historyEntryId | Notiz |
|
||||||
|-------|--------|
|
|-------|--------|---------------|-------|
|
||||||
| `HistoryItem.svelte` | ✓ |
|
| `HistoryItem.svelte` | `entry` | ✓ | |
|
||||||
| `JournalView.svelte` (daily-log Branch) | ✓ |
|
| `JournalView.svelte:626/640` | `entry` (daily-log) | ✓ | |
|
||||||
| `JournalView.svelte` (Meeting-Scope Branch) | ✓ |
|
| `JournalView.svelte:674` | `entry` (Meeting-Scope) | ✓ | |
|
||||||
| `EventCard.svelte` (Meeting-Notizen) | ✓ |
|
| `EventCard.svelte` | `notesEntry` (versteckt) | ✓ | `notesEntry` = erster Entry aus `notesTopicId(event.id)` |
|
||||||
| `DashboardView.svelte`, `ArchivedView.svelte`, `WiedervorlageCard.svelte` | — kein Task-Kontext nötig |
|
| `DashboardView.svelte:539/541` | `entry` | ✗ | read-only Dashboard, kein Task-Flow vorgesehen |
|
||||||
| `EditableMarkdown.svelte` (Topic-Body Preview) | — kein HistoryEntry, Task-Chips dort sind aktuell nicht aktiv |
|
| `DashboardView.svelte:569` | `entry` | ✗ | read-only Dashboard |
|
||||||
|
| `ArchivedView.svelte` | `entry` | ✗ | archivierte Einträge, kein Task-Flow vorgesehen |
|
||||||
|
| `WiedervorlageCard.svelte` | `entry` (body-Split) | ✗ | Wiedervorlage-Kontext, kein Task-Flow vorgesehen |
|
||||||
|
| `PersonsView.svelte` | `task.text` | — | kein HistoryEntry |
|
||||||
|
| `EditableMarkdown.svelte` | Topic-Body | — | kein HistoryEntry, `topicId` wäre theoretisch möglich |
|
||||||
|
|
||||||
|
Wenn zukünftig Task-Chips auch in Dashboard/Archiv/Wiedervorlage aktiv werden sollen, müssen dort `historyEntryId` und `topicId` ergänzt werden.
|
||||||
|
|
||||||
### Debugging-Anleitung
|
### Debugging-Anleitung
|
||||||
|
|
||||||
Symptom: Chip klickbar, Modal öffnet, aber `historyEntryId: null` → Text wird nie geändert.
|
Symptom: Chip klickbar, Modal öffnet, aber Text bleibt `[] …` (kein `[T:id]`).
|
||||||
|
|
||||||
Diagnose: Browser-Konsole im Klick-Event, dann im DOM:
|
Diagnose im Browser-Devtools-Konsole:
|
||||||
```js
|
```js
|
||||||
document.querySelector('[data-task-new]').closest('.markdown-content').parentElement.className
|
document.querySelector('[data-task-new]').closest('.markdown-content').parentElement.className
|
||||||
```
|
```
|
||||||
→ Wenn die Klasse **nicht** aus `HistoryItem`/`JournalView` stammt, fehlt der `historyEntryId`-Prop in der entsprechenden Komponente.
|
→ Klasse identifizieren → zugehörige Komponente finden → prüfen ob `historyEntryId` Prop gesetzt ist.
|
||||||
|
|
||||||
Häufige Falle: `EventCard` rendert Meeting-Notizen via `RenderedMarkdown` ohne Kontext — Notizen sind intern als HistoryEntry (`notesTopicId(event.id)`) gespeichert, müssen explizit aufgelöst werden.
|
### Empfohlenes Refactoring: `HistoryEntryText`-Komponente
|
||||||
|
|
||||||
|
Die Wurzel des Problems ist, dass `RenderedMarkdown` ein generisches Render-Primitive ist, aber der HistoryEntry-Kontext implizit mitgegeben werden muss. Eine dedizierte Wrapper-Komponente würde das explizit machen:
|
||||||
|
|
||||||
|
```svelte
|
||||||
|
<!-- HistoryEntryText.svelte -->
|
||||||
|
<script lang="ts">
|
||||||
|
import RenderedMarkdown from './RenderedMarkdown.svelte';
|
||||||
|
import type { HistoryEntry } from '@ka-note/shared';
|
||||||
|
interface Props { entry: HistoryEntry; class?: string; textOverride?: string; }
|
||||||
|
let { entry, class: className = '', textOverride }: Props = $props();
|
||||||
|
</script>
|
||||||
|
<RenderedMarkdown
|
||||||
|
text={textOverride ?? entry.text}
|
||||||
|
{class}
|
||||||
|
historyEntryId={entry.id}
|
||||||
|
topicId={entry.topicId}
|
||||||
|
/>
|
||||||
|
```
|
||||||
|
|
||||||
|
Verwendung dann z.B.:
|
||||||
|
```svelte
|
||||||
|
<HistoryEntryText {entry} /> <!-- statt RenderedMarkdown text={entry.text} -->
|
||||||
|
<HistoryEntryText {entry} textOverride={body} class="text-sm" /> <!-- body-Split-Fall -->
|
||||||
|
```
|
||||||
|
|
||||||
|
**Vorteile:**
|
||||||
|
- `historyEntryId` kann nicht mehr vergessen werden — es ist Pflicht-Prop (TypeScript-Fehler sonst)
|
||||||
|
- Neue Views die HistoryEntry-Text rendern, verwenden automatisch die richtige Komponente
|
||||||
|
- `RenderedMarkdown` ohne `historyEntryId` bleibt erlaubt für echte read-only-Kontexte (Dashboard, Archiv)
|
||||||
|
|
||||||
|
**Aufwand:** ~7 Stellen umbauen, kein Logik-Änderung. Kein dringender Handlungsbedarf, aber sinnvoll beim nächsten größeren Refactoring-Sprint.
|
||||||
|
|
|
||||||
|
|
@ -1 +1 @@
|
||||||
1.2.1
|
1.2.2
|
||||||
|
|
@ -3,7 +3,7 @@
|
||||||
import { db } from '$lib/db/schema';
|
import { db } from '$lib/db/schema';
|
||||||
import { updateTopic } from '$lib/db/repositories';
|
import { updateTopic } from '$lib/db/repositories';
|
||||||
import LinkTitle from './LinkTitle.svelte';
|
import LinkTitle from './LinkTitle.svelte';
|
||||||
import RenderedMarkdown from './RenderedMarkdown.svelte';
|
import HistoryEntryText from './HistoryEntryText.svelte';
|
||||||
import type { Topic, HistoryEntry } from '@ka-note/shared';
|
import type { Topic, HistoryEntry } from '@ka-note/shared';
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
|
|
@ -60,7 +60,7 @@
|
||||||
{#each historyCache.get(topic.id) ?? [] as entry (entry.id)}
|
{#each historyCache.get(topic.id) ?? [] as entry (entry.id)}
|
||||||
<div class="mb-3 border-b border-[#333] pb-2 last:border-b-0">
|
<div class="mb-3 border-b border-[#333] pb-2 last:border-b-0">
|
||||||
<div class="mb-1 text-xs font-bold text-accent">[{entry.date}]</div>
|
<div class="mb-1 text-xs font-bold text-accent">[{entry.date}]</div>
|
||||||
<RenderedMarkdown text={entry.text} />
|
<HistoryEntryText {entry} />
|
||||||
</div>
|
</div>
|
||||||
{:else}
|
{:else}
|
||||||
<div class="text-sm text-muted">Keine Einträge.</div>
|
<div class="text-sm text-muted">Keine Einträge.</div>
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,7 @@
|
||||||
import { extractAssignments, extractProjects, extractMentions, extractCompanies } from '$lib/utils/extractors';
|
import { extractAssignments, extractProjects, extractMentions, extractCompanies } from '$lib/utils/extractors';
|
||||||
import { mention } from '$lib/actions/mention';
|
import { mention } from '$lib/actions/mention';
|
||||||
import RenderedMarkdown from './RenderedMarkdown.svelte';
|
import RenderedMarkdown from './RenderedMarkdown.svelte';
|
||||||
|
import HistoryEntryText from './HistoryEntryText.svelte';
|
||||||
import LinkTitle from './LinkTitle.svelte';
|
import LinkTitle from './LinkTitle.svelte';
|
||||||
import MarkdownEditor from './MarkdownEditor.svelte';
|
import MarkdownEditor from './MarkdownEditor.svelte';
|
||||||
import EditableMarkdown from './EditableMarkdown.svelte';
|
import EditableMarkdown from './EditableMarkdown.svelte';
|
||||||
|
|
@ -536,9 +537,9 @@
|
||||||
>×</button>
|
>×</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="font-bold"><RenderedMarkdown text={title} /></div>
|
<div class="font-bold"><HistoryEntryText {entry} textOverride={title} /></div>
|
||||||
{#if body}
|
{#if body}
|
||||||
<RenderedMarkdown text={body} class="mt-1 text-sm text-[#ccc]" />
|
<HistoryEntryText {entry} textOverride={body} class="mt-1 text-sm text-[#ccc]" />
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
@ -566,7 +567,7 @@
|
||||||
>{entry.contextName}</a>:
|
>{entry.contextName}</a>:
|
||||||
<span>{entry.topicTitle}</span>
|
<span>{entry.topicTitle}</span>
|
||||||
</div>
|
</div>
|
||||||
<RenderedMarkdown text={entry.text} />
|
<RenderedMarkdown text={entry.text} historyEntryId={entry.historyEntryId} topicId={entry.topicId} />
|
||||||
</div>
|
</div>
|
||||||
{:else}
|
{:else}
|
||||||
<div class="text-muted">Keine Einträge gefunden.</div>
|
<div class="text-muted">Keine Einträge gefunden.</div>
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@
|
||||||
import { liveQuery } from 'dexie';
|
import { liveQuery } from 'dexie';
|
||||||
import { db } from '$lib/db/schema';
|
import { db } from '$lib/db/schema';
|
||||||
import MarkdownEditor from './MarkdownEditor.svelte';
|
import MarkdownEditor from './MarkdownEditor.svelte';
|
||||||
import RenderedMarkdown from './RenderedMarkdown.svelte';
|
import HistoryEntryText from './HistoryEntryText.svelte';
|
||||||
import { updateEvent, updateEventNotes, softDeleteContext } from '$lib/db/repositories';
|
import { updateEvent, updateEventNotes, softDeleteContext } from '$lib/db/repositories';
|
||||||
import { notesTopicId } from '$lib/db/repositories';
|
import { notesTopicId } from '$lib/db/repositories';
|
||||||
import { mention } from '$lib/actions/mention';
|
import { mention } from '$lib/actions/mention';
|
||||||
|
|
@ -207,8 +207,8 @@
|
||||||
onclick={() => (editingNotes = true)}
|
onclick={() => (editingNotes = true)}
|
||||||
onkeydown={(e) => e.key === 'Enter' && (editingNotes = true)}
|
onkeydown={(e) => e.key === 'Enter' && (editingNotes = true)}
|
||||||
>
|
>
|
||||||
{#if notesText}
|
{#if notesEntry && notesEntry.text}
|
||||||
<RenderedMarkdown text={notesText} class="text-[#ccc]" historyEntryId={notesEntry?.id} topicId={notesEntry?.topicId} />
|
<HistoryEntryText entry={notesEntry} class="text-[#ccc]" />
|
||||||
{:else}
|
{:else}
|
||||||
<span class="italic text-muted">Notizen hinzufügen...</span>
|
<span class="italic text-muted">Notizen hinzufügen...</span>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,17 @@
|
||||||
|
<script lang="ts">
|
||||||
|
import RenderedMarkdown from './RenderedMarkdown.svelte';
|
||||||
|
|
||||||
|
interface EntryLike { id: string; topicId: string; text: string; }
|
||||||
|
interface Props {
|
||||||
|
entry: EntryLike;
|
||||||
|
textOverride?: string;
|
||||||
|
class?: string;
|
||||||
|
}
|
||||||
|
let { entry, textOverride, class: className = '' }: Props = $props();
|
||||||
|
</script>
|
||||||
|
<RenderedMarkdown
|
||||||
|
text={textOverride ?? entry.text}
|
||||||
|
class={className}
|
||||||
|
historyEntryId={entry.id}
|
||||||
|
topicId={entry.topicId}
|
||||||
|
/>
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import type { HistoryEntry } from '@ka-note/shared';
|
import type { HistoryEntry } from '@ka-note/shared';
|
||||||
import MarkdownEditor from './MarkdownEditor.svelte';
|
import MarkdownEditor from './MarkdownEditor.svelte';
|
||||||
import RenderedMarkdown from './RenderedMarkdown.svelte';
|
import HistoryEntryText from './HistoryEntryText.svelte';
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
entry: HistoryEntry;
|
entry: HistoryEntry;
|
||||||
|
|
@ -85,7 +85,7 @@
|
||||||
{:else}
|
{:else}
|
||||||
<!-- svelte-ignore a11y_no_static_element_interactions -->
|
<!-- svelte-ignore a11y_no_static_element_interactions -->
|
||||||
<div ondblclick={startEdit}>
|
<div ondblclick={startEdit}>
|
||||||
<RenderedMarkdown text={entry.text} historyEntryId={entry.id} topicId={entry.topicId} />
|
<HistoryEntryText {entry} />
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,7 @@
|
||||||
import { today } from '$lib/db/helpers';
|
import { today } from '$lib/db/helpers';
|
||||||
import { mention } from '$lib/actions/mention';
|
import { mention } from '$lib/actions/mention';
|
||||||
import MarkdownEditor from './MarkdownEditor.svelte';
|
import MarkdownEditor from './MarkdownEditor.svelte';
|
||||||
import RenderedMarkdown from './RenderedMarkdown.svelte';
|
import HistoryEntryText from './HistoryEntryText.svelte';
|
||||||
import DateNavigator from './DateNavigator.svelte';
|
import DateNavigator from './DateNavigator.svelte';
|
||||||
import ConfirmDialog from './ConfirmDialog.svelte';
|
import ConfirmDialog from './ConfirmDialog.svelte';
|
||||||
import WiedervorlageSection from './WiedervorlageSection.svelte';
|
import WiedervorlageSection from './WiedervorlageSection.svelte';
|
||||||
|
|
@ -623,7 +623,7 @@
|
||||||
<span class="mt-0.5 text-xs text-muted whitespace-nowrap">{formatTime(entry)}</span>
|
<span class="mt-0.5 text-xs text-muted whitespace-nowrap">{formatTime(entry)}</span>
|
||||||
<div class="flex-1">
|
<div class="flex-1">
|
||||||
{#if /^\s*(?:\\?\[\\?\]|\[T:[^\]]+\]|\\?\[X\\?\])/.test(title)}
|
{#if /^\s*(?:\\?\[\\?\]|\[T:[^\]]+\]|\\?\[X\\?\])/.test(title)}
|
||||||
<RenderedMarkdown text={entry.text} class="font-bold" historyEntryId={entry.id} topicId={entry.topicId} />
|
<HistoryEntryText {entry} class="font-bold" />
|
||||||
{:else}
|
{:else}
|
||||||
<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} />
|
||||||
|
|
@ -637,7 +637,7 @@
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
{#if body}
|
{#if body}
|
||||||
<RenderedMarkdown text={body} class="mt-1 text-sm text-[#ccc]" historyEntryId={entry.id} topicId={entry.topicId} />
|
<HistoryEntryText {entry} textOverride={body} class="mt-1 text-sm text-[#ccc]" />
|
||||||
{/if}
|
{/if}
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -671,7 +671,7 @@
|
||||||
<span class="ml-1 rounded bg-accent/20 px-1 py-0.5 normal-case text-accent">Journal</span>
|
<span class="ml-1 rounded bg-accent/20 px-1 py-0.5 normal-case text-accent">Journal</span>
|
||||||
{/if}
|
{/if}
|
||||||
</span>
|
</span>
|
||||||
<RenderedMarkdown text={entry.text} historyEntryId={entry.id} topicId={entry.topicId} />
|
<HistoryEntryText {entry} />
|
||||||
</div>
|
</div>
|
||||||
{/each}
|
{/each}
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@
|
||||||
import { extractAssignments, extractMentions } from '$lib/utils/extractors';
|
import { extractAssignments, extractMentions } from '$lib/utils/extractors';
|
||||||
import { today } from '$lib/db/helpers';
|
import { today } from '$lib/db/helpers';
|
||||||
import { JOURNAL_TOPIC_ID } from '$lib/db/repositories';
|
import { JOURNAL_TOPIC_ID } from '$lib/db/repositories';
|
||||||
import RenderedMarkdown from './RenderedMarkdown.svelte';
|
import HistoryEntryText from './HistoryEntryText.svelte';
|
||||||
import DateNavigator from './DateNavigator.svelte';
|
import DateNavigator from './DateNavigator.svelte';
|
||||||
import type { EventMeta } from '@ka-note/shared';
|
import type { EventMeta } from '@ka-note/shared';
|
||||||
|
|
||||||
|
|
@ -34,14 +34,14 @@
|
||||||
.toArray()
|
.toArray()
|
||||||
: [];
|
: [];
|
||||||
|
|
||||||
const byPerson = new Map<string, { title: string; date: string; text: string }[]>();
|
const byPerson = new Map<string, { id: string; topicId: string; title: string; date: string; text: string }[]>();
|
||||||
|
|
||||||
for (const h of history) {
|
for (const h of history) {
|
||||||
const topic = allTopics.find(t => t.id === h.topicId);
|
const topic = allTopics.find(t => t.id === h.topicId);
|
||||||
const names = new Set([...extractAssignments(h.text), ...extractMentions(h.text)]);
|
const names = new Set([...extractAssignments(h.text), ...extractMentions(h.text)]);
|
||||||
for (const name of names) {
|
for (const name of names) {
|
||||||
const list = byPerson.get(name) ?? [];
|
const list = byPerson.get(name) ?? [];
|
||||||
list.push({ title: topic?.title ?? '', date: h.date, text: h.text });
|
list.push({ id: h.id, topicId: h.topicId, title: topic?.title ?? '', date: h.date, text: h.text });
|
||||||
byPerson.set(name, list);
|
byPerson.set(name, list);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -56,7 +56,7 @@
|
||||||
if (!meta) continue;
|
if (!meta) continue;
|
||||||
for (const name of meta.participants ?? []) {
|
for (const name of meta.participants ?? []) {
|
||||||
const list = byPerson.get(name) ?? [];
|
const list = byPerson.get(name) ?? [];
|
||||||
list.push({ title: ev.name, date: meta.date, text: '' });
|
list.push({ id: '', topicId: '', title: ev.name, date: meta.date, text: '' });
|
||||||
byPerson.set(name, list);
|
byPerson.set(name, list);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -80,7 +80,7 @@
|
||||||
{#each tasks as task}
|
{#each tasks as task}
|
||||||
<div class="mb-2.5 border-l-2 border-[#444] pl-2.5">
|
<div class="mb-2.5 border-l-2 border-[#444] pl-2.5">
|
||||||
<div class="mb-0.5 text-xs text-[#aaa]">{task.title}</div>
|
<div class="mb-0.5 text-xs text-[#aaa]">{task.title}</div>
|
||||||
<RenderedMarkdown text={task.text} />
|
<HistoryEntryText entry={task} />
|
||||||
</div>
|
</div>
|
||||||
{/each}
|
{/each}
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@
|
||||||
import { db } from '$lib/db/schema';
|
import { db } from '$lib/db/schema';
|
||||||
import { resolveWiedervorlage, setWiedervorlage, convertToTopic, softDeleteHistoryEntry } from '$lib/db/repositories';
|
import { resolveWiedervorlage, setWiedervorlage, convertToTopic, softDeleteHistoryEntry } from '$lib/db/repositories';
|
||||||
import type { HistoryEntry } from '@ka-note/shared';
|
import type { HistoryEntry } from '@ka-note/shared';
|
||||||
import RenderedMarkdown from './RenderedMarkdown.svelte';
|
import HistoryEntryText from './HistoryEntryText.svelte';
|
||||||
import LinkTitle from './LinkTitle.svelte';
|
import LinkTitle from './LinkTitle.svelte';
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
|
|
@ -56,7 +56,7 @@
|
||||||
<div class="mb-3">
|
<div class="mb-3">
|
||||||
<div class="font-bold text-white"><LinkTitle text={title} /></div>
|
<div class="font-bold text-white"><LinkTitle text={title} /></div>
|
||||||
{#if body}
|
{#if body}
|
||||||
<RenderedMarkdown text={body} class="mt-1 text-sm text-[#ccc]" />
|
<HistoryEntryText {entry} textOverride={body} class="mt-1 text-sm text-[#ccc]" />
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@
|
||||||
import { page } from '$app/state';
|
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 HistoryEntryText from '$lib/components/HistoryEntryText.svelte';
|
||||||
|
|
||||||
const JOURNAL_TOPIC_ID = 'daily-log-journal';
|
const JOURNAL_TOPIC_ID = 'daily-log-journal';
|
||||||
|
|
||||||
|
|
@ -27,7 +27,7 @@
|
||||||
// ── Data ─────────────────────────────────────────────────────────────────
|
// ── Data ─────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
// All journal entries (non-deleted, correct scope)
|
// All journal entries (non-deleted, correct scope)
|
||||||
let allEntries = $state<{ date: string; text: string; id: string }[]>([]);
|
let allEntries = $state<{ id: string; topicId: string; date: string; text: string }[]>([]);
|
||||||
$effect(() => {
|
$effect(() => {
|
||||||
const isPrivate = scope === 'private';
|
const isPrivate = scope === 'private';
|
||||||
db.historyEntries
|
db.historyEntries
|
||||||
|
|
@ -36,7 +36,7 @@
|
||||||
.filter(e => !e.deletedAt && (e.isPrivate ?? false) === isPrivate)
|
.filter(e => !e.deletedAt && (e.isPrivate ?? false) === isPrivate)
|
||||||
.toArray()
|
.toArray()
|
||||||
.then(rows => {
|
.then(rows => {
|
||||||
allEntries = rows.map(e => ({ id: e.id, date: e.date, text: e.text }));
|
allEntries = rows.map(e => ({ id: e.id, topicId: e.topicId, date: e.date, text: e.text }));
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -85,16 +85,16 @@
|
||||||
});
|
});
|
||||||
|
|
||||||
// ── Day view: group by year ───────────────────────────────────────────────
|
// ── Day view: group by year ───────────────────────────────────────────────
|
||||||
type YearGroup = { year: string; date: string; label: string; entries: { id: string; text: string }[] };
|
type YearGroup = { year: string; date: string; label: string; entries: { id: string; topicId: string; text: string }[] };
|
||||||
const suffix = $derived(month.length === 7 && day.length === 2 ? `-${month.substring(5)}-${day}` : '');
|
const suffix = $derived(month.length === 7 && day.length === 2 ? `-${month.substring(5)}-${day}` : '');
|
||||||
const dayViewGroups = $derived.by<YearGroup[]>(() => {
|
const dayViewGroups = $derived.by<YearGroup[]>(() => {
|
||||||
if (view !== 'day' || !suffix) return [];
|
if (view !== 'day' || !suffix) return [];
|
||||||
const filtered = allEntries.filter(e => e.date.endsWith(suffix));
|
const filtered = allEntries.filter(e => e.date.endsWith(suffix));
|
||||||
const map = new Map<string, { id: string; text: string }[]>();
|
const map = new Map<string, { id: string; topicId: string; text: string }[]>();
|
||||||
for (const e of filtered) {
|
for (const e of filtered) {
|
||||||
const yr = e.date.substring(0, 4);
|
const yr = e.date.substring(0, 4);
|
||||||
if (!map.has(yr)) map.set(yr, []);
|
if (!map.has(yr)) map.set(yr, []);
|
||||||
map.get(yr)!.push({ id: e.id, text: e.text });
|
map.get(yr)!.push({ id: e.id, topicId: e.topicId, text: e.text });
|
||||||
}
|
}
|
||||||
return [...map.entries()]
|
return [...map.entries()]
|
||||||
.sort((a, b) => b[0].localeCompare(a[0]))
|
.sort((a, b) => b[0].localeCompare(a[0]))
|
||||||
|
|
@ -276,7 +276,7 @@
|
||||||
<div class="rounded-lg border border-border bg-surface px-4 py-3 flex flex-col gap-3">
|
<div class="rounded-lg border border-border bg-surface px-4 py-3 flex flex-col gap-3">
|
||||||
{#each yg.entries as entry}
|
{#each yg.entries as entry}
|
||||||
<div class="prose prose-invert prose-sm max-w-none">
|
<div class="prose prose-invert prose-sm max-w-none">
|
||||||
<RenderedMarkdown text={entry.text} />
|
<HistoryEntryText {entry} />
|
||||||
</div>
|
</div>
|
||||||
{#if yg.entries.length > 1}
|
{#if yg.entries.length > 1}
|
||||||
<hr class="border-border" />
|
<hr class="border-border" />
|
||||||
|
|
|
||||||
Binary file not shown.
Binary file not shown.
Loading…
Reference in New Issue