feat(reader): continuous reading mode, seamless infinite scroll, and end-of-chapter nav cards

This commit is contained in:
Antigravity committed 2026-10-06 09:33:06 +03:00
1 parent 4d40b13bce
commit 0d05da7b9e
4 files changed
+623 -56

No files matched your search

+451 -54
View File
@@ -113,6 +113,9 @@ document.addEventListener('DOMContentLoaded', () => {
const readerMarginsGroup = document.getElementById('reader-margins-group');
const readerThemeGroup = document.getElementById('reader-theme-group');
const readerAlignGroup = document.getElementById('reader-align-group');
const readerModeGroup = document.getElementById('reader-mode-group');
const readerAutoloadGroup = document.getElementById('reader-autoload-group');
const settingAutoloadRow = document.getElementById('setting-autoload-row');
const readerToggleHighlightBtn = document.getElementById('reader-toggle-highlight-btn');
const readerTrainBtn = document.getElementById('reader-train-btn');
@@ -143,11 +146,15 @@ document.addEventListener('DOMContentLoaded', () => {
lineHeight: 1.65,
marginWidth: 'normal',
theme: 'dark',
textAlign: 'left'
textAlign: 'left',
flowMode: 'chapters', // 'chapters' | 'continuous'
autoLoadNext: true // boolean: seamlessly load next chapter on scroll
};
let readerSettings = { ...DEFAULT_READER_SETTINGS };
let currentBook = null;
let currentChapterIndex = 0;
const loadedChaptersSet = new Set();
let isAppendingChapter = false;
let isHighlightEnabled = false;
let popupCurrentData = null;
@@ -2127,7 +2134,17 @@ document.addEventListener('DOMContentLoaded', () => {
if (currentBook && currentBook.id === bookId) {
if (specificChapterIdx !== null && specificChapterIdx !== undefined && !isNaN(specificChapterIdx)) {
if (specificChapterIdx !== currentChapterIndex) {
renderChapter(specificChapterIdx, 0, 0, 0);
if (readerSettings.flowMode === 'continuous' || loadedChaptersSet.has(specificChapterIdx)) {
currentChapterIndex = specificChapterIdx;
if (readerChapterSelect) readerChapterSelect.value = currentChapterIndex;
const target = document.getElementById(`chapter-anchor-${specificChapterIdx}`)
|| readerContentBody.querySelector(`[data-chapter-idx="${specificChapterIdx}"]`);
if (target) {
target.scrollIntoView({ behavior: 'smooth', block: 'start' });
}
} else {
renderChapter(specificChapterIdx, 0, 0, 0);
}
}
}
if (updateHash) {
@@ -2213,40 +2230,38 @@ document.addEventListener('DOMContentLoaded', () => {
}
}
function renderChapter(index, restoreScrollTop = null, restoreFraction = null, restoreAnchorIdx = null) {
if (!currentBook || !currentBook.chapters) return;
currentChapterIndex = Math.max(0, Math.min(index, currentBook.chapters.length - 1));
readerChapterSelect.value = currentChapterIndex;
function buildChapterDOM(chapterIdx, knownWordsMap, isContinuous = false) {
const chapter = currentBook.chapters[chapterIdx] || { title: '', content: '' };
const frag = document.createDocumentFragment();
// Refresh status icons in chapter select
if (readerChapterSelect.options) {
for (let i = 0; i < readerChapterSelect.options.length; i++) {
const rawTitle = currentBook.chapters[i]?.title || `Глава ${i + 1}`;
const icon = i < currentChapterIndex ? '✔ ' : (i === currentChapterIndex ? '▶ ' : '○ ');
readerChapterSelect.options[i].textContent = `${icon}${rawTitle}`;
if (chapter.title || isContinuous) {
if (isContinuous || chapterIdx > 0) {
const divider = document.createElement('div');
divider.className = 'reader-chapter-divider';
divider.id = `chapter-anchor-${chapterIdx}`;
divider.setAttribute('data-chapter-idx', chapterIdx.toString());
const badge = document.createElement('span');
badge.className = 'divider-badge';
badge.textContent = `Глава ${chapterIdx + 1} из ${currentBook.chapters.length}`;
const titleEl = document.createElement('h2');
titleEl.className = 'divider-title';
titleEl.textContent = chapter.title || `Глава ${chapterIdx + 1}`;
divider.appendChild(badge);
divider.appendChild(titleEl);
frag.appendChild(divider);
} else if (chapter.title) {
const h = document.createElement('h2');
h.textContent = chapter.title;
h.setAttribute('data-block-idx', '0');
h.setAttribute('data-chapter-idx', chapterIdx.toString());
h.id = `chapter-anchor-${chapterIdx}`;
frag.appendChild(h);
}
}
readerPrevChBtn.disabled = currentChapterIndex <= 0;
readerNextChBtn.disabled = currentChapterIndex >= currentBook.chapters.length - 1;
const chapter = currentBook.chapters[currentChapterIndex] || { title: '', content: '' };
readerContentBody.innerHTML = '';
if (chapter.title) {
const h = document.createElement('h2');
h.textContent = chapter.title;
h.setAttribute('data-block-idx', '0');
readerContentBody.appendChild(h);
}
// Map existing user vocabulary for status marking
const knownWordsMap = new Map();
allCards.forEach(c => {
if (c.term) knownWordsMap.set(c.term.trim().toLowerCase(), c);
});
// Parse content into structured blocks (code, headings, lists, quotes, paragraphs)
const blocks = parseContentBlocks(chapter.content);
blocks.forEach((block, blockIdx) => {
@@ -2346,7 +2361,6 @@ document.addEventListener('DOMContentLoaded', () => {
} else if (block.type === 'hr') {
blockEl = document.createElement('hr');
} else {
// paragraph
blockEl = document.createElement('p');
if (block.align) {
blockEl.style.textAlign = block.align;
@@ -2357,14 +2371,242 @@ document.addEventListener('DOMContentLoaded', () => {
if (blockEl) {
blockEl.setAttribute('data-block-idx', domIdx.toString());
readerContentBody.appendChild(blockEl);
blockEl.setAttribute('data-chapter-idx', chapterIdx.toString());
frag.appendChild(blockEl);
}
});
function applyScroll() {
if (!readerScrollArea) return;
return frag;
}
function buildChapterFooterDOM(chapterIdx) {
if (!currentBook || !currentBook.chapters) return null;
const totalCh = currentBook.chapters.length;
const isLastChapter = (chapterIdx >= totalCh - 1);
const footer = document.createElement('div');
footer.className = 'reader-chapter-footer';
footer.id = `reader-chapter-footer-${chapterIdx}`;
footer.setAttribute('data-footer-for-ch', chapterIdx.toString());
if (isLastChapter) {
footer.classList.add('reader-book-finished');
footer.innerHTML = `
<div class="chapter-footer-status">
<span class="status-check">🎉</span>
<strong style="color: var(--primary); font-size: 16px;">Книга прочитана!</strong>
</div>
<p class="chapter-footer-hint">Поздравляем! Вы завершили чтение «${escapeHtml(currentBook.title)}».</p>
<div class="chapter-footer-nav">
<button type="button" class="btn btn-trainer btn-sm reader-footer-nav-btn" id="footer-train-btn-${chapterIdx}">
🎓 Тренировать слова книги
</button>
<button type="button" class="btn btn-outline btn-sm reader-footer-nav-btn" id="footer-shelf-btn-${chapterIdx}">
📚 К библиотеке
</button>
</div>
`;
const trainBtn = footer.querySelector(`#footer-train-btn-${chapterIdx}`);
if (trainBtn) {
trainBtn.onclick = () => {
const tag = slugify(currentBook.title);
window.location.hash = `#srs/${encodeURIComponent(tag)}`;
};
}
const shelfBtn = footer.querySelector(`#footer-shelf-btn-${chapterIdx}`);
if (shelfBtn) {
shelfBtn.onclick = () => {
window.location.hash = '#reader';
};
}
return footer;
}
const nextCh = chapterIdx + 1;
const nextTitle = currentBook.chapters[nextCh]?.title || `Глава ${nextCh + 1}`;
footer.innerHTML = `
<div class="chapter-footer-status">
<span class="status-check">✔</span>
<span>Глава ${chapterIdx + 1} завершена</span>
</div>
<div class="chapter-footer-nav">
${chapterIdx > 0 ? `
<button type="button" class="btn btn-outline btn-sm reader-footer-nav-btn prev" id="footer-prev-btn-${chapterIdx}">
◀ Назад
</button>` : ''}
<button type="button" class="btn btn-primary btn-sm reader-footer-nav-btn next" id="footer-next-btn-${chapterIdx}">
Следующая: ${escapeHtml(nextTitle)} ▶
</button>
</div>
<div class="chapter-footer-hint">
${readerSettings.flowMode === 'chapters' && readerSettings.autoLoadNext
? '⚡ Автоподгрузка включена: следующая глава подгрузится автоматически при скролле вниз'
: '💡 Нажмите кнопку выше для перехода к следующей главе'}
</div>
`;
const nextBtn = footer.querySelector(`#footer-next-btn-${chapterIdx}`);
if (nextBtn) {
nextBtn.onclick = () => {
if (readerSettings.flowMode === 'continuous') {
const target = document.getElementById(`chapter-anchor-${nextCh}`);
if (target) target.scrollIntoView({ behavior: 'smooth', block: 'start' });
} else if (readerSettings.autoLoadNext) {
appendNextChapter(nextCh, true);
} else {
window.location.hash = `#reader/book/${currentBook.id}/ch/${nextCh}`;
}
};
}
const prevBtn = footer.querySelector(`#footer-prev-btn-${chapterIdx}`);
if (prevBtn) {
prevBtn.onclick = () => {
window.location.hash = `#reader/book/${currentBook.id}/ch/${chapterIdx - 1}`;
};
}
return footer;
}
function appendNextChapter(nextCh, smoothScroll = false) {
if (!currentBook || !currentBook.chapters || nextCh >= currentBook.chapters.length) return;
if (loadedChaptersSet.has(nextCh)) {
if (smoothScroll) {
const target = document.getElementById(`chapter-anchor-${nextCh}`);
if (target) target.scrollIntoView({ behavior: 'smooth', block: 'start' });
}
return;
}
const oldFooter = readerContentBody.querySelector(`.reader-chapter-footer[data-footer-for-ch="${nextCh - 1}"]`);
if (oldFooter) {
oldFooter.remove();
}
const knownWordsMap = new Map();
allCards.forEach(c => {
if (c.term) knownWordsMap.set(c.term.trim().toLowerCase(), c);
});
const chFrag = buildChapterDOM(nextCh, knownWordsMap, true);
readerContentBody.appendChild(chFrag);
const newFooter = buildChapterFooterDOM(nextCh);
if (newFooter) {
readerContentBody.appendChild(newFooter);
}
loadedChaptersSet.add(nextCh);
readerPrevChBtn.disabled = false;
readerNextChBtn.disabled = nextCh >= currentBook.chapters.length - 1;
if (smoothScroll) {
const anchor = document.getElementById(`chapter-anchor-${nextCh}`);
if (anchor) {
anchor.scrollIntoView({ behavior: 'smooth', block: 'start' });
}
}
}
function renderContinuousBook(targetChapter = 0, restoreScrollTop = null, restoreFraction = null, restoreAnchorIdx = null) {
if (!currentBook || !currentBook.chapters) return;
currentChapterIndex = Math.max(0, Math.min(targetChapter, currentBook.chapters.length - 1));
readerChapterSelect.value = currentChapterIndex;
readerPrevChBtn.disabled = currentChapterIndex <= 0;
readerNextChBtn.disabled = currentChapterIndex >= currentBook.chapters.length - 1;
readerContentBody.innerHTML = '';
loadedChaptersSet.clear();
const knownWordsMap = new Map();
allCards.forEach(c => {
if (c.term) knownWordsMap.set(c.term.trim().toLowerCase(), c);
});
currentBook.chapters.forEach((_, idx) => {
loadedChaptersSet.add(idx);
const chFrag = buildChapterDOM(idx, knownWordsMap, idx > 0);
readerContentBody.appendChild(chFrag);
});
const finalFooter = buildChapterFooterDOM(currentBook.chapters.length - 1);
if (finalFooter) {
readerContentBody.appendChild(finalFooter);
}
applyScroll(restoreAnchorIdx, restoreScrollTop, restoreFraction, currentChapterIndex);
const posToSave = (restoreFraction !== null && restoreFraction > 0) ? restoreFraction : 0;
fetch(`/api/books/${currentBook.id}/progress`, {
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ current_chapter: currentChapterIndex, current_position: posToSave }),
}).catch(err => console.log('Progress save error:', err));
}
function renderChapter(index, restoreScrollTop = null, restoreFraction = null, restoreAnchorIdx = null) {
if (!currentBook || !currentBook.chapters) return;
if (readerSettings.flowMode === 'continuous') {
renderContinuousBook(index, restoreScrollTop, restoreFraction, restoreAnchorIdx);
return;
}
currentChapterIndex = Math.max(0, Math.min(index, currentBook.chapters.length - 1));
readerChapterSelect.value = currentChapterIndex;
// Refresh status icons in chapter select
if (readerChapterSelect.options) {
for (let i = 0; i < readerChapterSelect.options.length; i++) {
const rawTitle = currentBook.chapters[i]?.title || `Глава ${i + 1}`;
const icon = i < currentChapterIndex ? '✔ ' : (i === currentChapterIndex ? '▶ ' : '○ ');
readerChapterSelect.options[i].textContent = `${icon}${rawTitle}`;
}
}
readerPrevChBtn.disabled = currentChapterIndex <= 0;
readerNextChBtn.disabled = currentChapterIndex >= currentBook.chapters.length - 1;
readerContentBody.innerHTML = '';
loadedChaptersSet.clear();
loadedChaptersSet.add(currentChapterIndex);
const knownWordsMap = new Map();
allCards.forEach(c => {
if (c.term) knownWordsMap.set(c.term.trim().toLowerCase(), c);
});
const chFrag = buildChapterDOM(currentChapterIndex, knownWordsMap, false);
readerContentBody.appendChild(chFrag);
const footer = buildChapterFooterDOM(currentChapterIndex);
if (footer) {
readerContentBody.appendChild(footer);
}
applyScroll(restoreAnchorIdx, restoreScrollTop, restoreFraction, currentChapterIndex);
// Save progress to server
const posToSave = (restoreFraction !== null && restoreFraction > 0) ? restoreFraction : 0;
fetch(`/api/books/${currentBook.id}/progress`, {
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ current_chapter: currentChapterIndex, current_position: posToSave }),
}).catch(err => console.log('Progress save error:', err));
}
function applyScroll(restoreAnchorIdx = null, restoreScrollTop = null, restoreFraction = null, chIdx = 0) {
if (!readerScrollArea) return;
function doScroll() {
if (restoreAnchorIdx !== null && restoreAnchorIdx !== undefined) {
const el = readerContentBody.querySelector(`[data-block-idx="${restoreAnchorIdx}"]`);
const el = readerContentBody.querySelector(`[data-chapter-idx="${chIdx}"][data-block-idx="${restoreAnchorIdx}"]`)
|| readerContentBody.querySelector(`[data-block-idx="${restoreAnchorIdx}"]`);
if (el) {
const containerTop = readerScrollArea.getBoundingClientRect().top;
const elTop = el.getBoundingClientRect().top;
@@ -2378,6 +2620,15 @@ document.addEventListener('DOMContentLoaded', () => {
} else if (restoreFraction !== null && restoreFraction > 0) {
const maxScroll = Math.max(0, readerScrollArea.scrollHeight - readerScrollArea.clientHeight);
readerScrollArea.scrollTop = Math.round(maxScroll * restoreFraction);
} else if (chIdx > 0) {
const anchor = document.getElementById(`chapter-anchor-${chIdx}`);
if (anchor) {
const containerTop = readerScrollArea.getBoundingClientRect().top;
const elTop = anchor.getBoundingClientRect().top;
readerScrollArea.scrollTop += (elTop - containerTop - 10);
} else {
readerScrollArea.scrollTop = 0;
}
} else {
readerScrollArea.scrollTop = 0;
}
@@ -2385,17 +2636,9 @@ document.addEventListener('DOMContentLoaded', () => {
}
requestAnimationFrame(() => {
applyScroll();
setTimeout(applyScroll, 100);
doScroll();
setTimeout(doScroll, 100);
});
// Save progress to server
const posToSave = (restoreFraction !== null && restoreFraction > 0) ? restoreFraction : 0;
fetch(`/api/books/${currentBook.id}/progress`, {
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ current_chapter: currentChapterIndex, current_position: posToSave }),
}).catch(err => console.log('Progress save error:', err));
}
// Parse text into structured semantic blocks
@@ -3054,20 +3297,39 @@ document.addEventListener('DOMContentLoaded', () => {
});
}
// Chapter Navigation & Reader Toolbar (URL Hash Driven)
// Chapter Navigation & Reader Toolbar (URL Hash & Continuous Scroll Driven)
if (readerChapterSelect) {
readerChapterSelect.addEventListener('change', () => {
const ch = parseInt(readerChapterSelect.value, 10);
if (currentBook) {
window.location.hash = `#reader/book/${currentBook.id}/ch/${ch}`;
if (!currentBook) return;
if (readerSettings.flowMode === 'continuous' || loadedChaptersSet.has(ch)) {
currentChapterIndex = ch;
const target = document.getElementById(`chapter-anchor-${ch}`)
|| readerContentBody.querySelector(`[data-chapter-idx="${ch}"]`);
if (target) {
target.scrollIntoView({ behavior: 'smooth', block: 'start' });
return;
}
}
window.location.hash = `#reader/book/${currentBook.id}/ch/${ch}`;
});
}
if (readerPrevChBtn) {
readerPrevChBtn.addEventListener('click', () => {
if (currentBook && currentChapterIndex > 0) {
window.location.hash = `#reader/book/${currentBook.id}/ch/${currentChapterIndex - 1}`;
const prev = currentChapterIndex - 1;
if (readerSettings.flowMode === 'continuous' || loadedChaptersSet.has(prev)) {
currentChapterIndex = prev;
if (readerChapterSelect) readerChapterSelect.value = prev;
const target = document.getElementById(`chapter-anchor-${prev}`)
|| readerContentBody.querySelector(`[data-chapter-idx="${prev}"]`);
if (target) {
target.scrollIntoView({ behavior: 'smooth', block: 'start' });
return;
}
}
window.location.hash = `#reader/book/${currentBook.id}/ch/${prev}`;
}
});
}
@@ -3075,7 +3337,22 @@ document.addEventListener('DOMContentLoaded', () => {
if (readerNextChBtn) {
readerNextChBtn.addEventListener('click', () => {
if (currentBook && currentBook.chapters && currentChapterIndex < currentBook.chapters.length - 1) {
window.location.hash = `#reader/book/${currentBook.id}/ch/${currentChapterIndex + 1}`;
const next = currentChapterIndex + 1;
if (readerSettings.flowMode === 'continuous' || loadedChaptersSet.has(next)) {
currentChapterIndex = next;
if (readerChapterSelect) readerChapterSelect.value = next;
const target = document.getElementById(`chapter-anchor-${next}`)
|| readerContentBody.querySelector(`[data-chapter-idx="${next}"]`);
if (target) {
target.scrollIntoView({ behavior: 'smooth', block: 'start' });
return;
}
}
if (readerSettings.flowMode === 'chapters' && readerSettings.autoLoadNext) {
appendNextChapter(next, true);
} else {
window.location.hash = `#reader/book/${currentBook.id}/ch/${next}`;
}
}
});
}
@@ -3112,8 +3389,42 @@ document.addEventListener('DOMContentLoaded', () => {
return 0;
}
function getActiveVisibleChapter() {
if (!readerScrollArea || !readerContentBody) return currentChapterIndex;
const dividers = readerContentBody.querySelectorAll('[data-chapter-idx]');
if (!dividers || dividers.length === 0) return currentChapterIndex;
const containerTop = readerScrollArea.getBoundingClientRect().top;
let activeIdx = currentChapterIndex;
for (let el of dividers) {
const rect = el.getBoundingClientRect();
if (rect.top <= containerTop + 140) {
const ch = parseInt(el.getAttribute('data-chapter-idx'), 10);
if (!isNaN(ch)) activeIdx = ch;
} else {
break;
}
}
return activeIdx;
}
function updateReaderProgressBar() {
const { pct, fraction } = getReaderScrollMetrics();
if (readerSettings.flowMode === 'continuous') {
if (readerChapterProgressFill) {
readerChapterProgressFill.style.width = `${pct}%`;
}
if (readerScrollPctBadge) {
readerScrollPctBadge.textContent = `${pct}%`;
const totalCh = currentBook && currentBook.chapters ? currentBook.chapters.length : 1;
readerScrollPctBadge.title = `Вся книга: ${pct}% • Глава ${currentChapterIndex + 1} из ${totalCh}`;
}
return;
}
// Chapters mode
if (readerChapterProgressFill) {
readerChapterProgressFill.style.width = `${pct}%`;
}
@@ -3136,10 +3447,47 @@ document.addEventListener('DOMContentLoaded', () => {
if (!currentBook || !readerScrollArea) return;
updateReaderProgressBar();
// 1. Detect active visible chapter in continuous or multi-chapter view
if (readerSettings.flowMode === 'continuous' || loadedChaptersSet.size > 1) {
const activeCh = getActiveVisibleChapter();
if (activeCh !== null && activeCh !== undefined && activeCh !== currentChapterIndex) {
currentChapterIndex = activeCh;
if (readerChapterSelect) readerChapterSelect.value = currentChapterIndex;
if (readerChapterSelect && readerChapterSelect.options) {
for (let i = 0; i < readerChapterSelect.options.length; i++) {
const rawTitle = currentBook.chapters[i]?.title || `Глава ${i + 1}`;
const icon = i < currentChapterIndex ? '✔ ' : (i === currentChapterIndex ? '▶ ' : '○ ');
readerChapterSelect.options[i].textContent = `${icon}${rawTitle}`;
}
}
if (readerPrevChBtn) readerPrevChBtn.disabled = currentChapterIndex <= 0;
if (readerNextChBtn) readerNextChBtn.disabled = currentChapterIndex >= currentBook.chapters.length - 1;
const desiredHash = `#reader/book/${currentBook.id}/ch/${currentChapterIndex}`;
if (window.location.hash !== desiredHash) {
history.replaceState(null, '', desiredHash);
}
}
}
// 2. Infinite scroll: auto-append next chapter in chapters mode
if (readerSettings.flowMode === 'chapters' && readerSettings.autoLoadNext && !isAppendingChapter) {
const distanceToBottom = readerScrollArea.scrollHeight - (readerScrollArea.scrollTop + readerScrollArea.clientHeight);
if (distanceToBottom < 450) {
const loadedArr = Array.from(loadedChaptersSet);
const highestCh = loadedArr.length > 0 ? Math.max(...loadedArr) : currentChapterIndex;
if (currentBook && currentBook.chapters && highestCh < currentBook.chapters.length - 1) {
isAppendingChapter = true;
appendNextChapter(highestCh + 1, false);
setTimeout(() => { isAppendingChapter = false; }, 300);
}
}
}
const { scrollTop, fraction } = getReaderScrollMetrics();
const anchorIdx = getActiveAnchorIndex();
// 1. Instant local persistence
// 3. Instant local persistence
try {
const key = `notes_book_${currentBook.id}_pos`;
localStorage.setItem(key, JSON.stringify({
@@ -3153,7 +3501,7 @@ document.addEventListener('DOMContentLoaded', () => {
localStorage.setItem('notes_active_view', 'reader-view');
} catch (e) {}
// 2. Debounced server sync (every 800ms)
// 4. Debounced server sync (every 800ms)
clearTimeout(readerScrollSaveTimer);
readerScrollSaveTimer = setTimeout(() => {
if (!currentBook) return;
@@ -3281,6 +3629,22 @@ document.addEventListener('DOMContentLoaded', () => {
btn.classList.toggle('active', btn.dataset.align === readerSettings.textAlign);
});
}
if (readerModeGroup) {
readerModeGroup.querySelectorAll('.seg-btn').forEach(btn => {
btn.classList.toggle('active', btn.dataset.flow === readerSettings.flowMode);
});
}
if (settingAutoloadRow) {
settingAutoloadRow.style.display = (readerSettings.flowMode === 'chapters') ? '' : 'none';
}
if (readerAutoloadGroup) {
readerAutoloadGroup.querySelectorAll('.seg-btn').forEach(btn => {
btn.classList.toggle('active', (btn.dataset.autoload === 'true') === readerSettings.autoLoadNext);
});
}
}
function positionSettingsPanel() {
@@ -3406,6 +3770,39 @@ document.addEventListener('DOMContentLoaded', () => {
});
}
if (readerModeGroup) {
readerModeGroup.addEventListener('click', (e) => {
const btn = e.target.closest('.seg-btn');
if (btn && btn.dataset.flow) {
if (readerSettings.flowMode !== btn.dataset.flow) {
readerSettings.flowMode = btn.dataset.flow;
saveReaderSettings();
updateReaderSettingsUI();
if (currentBook) {
renderChapter(currentChapterIndex, 0, 0, 0);
}
}
}
});
}
if (readerAutoloadGroup) {
readerAutoloadGroup.addEventListener('click', (e) => {
const btn = e.target.closest('.seg-btn');
if (btn && btn.dataset.autoload !== undefined) {
readerSettings.autoLoadNext = (btn.dataset.autoload === 'true');
saveReaderSettings();
updateReaderSettingsUI();
const footerHint = readerContentBody.querySelector('.chapter-footer-hint');
if (footerHint) {
footerHint.textContent = (readerSettings.flowMode === 'chapters' && readerSettings.autoLoadNext)
? '⚡ Автоподгрузка включена: следующая глава подгрузится автоматически при скролле вниз'
: '💡 Нажмите кнопку выше для перехода к следующей главе';
}
}
});
}
document.addEventListener('click', (e) => {
if (readerSettingsPanel && !readerSettingsPanel.classList.contains('hidden')) {
if (!readerSettingsPanel.contains(e.target) && e.target !== readerSettingsBtn && !readerSettingsBtn.contains(e.target)) {