From 5cf775c920ab1a61ac25fab8632058b5e5015ced Mon Sep 17 00:00:00 2001 From: Lars Date: Fri, 8 May 2026 12:35:28 +0200 Subject: [PATCH] feat(exercises): bump version to 0.8.64 and enhance media handling - Incremented application version to 0.8.64 and updated changelog with new features. - Improved media handling in the Rich Text Editor with auto-scrolling during drag-and-drop. - Added new CSS styles for video thumbnails and enhanced layout for media items. - Removed deprecated `ExerciseAttachmentMediaStrip` from the ExerciseFullContent component. - Updated ExerciseFormPage to manage form dirty state and prevent data loss on navigation. --- backend/version.py | 9 +- frontend/src/app.css | 21 +++-- .../src/components/ExerciseFullContent.jsx | 2 - .../ExerciseInlineFileMediaModal.jsx | 46 +++++++--- frontend/src/components/RichTextEditor.jsx | 2 + frontend/src/pages/ExerciseDetailPage.jsx | 14 +-- frontend/src/pages/ExerciseFormPage.jsx | 90 +++++++++++-------- frontend/src/utils/dragAutoScroll.js | 66 ++++++++++++++ 8 files changed, 185 insertions(+), 65 deletions(-) create mode 100644 frontend/src/utils/dragAutoScroll.js diff --git a/backend/version.py b/backend/version.py index e26af3f..1272717 100644 --- a/backend/version.py +++ b/backend/version.py @@ -1,6 +1,6 @@ # Shinkan Jinkendo Version Information -APP_VERSION = "0.8.63" +APP_VERSION = "0.8.64" BUILD_DATE = "2026-05-08" DB_SCHEMA_VERSION = "20260508049" @@ -29,6 +29,13 @@ MODULE_VERSIONS = { } CHANGELOG = [ + { + "version": "0.8.64", + "date": "2026-05-08", + "changes": [ + "Übung bearbeiten: Auto-Scroll beim Drag von Medien zu Textfeldern; Medien-Kacheln mehrspaltig; Sektion/Ablauf-Zuordnung an Medien entfernt (nur noch Titel bearbeiten); Picker: Video-Vorschau-Frame; Katalogvorschau ohne Anhangs-Medienliste; Ansehen mit Speichern-Hinweis + Zurück zur Bearbeitung von der Ansicht", + ], + }, { "version": "0.8.63", "date": "2026-05-08", diff --git a/frontend/src/app.css b/frontend/src/app.css index aad7f2a..bccdf36 100644 --- a/frontend/src/app.css +++ b/frontend/src/app.css @@ -4018,6 +4018,13 @@ a.analysis-split__nav-item { height: 100%; object-fit: cover; } +.rte-inline-asset-tile__thumb-video { + width: 100%; + height: 100%; + object-fit: cover; + display: block; + pointer-events: none; +} .rte-inline-asset-tile__thumb-fallback { font-size: 11px; color: var(--text3); @@ -4066,9 +4073,10 @@ a.analysis-split__nav-item { list-style: none; padding: 0; margin: 14px 0 0; - display: flex; - flex-direction: column; - gap: 10px; + display: grid; + grid-template-columns: repeat(auto-fill, minmax(300px, 1fr)); + gap: 12px; + align-items: stretch; } .exercise-edit-media-strip__item { display: flex; @@ -4129,15 +4137,10 @@ a.analysis-split__nav-item { } .exercise-edit-media-strip__toolbar { display: grid; - grid-template-columns: 1fr minmax(120px, 160px); + grid-template-columns: 1fr; gap: 8px; margin-top: 8px; } -@media (max-width: 520px) { - .exercise-edit-media-strip__toolbar { - grid-template-columns: 1fr; - } -} .exercise-edit-media-strip__actions { display: flex; flex-wrap: wrap; diff --git a/frontend/src/components/ExerciseFullContent.jsx b/frontend/src/components/ExerciseFullContent.jsx index 70b83e4..73253b8 100644 --- a/frontend/src/components/ExerciseFullContent.jsx +++ b/frontend/src/components/ExerciseFullContent.jsx @@ -4,7 +4,6 @@ import React from 'react' import { Link } from 'react-router-dom' import ExerciseRichTextBlock from './ExerciseRichTextBlock' -import ExerciseAttachmentMediaStrip from './ExerciseAttachmentMediaStrip' function TagRow({ exercise }) { const tags = [] @@ -118,7 +117,6 @@ export default function ExerciseFullContent({ exercise, loading, error, exercise )} - {exercise.trainer_notes && (

diff --git a/frontend/src/components/ExerciseInlineFileMediaModal.jsx b/frontend/src/components/ExerciseInlineFileMediaModal.jsx index 7662079..8569cd8 100644 --- a/frontend/src/components/ExerciseInlineFileMediaModal.jsx +++ b/frontend/src/components/ExerciseInlineFileMediaModal.jsx @@ -11,6 +11,41 @@ import { } from '../constants/inlineExerciseMedia' import { sanitizeInlineMediaCaption } from '../utils/inlineMediaCaption' +function RtePickerAssetThumb({ asset }) { + const id = asset.id + const src = resolveMediaAssetFileUrl(id) + const mt = (asset.mime_type || '').toLowerCase() + if (mt.startsWith('image/') && src) { + return + } + if (mt.startsWith('video/') && src) { + return ( +