/** * §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} */ 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} */ 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 } }