2026-05-08 12:00:02 +02:00
/ * *
* Modal : Medium aus Archiv verknüpfen oder neue Datei hochladen , dann Inline - Platzhalter § 11 einfügen .
* /
2026-05-08 12:20:24 +02:00
import React , { useEffect , useState , useCallback , useMemo } from 'react'
2026-05-08 12:00:02 +02:00
import api from '../utils/api'
2026-05-08 12:20:24 +02:00
import { resolveMediaAssetFileUrl } from '../utils/exerciseMediaUrl'
2026-05-08 12:00:02 +02:00
import {
INLINE _MEDIA _SIZES ,
DEFAULT _INLINE _MEDIA _SIZE ,
sanitizeInlineMediaSize ,
} from '../constants/inlineExerciseMedia'
2026-05-08 12:20:24 +02:00
import { sanitizeInlineMediaCaption } from '../utils/inlineMediaCaption'
2026-05-08 12:00:02 +02:00
2026-05-08 12:35:28 +02:00
function RtePickerAssetThumb ( { asset } ) {
const id = asset . id
const src = resolveMediaAssetFileUrl ( id )
const mt = ( asset . mime _type || '' ) . toLowerCase ( )
if ( mt . startsWith ( 'image/' ) && src ) {
return < img alt = "" src = { src } className = "rte-inline-asset-tile__thumb-img" / >
}
if ( mt . startsWith ( 'video/' ) && src ) {
return (
< video
key = { ` v- ${ id } ` }
className = "rte-inline-asset-tile__thumb-video"
src = { src }
muted
playsInline
preload = "metadata"
onLoadedMetadata = { ( e ) => {
try {
const el = e . currentTarget
const d = el . duration
el . currentTime = Number . isFinite ( d ) && d > 0 ? Math . min ( 0.05 , d * 0.01 ) : 0.05
} catch ( _ ) {
/* ignore */
}
} }
/ >
)
}
const nameLow = String ( asset . original _filename || '' ) . toLowerCase ( )
if ( mt . includes ( 'pdf' ) || nameLow . endsWith ( '.pdf' ) ) {
return < span className = "rte-inline-asset-tile__thumb-fallback" > PDF < / span >
}
return < span className = "rte-inline-asset-tile__thumb-fallback" > Datei < / span >
}
2026-05-08 12:00:02 +02:00
/** MIME/Dateiname → Übungs-media_type */
function inferExerciseMediaType ( file ) {
if ( ! file ) return 'image'
const mime = ( file . type || '' ) . toLowerCase ( )
if ( mime . startsWith ( 'image/' ) ) return 'image'
if ( mime . startsWith ( 'video/' ) ) return 'video'
if ( mime === 'application/pdf' || mime . includes ( 'pdf' ) ) return 'document'
const name = ( file . name || '' ) . toLowerCase ( )
if ( /\.(mp4|webm|mov|mkv|avi|m4v|mpeg|mpg)$/ . test ( name ) ) return 'video'
if ( /\.(jpg|jpeg|png|gif|webp|bmp|svg)$/ . test ( name ) ) return 'image'
if ( /\.pdf$/ . test ( name ) ) return 'document'
return 'image'
}
2026-05-11 08:44:37 +02:00
const DECL _INIT = {
feat(p06): Copyright-Feld und Einwilligungskontext in Rechte-Erklaerung
Migration 049: 4 optionale TEXT-Spalten in media_asset_rights_declarations
(person_consent_context, parental_consent_context, music_rights_context,
third_party_rights_context) fuer Freitext zum Einwilligungskontext.
Backend:
- media_rights.py: write_rights_declaration speichert 4 Kontextfelder
- media_assets.py: copyright_notice + 4 Kontextfelder in Bulk-Upload,
RightsDeclarationBody, MediaAssetPatch, MediaBulkPatchBody
- exercises.py: copyright_notice + 4 Kontextfelder in upload_exercise_media,
wird in INSERT gespeichert
Frontend (alle 3 Formulare):
- RightsDeclarationDialog: Copyright-Eingabefeld (immer sichtbar) +
Freitext-Textarea bei jeder Ja-Antwort (Personen, Minderjaehrige,
Musik, Fremdinhalte)
- ExerciseInlineFileMediaModal: gleiche Felder inline im Upload-Tab
- ExerciseInlineEmbedModal: gleiche Felder inline
- api.js: copyright_notice + 4 Kontextfelder in bulkUploadMediaAssets
version: 0.8.77
module: media_rights 1.1.0, media_assets 1.14.0, exercises 2.21.0
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-11 09:06:47 +02:00
copyright _notice : '' ,
2026-05-11 08:44:37 +02:00
rights _holder _confirmed : false ,
contains _identifiable _persons : null ,
person _consent _confirmed : false ,
feat(p06): Copyright-Feld und Einwilligungskontext in Rechte-Erklaerung
Migration 049: 4 optionale TEXT-Spalten in media_asset_rights_declarations
(person_consent_context, parental_consent_context, music_rights_context,
third_party_rights_context) fuer Freitext zum Einwilligungskontext.
Backend:
- media_rights.py: write_rights_declaration speichert 4 Kontextfelder
- media_assets.py: copyright_notice + 4 Kontextfelder in Bulk-Upload,
RightsDeclarationBody, MediaAssetPatch, MediaBulkPatchBody
- exercises.py: copyright_notice + 4 Kontextfelder in upload_exercise_media,
wird in INSERT gespeichert
Frontend (alle 3 Formulare):
- RightsDeclarationDialog: Copyright-Eingabefeld (immer sichtbar) +
Freitext-Textarea bei jeder Ja-Antwort (Personen, Minderjaehrige,
Musik, Fremdinhalte)
- ExerciseInlineFileMediaModal: gleiche Felder inline im Upload-Tab
- ExerciseInlineEmbedModal: gleiche Felder inline
- api.js: copyright_notice + 4 Kontextfelder in bulkUploadMediaAssets
version: 0.8.77
module: media_rights 1.1.0, media_assets 1.14.0, exercises 2.21.0
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-11 09:06:47 +02:00
person _consent _context : '' ,
2026-05-11 08:44:37 +02:00
contains _minors : null ,
parental _consent _confirmed : false ,
feat(p06): Copyright-Feld und Einwilligungskontext in Rechte-Erklaerung
Migration 049: 4 optionale TEXT-Spalten in media_asset_rights_declarations
(person_consent_context, parental_consent_context, music_rights_context,
third_party_rights_context) fuer Freitext zum Einwilligungskontext.
Backend:
- media_rights.py: write_rights_declaration speichert 4 Kontextfelder
- media_assets.py: copyright_notice + 4 Kontextfelder in Bulk-Upload,
RightsDeclarationBody, MediaAssetPatch, MediaBulkPatchBody
- exercises.py: copyright_notice + 4 Kontextfelder in upload_exercise_media,
wird in INSERT gespeichert
Frontend (alle 3 Formulare):
- RightsDeclarationDialog: Copyright-Eingabefeld (immer sichtbar) +
Freitext-Textarea bei jeder Ja-Antwort (Personen, Minderjaehrige,
Musik, Fremdinhalte)
- ExerciseInlineFileMediaModal: gleiche Felder inline im Upload-Tab
- ExerciseInlineEmbedModal: gleiche Felder inline
- api.js: copyright_notice + 4 Kontextfelder in bulkUploadMediaAssets
version: 0.8.77
module: media_rights 1.1.0, media_assets 1.14.0, exercises 2.21.0
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-11 09:06:47 +02:00
parental _consent _context : '' ,
2026-05-11 08:44:37 +02:00
contains _music : null ,
music _rights _confirmed : false ,
feat(p06): Copyright-Feld und Einwilligungskontext in Rechte-Erklaerung
Migration 049: 4 optionale TEXT-Spalten in media_asset_rights_declarations
(person_consent_context, parental_consent_context, music_rights_context,
third_party_rights_context) fuer Freitext zum Einwilligungskontext.
Backend:
- media_rights.py: write_rights_declaration speichert 4 Kontextfelder
- media_assets.py: copyright_notice + 4 Kontextfelder in Bulk-Upload,
RightsDeclarationBody, MediaAssetPatch, MediaBulkPatchBody
- exercises.py: copyright_notice + 4 Kontextfelder in upload_exercise_media,
wird in INSERT gespeichert
Frontend (alle 3 Formulare):
- RightsDeclarationDialog: Copyright-Eingabefeld (immer sichtbar) +
Freitext-Textarea bei jeder Ja-Antwort (Personen, Minderjaehrige,
Musik, Fremdinhalte)
- ExerciseInlineFileMediaModal: gleiche Felder inline im Upload-Tab
- ExerciseInlineEmbedModal: gleiche Felder inline
- api.js: copyright_notice + 4 Kontextfelder in bulkUploadMediaAssets
version: 0.8.77
module: media_rights 1.1.0, media_assets 1.14.0, exercises 2.21.0
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-11 09:06:47 +02:00
music _rights _context : '' ,
2026-05-11 08:44:37 +02:00
contains _third _party _content : null ,
third _party _rights _confirmed : false ,
feat(p06): Copyright-Feld und Einwilligungskontext in Rechte-Erklaerung
Migration 049: 4 optionale TEXT-Spalten in media_asset_rights_declarations
(person_consent_context, parental_consent_context, music_rights_context,
third_party_rights_context) fuer Freitext zum Einwilligungskontext.
Backend:
- media_rights.py: write_rights_declaration speichert 4 Kontextfelder
- media_assets.py: copyright_notice + 4 Kontextfelder in Bulk-Upload,
RightsDeclarationBody, MediaAssetPatch, MediaBulkPatchBody
- exercises.py: copyright_notice + 4 Kontextfelder in upload_exercise_media,
wird in INSERT gespeichert
Frontend (alle 3 Formulare):
- RightsDeclarationDialog: Copyright-Eingabefeld (immer sichtbar) +
Freitext-Textarea bei jeder Ja-Antwort (Personen, Minderjaehrige,
Musik, Fremdinhalte)
- ExerciseInlineFileMediaModal: gleiche Felder inline im Upload-Tab
- ExerciseInlineEmbedModal: gleiche Felder inline
- api.js: copyright_notice + 4 Kontextfelder in bulkUploadMediaAssets
version: 0.8.77
module: media_rights 1.1.0, media_assets 1.14.0, exercises 2.21.0
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-11 09:06:47 +02:00
third _party _rights _context : '' ,
2026-05-11 08:44:37 +02:00
}
function validateDecl ( decl ) {
if ( ! decl . rights _holder _confirmed )
return 'Bitte bestätigen, dass du die erforderlichen Rechte an diesem Medium besitzt.'
if ( decl . contains _identifiable _persons === null )
return 'Bitte angeben, ob erkennbare Personen abgebildet sind.'
if ( decl . contains _identifiable _persons && ! decl . person _consent _confirmed )
return 'Bitte bestätigen, dass die Einwilligungen aller erkennbaren Personen vorliegen.'
if ( decl . contains _minors === null )
return 'Bitte angeben, ob Minderjährige abgebildet sind.'
if ( decl . contains _minors && ! decl . parental _consent _confirmed )
return 'Bitte bestätigen, dass die Einwilligungen der Sorgeberechtigten vorliegen.'
if ( decl . contains _music === null )
return 'Bitte angeben, ob das Medium Musik enthält.'
if ( decl . contains _music && ! decl . music _rights _confirmed )
return 'Bitte bestätigen, dass die erforderlichen Musikrechte vorliegen.'
if ( decl . contains _third _party _content === null )
return 'Bitte angeben, ob fremde geschützte Inhalte enthalten sind.'
if ( decl . contains _third _party _content && ! decl . third _party _rights _confirmed )
return 'Bitte bestätigen, dass die Rechte an allen enthaltenen Fremdmaterialien vorliegen.'
return ''
}
2026-05-08 12:00:02 +02:00
/ * *
* @ param { {
* open : boolean ,
* onClose : ( ) => void ,
* exerciseId : number ,
2026-05-08 12:20:24 +02:00
* linkedExerciseMedia ? : object [ ] ,
2026-05-08 12:00:02 +02:00
* onMediaListChanged : ( ) => Promise < void > ,
2026-05-08 12:20:24 +02:00
* onInserted : ( exerciseMediaId : number , displaySize : string , caption ? : string ) => void ,
2026-05-08 12:00:02 +02:00
* } } props
* /
export default function ExerciseInlineFileMediaModal ( {
open ,
onClose ,
exerciseId ,
2026-05-08 12:20:24 +02:00
linkedExerciseMedia = [ ] ,
2026-05-08 12:00:02 +02:00
onMediaListChanged ,
onInserted ,
} ) {
const [ tab , setTab ] = useState ( 'library' )
const [ q , setQ ] = useState ( '' )
const [ loading , setLoading ] = useState ( false )
const [ items , setItems ] = useState ( [ ] )
const [ err , setErr ] = useState ( null )
const [ selectedAssetId , setSelectedAssetId ] = useState ( null )
const [ busy , setBusy ] = useState ( false )
const [ uploadFile , setUploadFile ] = useState ( null )
const [ uploadTitle , setUploadTitle ] = useState ( '' )
const [ displaySize , setDisplaySize ] = useState ( DEFAULT _INLINE _MEDIA _SIZE )
2026-05-08 12:20:24 +02:00
const [ uploadInputKey , setUploadInputKey ] = useState ( 0 )
2026-05-11 08:44:37 +02:00
const [ decl , setDecl ] = useState ( { ... DECL _INIT } )
const [ declErr , setDeclErr ] = useState ( '' )
const setDeclField = ( key , val ) => setDecl ( ( d ) => ( { ... d , [ key ] : val } ) )
2026-05-08 12:20:24 +02:00
const assetToExerciseMedia = useMemo ( ( ) => {
const m = new Map ( )
for ( const row of linkedExerciseMedia || [ ] ) {
const aid = row ? . media _asset _id
if ( aid != null ) m . set ( Number ( aid ) , row )
}
return m
} , [ linkedExerciseMedia ] )
2026-05-08 12:00:02 +02:00
const loadAssets = useCallback ( async ( ) => {
setLoading ( true )
setErr ( null )
try {
const res = await api . listMediaAssets ( {
q : q . trim ( ) || undefined ,
limit : 48 ,
lifecycle : 'active' ,
} )
setItems ( Array . isArray ( res . items ) ? res . items : [ ] )
} catch ( e ) {
setErr ( e . message || String ( e ) )
setItems ( [ ] )
} finally {
setLoading ( false )
}
} , [ q ] )
useEffect ( ( ) => {
if ( ! open ) return undefined
setTab ( 'library' )
setSelectedAssetId ( null )
setUploadFile ( null )
setUploadTitle ( '' )
2026-05-08 12:20:24 +02:00
setUploadInputKey ( ( k ) => k + 1 )
2026-05-08 12:00:02 +02:00
setDisplaySize ( DEFAULT _INLINE _MEDIA _SIZE )
setErr ( null )
2026-05-11 08:44:37 +02:00
setDecl ( { ... DECL _INIT } )
setDeclErr ( '' )
2026-05-08 12:00:02 +02:00
const t = setTimeout ( loadAssets , 280 )
return ( ) => clearTimeout ( t )
} , [ open ] )
useEffect ( ( ) => {
if ( ! open || tab !== 'library' ) return undefined
const t = setTimeout ( loadAssets , 300 )
return ( ) => clearTimeout ( t )
} , [ q , open , tab , loadAssets ] )
const handleLinkSelected = async ( ) => {
if ( ! selectedAssetId ) {
alert ( 'Bitte ein Archiv-Medium auswählen.' )
return
}
const size = sanitizeInlineMediaSize ( displaySize )
2026-05-08 12:20:24 +02:00
const assetMeta = items . find ( ( x ) => x . id === selectedAssetId )
const capFromExisting = ( row ) =>
sanitizeInlineMediaCaption ( row ? . original _filename || row ? . title || assetMeta ? . original _filename || '' )
const existing = assetToExerciseMedia . get ( Number ( selectedAssetId ) )
if ( existing ? . id != null ) {
onInserted ( Number ( existing . id ) , size , capFromExisting ( existing ) )
onClose ( )
return
}
2026-05-08 12:00:02 +02:00
setBusy ( true )
setErr ( null )
try {
const row = await api . attachExerciseMediaFromAsset ( exerciseId , {
media _asset _id : selectedAssetId ,
title : '' ,
description : '' ,
context : 'ablauf' ,
is _primary : false ,
} )
const mid = row ? . id
if ( mid == null ) {
throw new Error ( 'Antwort ohne exercise_media-ID' )
}
await onMediaListChanged ( )
2026-05-08 12:20:24 +02:00
onInserted (
Number ( mid ) ,
size ,
sanitizeInlineMediaCaption ( assetMeta ? . original _filename || '' ) ,
)
2026-05-08 12:00:02 +02:00
onClose ( )
} catch ( e ) {
const msg = e . message || String ( e )
setErr ( msg )
} finally {
setBusy ( false )
}
}
2026-05-11 08:44:37 +02:00
const handleUploadAndInsert = async ( ) => {
2026-05-08 12:00:02 +02:00
if ( ! uploadFile ) {
alert ( 'Bitte eine Datei wählen.' )
return
}
2026-05-11 08:44:37 +02:00
const validErr = validateDecl ( decl )
if ( validErr ) {
setDeclErr ( validErr )
return
}
setDeclErr ( '' )
2026-05-08 12:00:02 +02:00
const size = sanitizeInlineMediaSize ( displaySize )
const inferred = inferExerciseMediaType ( uploadFile )
const fd = new FormData ( )
fd . append ( 'file' , uploadFile )
fd . append ( 'media_type' , inferred )
fd . append ( 'title' , uploadTitle . trim ( ) )
fd . append ( 'description' , '' )
fd . append ( 'context' , 'ablauf' )
fd . append ( 'is_primary' , 'false' )
2026-05-11 08:25:00 +02:00
for ( const [ k , v ] of Object . entries ( decl ) ) {
fd . append ( k , String ( v ) )
}
2026-05-08 12:00:02 +02:00
setBusy ( true )
setErr ( null )
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 ( )
2026-05-08 12:20:24 +02:00
const cap = sanitizeInlineMediaCaption (
uploadTitle . trim ( ) || uploadFile . name || '' ,
)
onInserted ( Number ( mid ) , size , cap )
2026-05-08 12:00:02 +02:00
setUploadFile ( null )
setUploadTitle ( '' )
2026-05-08 12:20:24 +02:00
setUploadInputKey ( ( k ) => k + 1 )
2026-05-08 12:00:02 +02:00
onClose ( )
} catch ( e ) {
if ( e . code === 'MEDIA_ASSET_IN_TRASH' && e . payload ? . media _asset _id != null ) {
alert (
'Dieselbe Datei existiert bereits im Papierkorb — bitte in der Medienbibliothek reaktivieren oder eine andere Datei wählen.' ,
)
} else {
alert ( e . message || String ( e ) )
}
setErr ( e . message || String ( e ) )
} finally {
setBusy ( false )
}
}
2026-05-08 12:20:24 +02:00
const selectedLinked = selectedAssetId != null && assetToExerciseMedia . has ( Number ( selectedAssetId ) )
2026-05-08 12:00:02 +02:00
if ( ! open ) return null
return (
2026-05-11 08:44:37 +02:00
< div className = "admin-modal-backdrop" role = "presentation" onClick = { ( e ) => e . target === e . currentTarget && ! busy && onClose ( ) } >
2026-05-08 12:00:02 +02:00
< div
className = "admin-modal-sheet rte-inline-media-modal"
role = "dialog"
aria - modal = "true"
aria - labelledby = "rte-inline-file-title"
style = { {
maxWidth : '560px' ,
width : '100%' ,
maxHeight : '90vh' ,
display : 'flex' ,
flexDirection : 'column' ,
} }
onClick = { ( e ) => e . stopPropagation ( ) }
>
< div className = "admin-modal-sheet__header" >
< h3 id = "rte-inline-file-title" className = "admin-modal-sheet__title" >
Medium 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 : '10px 12px' , borderBottom : '1px solid var(--border)' , display : 'flex' , gap : '8px' , flexWrap : 'wrap' } } >
< button
type = "button"
className = { ` btn btn-secondary ${ tab === 'library' ? 'rte-tab--active' : '' } ` }
style = { { fontSize : '13px' } }
onClick = { ( ) => setTab ( 'library' ) }
disabled = { busy }
>
Aus Mediathek
< / button >
< button
type = "button"
className = { ` btn btn-secondary ${ tab === 'upload' ? 'rte-tab--active' : '' } ` }
style = { { fontSize : '13px' } }
onClick = { ( ) => setTab ( 'upload' ) }
disabled = { busy }
>
Neu hochladen
< / button >
< / div >
< div style = { { overflowY : 'auto' , flex : 1 , padding : '12px 14px' } } >
2026-05-08 12:20:24 +02:00
{ err && ! loading ? (
< p style = { { color : 'var(--danger)' , marginTop : '0' , marginBottom : '12px' , fontSize : '0.9rem' } } > { err } < / p >
) : null }
2026-05-08 12:00:02 +02:00
{ tab === 'library' && (
< >
< label className = "form-label" > Suche in der Bibliothek < / label >
< input
className = "form-input"
value = { q }
onChange = { ( e ) => setQ ( e . target . value ) }
placeholder = "Name, Tag, © …"
disabled = { busy }
/ >
{ loading ? < p style = { { color : 'var(--text3)' , marginTop : '12px' } } > Laden … < / p > : null }
< div className = "rte-inline-asset-grid" style = { { marginTop : '14px' } } >
{ items . map ( ( it ) => {
const id = it . id
const selected = selectedAssetId === id
const label = it . original _filename || it . copyright _notice || ` Archiv # ${ id } `
2026-05-08 12:20:24 +02:00
const linked = assetToExerciseMedia . has ( Number ( id ) )
2026-05-08 12:00:02 +02:00
return (
< button
key = { id }
type = "button"
className = { ` rte-inline-asset-tile ${ selected ? ' rte-inline-asset-tile--selected' : '' } ` }
onClick = { ( ) => setSelectedAssetId ( id ) }
disabled = { busy }
>
2026-05-08 12:20:24 +02:00
< div className = "rte-inline-asset-tile__thumb" aria - hidden >
2026-05-08 12:35:28 +02:00
< RtePickerAssetThumb asset = { it } / >
2026-05-08 12:20:24 +02:00
< / div >
{ linked ? (
< span className = "rte-inline-asset-tile__badge" > Bereits verknüpft < / span >
) : null }
2026-05-08 12:00:02 +02:00
< span className = "rte-inline-asset-tile__meta" >
{ ( it . mime _type || '' ) . split ( '/' ) [ 0 ] || 'datei' }
< / span >
< span className = "rte-inline-asset-tile__name" > { label } < / span >
< / button >
)
} ) }
< / div >
{ ! loading && items . length === 0 ? (
2026-05-11 08:44:37 +02:00
< p style = { { color : 'var(--text3)' , marginTop : '12px' } } > Keine Treffer — Suche anpassen oder „ Neu hochladen " . < / p >
2026-05-08 12:00:02 +02:00
) : null }
< / >
) }
{ tab === 'upload' && (
< >
< label className = "form-label" > Datei < / label >
2026-05-08 12:20:24 +02:00
< div className = "rte-inline-file-row" >
< input
key = { uploadInputKey }
id = "rte-inline-file-upload-input"
type = "file"
accept = "image/*,video/*,application/pdf"
className = "rte-inline-file-input-hidden"
disabled = { busy }
onChange = { ( e ) => {
const f = e . target . files ? . [ 0 ] || null
setUploadFile ( f )
} }
/ >
< label htmlFor = "rte-inline-file-upload-input" className = "btn btn-secondary rte-inline-file-pick-btn" >
Datei auswählen
< / label >
< span className = "rte-inline-file-name" title = { uploadFile ? . name || '' } >
{ uploadFile ? uploadFile . name : 'Keine Datei ausgewählt' }
< / span >
< / div >
2026-05-08 12:00:02 +02:00
< label className = "form-label" style = { { marginTop : '12px' } } >
Titel ( optional )
< / label >
< input
className = "form-input"
value = { uploadTitle }
onChange = { ( e ) => setUploadTitle ( e . target . value ) }
disabled = { busy }
/ >
2026-05-11 08:44:37 +02:00
{ /* P-06 Rechte-Erklärung */ }
< div style = { { marginTop : '18px' , paddingTop : '14px' , borderTop : '1px solid var(--border)' } } >
< p style = { { fontSize : '0.82rem' , fontWeight : 600 , marginBottom : '10px' , color : 'var(--text2)' } } >
Rechte - Erklärung < span style = { { fontWeight : 400 , color : 'var(--text3)' } } > ( VORLÄUFIG – p06 - v1 - conservative ) < / span >
< / p >
feat(p06): Copyright-Feld und Einwilligungskontext in Rechte-Erklaerung
Migration 049: 4 optionale TEXT-Spalten in media_asset_rights_declarations
(person_consent_context, parental_consent_context, music_rights_context,
third_party_rights_context) fuer Freitext zum Einwilligungskontext.
Backend:
- media_rights.py: write_rights_declaration speichert 4 Kontextfelder
- media_assets.py: copyright_notice + 4 Kontextfelder in Bulk-Upload,
RightsDeclarationBody, MediaAssetPatch, MediaBulkPatchBody
- exercises.py: copyright_notice + 4 Kontextfelder in upload_exercise_media,
wird in INSERT gespeichert
Frontend (alle 3 Formulare):
- RightsDeclarationDialog: Copyright-Eingabefeld (immer sichtbar) +
Freitext-Textarea bei jeder Ja-Antwort (Personen, Minderjaehrige,
Musik, Fremdinhalte)
- ExerciseInlineFileMediaModal: gleiche Felder inline im Upload-Tab
- ExerciseInlineEmbedModal: gleiche Felder inline
- api.js: copyright_notice + 4 Kontextfelder in bulkUploadMediaAssets
version: 0.8.77
module: media_rights 1.1.0, media_assets 1.14.0, exercises 2.21.0
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-11 09:06:47 +02:00
< div style = { { marginBottom : '10px' } } >
< label htmlFor = "up-cr" style = { { display : 'block' , fontSize : '0.82rem' , fontWeight : 600 , marginBottom : '3px' } } > Copyright - Angabe ( optional ) < / label >
< input id = "up-cr" type = "text" className = "form-input"
placeholder = "z.B. © 2026 Lars Stommer, CC BY-NC"
value = { decl . copyright _notice }
onChange = { ( e ) => setDeclField ( 'copyright_notice' , e . target . value ) }
disabled = { busy } style = { { fontSize : '0.85rem' } } / >
< / div >
2026-05-11 08:44:37 +02:00
< div style = { { display : 'flex' , alignItems : 'flex-start' , gap : '8px' , marginBottom : '10px' } } >
feat(p06): Copyright-Feld und Einwilligungskontext in Rechte-Erklaerung
Migration 049: 4 optionale TEXT-Spalten in media_asset_rights_declarations
(person_consent_context, parental_consent_context, music_rights_context,
third_party_rights_context) fuer Freitext zum Einwilligungskontext.
Backend:
- media_rights.py: write_rights_declaration speichert 4 Kontextfelder
- media_assets.py: copyright_notice + 4 Kontextfelder in Bulk-Upload,
RightsDeclarationBody, MediaAssetPatch, MediaBulkPatchBody
- exercises.py: copyright_notice + 4 Kontextfelder in upload_exercise_media,
wird in INSERT gespeichert
Frontend (alle 3 Formulare):
- RightsDeclarationDialog: Copyright-Eingabefeld (immer sichtbar) +
Freitext-Textarea bei jeder Ja-Antwort (Personen, Minderjaehrige,
Musik, Fremdinhalte)
- ExerciseInlineFileMediaModal: gleiche Felder inline im Upload-Tab
- ExerciseInlineEmbedModal: gleiche Felder inline
- api.js: copyright_notice + 4 Kontextfelder in bulkUploadMediaAssets
version: 0.8.77
module: media_rights 1.1.0, media_assets 1.14.0, exercises 2.21.0
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-11 09:06:47 +02:00
< input type = "checkbox" id = "up-rhc" checked = { decl . rights _holder _confirmed }
2026-05-11 08:44:37 +02:00
onChange = { ( e ) => setDeclField ( 'rights_holder_confirmed' , e . target . checked ) }
feat(p06): Copyright-Feld und Einwilligungskontext in Rechte-Erklaerung
Migration 049: 4 optionale TEXT-Spalten in media_asset_rights_declarations
(person_consent_context, parental_consent_context, music_rights_context,
third_party_rights_context) fuer Freitext zum Einwilligungskontext.
Backend:
- media_rights.py: write_rights_declaration speichert 4 Kontextfelder
- media_assets.py: copyright_notice + 4 Kontextfelder in Bulk-Upload,
RightsDeclarationBody, MediaAssetPatch, MediaBulkPatchBody
- exercises.py: copyright_notice + 4 Kontextfelder in upload_exercise_media,
wird in INSERT gespeichert
Frontend (alle 3 Formulare):
- RightsDeclarationDialog: Copyright-Eingabefeld (immer sichtbar) +
Freitext-Textarea bei jeder Ja-Antwort (Personen, Minderjaehrige,
Musik, Fremdinhalte)
- ExerciseInlineFileMediaModal: gleiche Felder inline im Upload-Tab
- ExerciseInlineEmbedModal: gleiche Felder inline
- api.js: copyright_notice + 4 Kontextfelder in bulkUploadMediaAssets
version: 0.8.77
module: media_rights 1.1.0, media_assets 1.14.0, exercises 2.21.0
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-11 09:06:47 +02:00
disabled = { busy } style = { { marginTop : '3px' , flexShrink : 0 } } / >
2026-05-11 08:44:37 +02:00
< label htmlFor = "up-rhc" style = { { fontSize : '0.85rem' } } >
Ich bestätige , dass ich die erforderlichen Rechte an diesem Medium besitze oder zur Veröffentlichung berechtigt bin . *
< / label >
< / div >
< fieldset style = { { border : 'none' , padding : 0 , marginBottom : '10px' } } >
< legend style = { { fontSize : '0.85rem' , fontWeight : 600 , marginBottom : '4px' } } > Erkennbare Personen abgebildet ? * < / legend >
feat(p06): Copyright-Feld und Einwilligungskontext in Rechte-Erklaerung
Migration 049: 4 optionale TEXT-Spalten in media_asset_rights_declarations
(person_consent_context, parental_consent_context, music_rights_context,
third_party_rights_context) fuer Freitext zum Einwilligungskontext.
Backend:
- media_rights.py: write_rights_declaration speichert 4 Kontextfelder
- media_assets.py: copyright_notice + 4 Kontextfelder in Bulk-Upload,
RightsDeclarationBody, MediaAssetPatch, MediaBulkPatchBody
- exercises.py: copyright_notice + 4 Kontextfelder in upload_exercise_media,
wird in INSERT gespeichert
Frontend (alle 3 Formulare):
- RightsDeclarationDialog: Copyright-Eingabefeld (immer sichtbar) +
Freitext-Textarea bei jeder Ja-Antwort (Personen, Minderjaehrige,
Musik, Fremdinhalte)
- ExerciseInlineFileMediaModal: gleiche Felder inline im Upload-Tab
- ExerciseInlineEmbedModal: gleiche Felder inline
- api.js: copyright_notice + 4 Kontextfelder in bulkUploadMediaAssets
version: 0.8.77
module: media_rights 1.1.0, media_assets 1.14.0, exercises 2.21.0
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-11 09:06:47 +02:00
< div style = { { display : 'flex' , gap : '14px' , marginBottom : '4px' } } >
< label style = { { fontSize : '0.85rem' } } > < input type = "radio" name = "up-cip" checked = { decl . contains _identifiable _persons === true } onChange = { ( ) => setDeclField ( 'contains_identifiable_persons' , true ) } disabled = { busy } / > Ja < / label >
< label style = { { fontSize : '0.85rem' } } > < input type = "radio" name = "up-cip" checked = { decl . contains _identifiable _persons === false } onChange = { ( ) => setDeclField ( 'contains_identifiable_persons' , false ) } disabled = { busy } / > Nein < / label >
2026-05-11 08:44:37 +02:00
< / div >
{ decl . contains _identifiable _persons === true && (
feat(p06): Copyright-Feld und Einwilligungskontext in Rechte-Erklaerung
Migration 049: 4 optionale TEXT-Spalten in media_asset_rights_declarations
(person_consent_context, parental_consent_context, music_rights_context,
third_party_rights_context) fuer Freitext zum Einwilligungskontext.
Backend:
- media_rights.py: write_rights_declaration speichert 4 Kontextfelder
- media_assets.py: copyright_notice + 4 Kontextfelder in Bulk-Upload,
RightsDeclarationBody, MediaAssetPatch, MediaBulkPatchBody
- exercises.py: copyright_notice + 4 Kontextfelder in upload_exercise_media,
wird in INSERT gespeichert
Frontend (alle 3 Formulare):
- RightsDeclarationDialog: Copyright-Eingabefeld (immer sichtbar) +
Freitext-Textarea bei jeder Ja-Antwort (Personen, Minderjaehrige,
Musik, Fremdinhalte)
- ExerciseInlineFileMediaModal: gleiche Felder inline im Upload-Tab
- ExerciseInlineEmbedModal: gleiche Felder inline
- api.js: copyright_notice + 4 Kontextfelder in bulkUploadMediaAssets
version: 0.8.77
module: media_rights 1.1.0, media_assets 1.14.0, exercises 2.21.0
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-11 09:06:47 +02:00
< div style = { { paddingLeft : 2 } } >
< div style = { { display : 'flex' , alignItems : 'flex-start' , gap : '8px' , marginBottom : '4px' } } >
< input type = "checkbox" id = "up-pcc" checked = { decl . person _consent _confirmed } onChange = { ( e ) => setDeclField ( 'person_consent_confirmed' , e . target . checked ) } disabled = { busy } style = { { marginTop : '3px' , flexShrink : 0 } } / >
< label htmlFor = "up-pcc" style = { { fontSize : '0.85rem' } } > Einwilligungen aller erkennbaren Personen liegen vor . * < / label >
< / div >
< label style = { { display : 'block' , fontSize : '0.8rem' , color : 'var(--text2)' , marginBottom : '2px' } } > Einwilligungskontext ( optional ) < / label >
< textarea className = "form-input" rows = { 2 } placeholder = "z.B. Schriftliche Einwilligung vom 2026-05-01" value = { decl . person _consent _context } onChange = { ( e ) => setDeclField ( 'person_consent_context' , e . target . value ) } disabled = { busy } style = { { fontSize : '0.82rem' , resize : 'vertical' } } / >
2026-05-11 08:44:37 +02:00
< / div >
) }
< / fieldset >
< fieldset style = { { border : 'none' , padding : 0 , marginBottom : '10px' } } >
< legend style = { { fontSize : '0.85rem' , fontWeight : 600 , marginBottom : '4px' } } > Minderjährige abgebildet ? * < / legend >
feat(p06): Copyright-Feld und Einwilligungskontext in Rechte-Erklaerung
Migration 049: 4 optionale TEXT-Spalten in media_asset_rights_declarations
(person_consent_context, parental_consent_context, music_rights_context,
third_party_rights_context) fuer Freitext zum Einwilligungskontext.
Backend:
- media_rights.py: write_rights_declaration speichert 4 Kontextfelder
- media_assets.py: copyright_notice + 4 Kontextfelder in Bulk-Upload,
RightsDeclarationBody, MediaAssetPatch, MediaBulkPatchBody
- exercises.py: copyright_notice + 4 Kontextfelder in upload_exercise_media,
wird in INSERT gespeichert
Frontend (alle 3 Formulare):
- RightsDeclarationDialog: Copyright-Eingabefeld (immer sichtbar) +
Freitext-Textarea bei jeder Ja-Antwort (Personen, Minderjaehrige,
Musik, Fremdinhalte)
- ExerciseInlineFileMediaModal: gleiche Felder inline im Upload-Tab
- ExerciseInlineEmbedModal: gleiche Felder inline
- api.js: copyright_notice + 4 Kontextfelder in bulkUploadMediaAssets
version: 0.8.77
module: media_rights 1.1.0, media_assets 1.14.0, exercises 2.21.0
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-11 09:06:47 +02:00
< div style = { { display : 'flex' , gap : '14px' , marginBottom : '4px' } } >
< label style = { { fontSize : '0.85rem' } } > < input type = "radio" name = "up-cm" checked = { decl . contains _minors === true } onChange = { ( ) => setDeclField ( 'contains_minors' , true ) } disabled = { busy } / > Ja < / label >
< label style = { { fontSize : '0.85rem' } } > < input type = "radio" name = "up-cm" checked = { decl . contains _minors === false } onChange = { ( ) => setDeclField ( 'contains_minors' , false ) } disabled = { busy } / > Nein < / label >
2026-05-11 08:44:37 +02:00
< / div >
{ decl . contains _minors === true && (
feat(p06): Copyright-Feld und Einwilligungskontext in Rechte-Erklaerung
Migration 049: 4 optionale TEXT-Spalten in media_asset_rights_declarations
(person_consent_context, parental_consent_context, music_rights_context,
third_party_rights_context) fuer Freitext zum Einwilligungskontext.
Backend:
- media_rights.py: write_rights_declaration speichert 4 Kontextfelder
- media_assets.py: copyright_notice + 4 Kontextfelder in Bulk-Upload,
RightsDeclarationBody, MediaAssetPatch, MediaBulkPatchBody
- exercises.py: copyright_notice + 4 Kontextfelder in upload_exercise_media,
wird in INSERT gespeichert
Frontend (alle 3 Formulare):
- RightsDeclarationDialog: Copyright-Eingabefeld (immer sichtbar) +
Freitext-Textarea bei jeder Ja-Antwort (Personen, Minderjaehrige,
Musik, Fremdinhalte)
- ExerciseInlineFileMediaModal: gleiche Felder inline im Upload-Tab
- ExerciseInlineEmbedModal: gleiche Felder inline
- api.js: copyright_notice + 4 Kontextfelder in bulkUploadMediaAssets
version: 0.8.77
module: media_rights 1.1.0, media_assets 1.14.0, exercises 2.21.0
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-11 09:06:47 +02:00
< div style = { { paddingLeft : 2 } } >
< div style = { { display : 'flex' , alignItems : 'flex-start' , gap : '8px' , marginBottom : '4px' } } >
< input type = "checkbox" id = "up-pcc2" checked = { decl . parental _consent _confirmed } onChange = { ( e ) => setDeclField ( 'parental_consent_confirmed' , e . target . checked ) } disabled = { busy } style = { { marginTop : '3px' , flexShrink : 0 } } / >
< label htmlFor = "up-pcc2" style = { { fontSize : '0.85rem' } } > Einwilligungen der Sorgeberechtigten liegen vor . * < / label >
< / div >
< label style = { { display : 'block' , fontSize : '0.8rem' , color : 'var(--text2)' , marginBottom : '2px' } } > Einwilligungskontext ( optional ) < / label >
< textarea className = "form-input" rows = { 2 } placeholder = "z.B. Elterliche Einwilligung per E-Mail vom 2026-04-15" value = { decl . parental _consent _context } onChange = { ( e ) => setDeclField ( 'parental_consent_context' , e . target . value ) } disabled = { busy } style = { { fontSize : '0.82rem' , resize : 'vertical' } } / >
2026-05-11 08:44:37 +02:00
< / div >
) }
< / fieldset >
< fieldset style = { { border : 'none' , padding : 0 , marginBottom : '10px' } } >
< legend style = { { fontSize : '0.85rem' , fontWeight : 600 , marginBottom : '4px' } } > Musik enthalten ? * < / legend >
feat(p06): Copyright-Feld und Einwilligungskontext in Rechte-Erklaerung
Migration 049: 4 optionale TEXT-Spalten in media_asset_rights_declarations
(person_consent_context, parental_consent_context, music_rights_context,
third_party_rights_context) fuer Freitext zum Einwilligungskontext.
Backend:
- media_rights.py: write_rights_declaration speichert 4 Kontextfelder
- media_assets.py: copyright_notice + 4 Kontextfelder in Bulk-Upload,
RightsDeclarationBody, MediaAssetPatch, MediaBulkPatchBody
- exercises.py: copyright_notice + 4 Kontextfelder in upload_exercise_media,
wird in INSERT gespeichert
Frontend (alle 3 Formulare):
- RightsDeclarationDialog: Copyright-Eingabefeld (immer sichtbar) +
Freitext-Textarea bei jeder Ja-Antwort (Personen, Minderjaehrige,
Musik, Fremdinhalte)
- ExerciseInlineFileMediaModal: gleiche Felder inline im Upload-Tab
- ExerciseInlineEmbedModal: gleiche Felder inline
- api.js: copyright_notice + 4 Kontextfelder in bulkUploadMediaAssets
version: 0.8.77
module: media_rights 1.1.0, media_assets 1.14.0, exercises 2.21.0
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-11 09:06:47 +02:00
< div style = { { display : 'flex' , gap : '14px' , marginBottom : '4px' } } >
< label style = { { fontSize : '0.85rem' } } > < input type = "radio" name = "up-cmu" checked = { decl . contains _music === true } onChange = { ( ) => setDeclField ( 'contains_music' , true ) } disabled = { busy } / > Ja < / label >
< label style = { { fontSize : '0.85rem' } } > < input type = "radio" name = "up-cmu" checked = { decl . contains _music === false } onChange = { ( ) => setDeclField ( 'contains_music' , false ) } disabled = { busy } / > Nein < / label >
2026-05-11 08:44:37 +02:00
< / div >
{ decl . contains _music === true && (
feat(p06): Copyright-Feld und Einwilligungskontext in Rechte-Erklaerung
Migration 049: 4 optionale TEXT-Spalten in media_asset_rights_declarations
(person_consent_context, parental_consent_context, music_rights_context,
third_party_rights_context) fuer Freitext zum Einwilligungskontext.
Backend:
- media_rights.py: write_rights_declaration speichert 4 Kontextfelder
- media_assets.py: copyright_notice + 4 Kontextfelder in Bulk-Upload,
RightsDeclarationBody, MediaAssetPatch, MediaBulkPatchBody
- exercises.py: copyright_notice + 4 Kontextfelder in upload_exercise_media,
wird in INSERT gespeichert
Frontend (alle 3 Formulare):
- RightsDeclarationDialog: Copyright-Eingabefeld (immer sichtbar) +
Freitext-Textarea bei jeder Ja-Antwort (Personen, Minderjaehrige,
Musik, Fremdinhalte)
- ExerciseInlineFileMediaModal: gleiche Felder inline im Upload-Tab
- ExerciseInlineEmbedModal: gleiche Felder inline
- api.js: copyright_notice + 4 Kontextfelder in bulkUploadMediaAssets
version: 0.8.77
module: media_rights 1.1.0, media_assets 1.14.0, exercises 2.21.0
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-11 09:06:47 +02:00
< div style = { { paddingLeft : 2 } } >
< div style = { { display : 'flex' , alignItems : 'flex-start' , gap : '8px' , marginBottom : '4px' } } >
< input type = "checkbox" id = "up-mrc" checked = { decl . music _rights _confirmed } onChange = { ( e ) => setDeclField ( 'music_rights_confirmed' , e . target . checked ) } disabled = { busy } style = { { marginTop : '3px' , flexShrink : 0 } } / >
< label htmlFor = "up-mrc" style = { { fontSize : '0.85rem' } } > Musikrechte ( GEMA / Lizenz ) liegen vor . * < / label >
< / div >
< label style = { { display : 'block' , fontSize : '0.8rem' , color : 'var(--text2)' , marginBottom : '2px' } } > Lizenz / GEMA - Kontext ( optional ) < / label >
< textarea className = "form-input" rows = { 2 } placeholder = "z.B. CC BY 4.0 oder GEMA-Freimeldung Nr. …" value = { decl . music _rights _context } onChange = { ( e ) => setDeclField ( 'music_rights_context' , e . target . value ) } disabled = { busy } style = { { fontSize : '0.82rem' , resize : 'vertical' } } / >
2026-05-11 08:44:37 +02:00
< / div >
) }
< / fieldset >
< fieldset style = { { border : 'none' , padding : 0 , marginBottom : '4px' } } >
< legend style = { { fontSize : '0.85rem' , fontWeight : 600 , marginBottom : '4px' } } > Fremde geschützte Inhalte ( Logos , Grafiken , Fotos Dritter ) ? * < / legend >
feat(p06): Copyright-Feld und Einwilligungskontext in Rechte-Erklaerung
Migration 049: 4 optionale TEXT-Spalten in media_asset_rights_declarations
(person_consent_context, parental_consent_context, music_rights_context,
third_party_rights_context) fuer Freitext zum Einwilligungskontext.
Backend:
- media_rights.py: write_rights_declaration speichert 4 Kontextfelder
- media_assets.py: copyright_notice + 4 Kontextfelder in Bulk-Upload,
RightsDeclarationBody, MediaAssetPatch, MediaBulkPatchBody
- exercises.py: copyright_notice + 4 Kontextfelder in upload_exercise_media,
wird in INSERT gespeichert
Frontend (alle 3 Formulare):
- RightsDeclarationDialog: Copyright-Eingabefeld (immer sichtbar) +
Freitext-Textarea bei jeder Ja-Antwort (Personen, Minderjaehrige,
Musik, Fremdinhalte)
- ExerciseInlineFileMediaModal: gleiche Felder inline im Upload-Tab
- ExerciseInlineEmbedModal: gleiche Felder inline
- api.js: copyright_notice + 4 Kontextfelder in bulkUploadMediaAssets
version: 0.8.77
module: media_rights 1.1.0, media_assets 1.14.0, exercises 2.21.0
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-11 09:06:47 +02:00
< div style = { { display : 'flex' , gap : '14px' , marginBottom : '4px' } } >
< label style = { { fontSize : '0.85rem' } } > < input type = "radio" name = "up-ctpc" checked = { decl . contains _third _party _content === true } onChange = { ( ) => setDeclField ( 'contains_third_party_content' , true ) } disabled = { busy } / > Ja < / label >
< label style = { { fontSize : '0.85rem' } } > < input type = "radio" name = "up-ctpc" checked = { decl . contains _third _party _content === false } onChange = { ( ) => setDeclField ( 'contains_third_party_content' , false ) } disabled = { busy } / > Nein < / label >
2026-05-11 08:44:37 +02:00
< / div >
{ decl . contains _third _party _content === true && (
feat(p06): Copyright-Feld und Einwilligungskontext in Rechte-Erklaerung
Migration 049: 4 optionale TEXT-Spalten in media_asset_rights_declarations
(person_consent_context, parental_consent_context, music_rights_context,
third_party_rights_context) fuer Freitext zum Einwilligungskontext.
Backend:
- media_rights.py: write_rights_declaration speichert 4 Kontextfelder
- media_assets.py: copyright_notice + 4 Kontextfelder in Bulk-Upload,
RightsDeclarationBody, MediaAssetPatch, MediaBulkPatchBody
- exercises.py: copyright_notice + 4 Kontextfelder in upload_exercise_media,
wird in INSERT gespeichert
Frontend (alle 3 Formulare):
- RightsDeclarationDialog: Copyright-Eingabefeld (immer sichtbar) +
Freitext-Textarea bei jeder Ja-Antwort (Personen, Minderjaehrige,
Musik, Fremdinhalte)
- ExerciseInlineFileMediaModal: gleiche Felder inline im Upload-Tab
- ExerciseInlineEmbedModal: gleiche Felder inline
- api.js: copyright_notice + 4 Kontextfelder in bulkUploadMediaAssets
version: 0.8.77
module: media_rights 1.1.0, media_assets 1.14.0, exercises 2.21.0
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-11 09:06:47 +02:00
< div style = { { paddingLeft : 2 } } >
< div style = { { display : 'flex' , alignItems : 'flex-start' , gap : '8px' , marginBottom : '4px' } } >
< input type = "checkbox" id = "up-tprc" checked = { decl . third _party _rights _confirmed } onChange = { ( e ) => setDeclField ( 'third_party_rights_confirmed' , e . target . checked ) } disabled = { busy } style = { { marginTop : '3px' , flexShrink : 0 } } / >
< label htmlFor = "up-tprc" style = { { fontSize : '0.85rem' } } > Rechte an allen enthaltenen Fremdmaterialien liegen vor . * < / label >
< / div >
< label style = { { display : 'block' , fontSize : '0.8rem' , color : 'var(--text2)' , marginBottom : '2px' } } > Rechtsgrundlage ( optional ) < / label >
< textarea className = "form-input" rows = { 2 } placeholder = "z.B. Verbandslogo mit Genehmigung vom 2026-03-10" value = { decl . third _party _rights _context } onChange = { ( e ) => setDeclField ( 'third_party_rights_context' , e . target . value ) } disabled = { busy } style = { { fontSize : '0.82rem' , resize : 'vertical' } } / >
2026-05-11 08:44:37 +02:00
< / div >
) }
< / fieldset >
{ declErr && (
< p style = { { color : 'var(--danger)' , fontSize : '0.85rem' , marginTop : '8px' } } > { declErr } < / p >
) }
< / div >
2026-05-08 12:00:02 +02:00
< / >
) }
< / div >
< div style = { { padding : '12px 14px' , borderTop : '1px solid var(--border)' , flexShrink : 0 } } >
< div className = "form-row" style = { { marginBottom : '12px' } } >
< label className = "form-label" style = { { marginBottom : '4px' } } >
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 >
< / div >
{ tab === 'library' ? (
2026-05-08 12:20:24 +02:00
< button
type = "button"
className = "btn btn-primary btn-full"
disabled = { busy || ! selectedAssetId }
onClick = { handleLinkSelected }
>
{ busy ? 'Bitte warten…' : selectedLinked ? 'In Text einfügen (bereits verknüpft)' : 'Verknüpfen & in Text einfügen' }
2026-05-08 12:00:02 +02:00
< / button >
) : (
< button type = "button" className = "btn btn-primary btn-full" disabled = { busy || ! uploadFile } onClick = { handleUploadAndInsert } >
{ busy ? 'Hochladen…' : 'Hochladen & in Text einfügen' }
< / button >
) }
< / div >
< / div >
< / div >
)
}