|
|
@@ -407,15 +407,20 @@
|
|
|
letter-spacing: 0.06em;
|
|
|
}
|
|
|
|
|
|
- .reader-settings-options {
|
|
|
- display: flex;
|
|
|
- flex-wrap: wrap;
|
|
|
- gap: 8px;
|
|
|
- }
|
|
|
-
|
|
|
- .reader-settings-option {
|
|
|
- border: 1px solid rgba(15, 23, 42, 0.12);
|
|
|
- background: #fff;
|
|
|
+ .reader-settings-options {
|
|
|
+ display: flex;
|
|
|
+ flex-wrap: wrap;
|
|
|
+ gap: 8px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .reader-settings-options.reader-settings-options-column {
|
|
|
+ flex-direction: column;
|
|
|
+ align-items: stretch;
|
|
|
+ }
|
|
|
+
|
|
|
+ .reader-settings-option {
|
|
|
+ border: 1px solid rgba(15, 23, 42, 0.12);
|
|
|
+ background: #fff;
|
|
|
border-radius: 999px;
|
|
|
font-size: 12px;
|
|
|
color: #0f172a;
|
|
|
@@ -423,11 +428,40 @@
|
|
|
cursor: pointer;
|
|
|
}
|
|
|
|
|
|
- .reader-settings-option.is-selected {
|
|
|
- background: #0f172a;
|
|
|
- color: #fff;
|
|
|
- border-color: #0f172a;
|
|
|
- }
|
|
|
+ .reader-settings-option.is-selected {
|
|
|
+ background: #0f172a;
|
|
|
+ color: #fff;
|
|
|
+ border-color: #0f172a;
|
|
|
+ }
|
|
|
+
|
|
|
+ .reader-settings-toggle {
|
|
|
+ width: 100%;
|
|
|
+ justify-content: space-between;
|
|
|
+ align-items: center;
|
|
|
+ padding-inline: 12px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .reader-highlight-colors {
|
|
|
+ display: flex;
|
|
|
+ flex-wrap: wrap;
|
|
|
+ gap: 10px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .reader-highlight-color {
|
|
|
+ width: 28px;
|
|
|
+ height: 28px;
|
|
|
+ border-radius: 999px;
|
|
|
+ border: 2px solid rgba(15, 23, 42, 0.16);
|
|
|
+ cursor: pointer;
|
|
|
+ padding: 0;
|
|
|
+ background: var(--swatch-color);
|
|
|
+ box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.75);
|
|
|
+ }
|
|
|
+
|
|
|
+ .reader-highlight-color.is-selected {
|
|
|
+ border-color: #0f172a;
|
|
|
+ box-shadow: 0 0 0 3px rgba(15, 23, 42, 0.12), inset 0 0 0 1px rgba(255, 255, 255, 0.85);
|
|
|
+ }
|
|
|
|
|
|
/* 顶部状态条 */
|
|
|
#loading-indicator {
|
|
|
@@ -495,9 +529,6 @@
|
|
|
}
|
|
|
|
|
|
.tts-progress-highlight {
|
|
|
- --highlight-bg-color: rgba(125, 190, 255, 0.16);
|
|
|
- --highlight-selected-bg-color: rgba(125, 190, 255, 0.16);
|
|
|
- background-color: rgba(125, 190, 255, 0.16) !important;
|
|
|
pointer-events: none;
|
|
|
}
|
|
|
|
|
|
@@ -1225,6 +1256,19 @@
|
|
|
<button class="reader-settings-option" type="button" data-size="large">大</button>
|
|
|
</div>
|
|
|
</div>
|
|
|
+ <div class="reader-settings-group">
|
|
|
+ <label class="reader-settings-label">文本高亮</label>
|
|
|
+ <button id="ttsHighlightToggle" class="reader-settings-option reader-settings-toggle" type="button" aria-pressed="true">开启</button>
|
|
|
+ </div>
|
|
|
+ <div class="reader-settings-group">
|
|
|
+ <label class="reader-settings-label">高亮颜色</label>
|
|
|
+ <div class="reader-highlight-colors" data-setting-group="highlight-color">
|
|
|
+ <button class="reader-highlight-color" type="button" data-highlight-color="blue" data-color-label="浅蓝" style="--swatch-color: rgba(125, 190, 255, 0.28);" title="浅蓝" aria-label="浅蓝"></button>
|
|
|
+ <button class="reader-highlight-color" type="button" data-highlight-color="green" data-color-label="浅绿" style="--swatch-color: rgba(147, 211, 163, 0.28);" title="浅绿" aria-label="浅绿"></button>
|
|
|
+ <button class="reader-highlight-color" type="button" data-highlight-color="yellow" data-color-label="浅黄" style="--swatch-color: rgba(248, 223, 125, 0.3);" title="浅黄" aria-label="浅黄"></button>
|
|
|
+ <button class="reader-highlight-color" type="button" data-highlight-color="purple" data-color-label="浅紫" style="--swatch-color: rgba(196, 167, 255, 0.28);" title="浅紫" aria-label="浅紫"></button>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
</div>
|
|
|
</div>
|
|
|
</aside>
|
|
|
@@ -1577,6 +1621,7 @@
|
|
|
const readerControlsCollapse = document.getElementById('readerControlsCollapse');
|
|
|
const readerSettingsButton = document.getElementById('readerSettingsButton');
|
|
|
const readerSettingsPanel = document.getElementById('readerSettingsPanel');
|
|
|
+ const ttsHighlightToggle = document.getElementById('ttsHighlightToggle');
|
|
|
const sidebarToggleButton = document.getElementById('sidebarToggleButton');
|
|
|
const browserFullscreenButton = document.getElementById('toggle-browser-fullscreen');
|
|
|
|
|
|
@@ -1591,6 +1636,14 @@
|
|
|
const NIGHT_MODE_STORAGE_KEY = 'reader_pro.night_mode';
|
|
|
const READER_SIZE_STORAGE_KEY = 'reader_pro.controls.size';
|
|
|
const READER_COLLAPSED_STORAGE_KEY = 'reader_pro.controls.collapsed';
|
|
|
+ const TTS_HIGHLIGHT_ENABLED_STORAGE_KEY = 'reader_pro.tts_highlight.enabled';
|
|
|
+ const TTS_HIGHLIGHT_COLOR_STORAGE_KEY = 'reader_pro.tts_highlight.color';
|
|
|
+ const TTS_HIGHLIGHT_COLORS = {
|
|
|
+ blue: 'rgba(125, 190, 255, 0.16)',
|
|
|
+ green: 'rgba(147, 211, 163, 0.16)',
|
|
|
+ yellow: 'rgba(248, 223, 125, 0.18)',
|
|
|
+ purple: 'rgba(196, 167, 255, 0.17)'
|
|
|
+ };
|
|
|
|
|
|
function updateReaderToggleIcon() {
|
|
|
const icon = readerControlsCollapse.querySelector('svg path');
|
|
|
@@ -1638,16 +1691,57 @@
|
|
|
document.body.classList.add(`reader-size-${normalized}`);
|
|
|
document.body.dataset.readerSize = normalized;
|
|
|
localStorage.setItem(READER_SIZE_STORAGE_KEY, normalized);
|
|
|
- document.querySelectorAll('[data-size]').forEach(button => {
|
|
|
- button.classList.toggle('is-selected', button.dataset.size === normalized);
|
|
|
- });
|
|
|
- }
|
|
|
-
|
|
|
- function initReaderControls() {
|
|
|
- setReaderControlsPosition('left');
|
|
|
- setReaderControlsSize(localStorage.getItem(READER_SIZE_STORAGE_KEY) || 'small');
|
|
|
- setReaderControlsCollapsed(localStorage.getItem(READER_COLLAPSED_STORAGE_KEY) === '1');
|
|
|
- }
|
|
|
+ document.querySelectorAll('[data-size]').forEach(button => {
|
|
|
+ button.classList.toggle('is-selected', button.dataset.size === normalized);
|
|
|
+ });
|
|
|
+ }
|
|
|
+
|
|
|
+ function isTtsHighlightEnabled() {
|
|
|
+ return localStorage.getItem(TTS_HIGHLIGHT_ENABLED_STORAGE_KEY) !== '0';
|
|
|
+ }
|
|
|
+
|
|
|
+ function getTtsHighlightColorKey() {
|
|
|
+ const colorKey = localStorage.getItem(TTS_HIGHLIGHT_COLOR_STORAGE_KEY) || 'blue';
|
|
|
+ return Object.prototype.hasOwnProperty.call(TTS_HIGHLIGHT_COLORS, colorKey) ? colorKey : 'blue';
|
|
|
+ }
|
|
|
+
|
|
|
+ function applyTtsHighlightSettingsToUi() {
|
|
|
+ const enabled = isTtsHighlightEnabled();
|
|
|
+ const colorKey = getTtsHighlightColorKey();
|
|
|
+ ttsHighlightToggle.classList.toggle('is-selected', enabled);
|
|
|
+ ttsHighlightToggle.textContent = enabled ? '开启' : '关闭';
|
|
|
+ ttsHighlightToggle.setAttribute('aria-pressed', enabled ? 'true' : 'false');
|
|
|
+ document.querySelectorAll('[data-highlight-color]').forEach(button => {
|
|
|
+ button.classList.toggle('is-selected', button.dataset.highlightColor === colorKey);
|
|
|
+ });
|
|
|
+ }
|
|
|
+
|
|
|
+ function refreshActiveHighlightIfNeeded() {
|
|
|
+ clearActiveTtsHighlight();
|
|
|
+ if (!isTtsHighlightEnabled()) return;
|
|
|
+ if (!playbackHighlightContext?.activeSentenceText && playbackHighlightContext?.activeSentenceIndex === null) return;
|
|
|
+ restoreActiveSentenceHighlight();
|
|
|
+ }
|
|
|
+
|
|
|
+ function setTtsHighlightEnabled(enabled) {
|
|
|
+ localStorage.setItem(TTS_HIGHLIGHT_ENABLED_STORAGE_KEY, enabled ? '1' : '0');
|
|
|
+ applyTtsHighlightSettingsToUi();
|
|
|
+ refreshActiveHighlightIfNeeded();
|
|
|
+ }
|
|
|
+
|
|
|
+ function setTtsHighlightColor(colorKey) {
|
|
|
+ const normalized = Object.prototype.hasOwnProperty.call(TTS_HIGHLIGHT_COLORS, colorKey) ? colorKey : 'blue';
|
|
|
+ localStorage.setItem(TTS_HIGHLIGHT_COLOR_STORAGE_KEY, normalized);
|
|
|
+ applyTtsHighlightSettingsToUi();
|
|
|
+ refreshActiveHighlightIfNeeded();
|
|
|
+ }
|
|
|
+
|
|
|
+ function initReaderControls() {
|
|
|
+ setReaderControlsPosition('left');
|
|
|
+ setReaderControlsSize(localStorage.getItem(READER_SIZE_STORAGE_KEY) || 'small');
|
|
|
+ setReaderControlsCollapsed(localStorage.getItem(READER_COLLAPSED_STORAGE_KEY) === '1');
|
|
|
+ applyTtsHighlightSettingsToUi();
|
|
|
+ }
|
|
|
|
|
|
function updateNightModeButton(isEnabled) {
|
|
|
const icon = nightModeButton.querySelector('svg');
|
|
|
@@ -1859,6 +1953,14 @@
|
|
|
readerSettingsPanel.querySelectorAll('[data-size]').forEach(button => {
|
|
|
button.addEventListener('click', () => setReaderControlsSize(button.dataset.size));
|
|
|
});
|
|
|
+ ttsHighlightToggle.addEventListener('click', () => {
|
|
|
+ setTtsHighlightEnabled(!isTtsHighlightEnabled());
|
|
|
+ });
|
|
|
+ readerSettingsPanel.querySelectorAll('[data-highlight-color]').forEach(button => {
|
|
|
+ button.addEventListener('click', () => {
|
|
|
+ setTtsHighlightColor(button.dataset.highlightColor);
|
|
|
+ });
|
|
|
+ });
|
|
|
document.addEventListener('fullscreenchange', updateBrowserFullscreenButton);
|
|
|
document.addEventListener('webkitfullscreenchange', updateBrowserFullscreenButton);
|
|
|
document.addEventListener('mozfullscreenchange', updateBrowserFullscreenButton);
|
|
|
@@ -2333,6 +2435,7 @@
|
|
|
}
|
|
|
|
|
|
function restoreActiveSentenceHighlight(retryCount = 0) {
|
|
|
+ if (!isTtsHighlightEnabled()) return;
|
|
|
const activeSentenceIndex = playbackHighlightContext?.activeSentenceIndex;
|
|
|
const activeSentenceText = playbackHighlightContext?.activeSentenceText || '';
|
|
|
if (activeSentenceIndex === null && !activeSentenceText) return;
|
|
|
@@ -2348,6 +2451,7 @@
|
|
|
}
|
|
|
|
|
|
function updateHighlightWindow(rangeStart, rangeEnd) {
|
|
|
+ if (!isTtsHighlightEnabled()) return;
|
|
|
const ctx = playbackHighlightContext;
|
|
|
if (!ctx?.textLayerIndex) return;
|
|
|
const layerText = ctx.textLayerIndex.text;
|
|
|
@@ -2385,6 +2489,10 @@
|
|
|
|
|
|
const overlay = document.createElement('div');
|
|
|
overlay.className = 'highlight appended tts-progress-highlight';
|
|
|
+ const highlightColor = TTS_HIGHLIGHT_COLORS[getTtsHighlightColorKey()];
|
|
|
+ overlay.style.setProperty('--highlight-bg-color', highlightColor, 'important');
|
|
|
+ overlay.style.setProperty('--highlight-selected-bg-color', highlightColor, 'important');
|
|
|
+ overlay.style.setProperty('background-color', highlightColor, 'important');
|
|
|
overlay.style.left = `${left}px`;
|
|
|
overlay.style.top = `${top}px`;
|
|
|
overlay.style.width = `${width}px`;
|
|
|
@@ -2419,6 +2527,7 @@
|
|
|
ctx.lastWindowKey = '';
|
|
|
ctx.activeSentenceIndex = sentenceIndex;
|
|
|
ctx.activeSentenceText = targetSentence;
|
|
|
+ if (!isTtsHighlightEnabled()) return;
|
|
|
updateHighlightWindow(matchStart, matchEnd);
|
|
|
}
|
|
|
|