Release 0.5.8

This commit is contained in:
hnzio committed 2026-10-23 07:47:27 +02:00
1 parent 22ae3a68d6
commit 2851c7d936
33 files changed
+1263 -272

No files matched your search

+15 -5
View File
@@ -3,12 +3,12 @@ import type { Actions, PageServerLoad } from './$types';
import { requireUser } from '#lib/server/auth.js';
import {
createPiece,
deletePieceTemplate,
duplicatePiece,
getTaxonomy,
listPieces,
pieceTemplates,
setPieceArchived,
type PieceTemplateId
listPieceTemplates,
setPieceArchived
} from '#lib/server/content.js';
import { getPieceUsageContexts } from '#lib/server/shows.js';
@@ -39,7 +39,7 @@ export const load: PageServerLoad = ({ locals, url }) => {
return {
pieces: pieces.map((piece) => ({ ...piece, ...usageContexts[piece.id] })),
taxonomy: getTaxonomy(),
templates: pieceTemplates,
templates: listPieceTemplates(),
filters
};
};
@@ -57,12 +57,22 @@ export const actions = {
const data = await request.formData();
let id: string;
try {
id = createPiece(String(data.get('template') || 'blank') as PieceTemplateId);
id = createPiece(String(data.get('template') || 'blank'));
} catch (cause) {
return fail(400, { intent: 'create', error: actionError(cause) });
}
redirect(303, `/fundus/${id}`);
},
deleteTemplate: async ({ locals, request }) => {
requireUser(locals.user);
const data = await request.formData();
try {
deletePieceTemplate(String(data.get('templateId') || ''), Number(data.get('revision')));
return { intent: 'deleteTemplate', success: 'Vorlage gelöscht.' };
} catch (cause) {
return fail(409, { intent: 'deleteTemplate', error: actionError(cause) });
}
},
duplicate: async ({ locals, request }) => {
requireUser(locals.user);
const data = await request.formData();
+29 -9
View File
@@ -65,15 +65,35 @@
</div>
<details class="template-picker">
<summary><span aria-hidden="true">+</span> Neues Piece</summary>
<div class="template-grid">
{#each data.templates as template (template.id)}
<form method="POST" action="?/create">
<input type="hidden" name="template" value={template.id} />
<button type="submit"
><strong>{template.name}</strong><span>{template.description}</span></button
>
</form>
{/each}
<div class="template-popover">
<div class="template-grid">
{#each data.templates as template (template.id)}
<form method="POST" action="?/create">
<input type="hidden" name="template" value={template.id} />
<button type="submit"
><strong>{template.name}</strong><span>{template.description}</span></button
>
</form>
{/each}
</div>
{#if data.templates.some((template) => template.id !== 'blank')}<details
class="template-management"
>
<summary>Vorlagen verwalten</summary>
{#each data.templates.filter((template) => template.id !== 'blank') as template (template.id)}
<form
method="POST"
action="?/deleteTemplate"
onsubmit={(event) =>
!confirm(`Vorlage „${template.name}“ löschen?`) && event.preventDefault()}
>
<input type="hidden" name="templateId" value={template.id} />
<input type="hidden" name="revision" value={template.revision} />
<span>{template.name}</span>
<button class="text-button danger-text" type="submit">Löschen</button>
</form>
{/each}
</details>{/if}
</div>
</details>
</div>
+28 -1
View File
@@ -6,8 +6,10 @@ import {
duplicatePiece,
getPiece,
getTaxonomy,
listPieceTemplates,
loadPiecePresentationMetadata,
savePiece,
savePieceAsTemplate,
setPieceArchived,
uploadPieceImage,
type EditablePieceStatus,
@@ -57,6 +59,7 @@ function pieceInput(data: FormData): PieceInput {
}
return {
title: text(data, 'title'),
outputTitle: text(data, 'outputTitle'),
summary: text(data, 'summary'),
formatId: text(data, 'formatId') || null,
status: text(data, 'status') as EditablePieceStatus,
@@ -120,7 +123,12 @@ export const load: PageServerLoad = ({ locals, params }) => {
const piece = getPiece(params.id);
if (!piece) error(404, 'Piece nicht gefunden');
if (piece.status === 'archived') redirect(303, '/archiv');
return { piece, taxonomy: getTaxonomy(), rendered: renderedFields(piece) };
return {
piece,
taxonomy: getTaxonomy(),
templates: listPieceTemplates().filter((template) => template.id !== 'blank'),
rendered: renderedFields(piece)
};
};
export const actions = {
@@ -161,6 +169,25 @@ export const actions = {
}
redirect(303, `/fundus/${params.id}`);
},
saveTemplate: async ({ locals, params, request }) => {
requireUser(locals.user);
const data = await request.formData();
const reference = text(data, 'templateRef');
const [templateId, revision] = reference.split('|');
try {
savePieceAsTemplate(
params.id,
text(data, 'templateName'),
text(data, 'templateDescription'),
templateId || undefined,
revision ? Number(revision) : undefined
);
return { intent: 'saveTemplate', success: 'Vorlage gespeichert.' };
} catch (cause) {
const problem = saveError(cause);
return fail(problem.status, { intent: 'saveTemplate', ...problem });
}
},
upload: async ({ locals, params, request }) => {
requireUser(locals.user);
const data = await request.formData();
+28 -2
View File
@@ -44,7 +44,7 @@
);
let previewContent = $derived<PresentationContent>({
mode: draft.presentationMode,
title: draft.title,
title: draft.outputTitle || draft.title,
html: rendered.presentation,
source: featuredSource
? {
@@ -413,6 +413,14 @@
</div>
<div class="piece-presentation-layout">
<div class="piece-presentation-controls">
<label
>Präsentationstitel<input
name="outputTitle"
maxlength="300"
placeholder="Optional, sonst interner Titel"
bind:value={draft.outputTitle}
/></label
>
<MarkdownEditorField
name="presentationText"
label="Öffentlicher Text"
@@ -443,7 +451,7 @@
></label
>
<label
>Titel<input
>Kartentitel<input
name="presentationTitle"
maxlength="300"
placeholder="Optionaler Anzeigetitel"
@@ -565,6 +573,24 @@
{#snippet footer()}
<div class="piece-dialog-secondary-actions">
<details class="template-save-menu">
<summary>Als Vorlage speichern</summary>
<form method="POST" action="?/saveTemplate">
<label
>Vorlage<select name="templateRef">
<option value="">Neue Vorlage</option>
{#each data.templates as template (template.id)}<option
value={`${template.id}|${template.revision}`}>{template.name}</option
>{/each}
</select></label
>
<label>Name<input name="templateName" required maxlength="120" /></label>
<label>Beschreibung<input name="templateDescription" maxlength="500" /></label>
<button class="text-button" type="submit" disabled={actionsBlocked}
>Vorlage speichern</button
>
</form>
</details>
<form method="POST" action="?/duplicate">
<button class="text-button" type="submit" disabled={actionsBlocked}>Duplizieren</button>
</form>
@@ -18,7 +18,7 @@ export const load: PageServerLoad = ({ locals, params }) => {
images: { id: string; altText: string }[];
} = {
mode: piece.presentationMode,
title: piece.title,
title: piece.outputTitle || piece.title,
html: renderMarkdown(piece.presentationText),
source,
previewTitle: piece.presentationTitle,