fix(frontend): export ui object, harmonize API signatures, and clean view switcher
This commit is contained in:
+78
-10
@@ -3,7 +3,7 @@
|
||||
// =======================================================================
|
||||
|
||||
export function escapeHtml(str) {
|
||||
if (!str) return '';
|
||||
if (str === null || str === undefined) return '';
|
||||
return String(str)
|
||||
.replace(/&/g, '&')
|
||||
.replace(/</g, '<')
|
||||
@@ -13,7 +13,8 @@ export function escapeHtml(str) {
|
||||
}
|
||||
|
||||
export function escapeRegex(string) {
|
||||
return string.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
||||
if (!string) return '';
|
||||
return String(string).replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
||||
}
|
||||
|
||||
export function slugify(text) {
|
||||
@@ -25,6 +26,7 @@ export function slugify(text) {
|
||||
}
|
||||
|
||||
export function shuffleArray(array) {
|
||||
if (!array || !Array.isArray(array)) return [];
|
||||
const arr = [...array];
|
||||
for (let i = arr.length - 1; i > 0; i--) {
|
||||
const j = Math.floor(Math.random() * (i + 1));
|
||||
@@ -37,6 +39,7 @@ export function shuffleArray(array) {
|
||||
// Toast Notifications
|
||||
// -----------------------------------------------------------------------
|
||||
export function showToast(message, type = 'info') {
|
||||
if (!message) return;
|
||||
let toastContainer = document.getElementById('notes-toast-container');
|
||||
if (!toastContainer) {
|
||||
toastContainer = document.createElement('div');
|
||||
@@ -51,7 +54,11 @@ export function showToast(message, type = 'info') {
|
||||
gap: 8px;
|
||||
pointer-events: none;
|
||||
`;
|
||||
document.body.appendChild(toastContainer);
|
||||
if (document.body) {
|
||||
document.body.appendChild(toastContainer);
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
const toast = document.createElement('div');
|
||||
@@ -98,14 +105,19 @@ export function showToast(message, type = 'info') {
|
||||
export function initTheme() {
|
||||
const themeToggle = document.getElementById('theme-toggle');
|
||||
const mobileThemeBtn = document.getElementById('mobile-theme-btn');
|
||||
const savedTheme = localStorage.getItem('notes_theme') || 'dark';
|
||||
let savedTheme = 'dark';
|
||||
try {
|
||||
savedTheme = localStorage.getItem('notes_theme') || 'dark';
|
||||
} catch (e) {}
|
||||
document.documentElement.setAttribute('data-theme', savedTheme);
|
||||
|
||||
function toggle() {
|
||||
const current = document.documentElement.getAttribute('data-theme');
|
||||
const next = current === 'dark' ? 'light' : 'dark';
|
||||
document.documentElement.setAttribute('data-theme', next);
|
||||
localStorage.setItem('notes_theme', next);
|
||||
try {
|
||||
localStorage.setItem('notes_theme', next);
|
||||
} catch (e) {}
|
||||
}
|
||||
|
||||
if (themeToggle) {
|
||||
@@ -120,16 +132,47 @@ export function initTheme() {
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------
|
||||
// Modal Open / Close Helpers
|
||||
// Modal Open / Close Helpers (Accepts HTMLElement or string selector/ID)
|
||||
// -----------------------------------------------------------------------
|
||||
export function openModal(modalElem) {
|
||||
if (!modalElem) return;
|
||||
modalElem.classList.remove('hidden');
|
||||
const el = typeof modalElem === 'string'
|
||||
? (document.getElementById(modalElem) || document.querySelector(modalElem))
|
||||
: modalElem;
|
||||
if (el) el.classList.remove('hidden');
|
||||
}
|
||||
|
||||
export function closeModal(modalElem) {
|
||||
if (!modalElem) return;
|
||||
modalElem.classList.add('hidden');
|
||||
const el = typeof modalElem === 'string'
|
||||
? (document.getElementById(modalElem) || document.querySelector(modalElem))
|
||||
: modalElem;
|
||||
if (el) el.classList.add('hidden');
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------
|
||||
// View Switcher Helper
|
||||
// -----------------------------------------------------------------------
|
||||
export function switchView(targetViewId) {
|
||||
const allViews = [
|
||||
'dictionary-view',
|
||||
'srs-view',
|
||||
'reader-view',
|
||||
'cefr-view',
|
||||
'grammar-view',
|
||||
'lesson-view',
|
||||
'lesson-runner-view'
|
||||
];
|
||||
allViews.forEach(id => {
|
||||
const el = document.getElementById(id);
|
||||
if (el) {
|
||||
if (id === targetViewId) {
|
||||
el.classList.remove('hidden');
|
||||
} else {
|
||||
el.classList.add('hidden');
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------
|
||||
@@ -197,11 +240,36 @@ export function playAudio(url, speed = 1.0) {
|
||||
export function playTextSpeech(text, lang = 'en', speed = 1.0, voice = null) {
|
||||
if (!text || !text.trim()) return null;
|
||||
const isRu = lang && lang.toLowerCase().startsWith('ru');
|
||||
const savedVoice = isRu ? localStorage.getItem('notes_tts_voice_ru') : localStorage.getItem('notes_tts_voice_en');
|
||||
let savedVoice = null;
|
||||
try {
|
||||
savedVoice = isRu
|
||||
? localStorage.getItem('notes_tts_voice_ru')
|
||||
: localStorage.getItem('notes_tts_voice_en');
|
||||
} catch (e) {}
|
||||
const activeVoice = voice || savedVoice;
|
||||
let url = `/api/tts?text=${encodeURIComponent(text.trim())}&lang=${lang}&speed=${speed}`;
|
||||
let url = `/api/tts?text=${encodeURIComponent(text.trim())}&lang=${lang || 'en'}&speed=${speed}`;
|
||||
if (activeVoice) {
|
||||
url += `&voice=${encodeURIComponent(activeVoice)}`;
|
||||
}
|
||||
return playAudio(url, speed);
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------
|
||||
// Unified UI Object Export (for modules importing `{ ui }` or `ui`)
|
||||
// -----------------------------------------------------------------------
|
||||
export const ui = {
|
||||
escapeHtml,
|
||||
escapeRegex,
|
||||
slugify,
|
||||
shuffleArray,
|
||||
showToast,
|
||||
initTheme,
|
||||
openModal,
|
||||
closeModal,
|
||||
switchView,
|
||||
updateHeaderStats,
|
||||
playAudio,
|
||||
playTextSpeech,
|
||||
};
|
||||
|
||||
export default ui;
|
||||
|
||||
Reference in New Issue
Block a user