feat(reader): implement full appearance customization panel with themes, fonts, margins, line-height, and localStorage persistence

This commit is contained in:
Antigravity committed 2026-10-05 16:15:20 +03:00
1 parent c0fdcd776b
commit 84e9be614e
4 files changed
+589 -37

No files matched your search

+205 -17
View File
@@ -102,8 +102,18 @@ document.addEventListener('DOMContentLoaded', () => {
const readerChapterSelect = document.getElementById('reader-chapter-select');
const readerPrevChBtn = document.getElementById('reader-prev-ch-btn');
const readerNextChBtn = document.getElementById('reader-next-ch-btn');
const readerFontDecBtn = document.getElementById('reader-font-dec-btn');
const readerFontIncBtn = document.getElementById('reader-font-inc-btn');
const readerSettingsBtn = document.getElementById('reader-settings-btn');
const readerSettingsPanel = document.getElementById('reader-settings-panel');
const readerSettingsCloseBtn = document.getElementById('reader-settings-close-btn');
const settingFontDecBtn = document.getElementById('setting-font-dec-btn');
const settingFontIncBtn = document.getElementById('setting-font-inc-btn');
const settingFontSizeVal = document.getElementById('setting-font-size-val');
const readerFontFamilyGroup = document.getElementById('reader-font-family-group');
const readerLineHeightGroup = document.getElementById('reader-line-height-group');
const readerMarginsGroup = document.getElementById('reader-margins-group');
const readerThemeGroup = document.getElementById('reader-theme-group');
const readerAlignGroup = document.getElementById('reader-align-group');
const readerToggleHighlightBtn = document.getElementById('reader-toggle-highlight-btn');
const readerTrainBtn = document.getElementById('reader-train-btn');
const readerContentBody = document.getElementById('reader-content-body');
@@ -123,10 +133,18 @@ document.addEventListener('DOMContentLoaded', () => {
const popupSavedBadge = document.getElementById('popup-saved-badge');
const popupAddContextBtn = document.getElementById('popup-add-context-btn');
// Reader State
// Reader State & Appearance Settings
const DEFAULT_READER_SETTINGS = {
fontSize: 18,
fontFamily: 'serif',
lineHeight: 1.65,
marginWidth: 'normal',
theme: 'dark',
textAlign: 'left'
};
let readerSettings = { ...DEFAULT_READER_SETTINGS };
let currentBook = null;
let currentChapterIndex = 0;
let readerFontSize = 19;
let isHighlightEnabled = false;
let popupCurrentData = null;
@@ -1989,6 +2007,7 @@ document.addEventListener('DOMContentLoaded', () => {
bookshelfContainer.classList.add('hidden');
readerInterface.classList.remove('hidden');
applyReaderSettings();
renderChapter(currentChapterIndex);
} catch (e) {
@@ -2826,23 +2845,192 @@ document.addEventListener('DOMContentLoaded', () => {
});
}
if (readerFontIncBtn) {
readerFontIncBtn.addEventListener('click', () => {
if (readerFontSize < 28) {
readerFontSize += 2;
readerContentBody.style.fontSize = `${readerFontSize}px`;
function loadSavedReaderSettings() {
try {
const saved = localStorage.getItem('notes_reader_settings');
if (saved) {
readerSettings = Object.assign({}, DEFAULT_READER_SETTINGS, JSON.parse(saved));
}
} catch (e) {}
}
function saveReaderSettings() {
try {
localStorage.setItem('notes_reader_settings', JSON.stringify(readerSettings));
} catch (e) {}
}
function applyReaderSettings() {
if (!readerScrollArea) return;
// 1. Font size
readerScrollArea.style.setProperty('--reader-font-size', `${readerSettings.fontSize}px`);
if (settingFontSizeVal) settingFontSizeVal.textContent = `${readerSettings.fontSize} px`;
// 2. Line height
readerScrollArea.style.setProperty('--reader-line-height', readerSettings.lineHeight);
// 3. Theme
readerScrollArea.classList.remove('reader-theme-dark', 'reader-theme-sepia', 'reader-theme-light', 'reader-theme-oled');
readerScrollArea.classList.add(`reader-theme-${readerSettings.theme}`);
// 4. Font family
readerScrollArea.classList.remove('reader-font-sans', 'reader-font-serif', 'reader-font-mono');
readerScrollArea.classList.add(`reader-font-${readerSettings.fontFamily}`);
// 5. Margin width
readerScrollArea.classList.remove('reader-margins-narrow', 'reader-margins-normal', 'reader-margins-wide');
readerScrollArea.classList.add(`reader-margins-${readerSettings.marginWidth}`);
// 6. Text align
readerScrollArea.classList.remove('reader-align-left', 'reader-align-justify');
readerScrollArea.classList.add(`reader-align-${readerSettings.textAlign}`);
updateReaderSettingsUI();
}
function updateReaderSettingsUI() {
if (!readerSettingsPanel) return;
if (settingFontSizeVal) settingFontSizeVal.textContent = `${readerSettings.fontSize} px`;
if (readerFontFamilyGroup) {
readerFontFamilyGroup.querySelectorAll('.seg-btn').forEach(btn => {
btn.classList.toggle('active', btn.dataset.font === readerSettings.fontFamily);
});
}
if (readerLineHeightGroup) {
readerLineHeightGroup.querySelectorAll('.seg-btn').forEach(btn => {
btn.classList.toggle('active', Math.abs(parseFloat(btn.dataset.lh) - parseFloat(readerSettings.lineHeight)) < 0.05);
});
}
if (readerMarginsGroup) {
readerMarginsGroup.querySelectorAll('.seg-btn').forEach(btn => {
btn.classList.toggle('active', btn.dataset.margin === readerSettings.marginWidth);
});
}
if (readerThemeGroup) {
readerThemeGroup.querySelectorAll('.theme-chip').forEach(btn => {
btn.classList.toggle('active', btn.dataset.theme === readerSettings.theme);
});
}
if (readerAlignGroup) {
readerAlignGroup.querySelectorAll('.seg-btn').forEach(btn => {
btn.classList.toggle('active', btn.dataset.align === readerSettings.textAlign);
});
}
}
function initReaderSettingsPanel() {
loadSavedReaderSettings();
applyReaderSettings();
if (readerSettingsBtn) {
readerSettingsBtn.addEventListener('click', (e) => {
e.stopPropagation();
if (readerSettingsPanel) {
const isHidden = readerSettingsPanel.classList.contains('hidden');
readerSettingsPanel.classList.toggle('hidden', !isHidden);
if (isHidden) updateReaderSettingsUI();
}
});
}
if (readerSettingsCloseBtn) {
readerSettingsCloseBtn.addEventListener('click', () => {
if (readerSettingsPanel) readerSettingsPanel.classList.add('hidden');
});
}
if (settingFontDecBtn) {
settingFontDecBtn.addEventListener('click', () => {
if (readerSettings.fontSize > 13) {
readerSettings.fontSize -= 1;
applyReaderSettings();
saveReaderSettings();
}
});
}
if (settingFontIncBtn) {
settingFontIncBtn.addEventListener('click', () => {
if (readerSettings.fontSize < 34) {
readerSettings.fontSize += 1;
applyReaderSettings();
saveReaderSettings();
}
});
}
if (readerFontFamilyGroup) {
readerFontFamilyGroup.addEventListener('click', (e) => {
const btn = e.target.closest('.seg-btn');
if (btn && btn.dataset.font) {
readerSettings.fontFamily = btn.dataset.font;
applyReaderSettings();
saveReaderSettings();
}
});
}
if (readerLineHeightGroup) {
readerLineHeightGroup.addEventListener('click', (e) => {
const btn = e.target.closest('.seg-btn');
if (btn && btn.dataset.lh) {
readerSettings.lineHeight = parseFloat(btn.dataset.lh);
applyReaderSettings();
saveReaderSettings();
}
});
}
if (readerMarginsGroup) {
readerMarginsGroup.addEventListener('click', (e) => {
const btn = e.target.closest('.seg-btn');
if (btn && btn.dataset.margin) {
readerSettings.marginWidth = btn.dataset.margin;
applyReaderSettings();
saveReaderSettings();
}
});
}
if (readerThemeGroup) {
readerThemeGroup.addEventListener('click', (e) => {
const btn = e.target.closest('.theme-chip');
if (btn && btn.dataset.theme) {
readerSettings.theme = btn.dataset.theme;
applyReaderSettings();
saveReaderSettings();
}
});
}
if (readerAlignGroup) {
readerAlignGroup.addEventListener('click', (e) => {
const btn = e.target.closest('.seg-btn');
if (btn && btn.dataset.align) {
readerSettings.textAlign = btn.dataset.align;
applyReaderSettings();
saveReaderSettings();
}
});
}
document.addEventListener('click', (e) => {
if (readerSettingsPanel && !readerSettingsPanel.classList.contains('hidden')) {
if (!readerSettingsPanel.contains(e.target) && e.target !== readerSettingsBtn && !readerSettingsBtn.contains(e.target)) {
readerSettingsPanel.classList.add('hidden');
}
}
});
}
if (readerFontDecBtn) {
readerFontDecBtn.addEventListener('click', () => {
if (readerFontSize > 14) {
readerFontSize -= 2;
readerContentBody.style.fontSize = `${readerFontSize}px`;
}
});
}
initReaderSettingsPanel();
if (readerToggleHighlightBtn) {
readerToggleHighlightBtn.addEventListener('click', () => {