All checks were successful
Deploy Development / deploy (push) Successful in 34s
Test Suite / pytest-backend (push) Successful in 24s
Test Suite / lint-backend (push) Successful in 0s
Test Suite / build-frontend (push) Successful in 7s
Test Suite / playwright-tests (push) Successful in 30s
- Incremented application version to 0.8.63 and updated changelog with new features. - Enhanced inline media handling in the Rich Text Editor, including support for captions. - Introduced new CSS styles for improved media display and layout in the editor. - Replaced `ExerciseMediaEmbed` with `ExerciseAttachmentMediaStrip` for better media management in exercise content.
133 lines
4.1 KiB
JavaScript
133 lines
4.1 KiB
JavaScript
/**
|
|
* Modal: Embed-URL als exercise_media anlegen und §11-Platzhalter einfügen.
|
|
*/
|
|
import React, { useEffect, useState } from 'react'
|
|
import api from '../utils/api'
|
|
import {
|
|
INLINE_MEDIA_SIZES,
|
|
DEFAULT_INLINE_MEDIA_SIZE,
|
|
sanitizeInlineMediaSize,
|
|
} from '../constants/inlineExerciseMedia'
|
|
import { sanitizeInlineMediaCaption } from '../utils/inlineMediaCaption'
|
|
|
|
/**
|
|
* @param {{
|
|
* open: boolean,
|
|
* onClose: () => void,
|
|
* exerciseId: number,
|
|
* onMediaListChanged: () => Promise<void>,
|
|
* onInserted: (exerciseMediaId: number, displaySize: string, caption?: string) => void,
|
|
* }} props
|
|
*/
|
|
export default function ExerciseInlineEmbedModal({
|
|
open,
|
|
onClose,
|
|
exerciseId,
|
|
onMediaListChanged,
|
|
onInserted,
|
|
}) {
|
|
const [url, setUrl] = useState('')
|
|
const [title, setTitle] = useState('')
|
|
const [displaySize, setDisplaySize] = useState(DEFAULT_INLINE_MEDIA_SIZE)
|
|
const [busy, setBusy] = useState(false)
|
|
|
|
useEffect(() => {
|
|
if (!open) return
|
|
setUrl('')
|
|
setTitle('')
|
|
setDisplaySize(DEFAULT_INLINE_MEDIA_SIZE)
|
|
}, [open])
|
|
|
|
const submit = async () => {
|
|
const u = url.trim()
|
|
if (!u) {
|
|
alert('Bitte eine Embed-URL eingeben (https://…).')
|
|
return
|
|
}
|
|
const size = sanitizeInlineMediaSize(displaySize)
|
|
const fd = new FormData()
|
|
fd.append('embed_url', u)
|
|
fd.append('media_type', 'video')
|
|
fd.append('title', title.trim())
|
|
fd.append('description', '')
|
|
fd.append('context', 'ablauf')
|
|
fd.append('is_primary', 'false')
|
|
setBusy(true)
|
|
try {
|
|
const row = await api.uploadExerciseMedia(exerciseId, fd)
|
|
const mid = row?.id
|
|
if (mid == null) {
|
|
throw new Error('Antwort ohne exercise_media-ID')
|
|
}
|
|
await onMediaListChanged()
|
|
const cap = sanitizeInlineMediaCaption(
|
|
title.trim() || u.replace(/^https?:\/\//i, '').slice(0, 96),
|
|
)
|
|
onInserted(Number(mid), size, cap)
|
|
onClose()
|
|
} catch (e) {
|
|
alert(e.message || String(e))
|
|
} finally {
|
|
setBusy(false)
|
|
}
|
|
}
|
|
|
|
if (!open) return null
|
|
|
|
return (
|
|
<div className="admin-modal-backdrop" role="presentation" onClick={(e) => e.target === e.currentTarget && !busy && onClose()}>
|
|
<div
|
|
className="admin-modal-sheet"
|
|
role="dialog"
|
|
aria-modal="true"
|
|
aria-labelledby="rte-inline-embed-title"
|
|
style={{ maxWidth: '480px', width: '100%' }}
|
|
onClick={(e) => e.stopPropagation()}
|
|
>
|
|
<div className="admin-modal-sheet__header">
|
|
<h3 id="rte-inline-embed-title" className="admin-modal-sheet__title">
|
|
Embed im Textfeld
|
|
</h3>
|
|
<button type="button" className="btn btn-secondary admin-modal-sheet__close" disabled={busy} onClick={onClose}>
|
|
Schließen
|
|
</button>
|
|
</div>
|
|
<div style={{ padding: '14px 16px' }}>
|
|
<label className="form-label">Embed-URL</label>
|
|
<input
|
|
type="url"
|
|
className="form-input"
|
|
placeholder="https://…"
|
|
value={url}
|
|
onChange={(e) => setUrl(e.target.value)}
|
|
disabled={busy}
|
|
/>
|
|
<label className="form-label" style={{ marginTop: '12px' }}>
|
|
Titel (optional)
|
|
</label>
|
|
<input
|
|
type="text"
|
|
className="form-input"
|
|
value={title}
|
|
onChange={(e) => setTitle(e.target.value)}
|
|
disabled={busy}
|
|
/>
|
|
<label className="form-label" style={{ marginTop: '12px' }}>
|
|
Darstellung im Text
|
|
</label>
|
|
<select className="form-input" value={displaySize} onChange={(e) => setDisplaySize(e.target.value)} disabled={busy}>
|
|
{INLINE_MEDIA_SIZES.map((o) => (
|
|
<option key={o.value} value={o.value}>
|
|
{o.label}
|
|
</option>
|
|
))}
|
|
</select>
|
|
<button type="button" className="btn btn-primary btn-full" style={{ marginTop: '16px' }} disabled={busy} onClick={submit}>
|
|
{busy ? 'Speichern…' : 'Hinzufügen & in Text einfügen'}
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
)
|
|
}
|