fix & feat: resolve user reports #1 through #8 (TTS voice & speed, modals, scrollbar, CEFR hint, SRS grammar exclusion, global word lookup)
This commit is contained in:
+64
-4
@@ -7,7 +7,7 @@ import { api } from './modules/api.js';
|
||||
import { initTheme, openModal, closeModal, showToast, playTextSpeech, switchView } from './modules/ui.js';
|
||||
import { initDictionary, loadInitialData } from './modules/dictionary.js';
|
||||
import { initSrs, startSrsTrainingSession, openStatsDashboard } from './modules/srs.js';
|
||||
import { initReader, loadBookshelf, openBook } from './modules/reader.js';
|
||||
import { initReader, loadBookshelf, openBook, openWordPopup, extractContextSentence } from './modules/reader.js';
|
||||
import { initCefr, loadCefrOverview, startCefrQuiz } from './modules/cefr.js';
|
||||
import { initGrammar, loadGrammarUnits, openGrammarUnit } from './modules/grammar.js';
|
||||
import { initLessonModule, openLessonConfigurator } from './modules/lesson.js';
|
||||
@@ -416,13 +416,73 @@ export function initNavigationDrawer() {
|
||||
if (drawerCloseBtn) drawerCloseBtn.addEventListener('click', closeDrawer);
|
||||
if (drawerBackdrop) drawerBackdrop.addEventListener('click', closeDrawer);
|
||||
|
||||
// Escape Key Support
|
||||
// Global Escape Key & Modal Dismiss Support
|
||||
document.addEventListener('keydown', (e) => {
|
||||
if (e.key === 'Escape' && drawer && drawer.classList.contains('active')) {
|
||||
closeDrawer();
|
||||
if (e.key === 'Escape') {
|
||||
// 1. If navigation drawer is open, close it
|
||||
if (drawer && drawer.classList.contains('active')) {
|
||||
closeDrawer();
|
||||
return;
|
||||
}
|
||||
// 2. Close any open modal backdrop (topmost visible modal)
|
||||
const openModals = Array.from(document.querySelectorAll('.modal-backdrop:not(.hidden)'));
|
||||
if (openModals.length > 0) {
|
||||
const topModal = openModals[openModals.length - 1];
|
||||
topModal.classList.add('hidden');
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Dismiss modals when clicking on dark backdrop outside modal window
|
||||
document.addEventListener('click', (e) => {
|
||||
if (e.target && e.target.classList && e.target.classList.contains('modal-backdrop')) {
|
||||
e.target.classList.add('hidden');
|
||||
}
|
||||
});
|
||||
|
||||
// -----------------------------------------------------------------------
|
||||
// Global Text Selection Lookup & Add-to-Dictionary (Anywhere on Page)
|
||||
// -----------------------------------------------------------------------
|
||||
function handleGlobalTextSelection(e) {
|
||||
if (e && e.target && e.target.closest('#reader-word-popup, button, input, textarea, select, .modal-window, #feedback-widget, .icon-btn, .opt-btn, .tab-item, .drawer-nav-item')) {
|
||||
return;
|
||||
}
|
||||
|
||||
const selection = window.getSelection();
|
||||
if (!selection || selection.isCollapsed) return;
|
||||
const raw = selection.toString().trim();
|
||||
if (!raw || raw.length < 2 || raw.length > 50) return;
|
||||
|
||||
// Clean punctuation / quotes
|
||||
const clean = raw.replace(/^['"“«`\s\-_.,;:!?()[\]{}]+|['"”»`\s\-_.,;:!?()[\]{}]+$/g, '').trim();
|
||||
// Require Latin characters (English vocabulary)
|
||||
if (!clean || !/^[A-Za-z\s'-]+$/.test(clean)) return;
|
||||
|
||||
if (selection.rangeCount === 0) return;
|
||||
const range = selection.getRangeAt(0);
|
||||
const rect = range.getBoundingClientRect();
|
||||
if (rect.width === 0 && rect.height === 0) return;
|
||||
|
||||
const containerElem = range.startContainer?.nodeType === 1 ? range.startContainer : range.startContainer?.parentElement;
|
||||
// Avoid re-triggering if already inside reader word span (which handles its own click)
|
||||
if (containerElem?.closest('.reader-word')) return;
|
||||
|
||||
const fullText = containerElem?.innerText || containerElem?.textContent || '';
|
||||
const contextSentence = extractContextSentence(fullText, clean);
|
||||
|
||||
const virtualTarget = {
|
||||
getBoundingClientRect: () => range.getBoundingClientRect(),
|
||||
isConnected: true
|
||||
};
|
||||
|
||||
openWordPopup(clean, contextSentence, virtualTarget, fullText);
|
||||
}
|
||||
|
||||
document.addEventListener('mouseup', handleGlobalTextSelection);
|
||||
document.addEventListener('touchend', (e) => {
|
||||
setTimeout(() => handleGlobalTextSelection(e), 150);
|
||||
});
|
||||
|
||||
// Touch Swipe Left to Dismiss (Mobile & Tablet)
|
||||
let touchStartX = 0;
|
||||
let touchCurrentX = 0;
|
||||
|
||||
Reference in New Issue
Block a user