diff --git a/src/pages/ContentCreation.jsx b/src/pages/ContentCreation.jsx index 31d8297..0fd24d2 100644 --- a/src/pages/ContentCreation.jsx +++ b/src/pages/ContentCreation.jsx @@ -6,15 +6,21 @@ import { STATUS_COLORS } from '../lib/tables'; // ─── Word / placeholder helpers ─────────────────────────────────────────────── function tokenize(text, wordMap) { - const titles = Object.keys(wordMap); - if (!titles.length || !text) return [{ text }]; - const escaped = titles.sort((a, b) => b.length - a.length) - .map(t => t.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')); - const re = new RegExp(`(${escaped.join('|')})`, 'gi'); - return text.split(re).filter(s => s !== '').map(part => ({ - text: part, - word: wordMap[part.toLowerCase()] || null, - })); + try { + const titles = Object.keys(wordMap).filter(k => k && k.length > 0); + if (!titles.length || !text) return [{ text: text || '' }]; + const escaped = titles.sort((a, b) => b.length - a.length) + .map(t => t.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')) + .filter(e => e.length > 0); + if (!escaped.length) return [{ text }]; + const re = new RegExp(`(${escaped.join('|')})`, 'gi'); + return text.split(re).filter(s => s != null && s !== '').map(part => ({ + text: part, + word: wordMap[part.toLowerCase()] || null, + })); + } catch { + return [{ text: text || '', word: null }]; + } } function withPlaceholders(text, wordMap, objectAssignments = {}) { @@ -206,19 +212,27 @@ function PairForm({ objectId, allObjects, onPairSaved }) { useEffect(() => { if (!allText.trim()) { setWordMap({}); return; } const t = setTimeout(async () => { - const tokens = [...new Set(allText.split(/[\s.,!?;:()\[\]"']+/).filter(w => w.length >= 2))]; - if (!tokens.length) return; - const results = await Promise.allSettled( - tokens.map(async w => { - const data = await apiFetch(`/words?search=${encodeURIComponent(w)}&limit=5`); - const candidates = Array.isArray(data) ? data : []; - const match = candidates.find(word => fuzzyMatch(w, word[`titel_${lang}`] || word.titel_de || '')); - return match ? { key: w.toLowerCase(), word: match } : null; - }) - ); - const map = {}; - results.forEach(r => { if (r.status === 'fulfilled' && r.value) map[r.value.key] = r.value.word; }); - setWordMap(map); + try { + const tokens = [...new Set(allText.split(/[\s.,!?;:()\[\]"']+/).filter(w => w.length >= 2))]; + if (!tokens.length) return; + const results = await Promise.allSettled( + tokens.map(async w => { + try { + const data = await apiFetch(`/words?search=${encodeURIComponent(w)}&limit=5`); + const candidates = Array.isArray(data) ? data.filter(Boolean) : []; + const match = candidates.find(word => word && word.id && fuzzyMatch(w, word[`titel_${lang}`] || word.titel_de || '')); + return match ? { key: w.toLowerCase(), word: match } : null; + } catch { return null; } + }) + ); + const map = {}; + results.forEach(r => { + if (r.status === 'fulfilled' && r.value && r.value.key && r.value.word?.id) { + map[r.value.key] = r.value.word; + } + }); + setWordMap(map); + } catch { /* ignore word detection errors */ } }, 600); return () => clearTimeout(t); }, [allText, lang]); @@ -228,7 +242,7 @@ function PairForm({ objectId, allObjects, onPairSaved }) { setCreatingWord(true); try { const w = await apiPost('/words', { [`titel_${lang}`]: wordInput.trim() }); - setWordMap(prev => ({ ...prev, [selection.trim().toLowerCase()]: w })); + if (w?.id) setWordMap(prev => ({ ...prev, [selection.trim().toLowerCase()]: w })); setWordInput(''); } catch (e) { alert('Fehler: ' + e.message); } finally { setCreatingWord(false); } @@ -287,6 +301,9 @@ function PairForm({ objectId, allObjects, onPairSaved }) { }); await apiLink(`/objects/${objectId}/pairs/${pair.id}`); setType(''); setYesNoAnswer(null); + setQuestion(''); setPositive(''); setNegative(''); + setWordMap({}); setObjectAssignments({}); + setPositiveWords([]); setNegativeWords([]); setSavedFlash(true); setTimeout(() => setSavedFlash(false), 2000); onPairSaved(pair); } catch (e) { alert('Fehler: ' + e.message); } @@ -384,8 +401,9 @@ function PairForm({ objectId, allObjects, onPairSaved }) { {Object.keys(wordMap).length > 0 && (
Erkannte Wörter - {Object.entries(wordMap).map(([title, w]) => { - const matchingObjs = allObjects.filter(o => o._words?.some(ow => ow.id === w.id)); + {Object.entries(wordMap).filter(([, w]) => w?.id).map(([title, w]) => { + const safeObjects = Array.isArray(allObjects) ? allObjects : []; + const matchingObjs = safeObjects.filter(o => o._words?.some(ow => ow.id === w.id)); const assigned = objectAssignments[w.id] || ''; return (
@@ -395,7 +413,7 @@ function PairForm({ objectId, allObjects, onPairSaved }) { className={`flex-1 text-xs border rounded px-1.5 py-0.5 bg-white focus:outline-none focus:ring-1 focus:ring-indigo-400 ${assigned ? 'border-indigo-400 text-indigo-700 bg-indigo-50' : 'border-slate-200 text-slate-500'}`}> {matchingObjs.map(obj => { - const idx = allObjects.indexOf(obj); + const idx = safeObjects.indexOf(obj); const labels = (obj._words || []).slice(0, 3).map(ow => ow.titel_de || ow.id).join(', '); return ; })} @@ -491,19 +509,27 @@ function EditPairForm({ pair, allObjects, onSaved, onCancel, onDeleted }) { useEffect(() => { if (!allText.trim()) { setWordMap({}); return; } const t = setTimeout(async () => { - const tokens = [...new Set(allText.split(/[\s.,!?;:()\[\]"']+/).filter(w => w.length >= 2))]; - if (!tokens.length) return; - const results = await Promise.allSettled( - tokens.map(async w => { - const data = await apiFetch(`/words?search=${encodeURIComponent(w)}&limit=5`); - const candidates = Array.isArray(data) ? data : []; - const match = candidates.find(word => fuzzyMatch(w, word[`titel_${lang}`] || word.titel_de || '')); - return match ? { key: w.toLowerCase(), word: match } : null; - }) - ); - const map = {}; - results.forEach(r => { if (r.status === 'fulfilled' && r.value) map[r.value.key] = r.value.word; }); - setWordMap(map); + try { + const tokens = [...new Set(allText.split(/[\s.,!?;:()\[\]"']+/).filter(w => w.length >= 2))]; + if (!tokens.length) return; + const results = await Promise.allSettled( + tokens.map(async w => { + try { + const data = await apiFetch(`/words?search=${encodeURIComponent(w)}&limit=5`); + const candidates = Array.isArray(data) ? data.filter(Boolean) : []; + const match = candidates.find(word => word && word.id && fuzzyMatch(w, word[`titel_${lang}`] || word.titel_de || '')); + return match ? { key: w.toLowerCase(), word: match } : null; + } catch { return null; } + }) + ); + const map = {}; + results.forEach(r => { + if (r.status === 'fulfilled' && r.value && r.value.key && r.value.word?.id) { + map[r.value.key] = r.value.word; + } + }); + setWordMap(map); + } catch { /* ignore word detection errors */ } }, 600); return () => clearTimeout(t); }, [allText, lang]); @@ -513,7 +539,7 @@ function EditPairForm({ pair, allObjects, onSaved, onCancel, onDeleted }) { setCreatingWord(true); try { const w = await apiPost('/words', { [`titel_${lang}`]: wordInput.trim() }); - setWordMap(prev => ({ ...prev, [selection.trim().toLowerCase()]: w })); + if (w?.id) setWordMap(prev => ({ ...prev, [selection.trim().toLowerCase()]: w })); setWordInput(''); } catch (e) { alert('Fehler: ' + e.message); } finally { setCreatingWord(false); } @@ -663,8 +689,9 @@ function EditPairForm({ pair, allObjects, onSaved, onCancel, onDeleted }) { {Object.keys(wordMap).length > 0 && (
Erkannte Wörter - {Object.entries(wordMap).map(([title, w]) => { - const matchingObjs = allObjects.filter(o => o._words?.some(ow => ow.id === w.id)); + {Object.entries(wordMap).filter(([, w]) => w?.id).map(([title, w]) => { + const safeObjects = Array.isArray(allObjects) ? allObjects : []; + const matchingObjs = safeObjects.filter(o => o._words?.some(ow => ow.id === w.id)); const assigned = objectAssignments[w.id] || ''; return (
@@ -674,7 +701,7 @@ function EditPairForm({ pair, allObjects, onSaved, onCancel, onDeleted }) { className={`flex-1 text-xs border rounded px-1.5 py-0.5 bg-white focus:outline-none focus:ring-1 focus:ring-amber-400 ${assigned ? 'border-amber-400 text-amber-700 bg-amber-50' : 'border-slate-200 text-slate-500'}`}> {matchingObjs.map(obj => { - const idx = allObjects.indexOf(obj); + const idx = safeObjects.indexOf(obj); const labels = (obj._words || []).slice(0, 3).map(ow => ow.titel_de || ow.id).join(', '); return ; })}