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.
86 lines
2.5 KiB
JavaScript
86 lines
2.5 KiB
JavaScript
/**
|
|
* §11 Inline-Medien: aus HTML / Übungsobjekt referenzierte exercise_media-IDs sammeln.
|
|
*/
|
|
|
|
const DATA_ATTR_RE = /data-shinkan-exercise-media\s*=\s*["']?(\d+)/gi
|
|
|
|
export const SHINKAN_EXERCISE_MEDIA_DRAG_MIME = 'application/x-shinkan-exercise-media'
|
|
|
|
/**
|
|
* @param {string|null|undefined} html
|
|
* @returns {Set<number>}
|
|
*/
|
|
export function collectInlineExerciseMediaIdsFromHtml(html) {
|
|
const ids = new Set()
|
|
if (!html || typeof html !== 'string') return ids
|
|
let m
|
|
const re = new RegExp(DATA_ATTR_RE.source, 'gi')
|
|
while ((m = re.exec(html)) !== null) {
|
|
const n = parseInt(m[1], 10)
|
|
if (Number.isFinite(n) && n > 0) ids.add(n)
|
|
}
|
|
return ids
|
|
}
|
|
|
|
const EXERCISE_RTF_FIELDS = ['summary', 'goal', 'execution', 'preparation', 'trainer_notes']
|
|
|
|
/**
|
|
* HTML-Schnipsel aus Übung + Varianten-Fließtext für Inline-Scan.
|
|
* @param {object|null|undefined} exercise
|
|
* @returns {string[]}
|
|
*/
|
|
export function gatherExerciseHtmlSlicesForInlineScan(exercise) {
|
|
if (!exercise || typeof exercise !== 'object') return []
|
|
const slices = []
|
|
for (const f of EXERCISE_RTF_FIELDS) {
|
|
const html = exercise[f]
|
|
if (typeof html === 'string' && html.trim()) slices.push(html)
|
|
}
|
|
for (const v of exercise.variants || []) {
|
|
const ec = v?.execution_changes
|
|
if (typeof ec === 'string' && ec.trim()) slices.push(ec)
|
|
}
|
|
return slices
|
|
}
|
|
|
|
/**
|
|
* Alle im Fließtext eingebetteten exercise_media-IDs (Übung + Varianten).
|
|
* @param {object|null|undefined} exercise
|
|
* @returns {Set<number>}
|
|
*/
|
|
export function collectInlineExerciseMediaIdsFromExercise(exercise) {
|
|
const ids = new Set()
|
|
for (const html of gatherExerciseHtmlSlicesForInlineScan(exercise)) {
|
|
collectInlineExerciseMediaIdsFromHtml(html).forEach((id) => ids.add(id))
|
|
}
|
|
return ids
|
|
}
|
|
|
|
/**
|
|
* @param {number} exerciseMediaId
|
|
* @param {string} [caption]
|
|
*/
|
|
export function buildExerciseMediaDragPayload(exerciseMediaId, caption = '') {
|
|
return JSON.stringify({
|
|
exerciseMediaId: Number(exerciseMediaId),
|
|
caption: typeof caption === 'string' ? caption : '',
|
|
})
|
|
}
|
|
|
|
/**
|
|
* @param {string} raw
|
|
* @returns {{ exerciseMediaId: number, caption: string }|null}
|
|
*/
|
|
export function parseExerciseMediaDragPayload(raw) {
|
|
if (!raw || typeof raw !== 'string') return null
|
|
try {
|
|
const o = JSON.parse(raw)
|
|
const id = Number(o.exerciseMediaId)
|
|
if (!Number.isFinite(id) || id < 1) return null
|
|
const caption = typeof o.caption === 'string' ? o.caption : ''
|
|
return { exerciseMediaId: id, caption }
|
|
} catch {
|
|
return null
|
|
}
|
|
}
|