Release 0.5.10
This commit is contained in:
1 parent
c3033479b5
commit
4fe61971e4
52 files changed
+38565
-466
No files matched your search
@@ -9,6 +9,7 @@ import {
|
||||
listPieces,
|
||||
listPieceTemplates,
|
||||
setPieceArchived,
|
||||
setPieceDeleted,
|
||||
setPieceFavorite
|
||||
} from '#lib/server/content.js';
|
||||
import { getPieceUsageContexts } from '#lib/server/shows.js';
|
||||
@@ -104,5 +105,18 @@ export const actions = {
|
||||
} catch (cause) {
|
||||
return fail(404, { intent: 'favorite', error: actionError(cause) });
|
||||
}
|
||||
},
|
||||
delete: async ({ locals, request }) => {
|
||||
requireUser(locals.user);
|
||||
const data = await request.formData();
|
||||
try {
|
||||
setPieceDeleted(String(data.get('pieceId') || ''), Number(data.get('revision')), true);
|
||||
return {
|
||||
intent: 'delete',
|
||||
success: 'Piece gelöscht. Bestehende Sendungsfassungen bleiben erhalten.'
|
||||
};
|
||||
} catch (cause) {
|
||||
return fail(409, { intent: 'delete', error: actionError(cause) });
|
||||
}
|
||||
}
|
||||
} satisfies Actions;
|
||||
@@ -5,6 +5,7 @@
|
||||
import { page } from '$app/state';
|
||||
import { onMount, untrack } from 'svelte';
|
||||
import PieceUsageHistory from '#lib/components/PieceUsageHistory.svelte';
|
||||
import AppIcon from '#lib/components/AppIcon.svelte';
|
||||
import { formatClock } from '#lib/format.js';
|
||||
import type { PageProps } from './$types';
|
||||
|
||||
@@ -65,14 +66,16 @@
|
||||
<strong>{visiblePieces.length}</strong><span>Inhalte</span>
|
||||
</div>
|
||||
<details class="template-picker">
|
||||
<summary><span aria-hidden="true">+</span> Neues Piece</summary>
|
||||
<summary><AppIcon name="plus" /> Neues Piece</summary>
|
||||
<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
|
||||
><AppIcon name={template.iconName} size="1.35rem" /><strong
|
||||
>{template.name}</strong
|
||||
><span>{template.description}</span></button
|
||||
>
|
||||
</form>
|
||||
{/each}
|
||||
@@ -110,7 +113,7 @@
|
||||
>
|
||||
<div class="fundus-search-row">
|
||||
<label class="search-field"
|
||||
><span aria-hidden="true">⌕</span><span class="sr-only">Fundus durchsuchen</span><input
|
||||
><AppIcon name="magnifying-glass" /><span class="sr-only">Fundus durchsuchen</span><input
|
||||
name="q"
|
||||
type="search"
|
||||
value={data.filters.query}
|
||||
@@ -119,7 +122,7 @@
|
||||
>
|
||||
<button class="secondary-button" type="submit">Suchen</button>
|
||||
<details class="filter-picker">
|
||||
<summary><span aria-hidden="true">≡</span> Weitere Filter</summary>
|
||||
<summary><AppIcon name="list-check" /> Weitere Filter</summary>
|
||||
<div class="filter-panel">
|
||||
<label
|
||||
><span>Status</span><select name="status"
|
||||
@@ -204,7 +207,7 @@
|
||||
aria-label={piece.isFavorite
|
||||
? `${piece.title} aus Favoriten entfernen`
|
||||
: `${piece.title} als Favorit markieren`}
|
||||
aria-pressed={piece.isFavorite}>★</button
|
||||
aria-pressed={piece.isFavorite}><AppIcon name="star" /></button
|
||||
>
|
||||
</form>
|
||||
{#if piece.mediaId}<img
|
||||
@@ -212,14 +215,11 @@
|
||||
src={`/media/${piece.mediaId}`}
|
||||
alt=""
|
||||
/>{:else}<span class="piece-thumbnail piece-thumbnail-placeholder" aria-hidden="true">
|
||||
<svg viewBox="0 0 24 24" fill="none">
|
||||
<path d="M4.5 6.5h15v11h-15zM7.5 14l3-3 2.2 2.2 1.8-1.7 2.5 2.5" />
|
||||
<circle cx="15.8" cy="9.2" r="1.2" />
|
||||
</svg>
|
||||
<AppIcon name={piece.topics[0]?.iconName || 'newspaper'} size="2rem" />
|
||||
</span>{/if}
|
||||
<div class="piece-primary">
|
||||
<h2><a href={resolve('/fundus/[id]', { id: piece.id })}>{piece.title}</a></h2>
|
||||
<p>{piece.summary || 'Noch keine Kurzbeschreibung.'}</p>
|
||||
{#if piece.summary}<p>{piece.summary}</p>{/if}
|
||||
<PieceUsageHistory history={piece.sentHistory} />
|
||||
</div>
|
||||
<div class="piece-classification">
|
||||
@@ -227,23 +227,24 @@
|
||||
><i></i>{piece.categories.map((item) => item.name).join(', ') || 'Offen'}</span
|
||||
>
|
||||
<div class="piece-tags">
|
||||
<span>{piece.format}</span>{#each piece.topics as topic (topic.id)}<span
|
||||
>{topic.name}</span
|
||||
>{/each}
|
||||
{#if piece.formatId}<span>{piece.format}</span
|
||||
>{/if}{#each piece.topics as topic (topic.id)}<span>{topic.name}</span>{/each}
|
||||
</div>
|
||||
</div>
|
||||
<div class="piece-state">
|
||||
<span class:ready={piece.status === 'ready'} class="status-label"
|
||||
>{statusLabel[piece.status]}</span
|
||||
><span class="piece-facts"
|
||||
><span><b>{formatClock(piece.durationSeconds)}</b> geplant</span><span
|
||||
>{piece.sourceCount} {piece.sourceCount === 1 ? 'Quelle' : 'Quellen'}</span
|
||||
></span
|
||||
>{#if piece.durationSeconds || piece.sourceCount}<span class="piece-facts"
|
||||
>{#if piece.durationSeconds}<span
|
||||
><b>{formatClock(piece.durationSeconds)}</b> geplant</span
|
||||
>{/if}{#if piece.sourceCount}<span
|
||||
>{piece.sourceCount} {piece.sourceCount === 1 ? 'Quelle' : 'Quellen'}</span
|
||||
>{/if}</span
|
||||
>{/if}
|
||||
>
|
||||
</div>
|
||||
<details class="piece-actions">
|
||||
<summary aria-label={`Aktionen für ${piece.title}`}
|
||||
><span aria-hidden="true">•••</span></summary
|
||||
<summary aria-label={`Aktionen für ${piece.title}`}><AppIcon name="ellipsis" /></summary
|
||||
>
|
||||
<div class="piece-actions-popover">
|
||||
<form method="POST" action="?/duplicate">
|
||||
@@ -259,6 +260,21 @@
|
||||
value={piece.revision}
|
||||
/><button class="text-button danger-text" type="submit">Archivieren</button>
|
||||
</form>
|
||||
<form
|
||||
method="POST"
|
||||
action="?/delete"
|
||||
onsubmit={(event) =>
|
||||
!confirm(
|
||||
`Piece „${piece.title}“ löschen? Bestehende Sendungsfassungen bleiben erhalten.`
|
||||
) && event.preventDefault()}
|
||||
>
|
||||
<input type="hidden" name="pieceId" value={piece.id} /><input
|
||||
type="hidden"
|
||||
name="revision"
|
||||
value={piece.revision}
|
||||
/>
|
||||
<button class="text-button danger-text" type="submit">Piece löschen</button>
|
||||
</form>
|
||||
</div>
|
||||
</details>
|
||||
</article>
|
||||
@@ -266,7 +282,7 @@
|
||||
</div>
|
||||
{:else}
|
||||
<div class="empty-state">
|
||||
<span aria-hidden="true">⌕</span>
|
||||
<span aria-hidden="true"><AppIcon name="magnifying-glass" /></span>
|
||||
<h2>Kein passendes Piece</h2>
|
||||
<p>Andere Filter wählen oder ein neues Piece anlegen.</p>
|
||||
<a class="secondary-button" href={resolve('/fundus')}>Filter zurücksetzen</a>
|
||||
|
||||
@@ -11,6 +11,7 @@ import {
|
||||
savePiece,
|
||||
savePieceAsTemplate,
|
||||
setPieceArchived,
|
||||
setPieceDeleted,
|
||||
setPieceFavorite,
|
||||
uploadPieceImage,
|
||||
type EditablePieceStatus,
|
||||
@@ -88,6 +89,7 @@ function pieceInput(data: FormData): PieceInput {
|
||||
presentationDescriptionManual: text(data, 'presentationDescriptionManual') === '1',
|
||||
presentationImageManual: text(data, 'presentationImageManual') === '1',
|
||||
presentationMetadataFetchedAtUtc: text(data, 'presentationMetadataFetchedAtUtc') || null,
|
||||
presentationMediaId: text(data, 'presentationMediaId') || null,
|
||||
topicIds: data.getAll('topicIds').map(String),
|
||||
categoryIds: data.getAll('categoryIds').map(String),
|
||||
sources,
|
||||
@@ -206,7 +208,13 @@ export const actions = {
|
||||
if (!(image instanceof File))
|
||||
return fail(400, { intent: 'upload', error: 'Bitte ein Bild auswählen.' });
|
||||
try {
|
||||
await uploadPieceImage(params.id, Number(data.get('revision')), image, text(data, 'altText'));
|
||||
await uploadPieceImage(
|
||||
params.id,
|
||||
Number(data.get('revision')),
|
||||
image,
|
||||
text(data, 'altText'),
|
||||
data.get('selectForPresentation') === '1'
|
||||
);
|
||||
} catch (cause) {
|
||||
const code = cause instanceof Error ? cause.message : '';
|
||||
if (code === 'CONFLICT')
|
||||
@@ -264,5 +272,15 @@ export const actions = {
|
||||
} catch (cause) {
|
||||
return fail(404, { intent: 'favorite', error: saveError(cause).error });
|
||||
}
|
||||
},
|
||||
delete: async ({ locals, params, request }) => {
|
||||
requireUser(locals.user);
|
||||
const data = await request.formData();
|
||||
try {
|
||||
setPieceDeleted(params.id, Number(data.get('revision')), true);
|
||||
} catch (cause) {
|
||||
return fail(409, { intent: 'delete', error: saveError(cause).error });
|
||||
}
|
||||
redirect(303, '/fundus');
|
||||
}
|
||||
} satisfies Actions;
|
||||
@@ -4,6 +4,9 @@
|
||||
import { resolve } from '$app/paths';
|
||||
import { onDestroy, onMount, untrack } from 'svelte';
|
||||
import MarkdownEditorField from '#lib/components/MarkdownEditorField.svelte';
|
||||
import AppIcon from '#lib/components/AppIcon.svelte';
|
||||
import ChoicePicker from '#lib/components/ChoicePicker.svelte';
|
||||
import InlineTitleEditor from '#lib/components/InlineTitleEditor.svelte';
|
||||
import PieceEditorDialog from '#lib/components/PieceEditorDialog.svelte';
|
||||
import PresentationDisplay from '#lib/components/PresentationDisplay.svelte';
|
||||
import type { PresentationContent } from '#lib/server/presentation.js';
|
||||
@@ -15,7 +18,6 @@
|
||||
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,7 +46,12 @@
|
||||
? resolve('/fundus/[id]/praesentation/preview-image', { id: data.piece.id })
|
||||
: ''
|
||||
);
|
||||
let previewContent = $derived<PresentationContent>({
|
||||
let selectedMedia = $derived(
|
||||
draft.media.find((media) => media.id === draft.presentationMediaId) ?? null
|
||||
);
|
||||
let previewContent = $derived<
|
||||
PresentationContent & { images: { id: string; altText: string }[] }
|
||||
>({
|
||||
mode: draft.presentationMode,
|
||||
topics: data.taxonomy.topics
|
||||
.filter((topic) => draft.topicIds.includes(topic.id))
|
||||
@@ -69,8 +76,16 @@
|
||||
? `https://www.youtube-nocookie.com/embed/${draft.presentationProviderId}${featuredSource?.timecodeSeconds ? `?start=${featuredSource.timecodeSeconds}` : ''}`
|
||||
: draft.presentationProvider === 'tiktok' && draft.presentationProviderId
|
||||
? `https://www.tiktok.com/player/v1/${draft.presentationProviderId}`
|
||||
: ''
|
||||
: '',
|
||||
images: selectedMedia ? [{ id: selectedMedia.id, altText: selectedMedia.altText }] : []
|
||||
});
|
||||
let presentationPreviewImage = $derived(
|
||||
selectedMedia
|
||||
? draft.presentationMode === 'text'
|
||||
? '/media'
|
||||
: `/media/${selectedMedia.id}`
|
||||
: sourcePreviewImage
|
||||
);
|
||||
let pendingHref: string | null = null;
|
||||
let allowNavigation = false;
|
||||
|
||||
@@ -152,24 +167,28 @@
|
||||
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 = '';
|
||||
function removeTool(index: number) {
|
||||
pieceTools.splice(index, 1);
|
||||
scheduleSave();
|
||||
}
|
||||
|
||||
function removeTool(index: number) {
|
||||
pieceTools.splice(index, 1);
|
||||
function selectTools(ids: string[]) {
|
||||
for (const id of ids) {
|
||||
if (pieceTools.some((tool) => tool.toolId === id)) continue;
|
||||
const tool = data.tools.find((item) => item.id === id);
|
||||
if (tool)
|
||||
pieceTools.push({
|
||||
id: crypto.randomUUID(),
|
||||
toolId: tool.id,
|
||||
name: tool.name,
|
||||
startUrl: tool.startUrl,
|
||||
iconName: tool.iconName,
|
||||
isActive: tool.isActive,
|
||||
workUrl: '',
|
||||
note: ''
|
||||
});
|
||||
}
|
||||
pieceTools = pieceTools.filter((tool) => ids.includes(tool.toolId));
|
||||
scheduleSave();
|
||||
}
|
||||
|
||||
@@ -244,22 +263,15 @@
|
||||
class:active={data.piece.isFavorite}
|
||||
type="submit"
|
||||
aria-label={data.piece.isFavorite ? 'Aus Favoriten entfernen' : 'Als Favorit markieren'}
|
||||
aria-pressed={data.piece.isFavorite}>★</button
|
||||
aria-pressed={data.piece.isFavorite}><AppIcon name="star" /></button
|
||||
>
|
||||
</form>
|
||||
</div>
|
||||
<label class="piece-dialog-title">
|
||||
<span class="sr-only">Titel</span>
|
||||
<textarea
|
||||
form="piece-editor-form"
|
||||
name="title"
|
||||
required
|
||||
rows="1"
|
||||
maxlength="200"
|
||||
aria-label="Titel"
|
||||
bind:value={draft.title}
|
||||
oninput={scheduleSave}></textarea>
|
||||
</label>
|
||||
<InlineTitleEditor
|
||||
form="piece-editor-form"
|
||||
bind:value={draft.title}
|
||||
oncommit={scheduleSave}
|
||||
/>
|
||||
<h1 class="sr-only" id="piece-editor-title">{draft.title}</h1>
|
||||
{/snippet}
|
||||
|
||||
@@ -345,17 +357,16 @@
|
||||
bind:value={draft.summary}></textarea></label
|
||||
>
|
||||
<fieldset>
|
||||
<legend>Themen</legend>
|
||||
<div class="choice-grid">
|
||||
{#each data.taxonomy.topics as item (item.id)}<label
|
||||
><input
|
||||
type="checkbox"
|
||||
name="topicIds"
|
||||
value={item.id}
|
||||
bind:group={draft.topicIds}
|
||||
/>{item.name}</label
|
||||
>{/each}
|
||||
</div>
|
||||
<legend>Themen</legend><ChoicePicker
|
||||
items={data.taxonomy.topics}
|
||||
selectedIds={draft.topicIds}
|
||||
label="Themen"
|
||||
fieldName="topicIds"
|
||||
onselectionchange={(ids) => {
|
||||
draft.topicIds = ids;
|
||||
scheduleSave();
|
||||
}}
|
||||
/>
|
||||
</fieldset>
|
||||
<fieldset>
|
||||
<legend>Vorgeschlagene Abschnittskategorien</legend>
|
||||
@@ -440,22 +451,20 @@
|
||||
<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>
|
||||
<ChoicePicker
|
||||
items={data.tools
|
||||
.filter((tool) => tool.isActive)
|
||||
.map((tool) => ({
|
||||
id: tool.id,
|
||||
name: tool.name,
|
||||
description: tool.description,
|
||||
iconName: tool.iconName,
|
||||
meta: tool.formatIds.includes(draft.formatId ?? '') ? 'Passt zum Format' : ''
|
||||
}))}
|
||||
selectedIds={pieceTools.map((tool) => tool.toolId)}
|
||||
label="Werkzeuge"
|
||||
onselectionchange={selectTools}
|
||||
/>
|
||||
</div>
|
||||
{#if pieceTools.length}<div class="source-list">
|
||||
{#each pieceTools as tool, index (tool.id)}<div
|
||||
@@ -463,7 +472,10 @@
|
||||
>
|
||||
<input type="hidden" name="toolId" value={tool.toolId} />
|
||||
<div class="tool-assignment-name">
|
||||
<strong>{tool.name}</strong>{#if !tool.isActive}<small>Deaktiviert</small>{/if}
|
||||
<AppIcon name={tool.iconName || 'wrench'} /><span
|
||||
><strong>{tool.name}</strong>{#if !tool.isActive}<small>Deaktiviert</small
|
||||
>{/if}</span
|
||||
>
|
||||
</div>
|
||||
<label
|
||||
>Arbeitslink<input
|
||||
@@ -590,7 +602,7 @@
|
||||
<aside class="piece-presentation-preview" aria-label="Präsentationsvorschau">
|
||||
<PresentationDisplay
|
||||
content={previewContent}
|
||||
imageSrc={sourceImageUnavailable ? '' : sourcePreviewImage}
|
||||
imageSrc={sourceImageUnavailable ? '' : presentationPreviewImage}
|
||||
/>
|
||||
</aside>
|
||||
</div>
|
||||
@@ -620,17 +632,44 @@
|
||||
</section>
|
||||
</form>
|
||||
|
||||
<section class="piece-editor-panel piece-media-panel" hidden={activeSection !== 'research'}>
|
||||
<section class="piece-editor-panel piece-media-panel" hidden={activeSection !== 'presentation'}>
|
||||
<div class="piece-subsection-heading">
|
||||
<div>
|
||||
<p class="eyebrow">Private Dateien</p>
|
||||
<h3>Material</h3>
|
||||
<p class="eyebrow">Hochgeladene Bilder</p>
|
||||
<h3>Präsentationsbild</h3>
|
||||
</div>
|
||||
<small>JPEG, PNG, WebP oder GIF bis 5 MB</small>
|
||||
</div>
|
||||
{#if data.piece.media.length}<div class="media-grid">
|
||||
{#if data.piece.media.length}<label class="media-none"
|
||||
><input
|
||||
form="piece-editor-form"
|
||||
type="radio"
|
||||
name="presentationMediaId"
|
||||
value=""
|
||||
checked={!draft.presentationMediaId}
|
||||
onchange={() => {
|
||||
draft.presentationMediaId = null;
|
||||
scheduleSave();
|
||||
}}
|
||||
/> Kein hochgeladenes Bild</label
|
||||
>
|
||||
<div class="media-grid">
|
||||
{#each data.piece.media as media (media.id)}<figure>
|
||||
<img src={`/media/${media.id}`} alt={media.altText} />
|
||||
<label class="media-presentation-choice"
|
||||
><input
|
||||
form="piece-editor-form"
|
||||
type="radio"
|
||||
name="presentationMediaId"
|
||||
value={media.id}
|
||||
checked={draft.presentationMediaId === media.id}
|
||||
onchange={() => {
|
||||
draft.presentationMediaId = media.id;
|
||||
scheduleSave();
|
||||
}}
|
||||
/><img src={`/media/${media.id}`} alt={media.altText} /><span
|
||||
>Als Präsentationsbild</span
|
||||
></label
|
||||
>
|
||||
<figcaption>
|
||||
<span>{media.altText || media.originalName}</span>
|
||||
<form method="POST" action="?/deleteImage">
|
||||
@@ -662,6 +701,10 @@
|
||||
><button class="secondary-button" type="submit" disabled={actionsBlocked}
|
||||
>Bild hochladen</button
|
||||
>
|
||||
<label class="upload-select"
|
||||
><input type="checkbox" name="selectForPresentation" value="1" checked /> Direkt als Präsentationsbild
|
||||
verwenden</label
|
||||
>
|
||||
</form>
|
||||
</section>
|
||||
|
||||
@@ -696,6 +739,19 @@
|
||||
disabled={actionsBlocked}>Archivieren</button
|
||||
>
|
||||
</form>
|
||||
<form
|
||||
method="POST"
|
||||
action="?/delete"
|
||||
onsubmit={(event) =>
|
||||
!confirm('Piece löschen? Bestehende Sendungsfassungen bleiben erhalten.') &&
|
||||
event.preventDefault()}
|
||||
>
|
||||
<input type="hidden" name="revision" value={revision} /><button
|
||||
class="text-button danger-text"
|
||||
type="submit"
|
||||
disabled={actionsBlocked}>Piece löschen</button
|
||||
>
|
||||
</form>
|
||||
</div>
|
||||
<div class="piece-dialog-primary-action">
|
||||
<span
|
||||
|
||||
@@ -40,8 +40,10 @@ export const load: PageServerLoad = ({ locals, params }) => {
|
||||
piece.presentationProviderId,
|
||||
source?.timecodeSeconds ?? null
|
||||
),
|
||||
sources: piece.sources,
|
||||
images: piece.media.map((item) => ({ id: item.id, altText: item.altText }))
|
||||
sources: [],
|
||||
images: piece.media
|
||||
.filter((item) => item.id === piece.presentationMediaId)
|
||||
.map((item) => ({ id: item.id, altText: item.altText }))
|
||||
};
|
||||
return { pieceId: piece.id, content };
|
||||
};
|
||||
@@ -14,8 +14,10 @@
|
||||
<div class="presentation-test-status" role="status">Isolierte Testansicht</div>
|
||||
<PresentationDisplay
|
||||
content={data.content}
|
||||
imageSrc={data.content.mode === 'text'
|
||||
? '/media'
|
||||
imageSrc={data.content.images.length
|
||||
? data.content.mode === 'text'
|
||||
? '/media'
|
||||
: `/media/${data.content.images[0].id}`
|
||||
: `/fundus/${data.pieceId}/praesentation/preview-image`}
|
||||
/>
|
||||
</main>
|
||||
Reference in new issue
Block a user