// Alias db.js functions const dbGetAllEntries = getAllEntries; const dbGetEntry = getEntry; const dbAddEntry = addEntry; const dbUpdateEntry = updateEntry; const dbDeleteEntry = deleteEntry; var savedSelStart = 0; var savedSelEnd = 0; let state = { entries: [], currentId: null }; function uuid() { return Date.now().toString(36) + Math.random().toString(36).slice(2, 8); } function todayStr() { const d = new Date(); return d.getFullYear() + '-' + String(d.getMonth() + 1).padStart(2, '0') + '-' + String(d.getDate()).padStart(2, '0'); } // ----- Render functions ----- function renderList() { const ul = document.getElementById('entry-list'); ul.innerHTML = state.entries.map(e => { const preview = e.spanish.slice(0, 30) + (e.spanish.length > 30 ? '...' : ''); const active = e.id === state.currentId ? 'active' : ''; return '
  • ' + '
    ' + escHtml(e.date) + '
    ' + '
    ' + escHtml(preview) + '
    ' + '
    ' + '' + '
    ' + '
  • '; }).join(''); } function escHtml(s) { const div = document.createElement('div'); div.textContent = s; return div.innerHTML; } function renderTranslate(entry) { document.getElementById('translate-en').textContent = entry ? entry.english : '-'; document.getElementById('translate-cn').textContent = entry ? entry.chinese : '-'; } function setEditor(entry) { document.getElementById('entry-date').value = entry ? entry.date : todayStr(); document.getElementById('entry-text').value = entry ? entry.spanish : ''; const hint = document.getElementById('entry-hint'); hint.textContent = entry && entry.spanish ? '已保存。修改后点击保存更新。' : '输入西语后实时显示翻译'; updateLiveTranslation(); } function clearEditor() { state.currentId = null; document.querySelectorAll('#entry-list li').forEach(function(el) { el.classList.remove('active'); }); setEditor(null); renderTranslate(null); clearLiveTranslation(); } // ----- Live translation & vocabulary ----- function updateLiveTranslation() { var text = document.getElementById('entry-text').value; if (!text.trim()) { clearLiveTranslation(); return; } var result = translateText(text); document.getElementById('editor-translate-en').textContent = result.english; document.getElementById('editor-translate-cn').textContent = result.chinese; updateVocabulary(text); } function clearLiveTranslation() { document.getElementById('editor-translate-en').textContent = '-'; document.getElementById('editor-translate-cn').textContent = '-'; document.getElementById('vocab-list').innerHTML = ''; document.getElementById('selection-translate').style.display = 'none'; } function updateVocabulary(text) { var vocab = getVocabulary(text); var container = document.getElementById('vocab-list'); if (vocab.length === 0) { container.innerHTML = '
    输入西语后显示单词
    '; return; } container.innerHTML = vocab.map(function(v) { return '
    ' + '' + escHtml(v.word) + '' + '' + '' + escHtml(v.english) + '' + '' + '' + escHtml(v.chinese) + '' + '' + '
    '; }).join(''); } function handleVocabSpeech(word, lang) { var entry = getVocabForWord(word); if (entry) { if (lang === 'en') speakText(entry.en, 'en'); else if (lang === 'zh') speakText(entry.zh, 'zh'); else speakText(word, 'es'); } } function handleSelection() { var ta = document.getElementById('entry-text'); var selText = ta.value.substring(ta.selectionStart, ta.selectionEnd).trim(); var el = document.getElementById('selection-translate'); if (selText && selText.length > 0) { var result = translateSelection(selText); document.getElementById('selected-translation').textContent = result.english + ' / ' + result.chinese; el.style.display = 'block'; } else { el.style.display = 'none'; } } // ----- Event handlers ----- function selectEntry(id) { state.currentId = id; dbGetEntry(id).then(function(entry) { if (entry) { setEditor(entry); renderTranslate(entry); renderList(); } }); } function saveCurrentEntry() { var date = document.getElementById('entry-date').value; var spanish = document.getElementById('entry-text').value.trim(); if (!spanish) { alert('请输入西语内容'); return; } var translation = translateText(spanish); var savePromise; if (state.currentId) { savePromise = dbGetEntry(state.currentId).then(function(existing) { existing.date = date; existing.spanish = spanish; existing.english = translation.english; existing.chinese = translation.chinese; existing.updatedAt = new Date().toISOString(); return dbUpdateEntry(existing); }); } else { var entry = { id: uuid(), date: date, spanish: spanish, english: translation.english, chinese: translation.chinese, createdAt: new Date().toISOString(), updatedAt: new Date().toISOString() }; savePromise = dbAddEntry(entry).then(function() { state.currentId = entry.id; }); } savePromise.then(function() { return dbGetAllEntries(); }).then(function(entries) { state.entries = entries; renderList(); renderTranslate({ english: translation.english, chinese: translation.chinese }); var found = entries.find(function(e) { return e.id === state.currentId; }); if (found) setEditor(found); document.getElementById('btn-save').textContent = '\uD83D\uDCBE 已保存 \u2713'; setTimeout(function() { document.getElementById('btn-save').textContent = '\uD83D\uDCBE 保存'; }, 1500); }); } function newEntry() { clearEditor(); document.getElementById('entry-date').value = todayStr(); document.getElementById('entry-text').focus(); } // ----- Modal ----- var pendingDeleteId = null; function showDeleteModal(id) { pendingDeleteId = id; document.getElementById('modal-overlay').classList.add('show'); } function hideDeleteModal() { pendingDeleteId = null; document.getElementById('modal-overlay').classList.remove('show'); } function confirmDelete(id) { dbDeleteEntry(id).then(function() { if (state.currentId === id) clearEditor(); return dbGetAllEntries(); }).then(function(entries) { state.entries = entries; renderList(); hideDeleteModal(); }); } function saveTextareaSelection() { var ta = document.getElementById('entry-text'); savedSelStart = ta.selectionStart; savedSelEnd = ta.selectionEnd; } // ----- Speech handler ----- function handleSpeech(lang) { var text = lang === 'en' ? document.getElementById('translate-en').textContent : document.getElementById('translate-cn').textContent; if (text && text !== '-') speakText(text, lang); } // ----- Initialization ----- function init() { dbGetAllEntries().then(function(entries) { state.entries = entries; renderList(); setEditor(null); renderTranslate(null); }); document.getElementById('entry-list').addEventListener('click', function(e) { var li = e.target.closest('li[data-id]'); if (!li) return; if (e.target.closest('.btn-delete')) { showDeleteModal(li.dataset.id); return; } selectEntry(li.dataset.id); }); document.getElementById('btn-new').addEventListener('click', newEntry); document.getElementById('btn-save').addEventListener('click', saveCurrentEntry); document.querySelectorAll('.btn-speech').forEach(function(btn) { btn.addEventListener('click', function() { handleSpeech(btn.dataset.lang); }); }); // Live translation on input document.getElementById('entry-text').addEventListener('input', updateLiveTranslation); // Selection translation document.getElementById('entry-text').addEventListener('mouseup', function(e) { handleSelection(); saveTextareaSelection(); }); document.getElementById('entry-text').addEventListener('keyup', function(e) { handleSelection(); saveTextareaSelection(); }); // Vocabulary speech delegation document.getElementById('editor-vocab').addEventListener('click', function(e) { var btn = e.target.closest('.vocab-speech'); if (btn) handleVocabSpeech(btn.dataset.word, btn.dataset.lang); }); // Vocabulary double-click to auto-read document.getElementById('editor-vocab').addEventListener('dblclick', function(e) { var item = e.target.closest('.vocab-item'); if (!item) return; var word = item.querySelector('.vocab-word').textContent; var el = e.target; if (el.classList.contains('vocab-en') || el.closest('.vocab-en')) { speakText(item.querySelector('.vocab-en').textContent, 'en'); } else if (el.classList.contains('vocab-zh') || el.closest('.vocab-zh')) { speakText(item.querySelector('.vocab-zh').textContent, 'zh'); } else { speakText(word, 'es'); } }); // Textarea double-click auto-read document.getElementById('entry-text').addEventListener('dblclick', function() { var ta = document.getElementById('entry-text'); setTimeout(function() { var txt = ta.value.substring(ta.selectionStart, ta.selectionEnd).trim(); if (txt) speakText(txt, 'es'); }, 50); }); // Speak selected button - reads selection aloud document.getElementById('btn-speak-selected').addEventListener('click', function() { var ta = document.getElementById('entry-text'); var start = savedSelStart || ta.selectionStart; var end = savedSelEnd || ta.selectionEnd; if (start === end) { start = 0; end = ta.value.length; } var sel = ta.value.substring(start, end).trim(); if (sel) { speakText(sel, 'es'); document.getElementById('selected-translation').textContent = sel; document.getElementById('selection-translate').style.display = 'block'; } }); document.querySelector('#modal-box .btn-cancel').addEventListener('click', hideDeleteModal); document.querySelector('#modal-box .btn-confirm').addEventListener('click', function() { if (pendingDeleteId) confirmDelete(pendingDeleteId); }); document.addEventListener('keydown', function(e) { if (e.ctrlKey && e.key === 'Enter') saveCurrentEntry(); }); } document.addEventListener('DOMContentLoaded', init);