feat(reader): implement full appearance customization panel with themes, fonts, margins, line-height, and localStorage persistence
This commit is contained in:
+205
-17
@@ -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', () => {
|
||||
|
||||
Reference in New Issue
Block a user