feat: prevent duplicate cards, merge contexts, and display star badge when word is in library
This commit is contained in:
+124
-7
@@ -88,6 +88,7 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||
const readerWordPopup = document.getElementById('reader-word-popup');
|
||||
const popupTerm = document.getElementById('popup-term');
|
||||
const popupPhonetic = document.getElementById('popup-phonetic');
|
||||
const popupInLibrary = document.getElementById('popup-in-library');
|
||||
const popupAudioBtn = document.getElementById('popup-audio-btn');
|
||||
const popupCloseBtn = document.getElementById('popup-close-btn');
|
||||
const popupLoading = document.getElementById('popup-loading');
|
||||
@@ -96,6 +97,7 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||
const popupContext = document.getElementById('popup-context');
|
||||
const popupSaveBtn = document.getElementById('popup-save-btn');
|
||||
const popupSavedBadge = document.getElementById('popup-saved-badge');
|
||||
const popupAddContextBtn = document.getElementById('popup-add-context-btn');
|
||||
|
||||
// Reader State
|
||||
let currentBook = null;
|
||||
@@ -116,6 +118,7 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||
const lookupCard = document.getElementById('lookup-card');
|
||||
const resTerm = document.getElementById('res-term');
|
||||
const resPhonetic = document.getElementById('res-phonetic');
|
||||
const resInLibrary = document.getElementById('res-in-library');
|
||||
const audioPlayBtn = document.getElementById('audio-play-btn');
|
||||
const resPos = document.getElementById('res-pos');
|
||||
const resDefinitionWrap = document.getElementById('res-definition-wrap');
|
||||
@@ -580,15 +583,36 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||
resDefinitionWrap.classList.add('hidden');
|
||||
}
|
||||
|
||||
editTranslation.value = data.translation || '';
|
||||
editContext.value = '';
|
||||
editNotes.value = '';
|
||||
editCollection.value = selectedCollection || 'General';
|
||||
const inLib = Boolean(data.in_library || foundDuplicateCard);
|
||||
if (resInLibrary) {
|
||||
resInLibrary.classList.toggle('hidden', !inLib);
|
||||
}
|
||||
|
||||
if (inLib) {
|
||||
saveCardBtn.innerHTML = '⭐ Обновить в словаре <span class="btn-subtext">(Ctrl + Enter)</span>';
|
||||
const lib = data.library_card || foundDuplicateCard;
|
||||
if (lib) {
|
||||
if (lib.translation) editTranslation.value = lib.translation;
|
||||
if (lib.context_sentence) editContext.value = lib.context_sentence;
|
||||
if (lib.collection) editCollection.value = lib.collection;
|
||||
if (lib.tags) editTags.value = lib.tags;
|
||||
if (lib.notes) editNotes.value = lib.notes;
|
||||
} else {
|
||||
editTranslation.value = data.translation || '';
|
||||
}
|
||||
} else {
|
||||
saveCardBtn.innerHTML = '💾 Сохранить в словарь <span class="btn-subtext">(Ctrl + Enter)</span>';
|
||||
editTranslation.value = data.translation || '';
|
||||
editContext.value = '';
|
||||
editNotes.value = '';
|
||||
editCollection.value = selectedCollection || 'General';
|
||||
}
|
||||
|
||||
lookupCard.classList.remove('hidden');
|
||||
editTranslation.focus();
|
||||
}
|
||||
|
||||
|
||||
// Save Card Action
|
||||
saveCardBtn.addEventListener('click', saveNewCard);
|
||||
document.addEventListener('keydown', (e) => {
|
||||
@@ -2375,10 +2399,14 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||
const cleanTerm = (term || '').replace(/^['"“«`\s\-_.,;:!?()[\]{}]+|['"”»`\s\-_.,;:!?()[\]{}]+$/g, '').trim();
|
||||
if (!cleanTerm) return;
|
||||
|
||||
const lowerClean = cleanTerm.toLowerCase();
|
||||
const existingCard = allCards && allCards.find(c => c.term && c.term.trim().toLowerCase() === lowerClean);
|
||||
|
||||
popupCurrentData = {
|
||||
term: cleanTerm,
|
||||
contextSentence: contextSentence,
|
||||
targetElement: targetElement,
|
||||
existingCard: existingCard,
|
||||
};
|
||||
|
||||
popupTerm.textContent = cleanTerm;
|
||||
@@ -2387,9 +2415,27 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||
popupContext.textContent = contextSentence ? `“${contextSentence}”` : '';
|
||||
popupLoading.classList.remove('hidden');
|
||||
popupContent.classList.add('hidden');
|
||||
popupSavedBadge.classList.add('hidden');
|
||||
popupSaveBtn.classList.remove('hidden');
|
||||
popupSaveBtn.disabled = false;
|
||||
|
||||
// Immediate check against already-loaded user cards
|
||||
if (popupInLibrary) {
|
||||
popupInLibrary.classList.toggle('hidden', !existingCard);
|
||||
}
|
||||
|
||||
if (existingCard) {
|
||||
popupSaveBtn.classList.add('hidden');
|
||||
popupSavedBadge.classList.remove('hidden');
|
||||
popupSavedBadge.textContent = '⭐ Слово уже в библиотеке';
|
||||
if (popupAddContextBtn && contextSentence && (!existingCard.context_sentence || !existingCard.context_sentence.includes(contextSentence))) {
|
||||
popupAddContextBtn.classList.remove('hidden');
|
||||
} else if (popupAddContextBtn) {
|
||||
popupAddContextBtn.classList.add('hidden');
|
||||
}
|
||||
} else {
|
||||
popupSavedBadge.classList.add('hidden');
|
||||
popupSaveBtn.classList.remove('hidden');
|
||||
popupSaveBtn.disabled = false;
|
||||
if (popupAddContextBtn) popupAddContextBtn.classList.add('hidden');
|
||||
}
|
||||
|
||||
// Position popup near target element
|
||||
readerWordPopup.classList.remove('hidden');
|
||||
@@ -2407,6 +2453,27 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||
popupLoading.classList.add('hidden');
|
||||
popupContent.classList.remove('hidden');
|
||||
popupAudioBtn.classList.add('hidden');
|
||||
|
||||
const inLib = Boolean(data.in_library || existingCard);
|
||||
const libCard = data.library_card || existingCard;
|
||||
if (inLib) {
|
||||
popupCurrentData.existingCard = libCard;
|
||||
if (popupInLibrary) popupInLibrary.classList.remove('hidden');
|
||||
popupSaveBtn.classList.add('hidden');
|
||||
popupSavedBadge.classList.remove('hidden');
|
||||
popupSavedBadge.textContent = '⭐ Слово уже в библиотеке';
|
||||
if (popupAddContextBtn && contextSentence && libCard && (!libCard.context_sentence || !libCard.context_sentence.includes(contextSentence))) {
|
||||
popupAddContextBtn.classList.remove('hidden');
|
||||
} else if (popupAddContextBtn) {
|
||||
popupAddContextBtn.classList.add('hidden');
|
||||
}
|
||||
} else {
|
||||
if (popupInLibrary) popupInLibrary.classList.add('hidden');
|
||||
popupSaveBtn.classList.remove('hidden');
|
||||
popupSavedBadge.classList.add('hidden');
|
||||
if (popupAddContextBtn) popupAddContextBtn.classList.add('hidden');
|
||||
}
|
||||
|
||||
positionWordPopup(targetElement);
|
||||
} else {
|
||||
popupTranslation.textContent = 'Ошибка перевода';
|
||||
@@ -2491,8 +2558,12 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||
});
|
||||
|
||||
if (resp.ok) {
|
||||
const resData = await resp.json();
|
||||
popupSaveBtn.classList.add('hidden');
|
||||
popupSavedBadge.classList.remove('hidden');
|
||||
popupSavedBadge.textContent = resData.already_exists ? '⭐ Уже есть в библиотеке' : '✅ Сохранено в словарь!';
|
||||
if (popupInLibrary) popupInLibrary.classList.remove('hidden');
|
||||
if (popupAddContextBtn) popupAddContextBtn.classList.add('hidden');
|
||||
|
||||
if (d.targetElement) {
|
||||
d.targetElement.classList.add('status-learning');
|
||||
@@ -2510,6 +2581,52 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||
});
|
||||
}
|
||||
|
||||
if (popupAddContextBtn) {
|
||||
popupAddContextBtn.addEventListener('click', async () => {
|
||||
if (!popupCurrentData) return;
|
||||
const d = popupCurrentData;
|
||||
const lookup = d.lookup || {};
|
||||
const bookTag = currentBook ? slugify(currentBook.title) : 'reader';
|
||||
const payload = {
|
||||
term: d.term,
|
||||
translation: lookup.translation || lookup.translation_ru || popupTranslation.textContent,
|
||||
context_sentence: d.contextSentence || '',
|
||||
phonetic: lookup.phonetic || '',
|
||||
audio_url: lookup.audio_url || '',
|
||||
part_of_speech: lookup.part_of_speech || '',
|
||||
definition_en: lookup.definition_en || '',
|
||||
collection: 'Книги & Сериалы',
|
||||
tags: `${bookTag}, reader`,
|
||||
notes: currentBook ? `Из книги: ${currentBook.title}` : '',
|
||||
};
|
||||
|
||||
popupAddContextBtn.disabled = true;
|
||||
popupAddContextBtn.textContent = 'Сохраняем...';
|
||||
|
||||
try {
|
||||
const resp = await fetch('/api/cards', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify(payload),
|
||||
});
|
||||
if (resp.ok) {
|
||||
popupAddContextBtn.classList.add('hidden');
|
||||
popupSavedBadge.textContent = '✅ Цитата прикреплена к карточке!';
|
||||
popupSavedBadge.classList.remove('hidden');
|
||||
await fetchCards();
|
||||
} else {
|
||||
alert('Не удалось обновить контекст');
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('Add context error:', e);
|
||||
} finally {
|
||||
popupAddContextBtn.disabled = false;
|
||||
popupAddContextBtn.textContent = '+ Добавить эту цитату к карточке';
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
// Phrase/Chunk Selection in Reader
|
||||
if (readerContentBody) {
|
||||
readerContentBody.addEventListener('mouseup', () => {
|
||||
|
||||
Reference in New Issue
Block a user