Release 0.5.9
This commit is contained in:
1 parent
2851c7d936
commit
c3033479b5
32 files changed
+1598
-50
No files matched your search
@@ -8,7 +8,8 @@ import {
|
||||
getTaxonomy,
|
||||
listPieces,
|
||||
listPieceTemplates,
|
||||
setPieceArchived
|
||||
setPieceArchived,
|
||||
setPieceFavorite
|
||||
} from '#lib/server/content.js';
|
||||
import { getPieceUsageContexts } from '#lib/server/shows.js';
|
||||
|
||||
@@ -93,5 +94,15 @@ export const actions = {
|
||||
} catch (cause) {
|
||||
return fail(409, { intent: 'archive', error: actionError(cause) });
|
||||
}
|
||||
},
|
||||
favorite: async ({ locals, request }) => {
|
||||
requireUser(locals.user);
|
||||
const data = await request.formData();
|
||||
try {
|
||||
setPieceFavorite(String(data.get('pieceId') || ''), data.get('favorite') === '1');
|
||||
return { intent: 'favorite', success: 'Favorit aktualisiert.' };
|
||||
} catch (cause) {
|
||||
return fail(404, { intent: 'favorite', error: actionError(cause) });
|
||||
}
|
||||
}
|
||||
} satisfies Actions;
|
||||
@@ -1,4 +1,5 @@
|
||||
<script lang="ts">
|
||||
import { enhance } from '$app/forms';
|
||||
import { goto, replaceState } from '$app/navigation';
|
||||
import { resolve } from '$app/paths';
|
||||
import { page } from '$app/state';
|
||||
@@ -13,7 +14,7 @@
|
||||
let visiblePieces = $derived(
|
||||
data.pieces.filter(
|
||||
(piece) =>
|
||||
(showUsedPieces || piece.sentHistory.length === 0) &&
|
||||
(showUsedPieces || piece.sentHistory.length === 0 || piece.isFavorite) &&
|
||||
(showPlannedPieces || !piece.isPlannedInActiveShow)
|
||||
)
|
||||
);
|
||||
@@ -193,6 +194,19 @@
|
||||
class="piece-card"
|
||||
style={`--category: ${piece.categories[0]?.color ?? '#8b8f98'}`}
|
||||
>
|
||||
<form method="POST" action="?/favorite" class="piece-favorite-form" use:enhance>
|
||||
<input type="hidden" name="pieceId" value={piece.id} />
|
||||
<input type="hidden" name="favorite" value={piece.isFavorite ? '0' : '1'} />
|
||||
<button
|
||||
class="piece-favorite"
|
||||
class:active={piece.isFavorite}
|
||||
type="submit"
|
||||
aria-label={piece.isFavorite
|
||||
? `${piece.title} aus Favoriten entfernen`
|
||||
: `${piece.title} als Favorit markieren`}
|
||||
aria-pressed={piece.isFavorite}>★</button
|
||||
>
|
||||
</form>
|
||||
{#if piece.mediaId}<img
|
||||
class="piece-thumbnail"
|
||||
src={`/media/${piece.mediaId}`}
|
||||
|
||||
@@ -11,12 +11,14 @@ import {
|
||||
savePiece,
|
||||
savePieceAsTemplate,
|
||||
setPieceArchived,
|
||||
setPieceFavorite,
|
||||
uploadPieceImage,
|
||||
type EditablePieceStatus,
|
||||
type PieceInput,
|
||||
type PieceSourceInput
|
||||
} from '#lib/server/content.js';
|
||||
import { renderMarkdown } from '#lib/server/markdown.js';
|
||||
import { listTools } from '#lib/server/tools.js';
|
||||
|
||||
function renderedFields(
|
||||
piece: Pick<
|
||||
@@ -57,6 +59,9 @@ function pieceInput(data: FormData): PieceInput {
|
||||
timecodeSeconds: timecode ? Number(timecode) : null
|
||||
});
|
||||
}
|
||||
const toolIds = data.getAll('toolId').map(String);
|
||||
const toolWorkUrls = data.getAll('toolWorkUrl').map(String);
|
||||
const toolNotes = data.getAll('toolNote').map(String);
|
||||
return {
|
||||
title: text(data, 'title'),
|
||||
outputTitle: text(data, 'outputTitle'),
|
||||
@@ -85,7 +90,12 @@ function pieceInput(data: FormData): PieceInput {
|
||||
presentationMetadataFetchedAtUtc: text(data, 'presentationMetadataFetchedAtUtc') || null,
|
||||
topicIds: data.getAll('topicIds').map(String),
|
||||
categoryIds: data.getAll('categoryIds').map(String),
|
||||
sources
|
||||
sources,
|
||||
tools: toolIds.map((toolId, index) => ({
|
||||
toolId,
|
||||
workUrl: toolWorkUrls[index] ?? '',
|
||||
note: toolNotes[index] ?? ''
|
||||
}))
|
||||
};
|
||||
}
|
||||
|
||||
@@ -126,6 +136,7 @@ export const load: PageServerLoad = ({ locals, params }) => {
|
||||
return {
|
||||
piece,
|
||||
taxonomy: getTaxonomy(),
|
||||
tools: listTools(true),
|
||||
templates: listPieceTemplates().filter((template) => template.id !== 'blank'),
|
||||
rendered: renderedFields(piece)
|
||||
};
|
||||
@@ -243,5 +254,15 @@ export const actions = {
|
||||
return fail(409, { intent: 'archive', error: saveError(cause).error });
|
||||
}
|
||||
redirect(303, '/archiv');
|
||||
},
|
||||
favorite: async ({ locals, params, request }) => {
|
||||
requireUser(locals.user);
|
||||
const data = await request.formData();
|
||||
try {
|
||||
setPieceFavorite(params.id, data.get('favorite') === '1');
|
||||
return { intent: 'favorite', success: true };
|
||||
} catch (cause) {
|
||||
return fail(404, { intent: 'favorite', error: saveError(cause).error });
|
||||
}
|
||||
}
|
||||
} satisfies Actions;
|
||||
@@ -14,6 +14,8 @@
|
||||
let durationMinutes = $state(untrack(() => Number((data.piece.durationSeconds / 60).toFixed(2))));
|
||||
let revision = $state(untrack(() => data.piece.revision));
|
||||
let sources = $state(untrack(() => data.piece.sources.map((source) => ({ ...source }))));
|
||||
let pieceTools = $state(untrack(() => data.piece.tools.map((tool) => ({ ...tool }))));
|
||||
let toolToAdd = $state('');
|
||||
let rendered = $state(untrack(() => ({ ...data.rendered })));
|
||||
let activeSection = $state<'script' | 'research' | 'presentation'>('script');
|
||||
let saveState = $state<'idle' | 'dirty' | 'saving' | 'saved' | 'error' | 'conflict'>('idle');
|
||||
@@ -44,6 +46,9 @@
|
||||
);
|
||||
let previewContent = $derived<PresentationContent>({
|
||||
mode: draft.presentationMode,
|
||||
topics: data.taxonomy.topics
|
||||
.filter((topic) => draft.topicIds.includes(topic.id))
|
||||
.map((topic) => topic.name),
|
||||
title: draft.outputTitle || draft.title,
|
||||
html: rendered.presentation,
|
||||
source: featuredSource
|
||||
@@ -147,6 +152,27 @@
|
||||
scheduleSave();
|
||||
}
|
||||
|
||||
function addTool() {
|
||||
const tool = data.tools.find((item) => item.id === toolToAdd);
|
||||
if (!tool || pieceTools.some((item) => item.toolId === tool.id)) return;
|
||||
pieceTools.push({
|
||||
id: crypto.randomUUID(),
|
||||
toolId: tool.id,
|
||||
name: tool.name,
|
||||
startUrl: tool.startUrl,
|
||||
isActive: tool.isActive,
|
||||
workUrl: '',
|
||||
note: ''
|
||||
});
|
||||
toolToAdd = '';
|
||||
scheduleSave();
|
||||
}
|
||||
|
||||
function removeTool(index: number) {
|
||||
pieceTools.splice(index, 1);
|
||||
scheduleSave();
|
||||
}
|
||||
|
||||
function presentationSourceChanged() {
|
||||
draft.presentationMetadataFetchedAtUtc = null;
|
||||
draft.presentationProvider = '';
|
||||
@@ -209,7 +235,19 @@
|
||||
onrequestclose={requestClose}
|
||||
>
|
||||
{#snippet header()}
|
||||
<p class="eyebrow">Fundusfassung</p>
|
||||
<div class="piece-editor-kicker">
|
||||
<p class="eyebrow">Fundusfassung</p>
|
||||
<form method="POST" action="?/favorite" use:enhance>
|
||||
<input type="hidden" name="favorite" value={data.piece.isFavorite ? '0' : '1'} />
|
||||
<button
|
||||
class="piece-favorite"
|
||||
class:active={data.piece.isFavorite}
|
||||
type="submit"
|
||||
aria-label={data.piece.isFavorite ? 'Aus Favoriten entfernen' : 'Als Favorit markieren'}
|
||||
aria-pressed={data.piece.isFavorite}>★</button
|
||||
>
|
||||
</form>
|
||||
</div>
|
||||
<label class="piece-dialog-title">
|
||||
<span class="sr-only">Titel</span>
|
||||
<textarea
|
||||
@@ -396,6 +434,62 @@
|
||||
</div>{/each}
|
||||
</div>{:else}<p class="context-empty">Noch keine Quelle hinterlegt.</p>{/if}
|
||||
</section>
|
||||
<section class="piece-source-editor">
|
||||
<div class="piece-subsection-heading">
|
||||
<div>
|
||||
<p class="eyebrow">Arbeitsmittel</p>
|
||||
<h3>Werkzeuge</h3>
|
||||
</div>
|
||||
<div class="tool-picker">
|
||||
<label class="sr-only" for="piece-tool-picker">Werkzeug auswählen</label>
|
||||
<select id="piece-tool-picker" bind:value={toolToAdd}>
|
||||
<option value="">Werkzeug auswählen</option>
|
||||
{#each data.tools.filter((tool) => tool.isActive && !pieceTools.some((assigned) => assigned.toolId === tool.id)) as tool (tool.id)}
|
||||
<option value={tool.id}
|
||||
>{tool.name}{tool.formatIds.includes(draft.formatId ?? '')
|
||||
? ' · passt zum Format'
|
||||
: ''}</option
|
||||
>
|
||||
{/each}
|
||||
</select>
|
||||
<button class="secondary-button" type="button" onclick={addTool} disabled={!toolToAdd}
|
||||
>Hinzufügen</button
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
{#if pieceTools.length}<div class="source-list">
|
||||
{#each pieceTools as tool, index (tool.id)}<div
|
||||
class="source-row tool-assignment-row"
|
||||
>
|
||||
<input type="hidden" name="toolId" value={tool.toolId} />
|
||||
<div class="tool-assignment-name">
|
||||
<strong>{tool.name}</strong>{#if !tool.isActive}<small>Deaktiviert</small>{/if}
|
||||
</div>
|
||||
<label
|
||||
>Arbeitslink<input
|
||||
name="toolWorkUrl"
|
||||
type="url"
|
||||
maxlength="2000"
|
||||
placeholder={tool.startUrl || 'https://…'}
|
||||
bind:value={tool.workUrl}
|
||||
/></label
|
||||
>
|
||||
<label
|
||||
>Notiz<input
|
||||
name="toolNote"
|
||||
maxlength="2000"
|
||||
placeholder="Optional"
|
||||
bind:value={tool.note}
|
||||
/></label
|
||||
>
|
||||
<button
|
||||
class="text-button danger-text"
|
||||
type="button"
|
||||
onclick={() => removeTool(index)}>Entfernen</button
|
||||
>
|
||||
</div>{/each}
|
||||
</div>{:else}<p class="context-empty">Noch kein Werkzeug zugeordnet.</p>{/if}
|
||||
</section>
|
||||
</section>
|
||||
|
||||
<section class="piece-editor-panel" hidden={activeSection !== 'presentation'}>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { error } from '@sveltejs/kit';
|
||||
import type { PageServerLoad } from './$types';
|
||||
import { requireUser } from '#lib/server/auth.js';
|
||||
import { getPiece } from '#lib/server/content.js';
|
||||
import { getPiece, getTaxonomy } from '#lib/server/content.js';
|
||||
import { renderMarkdown } from '#lib/server/markdown.js';
|
||||
import { buildEmbedUrl, type PresentationContent } from '#lib/server/presentation.js';
|
||||
|
||||
@@ -18,6 +18,9 @@ export const load: PageServerLoad = ({ locals, params }) => {
|
||||
images: { id: string; altText: string }[];
|
||||
} = {
|
||||
mode: piece.presentationMode,
|
||||
topics: getTaxonomy()
|
||||
.topics.filter((topic) => piece.topicIds.includes(topic.id))
|
||||
.map((topic) => topic.name),
|
||||
title: piece.outputTitle || piece.title,
|
||||
html: renderMarkdown(piece.presentationText),
|
||||
source,
|
||||
|
||||
Reference in new issue
Block a user