diff --git a/docs/ACCEPTANCE.md b/docs/ACCEPTANCE.md
index 893dc47..c870f45 100644
--- a/docs/ACCEPTANCE.md
+++ b/docs/ACCEPTANCE.md
@@ -189,3 +189,29 @@
Pixeln visuell kontrolliert
- `npm run build`: erfolgreich mit `@sveltejs/adapter-node`
- `bash -n start.sh` und `git diff --check`: erfolgreich
+
+## Redaktionelles Piece-Arbeitsblatt
+
+| Kriterium | Nachweis | Stand |
+| ---------------------------------------- | ------------------------------------------------------------------------------------------------- | --------------------- |
+| Schreiben steht im Mittelpunkt | Desktop- und Mobilprüfung zeigt Fundstück, Recherche und drei Sendungstexte im primären Lesefluss | erfüllt am 22.10.2026 |
+| Kontext bleibt vollständig erreichbar | Quellen, Einordnung, Präsentation und Bilder auf Desktop sowie als vier mobile Werkzeuge geprüft | erfüllt am 22.10.2026 |
+| Textfelder unabhängig bearbeitbar | Recherche und Moderationsstichpunkte im Browser gleichzeitig geöffnet und gespeichert | erfüllt am 22.10.2026 |
+| Bestehender Formvertrag bleibt erhalten | Autosave, Reload, Taxonomie, Quelle, Markdown, Upload, Archiv und Duplikat im Browser durchlaufen | erfüllt am 22.10.2026 |
+| Konflikte und Speicherfehler sichtbar | parallele Revision sowie Offline-Autosave mit erfolgreicher manueller Wiederholung geprüft | erfüllt am 22.10.2026 |
+| Schneller Video-Einstieg ohne Einordnung | Link im Eingang aufgenommen, direkt geöffnet, beschrieben und isoliert als Video getestet | erfüllt am 22.10.2026 |
+| Responsive Darstellung und beide Themes | 1440 × 1000 und 390 × 844 in Light/Dark ohne horizontalen Überlauf visuell kontrolliert | erfüllt am 22.10.2026 |
+
+## Ausgeführte Prüfungen Piece-Arbeitsblatt
+
+- `npm run lint`: erfolgreich
+- `npm run check`: 0 Fehler und 0 Warnungen
+- `npm test`: 29 Tests in 6 Dateien erfolgreich
+- `npm run build`: erfolgreich mit `@sveltejs/adapter-node`
+- gezielter Playwright-Lauf für Autosave, Offline-Fehler, Konflikt und Upload:
+ 1 von 1 Browsertest erfolgreich
+- `npm run test:e2e`: 15 von 15 Browsertests erfolgreich
+- Desktop Light/Dark sowie Smartphone Light/Dark und mobiles
+ Präsentationswerkzeug visuell kontrolliert; langer Titel bricht vollständig um,
+ Werkzeugfläche ist opak und die Speicherleiste überdeckt mobil keine Eingaben
+- `bash -n start.sh` und `git diff --check`: erfolgreich
diff --git a/docs/ARCHITECTURE.md b/docs/ARCHITECTURE.md
index 637f23c..e240b0b 100644
--- a/docs/ARCHITECTURE.md
+++ b/docs/ARCHITECTURE.md
@@ -239,6 +239,18 @@ Der Live-Modus ist eine eigene, fokussierte Arbeitsansicht. Die
Präsentationsausgabe verwendet eine separate, strikt reduzierte Datenansicht
ohne interne Anwendungsnavigation.
+Der eigenständige Piece-Editor ordnet denselben Formvertrag als redaktionelles
+Arbeitsblatt an. Titel, Fundstück, private Recherche sowie Einleitung,
+Moderationsstichpunkte und Überleitung bilden die primäre Schreibfläche.
+Quellen, Einordnung, Präsentation und eine Bildzusammenfassung liegen auf breiten
+Ansichten in einer kontextuellen Seitenleiste; auf kleinen Ansichten öffnen
+dieselben DOM-Formfelder als fokussierbare Werkzeugflächen und werden dabei nicht
+neu erzeugt oder ausgehängt. `EditorialMarkdownField` hält Lese- und
+Bearbeitungszustand je Textfeld lokal, während alle Werte weiterhin gemeinsam
+über die bestehende `save`-Action und Piece-Revision gespeichert werden. Die
+separate Bild-Action und alle Präsentationsactions bleiben eigene serverseitige
+Mutationen.
+
## Sicherheitsgrenzen
Authentifizierung und Autorisierung werden seit Etappe 2 serverseitig an jeder
diff --git a/docs/STATUS.md b/docs/STATUS.md
index 33bd165..62d3681 100644
--- a/docs/STATUS.md
+++ b/docs/STATUS.md
@@ -71,6 +71,16 @@ als bereits implementiert verstanden werden.
Verwendungshistorie; gesendet bedeutet erledigt in abgeschlossener Sendung
- standardmäßig ausgeblendete Pieces, die in mindestens einer noch nicht
abgeschlossenen Sendung eingeplant sind, mit unabhängigem Sofortfilter
+- der eigenständige Piece-Editor ist als redaktionelles Arbeitsblatt aufgebaut:
+ Titel, Fundstück, private Recherche und die getrennt gespeicherten
+ Sendungstexte stehen im Schreibfluss; Quellen, Einordnung, Präsentation und
+ Bilder begleiten ihn als Desktop-Kontextleiste oder mobile Werkzeuge
+- Markdown-Felder wechseln einzeln zwischen Lesen und Bearbeiten, sodass mehrere
+ Textbereiche gleichzeitig offen bleiben; Autosave, Konfliktschutz und
+ sichtbare Speicherfehler verwenden unverändert die Piece-Revision
+- die Schnellaufnahme „Aufnehmen und öffnen“ leitet nach erfolgreicher Anlage
+ direkt zum neuen Piece weiter, statt den SvelteKit-Redirect als
+ Validierungsfehler abzufangen
## Umgesetzter Stand Etappe 4
diff --git a/package-lock.json b/package-lock.json
index 8b5a798..749232c 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,12 +1,12 @@
{
"name": "sendewerk",
- "version": "0.5.1",
+ "version": "0.5.2",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "sendewerk",
- "version": "0.5.1",
+ "version": "0.5.2",
"dependencies": {
"@node-rs/argon2": "^2.2.2",
"better-sqlite3": "^13.0.3",
diff --git a/package.json b/package.json
index 48eb5d7..61bcac6 100644
--- a/package.json
+++ b/package.json
@@ -1,7 +1,7 @@
{
"name": "sendewerk",
"private": true,
- "version": "0.5.1",
+ "version": "0.5.2",
"type": "module",
"engines": {
"node": ">=22.17 <25"
diff --git a/src/app.css b/src/app.css
index b4693e6..5a238f4 100644
--- a/src/app.css
+++ b/src/app.css
@@ -2544,6 +2544,467 @@ a {
margin-top: 0.75rem;
}
+.worksheet-page {
+ max-width: 94rem;
+}
+
+.worksheet-editor {
+ gap: 1rem;
+}
+
+.worksheet-header {
+ display: flex;
+ align-items: flex-end;
+ justify-content: space-between;
+ gap: 1.5rem;
+}
+
+.worksheet-heading {
+ min-width: 0;
+ flex: 1;
+}
+
+.worksheet-title {
+ display: block;
+ margin-top: 0.3rem;
+}
+
+.worksheet-title textarea {
+ width: 100%;
+ height: 1.2em;
+ padding: 0;
+ resize: none;
+ overflow: hidden;
+ border: 0;
+ outline-offset: 0.3rem;
+ background: transparent;
+ color: var(--text);
+ font: inherit;
+ font-size: clamp(1.65rem, 3.3vw, 2.8rem);
+ font-weight: 760;
+ letter-spacing: -0.045em;
+}
+
+.editor-workspace {
+ display: grid;
+ grid-template-columns: minmax(0, 1fr) minmax(19rem, 23rem);
+ align-items: start;
+ gap: 1rem;
+}
+
+.editorial-worksheet {
+ display: grid;
+ min-width: 0;
+ gap: 0.8rem;
+}
+
+.source-feature {
+ display: grid;
+ min-height: 8.5rem;
+ align-items: stretch;
+ overflow: hidden;
+ border: 1px solid var(--line);
+ border-radius: var(--radius-large);
+ background: var(--panel);
+}
+
+.source-feature.has-image {
+ grid-template-columns: minmax(9rem, 0.32fr) minmax(0, 1fr);
+}
+
+.source-feature img {
+ width: 100%;
+ height: 100%;
+ min-height: 8.5rem;
+ object-fit: cover;
+}
+
+.source-feature > div {
+ display: flex;
+ min-width: 0;
+ flex-direction: column;
+ align-items: flex-start;
+ justify-content: center;
+ padding: 1rem 1.15rem;
+}
+
+.source-feature h2,
+.writing-sheet h2,
+.context-card h2 {
+ margin: 0.15rem 0 0;
+ font-size: 1rem;
+}
+
+.source-feature p:not(.eyebrow) {
+ margin: 0.3rem 0;
+ color: var(--text-muted);
+ font-size: 0.78rem;
+ line-height: 1.5;
+}
+
+.source-feature small {
+ max-width: 100%;
+ margin-top: 0.2rem;
+ overflow-wrap: anywhere;
+ color: var(--text-faint);
+ font-size: 0.66rem;
+}
+
+.source-feature a,
+.source-feature .text-button {
+ margin-top: 0.65rem;
+ color: var(--accent);
+ font-size: 0.72rem;
+ font-weight: 750;
+}
+
+.writing-sheet {
+ padding: clamp(1rem, 2vw, 1.6rem);
+ border: 1px solid var(--line);
+ border-radius: var(--radius-large);
+ background: var(--panel);
+}
+
+.writing-sheet-heading {
+ display: flex;
+ align-items: end;
+ justify-content: space-between;
+ gap: 1rem;
+ padding-bottom: 1rem;
+ border-bottom: 1px solid var(--line);
+}
+
+.writing-sheet-heading small,
+.script-heading > p:last-child {
+ color: var(--text-faint);
+ font-size: 0.67rem;
+}
+
+.editorial-field {
+ padding: 1rem 0;
+ border-bottom: 1px solid var(--line);
+}
+
+.editorial-field.private-field {
+ margin: 0.85rem 0 0;
+ padding: 1rem;
+ border: 1px solid color-mix(in srgb, var(--gold) 38%, var(--line));
+ background: color-mix(in srgb, var(--gold) 5%, var(--panel));
+}
+
+.editorial-field > header {
+ display: flex;
+ align-items: flex-start;
+ justify-content: space-between;
+ gap: 0.75rem;
+}
+
+.editorial-field h3 {
+ margin: 0;
+ font-size: 0.9rem;
+}
+
+.editorial-field header p {
+ margin: 0.18rem 0 0;
+ color: var(--gold-strong);
+ font-size: 0.63rem;
+ font-weight: 750;
+ letter-spacing: 0.04em;
+ text-transform: uppercase;
+}
+
+.editorial-edit-button {
+ flex: 0 0 auto;
+ color: var(--accent);
+}
+
+.editorial-input {
+ display: block;
+ margin-top: 0.65rem;
+}
+
+.editorial-input textarea {
+ width: 100%;
+ min-height: 9rem;
+ padding: 0.8rem;
+ resize: vertical;
+ border: 1px solid var(--line-strong);
+ border-radius: var(--radius-small);
+ background: var(--panel-strong);
+ color: var(--text);
+ font: inherit;
+ font-size: 0.82rem;
+ line-height: 1.6;
+}
+
+.editorial-field > .markdown-preview {
+ min-height: 0;
+ margin-top: 0.55rem;
+ padding: 0;
+ border: 0;
+ color: var(--text);
+ font-size: 0.82rem;
+}
+
+.editorial-empty {
+ display: flex;
+ width: 100%;
+ align-items: center;
+ justify-content: space-between;
+ gap: 1rem;
+ margin-top: 0.5rem;
+ padding: 0.6rem 0;
+ border: 0;
+ border-radius: 0;
+ background: transparent;
+ color: var(--text-muted);
+ text-align: left;
+}
+
+.editorial-empty:hover {
+ color: var(--text);
+}
+
+.editorial-empty small {
+ flex: 0 0 auto;
+ color: var(--accent);
+ font-size: 0.64rem;
+ font-weight: 750;
+}
+
+.script-heading {
+ padding: 1.4rem 0 0.3rem;
+}
+
+.script-heading > p:last-child {
+ margin: 0.25rem 0 0;
+}
+
+.script-sections .editorial-field:last-child {
+ padding-bottom: 0;
+ border-bottom: 0;
+}
+
+.editor-context {
+ position: sticky;
+ top: 1rem;
+ display: grid;
+ max-height: calc(100vh - 2rem);
+ gap: 0.7rem;
+ overflow-y: auto;
+ overscroll-behavior: contain;
+}
+
+.context-card {
+ padding: 0.85rem;
+ border: 1px solid var(--line);
+ border-radius: var(--radius-medium);
+ background: color-mix(in srgb, var(--panel) 96%, transparent);
+}
+
+.context-card-heading {
+ display: flex;
+ align-items: flex-start;
+ justify-content: space-between;
+ gap: 0.75rem;
+ margin-bottom: 0.7rem;
+}
+
+.context-close,
+.mobile-editor-tools {
+ display: none;
+}
+
+.context-empty,
+.context-summary-copy,
+.asset-summary p,
+.audience-preview p {
+ margin: 0.4rem 0;
+ color: var(--text-muted);
+ font-size: 0.72rem;
+ line-height: 1.5;
+}
+
+.context-wide-action {
+ width: 100%;
+ margin-top: 0.65rem;
+}
+
+.context-card .source-list {
+ gap: 0.7rem;
+}
+
+.context-card .source-row {
+ display: grid;
+ grid-template-columns: minmax(0, 1fr) minmax(7rem, 0.65fr);
+ gap: 0.4rem;
+ padding: 0 0 0.7rem;
+}
+
+.context-card .source-row label:nth-child(2) {
+ grid-column: 1 / -1;
+ grid-row: 2;
+}
+
+.context-card .source-row label:nth-child(3) {
+ grid-column: 2;
+ grid-row: 1;
+}
+
+.context-card .source-row > button {
+ grid-column: 1 / -1;
+}
+
+.context-card .source-row label,
+.context-fields > label,
+.presentation-settings > label {
+ display: grid;
+ gap: 0.22rem;
+ color: var(--text-muted);
+ font-size: 0.66rem;
+ font-weight: 700;
+}
+
+.context-card input:not([type='checkbox'], [type='radio']),
+.context-card select,
+.context-card textarea {
+ width: 100%;
+ min-width: 0;
+ padding: 0.5rem 0.58rem;
+ border: 1px solid var(--line-strong);
+ border-radius: var(--radius-small);
+ background: var(--panel-strong);
+ color: var(--text);
+}
+
+.classification-summary,
+.context-tags {
+ display: flex;
+ flex-wrap: wrap;
+ gap: 0.35rem;
+}
+
+.classification-summary > *,
+.context-tags span {
+ padding: 0.3rem 0.48rem;
+ border-radius: 2rem;
+ background: var(--panel-soft);
+ color: var(--text-muted);
+ font-size: 0.64rem;
+}
+
+.classification-summary strong {
+ background: var(--accent-soft);
+ color: var(--accent);
+}
+
+.context-edit-toggle {
+ margin-top: 0.65rem;
+ color: var(--accent);
+}
+
+.context-fields,
+.presentation-controls {
+ display: grid;
+ gap: 0.7rem;
+ margin-top: 0.75rem;
+ padding-top: 0.75rem;
+ border-top: 1px solid var(--line);
+}
+
+.context-fields[hidden],
+.presentation-controls[hidden] {
+ display: none;
+}
+
+.context-fields fieldset {
+ margin: 0;
+ padding: 0;
+ border: 0;
+}
+
+.context-fields legend {
+ margin-bottom: 0.35rem;
+ color: var(--text-muted);
+ font-size: 0.66rem;
+ font-weight: 700;
+}
+
+.context-fields .choice-grid label {
+ padding: 0.3rem 0.45rem;
+ font-size: 0.64rem;
+}
+
+.audience-preview {
+ padding: 0.7rem;
+ border-radius: var(--radius-small);
+ background: var(--panel-soft);
+}
+
+.audience-preview > span {
+ color: var(--accent);
+ font-size: 0.6rem;
+ font-weight: 800;
+ letter-spacing: 0.06em;
+ text-transform: uppercase;
+}
+
+.audience-preview > strong {
+ display: block;
+ margin-top: 0.3rem;
+ font-size: 0.82rem;
+}
+
+.audience-preview .markdown-preview {
+ min-height: 0;
+ margin-top: 0.4rem;
+ padding: 0;
+ border: 0;
+ font-size: 0.7rem;
+}
+
+.presentation-card-actions {
+ display: grid;
+ gap: 0.45rem;
+ margin-top: 0.65rem;
+}
+
+.presentation-card-actions .text-button {
+ justify-self: start;
+ color: var(--accent);
+}
+
+.presentation-card-actions .secondary-button {
+ width: 100%;
+ text-align: center;
+}
+
+.presentation-controls .editorial-field {
+ padding: 0.2rem 0 0;
+ border: 0;
+}
+
+.presentation-controls .presentation-settings {
+ grid-template-columns: 1fr;
+ margin-top: 0;
+}
+
+.presentation-controls .presentation-preview-actions {
+ align-items: stretch;
+ flex-direction: column;
+}
+
+.asset-summary {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ gap: 0.75rem;
+}
+
+.supporting-editor-section {
+ max-width: calc(100% - 24rem);
+}
+
.shows-page {
max-width: 78rem;
}
@@ -3272,6 +3733,12 @@ a {
.source-row {
grid-template-columns: repeat(2, minmax(0, 1fr));
}
+ .editor-workspace {
+ grid-template-columns: minmax(0, 1fr) minmax(17rem, 20rem);
+ }
+ .supporting-editor-section {
+ max-width: calc(100% - 21rem);
+ }
.show-create-form {
grid-template-columns: repeat(2, minmax(0, 1fr));
}
@@ -3413,12 +3880,129 @@ a {
align-items: flex-start;
flex-direction: column;
}
+ .worksheet-header {
+ align-items: flex-start;
+ flex-direction: column;
+ gap: 0.6rem;
+ }
+ .worksheet-title textarea {
+ height: 2.3em;
+ font-size: 1.65rem;
+ line-height: 1.12;
+ }
+ .mobile-editor-tools {
+ position: sticky;
+ top: calc(var(--mobile-header) + 0.45rem);
+ z-index: 12;
+ display: grid;
+ grid-template-columns: repeat(4, minmax(0, 1fr));
+ gap: 0.3rem;
+ padding: 0.35rem;
+ border: 1px solid var(--line);
+ border-radius: var(--radius-medium);
+ background: color-mix(in srgb, var(--bg-elevated) 92%, transparent);
+ box-shadow: var(--shadow-soft);
+ backdrop-filter: blur(18px);
+ }
+ .mobile-editor-tools button,
+ .mobile-editor-tools a {
+ min-width: 0;
+ padding: 0.55rem 0.3rem;
+ border: 0;
+ border-radius: var(--radius-small);
+ background: transparent;
+ color: var(--text-muted);
+ font-size: 0.65rem;
+ font-weight: 750;
+ text-align: center;
+ }
+ .mobile-editor-tools button[aria-expanded='true'] {
+ background: var(--accent-soft);
+ color: var(--accent);
+ }
+ .editor-workspace {
+ display: block;
+ }
+ .source-feature.has-image {
+ grid-template-columns: 6.5rem minmax(0, 1fr);
+ }
+ .source-feature,
+ .source-feature img {
+ min-height: 7.5rem;
+ }
+ .source-feature > div {
+ padding: 0.85rem;
+ }
+ .writing-sheet {
+ margin-top: 0.7rem;
+ padding: 0.9rem;
+ border-radius: var(--radius-medium);
+ }
+ .writing-sheet-heading {
+ align-items: flex-start;
+ flex-direction: column;
+ gap: 0.35rem;
+ }
+ .editorial-field.private-field {
+ padding: 0.75rem;
+ }
+ .editorial-empty {
+ align-items: flex-start;
+ flex-direction: column;
+ gap: 0.2rem;
+ }
+ .editor-context {
+ position: static;
+ max-height: none;
+ overflow: visible;
+ }
+ .editor-context.context-open::before {
+ position: fixed;
+ inset: 0;
+ z-index: 38;
+ background: color-mix(in srgb, var(--text) 28%, transparent);
+ content: '';
+ }
+ .editor-context .context-card {
+ display: none;
+ }
+ .editor-context .context-card.mobile-active {
+ position: fixed;
+ inset: calc(var(--mobile-header) + 0.5rem) 0.5rem calc(var(--mobile-nav) + 0.5rem);
+ z-index: 40;
+ display: block;
+ overflow-y: auto;
+ padding: 1rem;
+ border-radius: var(--radius-large);
+ background: var(--panel-strong);
+ box-shadow: var(--shadow);
+ overscroll-behavior: contain;
+ }
+ .context-close {
+ display: inline-grid;
+ width: 2rem;
+ height: 2rem;
+ place-items: center;
+ padding: 0;
+ border: 1px solid var(--line);
+ border-radius: 50%;
+ background: var(--panel-soft);
+ color: var(--text);
+ font-size: 1.2rem;
+ }
+ .supporting-editor-section {
+ max-width: none;
+ scroll-margin-top: calc(var(--mobile-header) + 4.5rem);
+ }
+ .worksheet-editor:has(.editor-context.context-open) .editor-savebar {
+ visibility: hidden;
+ }
.editor-section {
padding: 0.85rem;
border-radius: var(--radius-medium);
}
.editor-savebar {
- bottom: calc(var(--mobile-nav) + 0.6rem);
+ position: static;
justify-content: space-between;
}
.split-section-title,
diff --git a/src/lib/components/EditorialMarkdownField.svelte b/src/lib/components/EditorialMarkdownField.svelte
new file mode 100644
index 0000000..df600c2
--- /dev/null
+++ b/src/lib/components/EditorialMarkdownField.svelte
@@ -0,0 +1,60 @@
+
+
+
+
+
+
{label}
+ {#if hint}
{hint}
{/if}
+
+
+
+
+ {#if editing}
+
+ {:else}
+
+ {#if value.trim()}
+
+ {:else}
+
+ {/if}
+ {/if}
+
diff --git a/src/routes/eingang/+page.server.ts b/src/routes/eingang/+page.server.ts
index 9ef83e8..da8b440 100644
--- a/src/routes/eingang/+page.server.ts
+++ b/src/routes/eingang/+page.server.ts
@@ -12,9 +12,9 @@ export const actions = {
capture: async ({ locals, request }) => {
requireUser(locals.user);
const data = await request.formData();
+ let id: string;
try {
- const id = captureInbox(String(data.get('title') || ''), String(data.get('url') || ''));
- redirect(303, `/fundus/${id}`);
+ id = captureInbox(String(data.get('title') || ''), String(data.get('url') || ''));
} catch (cause) {
const code = cause instanceof Error ? cause.message : '';
return fail(400, {
@@ -26,5 +26,6 @@ export const actions = {
: 'Ein Titel ist erforderlich.'
});
}
+ redirect(303, `/fundus/${id}`);
}
} satisfies Actions;
diff --git a/src/routes/fundus/[id]/+page.svelte b/src/routes/fundus/[id]/+page.svelte
index 7cca468..597bb59 100644
--- a/src/routes/fundus/[id]/+page.svelte
+++ b/src/routes/fundus/[id]/+page.svelte
@@ -2,10 +2,13 @@
import { enhance, type SubmitFunction } from '$app/forms';
import { beforeNavigate, goto } from '$app/navigation';
import { resolve } from '$app/paths';
- import { onDestroy, untrack } from 'svelte';
+ import { onDestroy, tick, untrack } from 'svelte';
+ import EditorialMarkdownField from '#lib/components/EditorialMarkdownField.svelte';
import MarkdownPreview from '#lib/components/MarkdownPreview.svelte';
import type { PageProps } from './$types';
+ type ContextPanel = 'sources' | 'classification' | 'presentation';
+
let { data, form }: PageProps = $props();
let draft = $state(untrack(() => ({ ...data.piece })));
let durationMinutes = $state(untrack(() => Number((data.piece.durationSeconds / 60).toFixed(2))));
@@ -14,6 +17,10 @@
let rendered = $state(untrack(() => ({ ...data.rendered })));
let saveState = $state<'idle' | 'dirty' | 'saving' | 'saved' | 'error' | 'conflict'>('idle');
let saveMessage = $state('Alle Änderungen gespeichert');
+ let classificationEditing = $state(false);
+ let presentationEditing = $state(false);
+ let mobileContext = $state(null);
+ let sourceImageUnavailable = $state(false);
let editorForm: HTMLFormElement;
let previewForm: HTMLFormElement;
let saveButton: HTMLButtonElement;
@@ -26,8 +33,27 @@
saveState === 'error' ||
saveState === 'conflict'
);
+ let featuredSource = $derived(
+ draft.presentationSourcePosition === null
+ ? (sources[0] ?? null)
+ : (sources[draft.presentationSourcePosition] ?? sources[0] ?? null)
+ );
+ let sourcePreviewImage = $derived(
+ draft.presentationImageUrl
+ ? resolve('/fundus/[id]/praesentation/preview-image', { id: data.piece.id })
+ : data.piece.media[0]
+ ? `/media/${data.piece.media[0].id}`
+ : ''
+ );
let pendingHref: string | null = null;
let allowNavigation = false;
+ let contextTrigger: HTMLElement | null = null;
+
+ const statusLabels = {
+ inbox: 'Eingang',
+ preparing: 'In Vorbereitung',
+ ready: 'Sendefertig'
+ };
onDestroy(() => clearTimeout(timer));
beforeNavigate((navigation) => {
@@ -87,11 +113,29 @@
draft.presentationMetadataFetchedAtUtc = null;
draft.presentationProvider = '';
draft.presentationProviderId = '';
+ sourceImageUnavailable = false;
if (!draft.presentationTitleManual) draft.presentationTitle = '';
if (!draft.presentationDescriptionManual) draft.presentationDescription = '';
if (!draft.presentationImageManual) draft.presentationImageUrl = '';
}
+ async function openContext(event: MouseEvent, panel: ContextPanel) {
+ contextTrigger = event.currentTarget instanceof HTMLElement ? event.currentTarget : null;
+ mobileContext = panel;
+ await tick();
+ document.querySelector(`[data-context-close="${panel}"]`)?.focus();
+ }
+
+ function closeContext() {
+ mobileContext = null;
+ contextTrigger?.focus();
+ contextTrigger = null;
+ }
+
+ function handleEscape(event: KeyboardEvent) {
+ if (event.key === 'Escape' && mobileContext) closeContext();
+ }
+
const enhanceSave: SubmitFunction = () => {
const submittedGeneration = generation;
saving = true;
@@ -132,22 +176,9 @@
{draft.title} | Fundus | Sendewerk
+
-
-
+
{#if form?.error}
{form.error}
{/if}