334 lines
10 KiB
Svelte
334 lines
10 KiB
Svelte
<script lang="ts">
|
|
import { enhance, type SubmitFunction } from '$app/forms';
|
|
import { untrack } from 'svelte';
|
|
import MarkdownEditorField from './MarkdownEditorField.svelte';
|
|
import NavIcon from './NavIcon.svelte';
|
|
import PieceEditorDialog from './PieceEditorDialog.svelte';
|
|
import type { PieceUsage } from '#lib/server/shows.js';
|
|
|
|
let {
|
|
usage,
|
|
revision,
|
|
sectionId,
|
|
sections,
|
|
error,
|
|
enhanceCommand
|
|
}: {
|
|
usage: PieceUsage;
|
|
revision: number;
|
|
sectionId: string;
|
|
sections: { id: string; name: string }[];
|
|
error?: string;
|
|
enhanceCommand: SubmitFunction;
|
|
} = $props();
|
|
|
|
let formId = $derived(`usage-editor-form-${usage.id}`);
|
|
let editor: { showModal: () => void; close: () => void };
|
|
let activeSection = $state<'script' | 'research' | 'presentation'>('script');
|
|
let draft = $state(untrack(() => ({ ...usage })));
|
|
let dirty = $state(false);
|
|
|
|
function durationInMinutes(seconds: number) {
|
|
return Number((seconds / 60).toFixed(2));
|
|
}
|
|
|
|
function requestClose() {
|
|
if (dirty && !confirm('Ungespeicherte Änderungen verwerfen und Editor schließen?')) return;
|
|
dirty = false;
|
|
editor.close();
|
|
}
|
|
|
|
const enhanceEditor: SubmitFunction = async (input) => {
|
|
const finish = await enhanceCommand(input);
|
|
return async (result) => {
|
|
if (finish) await finish(result);
|
|
if (result.result.type === 'success') dirty = false;
|
|
};
|
|
};
|
|
</script>
|
|
|
|
<button
|
|
class="usage-edit-button"
|
|
type="button"
|
|
aria-label={`${usage.title} bearbeiten und verschieben`}
|
|
title="Bearbeiten und verschieben"
|
|
onclick={() => editor.showModal()}
|
|
>
|
|
<NavIcon name="edit" />
|
|
</button>
|
|
|
|
<PieceEditorDialog
|
|
bind:this={editor}
|
|
bind:activeSection
|
|
labelledby={`usage-editor-title-${usage.id}`}
|
|
onrequestclose={requestClose}
|
|
>
|
|
{#snippet header()}
|
|
<p class="eyebrow">Sendungsfassung</p>
|
|
<label class="piece-dialog-title">
|
|
<span class="sr-only">Titel</span>
|
|
<textarea
|
|
form={formId}
|
|
name="title"
|
|
required
|
|
rows="1"
|
|
maxlength="200"
|
|
aria-label="Titel"
|
|
bind:value={draft.title}
|
|
oninput={() => (dirty = true)}></textarea>
|
|
</label>
|
|
<h2 class="sr-only" id={`usage-editor-title-${usage.id}`}>{draft.title}</h2>
|
|
{/snippet}
|
|
|
|
<form
|
|
id={formId}
|
|
use:enhance={enhanceEditor}
|
|
method="POST"
|
|
action="?/updateUsage"
|
|
class="piece-dialog-form"
|
|
oninput={() => (dirty = true)}
|
|
onchange={() => (dirty = true)}
|
|
>
|
|
<input type="hidden" name="revision" value={revision} />
|
|
<input type="hidden" name="usageId" value={usage.id} />
|
|
|
|
<section class="piece-editor-panel" hidden={activeSection !== 'script'}>
|
|
<div class="piece-panel-heading">
|
|
<div>
|
|
<p class="eyebrow">Für die Moderation</p>
|
|
<h3>Sendungstext</h3>
|
|
</div>
|
|
</div>
|
|
<div class="piece-writing-stack">
|
|
<MarkdownEditorField
|
|
name="intro"
|
|
label="Einleitung"
|
|
placeholder="Wie beginnt dieses Thema in der Sendung?"
|
|
bind:value={draft.intro}
|
|
/>
|
|
<MarkdownEditorField
|
|
name="moderation"
|
|
label="Moderationsstichpunkte"
|
|
placeholder="Argumente, Fragen und Ablaufpunkte"
|
|
rows={10}
|
|
bind:value={draft.moderation}
|
|
/>
|
|
<MarkdownEditorField
|
|
name="transition"
|
|
label="Überleitung"
|
|
placeholder="Wie geht es zum nächsten Thema weiter?"
|
|
bind:value={draft.transition}
|
|
/>
|
|
</div>
|
|
<details class="piece-meta-details">
|
|
<summary>Dauer und Einplanung</summary>
|
|
<div class="piece-meta-grid">
|
|
<label
|
|
>Dauer in Minuten<input
|
|
name="durationMinutes"
|
|
type="number"
|
|
min="0"
|
|
max="1440"
|
|
step="any"
|
|
value={durationInMinutes(usage.durationSeconds)}
|
|
/></label
|
|
>
|
|
</div>
|
|
</details>
|
|
</section>
|
|
|
|
<section class="piece-editor-panel" hidden={activeSection !== 'research'}>
|
|
<div class="piece-panel-heading">
|
|
<div>
|
|
<p class="eyebrow">Nicht öffentlich</p>
|
|
<h3>Recherche & Quellen</h3>
|
|
</div>
|
|
<small>Diese Inhalte bleiben innerhalb der Redaktion.</small>
|
|
</div>
|
|
<div class="piece-writing-stack">
|
|
<MarkdownEditorField
|
|
name="research"
|
|
label="Private Recherche"
|
|
placeholder="Hintergrund, Prüfung und offene Fragen"
|
|
rows={10}
|
|
privateField
|
|
bind:value={draft.research}
|
|
/>
|
|
<MarkdownEditorField
|
|
name="ownNotes"
|
|
label="Interne Notizen zu dieser Verwendung"
|
|
placeholder="Absprachen und Hinweise nur für diese Sendung"
|
|
privateField
|
|
bind:value={draft.ownNotes}
|
|
/>
|
|
</div>
|
|
<section class="piece-source-summary">
|
|
<p class="eyebrow">Snapshot-Quelle</p>
|
|
{#if draft.presentationSourceUrl}
|
|
<strong>{draft.presentationSourceLabel || draft.presentationSourceUrl}</strong>
|
|
<small>{draft.presentationSourceUrl}</small>
|
|
{:else}
|
|
<p>Für diese Verwendung wurde keine Präsentationsquelle kopiert.</p>
|
|
{/if}
|
|
</section>
|
|
</section>
|
|
|
|
<section class="piece-editor-panel" hidden={activeSection !== 'presentation'}>
|
|
<div class="piece-panel-heading">
|
|
<div>
|
|
<p class="eyebrow">Öffentliche Ausgabe</p>
|
|
<h3>Präsentation</h3>
|
|
</div>
|
|
<small>Private Recherche und Notizen werden nicht ausgegeben.</small>
|
|
</div>
|
|
<div class="piece-presentation-layout">
|
|
<div class="piece-presentation-controls">
|
|
<MarkdownEditorField
|
|
name="presentation"
|
|
label="Öffentlicher Text"
|
|
placeholder="Text für die Publikumsausgabe"
|
|
bind:value={draft.presentation}
|
|
/>
|
|
<div class="presentation-mode-picker" role="group" aria-label="Darstellung">
|
|
{#each [['text', 'Text'], ['link', 'Linkvorschau'], ['video', 'Video']] as mode (mode[0])}
|
|
<label
|
|
><input
|
|
type="radio"
|
|
name="presentationMode"
|
|
value={mode[0]}
|
|
bind:group={draft.presentationMode}
|
|
/>{mode[1]}</label
|
|
>
|
|
{/each}
|
|
</div>
|
|
<input
|
|
type="hidden"
|
|
name="presentationSourceLabel"
|
|
value={draft.presentationSourceLabel}
|
|
/>
|
|
<input type="hidden" name="presentationSourceUrl" value={draft.presentationSourceUrl} />
|
|
<input
|
|
type="hidden"
|
|
name="presentationSourceTimecodeSeconds"
|
|
value={draft.presentationSourceTimecodeSeconds ?? ''}
|
|
/>
|
|
<input type="hidden" name="presentationSiteName" value={draft.presentationSiteName} />
|
|
{#if draft.presentationMode !== 'text'}
|
|
<div class="presentation-settings">
|
|
<div class="presentation-source-summary wide-field">
|
|
<span>Ausgewählte Quelle</span>
|
|
<strong
|
|
>{draft.presentationSourceLabel ||
|
|
draft.presentationSourceUrl ||
|
|
'Keine Quelle'}</strong
|
|
>
|
|
{#if draft.presentationSourceUrl}<small>{draft.presentationSourceUrl}</small>{/if}
|
|
</div>
|
|
<label
|
|
>Titel<input
|
|
name="presentationTitle"
|
|
maxlength="300"
|
|
bind:value={draft.presentationTitle}
|
|
/></label
|
|
>
|
|
<label
|
|
>Bild-URL<input
|
|
name="presentationImageUrl"
|
|
type="url"
|
|
maxlength="2000"
|
|
bind:value={draft.presentationImageUrl}
|
|
/></label
|
|
>
|
|
<label class="wide-field"
|
|
>Anreißer<textarea
|
|
name="presentationDescription"
|
|
rows="3"
|
|
maxlength="1000"
|
|
bind:value={draft.presentationDescription}></textarea></label
|
|
>
|
|
</div>
|
|
{:else}
|
|
<input type="hidden" name="presentationTitle" value={draft.presentationTitle} />
|
|
<input
|
|
type="hidden"
|
|
name="presentationDescription"
|
|
value={draft.presentationDescription}
|
|
/>
|
|
<input type="hidden" name="presentationImageUrl" value={draft.presentationImageUrl} />
|
|
{/if}
|
|
</div>
|
|
<aside class="piece-presentation-preview" aria-label="Präsentationsvorschau">
|
|
<p class="output-kicker">Vorschau · {draft.presentationMode}</p>
|
|
<h4>{draft.presentationTitle || draft.title}</h4>
|
|
{#if draft.presentationMode === 'text'}
|
|
<p class="preview-markdown-source">
|
|
{draft.presentation || 'Noch kein öffentlicher Text.'}
|
|
</p>
|
|
{:else}
|
|
{#if draft.presentationImageUrl}<img src={draft.presentationImageUrl} alt="" />{/if}
|
|
<p>
|
|
{draft.presentationDescription ||
|
|
draft.presentationSourceUrl ||
|
|
'Noch keine Vorschauangaben.'}
|
|
</p>
|
|
{/if}
|
|
</aside>
|
|
</div>
|
|
</section>
|
|
</form>
|
|
|
|
{#snippet footer()}
|
|
<div class="piece-dialog-secondary-actions">
|
|
<form use:enhance={enhanceCommand} method="POST" action="?/refreshUsage">
|
|
<input type="hidden" name="revision" value={revision} />
|
|
<input type="hidden" name="usageId" value={usage.id} />
|
|
<button class="text-button" type="submit" disabled={!usage.originalPieceId || dirty}
|
|
>Aus Fundus aktualisieren</button
|
|
>
|
|
</form>
|
|
<form use:enhance={enhanceCommand} method="POST" action="?/duplicateUsage">
|
|
<input type="hidden" name="revision" value={revision} /><input
|
|
type="hidden"
|
|
name="usageId"
|
|
value={usage.id}
|
|
/>
|
|
<button class="text-button" type="submit" disabled={dirty}>Duplizieren</button>
|
|
</form>
|
|
<form use:enhance={enhanceCommand} method="POST" action="?/moveUsage">
|
|
<input type="hidden" name="revision" value={revision} /><input
|
|
type="hidden"
|
|
name="usageId"
|
|
value={usage.id}
|
|
/><input type="hidden" name="position" value="9999" />
|
|
<label
|
|
><span class="sr-only">Ziel</span><select name="sectionId"
|
|
><option value="">Reservebank</option>{#each sections as target (target.id)}<option
|
|
value={target.id}
|
|
selected={target.id === sectionId}>{target.name}</option
|
|
>{/each}</select
|
|
></label
|
|
>
|
|
<button class="text-button" type="submit" disabled={dirty}>Verschieben</button>
|
|
</form>
|
|
<form
|
|
use:enhance={enhanceCommand}
|
|
method="POST"
|
|
action="?/removeUsage"
|
|
onsubmit={(event) =>
|
|
!confirm('Verwendung aus der Sendung entfernen?') && event.preventDefault()}
|
|
>
|
|
<input type="hidden" name="revision" value={revision} /><input
|
|
type="hidden"
|
|
name="usageId"
|
|
value={usage.id}
|
|
/>
|
|
<button class="text-button danger-text" type="submit" disabled={dirty}>Entfernen</button>
|
|
</form>
|
|
</div>
|
|
<div class="piece-dialog-primary-action">
|
|
{#if error}<span class="problem" role="alert">{error}</span>{/if}
|
|
<button class="primary-button" type="submit" form={formId}>Verwendung speichern</button>
|
|
</div>
|
|
{/snippet}
|
|
</PieceEditorDialog>
|