diff --git a/backend/version.py b/backend/version.py index 23aaf67..06d1d39 100644 --- a/backend/version.py +++ b/backend/version.py @@ -1,6 +1,6 @@ # Shinkan Jinkendo Version Information -APP_VERSION = "0.8.87" +APP_VERSION = "0.8.88" BUILD_DATE = "2026-05-11" DB_SCHEMA_VERSION = "20260511052" @@ -30,10 +30,19 @@ MODULE_VERSIONS = { "membership": "1.0.0", "catalogs": "1.5.0", # Updated: Trainer Contexts API (Migration 012) "maturity_models": "1.4.0", # matrix_stack_bundle: vollständiger Katalog+Modelle+Bindings Export/Import - "content_reports": "1.0.0", # P-13: Content-Melde-Backend (DSA-konform, Inbox-Integration, P-11 Legal Hold) + "content_reports": "1.1.0", # P-13: Melde-Button in Medienbibliothek + ExerciseAttachmentMediaStrip } CHANGELOG = [ + { + "version": "0.8.88", + "date": "2026-05-11", + "changes": [ + "Feat P-13: Melde-Button in Medienbibliothek (Grid + Liste) — öffnet ReportContentModal; nur aktive Medien ohne Legal Hold.", + "Feat P-13: Melde-Link an jedem Medium in ExerciseAttachmentMediaStrip (Lesemodus Übung).", + "Feat P-13: ReportContentModal — wiederverwendbares Formular (Grund, Beschreibung, Name, E-Mail, Gutglaubenserklärung); Vorausfüllung für eingeloggte Nutzer.", + ], + }, { "version": "0.8.87", "date": "2026-05-11", diff --git a/frontend/src/components/ExerciseAttachmentMediaStrip.jsx b/frontend/src/components/ExerciseAttachmentMediaStrip.jsx index 503868a..aa6d697 100644 --- a/frontend/src/components/ExerciseAttachmentMediaStrip.jsx +++ b/frontend/src/components/ExerciseAttachmentMediaStrip.jsx @@ -4,6 +4,7 @@ import React, { useMemo, useState } from 'react' import ExerciseMediaEmbed from './ExerciseMediaEmbed' import ExerciseMediaThumbTile from './ExerciseMediaThumbTile' +import ReportContentModal from './ReportContentModal' import { resolveExerciseMediaFileUrl } from '../utils/exerciseMediaUrl' import { collectInlineExerciseMediaIdsFromExercise, @@ -15,6 +16,7 @@ function isTrashHidden(m) { export default function ExerciseAttachmentMediaStrip({ exerciseId, exercise }) { const [preview, setPreview] = useState(null) + const [reportTarget, setReportTarget] = useState(null) const inlineIds = useMemo(() => collectInlineExerciseMediaIdsFromExercise(exercise), [exercise]) const orphans = useMemo(() => { @@ -56,10 +58,37 @@ export default function ExerciseAttachmentMediaStrip({ exerciseId, exercise }) { + {(m.asset_lifecycle_state || 'active') === 'active' && !m.asset_legal_hold_active && ( + setReportTarget(m)} + style={{ + background: 'none', + border: 'none', + padding: '4px 0 0', + cursor: 'pointer', + fontSize: '0.72rem', + color: 'var(--text3)', + textDecoration: 'underline', + textAlign: 'left', + }} + > + Inhalt melden + + )} ) })} + {reportTarget && ( + setReportTarget(null)} + /> + )} + {preview && ( { if (e.target === e.currentTarget) onClose() }} + > + + + Inhalt melden + ✕ + + + {targetLabel && ( + + {targetType === 'media_asset' ? 'Medium' : 'Übung'}: {targetLabel} + + )} + + {success ? ( + + Meldung eingegangen. + + Deine Meldung wurde gespeichert und wird von unseren Moderatoren geprüft. Vielen Dank. + + + Schließen + + + ) : ( + + + Meldegrund * + setReason(e.target.value)} + required + > + {REASON_OPTIONS.map((o) => ( + {o.label} + ))} + + + + + Beschreibung * (mind. 10 Zeichen) + setDescription(e.target.value)} + placeholder="Bitte erläutere kurz, warum du diesen Inhalt meldest." + required + /> + + + + Dein Name * + setName(e.target.value)} + required + /> + + + + Deine E-Mail-Adresse * + setEmail(e.target.value)} + required + /> + + + + + setConfirmed(e.target.checked)} + style={{ marginTop: '3px', flexShrink: 0 }} + /> + + Ich melde diesen Inhalt nach bestem Wissen und Gewissen. Ich bestätige, dass meine Meldung + nicht missbräuchlich ist und ich der Überzeugung bin, dass der gemeldete Inhalt rechtswidrig + ist oder gegen die Nutzungsbedingungen verstößt. + + + + + {error && ( + {error} + )} + + + + {saving ? 'Wird gesendet…' : 'Meldung absenden'} + + + Abbrechen + + + + )} + + + ) +} diff --git a/frontend/src/pages/MediaLibraryPage.jsx b/frontend/src/pages/MediaLibraryPage.jsx index 3244781..885581e 100644 --- a/frontend/src/pages/MediaLibraryPage.jsx +++ b/frontend/src/pages/MediaLibraryPage.jsx @@ -25,6 +25,7 @@ import api from '../utils/api' import { activeClubMemberships } from '../utils/activeClub' import { resolveMediaAssetFileUrl } from '../utils/exerciseMediaUrl' import RightsDeclarationDialog from '../components/RightsDeclarationDialog' +import ReportContentModal from '../components/ReportContentModal' const LC_OPTIONS = [ { value: 'active', label: 'Aktiv' }, @@ -317,6 +318,7 @@ export default function MediaLibraryPage() { const [bulkApplyVis, setBulkApplyVis] = useState(false) const [busy, setBusy] = useState(false) const [preview, setPreview] = useState(null) + const [reportTarget, setReportTarget] = useState(null) const [mediaKind, setMediaKind] = useState('all') const [filterClubId, setFilterClubId] = useState('') const [filterUploaderId, setFilterUploaderId] = useState('') @@ -986,6 +988,24 @@ export default function MediaLibraryPage() { ) : null} + {(it.lifecycle_state || 'active') === 'active' && !it.legal_hold_active && ( + setReportTarget(it)} + style={{ + background: 'none', + border: 'none', + padding: '2px 0 0', + cursor: 'pointer', + fontSize: '0.72rem', + color: 'var(--text3)', + textDecoration: 'underline', + textAlign: 'left', + }} + > + Melden + + )} ) @@ -1045,7 +1065,7 @@ export default function MediaLibraryPage() { {viewer?.show_uploader_meta ? ( {uploaderLabel(it, viewer) || '—'} ) : null} - + + {(it.lifecycle_state || 'active') === 'active' && !it.legal_hold_active && ( + setReportTarget(it)} + aria-label="Melden" + title="Inhalt melden" + style={{ color: 'var(--text3)', fontSize: '0.72rem', padding: '4px 6px' }} + > + Melden + + )} ) @@ -2026,6 +2058,15 @@ export default function MediaLibraryPage() { ) : null} + + {reportTarget && ( + setReportTarget(null)} + /> + )} ) } diff --git a/frontend/src/version.js b/frontend/src/version.js index 5aa6e1e..8efb9c6 100644 --- a/frontend/src/version.js +++ b/frontend/src/version.js @@ -1,6 +1,6 @@ // Shinkan Jinkendo Frontend Version -export const APP_VERSION = "0.8.87" +export const APP_VERSION = "0.8.88" export const BUILD_DATE = "2026-05-11" export const PAGE_VERSIONS = { @@ -26,4 +26,6 @@ export const PAGE_VERSIONS = { ExerciseMediaEmbed: "1.1.0", // P-11: Legal-Hold-Placeholder statt Datei ExerciseMediaThumbTile: "1.1.0", // P-11: Legal-Hold-Kachel statt Datei-Vorschau InboxPage: "2.0.0", // P-13: Inhaltsmeldungen-Abschnitt integriert + MediaLibraryPage: "1.7.0", // P-13: Melde-Button an Medienkacheln + ExerciseAttachmentMediaStrip: "1.1.0", // P-13: Melde-Link an angehängten Medien }
+ {targetType === 'media_asset' ? 'Medium' : 'Übung'}: {targetLabel} +
Meldung eingegangen.
+ Deine Meldung wurde gespeichert und wird von unseren Moderatoren geprüft. Vielen Dank. +
{error}