Objekt-Placeholder indigo, Wort-Placeholder grün, geleakte ⟦PHn⟧-Tokens rot. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
75 lines
3.2 KiB
JavaScript
75 lines
3.2 KiB
JavaScript
// Geteilte Anzeige-Helfer für Pair-Inhalte (3-Sprachen-Grid).
|
|
// Genutzt von PairReviewModal und der Veröffentlichen-Seite.
|
|
|
|
// Platzhalter {{label.type:uuid}} → nur das Label anzeigen.
|
|
export function strip(text) {
|
|
if (!text) return '';
|
|
return text.replace(/\{\{([^}]+)\}\}/g, (_, inner) => {
|
|
const m = inner.match(/^(.+?)\.[a-z]+:[0-9a-f-]{36}$/i);
|
|
return m ? m[1] : inner;
|
|
});
|
|
}
|
|
|
|
// Zerlegt einen Satz in Segmente für die farbige Placeholder-Anzeige:
|
|
// { text, kind: 'object' | 'word' | 'broken' | null, id? }
|
|
// 'broken' = geleakte ⟦PHn:label⟧-Tokens aus der Übersetzung (sollten nicht vorkommen).
|
|
const SEGMENT_RE = /\{\{([^.{}]+)\.(w|o):([0-9a-f-]{36})\}\}|⟦PH\d+:([^⟧]*)⟧/g;
|
|
|
|
export function parsePlaceholderSegments(text) {
|
|
if (!text) return [];
|
|
const str = String(text);
|
|
const segs = [];
|
|
let last = 0;
|
|
for (const m of str.matchAll(SEGMENT_RE)) {
|
|
if (m.index > last) segs.push({ text: str.slice(last, m.index), kind: null });
|
|
if (m[1] !== undefined) segs.push({ text: m[1], kind: m[2] === 'o' ? 'object' : 'word', id: m[3] });
|
|
else segs.push({ text: m[4], kind: 'broken' });
|
|
last = m.index + m[0].length;
|
|
}
|
|
if (last < str.length) segs.push({ text: str.slice(last), kind: null });
|
|
return segs;
|
|
}
|
|
|
|
// Baut die Anzeigezeilen je nach answer_type. Jede Zeile ist entweder
|
|
// - { kind: 'lang', label, color, cell(l) } → eine Zelle pro Sprache (übersetzbar)
|
|
// - { kind: 'single', label, color, value } → ein einzelner Wert (nicht sprachabhängig)
|
|
export function buildRows(content) {
|
|
if (!content) return [];
|
|
const type = content?.answer_type;
|
|
const rows = [];
|
|
const wordsCell = (stmt) => (l) =>
|
|
(stmt?.words || []).map(w => w[`titel_${l}`] || '—').join(', ');
|
|
// Roher Text inkl. {{…}}-Placeholder — die Anzeige läuft über <PlaceholderText>.
|
|
const sentenceCell = (stmt, prefix) => (l) =>
|
|
stmt?.sentence?.[`${prefix}_${l}`] || '';
|
|
|
|
// Frage (yes_no / question / word)
|
|
if (content.question) {
|
|
rows.push({ kind: 'lang', label: 'Frage', color: 'text-slate-700',
|
|
cell: l => content.question[`sentence_${l}`] || '' });
|
|
}
|
|
|
|
if (type === 'yes_no') {
|
|
// Ja/Nein-Antwort ist ein boolescher Wert, keine Übersetzung
|
|
const a = content.positive?.answer;
|
|
rows.push({ kind: 'single', label: 'Antwort', color: 'text-green-700',
|
|
value: a === true ? '✓ Ja' : a === false ? '✗ Nein' : null });
|
|
} else if (type === 'word') {
|
|
rows.push({ kind: 'lang', label: 'Positiv-Wörter', color: 'text-green-700',
|
|
cell: wordsCell(content.positive) });
|
|
// 'word' braucht laut Datenmodell Negativ-Wörter → Zeile immer zeigen, fehlende sichtbar machen
|
|
rows.push({ kind: 'lang', label: 'Negativ-Wörter', color: 'text-red-600',
|
|
cell: wordsCell(content.negative) });
|
|
} else {
|
|
// text / question → Sätze
|
|
if (content.positive)
|
|
rows.push({ kind: 'lang', label: 'Positiv', color: 'text-green-700',
|
|
cell: sentenceCell(content.positive, 'positive_sentence') });
|
|
// 'question' = Frage + Positiv + Negativ → Negativ-Zeile immer zeigen, auch wenn leer
|
|
if (type === 'question')
|
|
rows.push({ kind: 'lang', label: 'Negativ', color: 'text-red-600',
|
|
cell: sentenceCell(content.negative, 'negative_sentence') });
|
|
}
|
|
return rows;
|
|
}
|