fix(reader): mount app volume in docker-compose, move popup to body root and add smooth scroll tracking
This commit is contained in:
+25
-25
@@ -5,7 +5,7 @@
|
|||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
|
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
|
||||||
<title>Notes — Персональный тренажер и словарь английского языка</title>
|
<title>Notes — Персональный тренажер и словарь английского языка</title>
|
||||||
<meta name="description" content="Защищённый персональный тренажёр и словарь английского языка с интервальными повторениями SRS SM-2, контекстным поиском и озвучкой">
|
<meta name="description" content="Защищённый персональный тренажёр и словарь английского языка с интервальными повторениями SRS SM-2, контекстным поиском и озвучкой">
|
||||||
<link rel="stylesheet" href="/static/css/style.css?v=2.3">
|
<link rel="stylesheet" href="/static/css/style.css?v=2.5">
|
||||||
|
|
||||||
<!-- PWA meta and icons -->
|
<!-- PWA meta and icons -->
|
||||||
<link rel="manifest" href="/manifest.json">
|
<link rel="manifest" href="/manifest.json">
|
||||||
@@ -581,29 +581,6 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Interactive Click-to-Translate Popup -->
|
|
||||||
<div id="reader-word-popup" class="reader-word-popup hidden">
|
|
||||||
<div class="popup-header">
|
|
||||||
<div class="popup-term-wrap">
|
|
||||||
<strong id="popup-term" class="popup-term">word</strong>
|
|
||||||
<span id="popup-phonetic" class="popup-phonetic">/wɜːd/</span>
|
|
||||||
<button id="popup-audio-btn" class="icon-btn audio-btn" style="display: none;" title="Слушать произношение">🔊</button>
|
|
||||||
</div>
|
|
||||||
<button id="popup-close-btn" class="popup-close-btn">✕</button>
|
|
||||||
</div>
|
|
||||||
<div class="popup-body">
|
|
||||||
<div id="popup-loading" class="popup-loading hidden">Переводим...</div>
|
|
||||||
<div id="popup-content" class="popup-content">
|
|
||||||
<div class="popup-translation" id="popup-translation">перевод</div>
|
|
||||||
<div class="popup-context" id="popup-context">“Контекст из книги...”</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="popup-footer">
|
|
||||||
<button id="popup-save-btn" class="btn btn-primary btn-sm btn-block">💾 Добавить в словарь</button>
|
|
||||||
<div id="popup-saved-badge" class="popup-saved-badge hidden">✅ Сохранено в словарь!</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<!-- Sticky Bulk Action Bar for Batch Selection Mode -->
|
<!-- Sticky Bulk Action Bar for Batch Selection Mode -->
|
||||||
@@ -919,9 +896,32 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- Interactive Click-to-Translate Popup (Mounted at body root to prevent scroll clipping) -->
|
||||||
|
<div id="reader-word-popup" class="reader-word-popup hidden">
|
||||||
|
<div class="popup-header">
|
||||||
|
<div class="popup-term-wrap">
|
||||||
|
<strong id="popup-term" class="popup-term">word</strong>
|
||||||
|
<span id="popup-phonetic" class="popup-phonetic">/wɜːd/</span>
|
||||||
|
<button id="popup-audio-btn" class="icon-btn audio-btn" style="display: none;" title="Слушать произношение">🔊</button>
|
||||||
|
</div>
|
||||||
|
<button id="popup-close-btn" class="popup-close-btn">✕</button>
|
||||||
|
</div>
|
||||||
|
<div class="popup-body">
|
||||||
|
<div id="popup-loading" class="popup-loading hidden">Переводим...</div>
|
||||||
|
<div id="popup-content" class="popup-content">
|
||||||
|
<div class="popup-translation" id="popup-translation">перевод</div>
|
||||||
|
<div class="popup-context" id="popup-context">“Контекст из книги...”</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="popup-footer">
|
||||||
|
<button id="popup-save-btn" class="btn btn-primary btn-sm btn-block">💾 Добавить в словарь</button>
|
||||||
|
<div id="popup-saved-badge" class="popup-saved-badge hidden">✅ Сохранено в словарь!</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<!-- Audio Element for native streaming -->
|
<!-- Audio Element for native streaming -->
|
||||||
<audio id="global-audio-player" preload="none"></audio>
|
<audio id="global-audio-player" preload="none"></audio>
|
||||||
|
|
||||||
<script src="/static/js/app.js?v=2.3"></script>
|
<script src="/static/js/app.js?v=2.5"></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
+30
-2
@@ -2133,8 +2133,8 @@ document.addEventListener('DOMContentLoaded', () => {
|
|||||||
let top = rect.bottom + 8;
|
let top = rect.bottom + 8;
|
||||||
// If bottom overflows viewport, flip above the word
|
// If bottom overflows viewport, flip above the word
|
||||||
if (top + popupHeight > viewportHeight - 12) {
|
if (top + popupHeight > viewportHeight - 12) {
|
||||||
if (rect.top - popupHeight - 8 >= 12 || rect.top > viewportHeight / 2) {
|
if (rect.top - popupHeight - 8 >= 65 || rect.top > viewportHeight / 2) {
|
||||||
top = Math.max(12, rect.top - popupHeight - 8);
|
top = Math.max(65, rect.top - popupHeight - 8);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2206,6 +2206,34 @@ document.addEventListener('DOMContentLoaded', () => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
function updatePopupPositionOnScroll() {
|
||||||
|
if (!readerWordPopup || readerWordPopup.classList.contains('hidden')) return;
|
||||||
|
if (!popupCurrentData || !popupCurrentData.targetElement || !popupCurrentData.targetElement.isConnected) {
|
||||||
|
readerWordPopup.classList.add('hidden');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const rect = popupCurrentData.targetElement.getBoundingClientRect();
|
||||||
|
const headerOffset = 65; // reader toolbar sticky height
|
||||||
|
if (rect.bottom < headerOffset || rect.top > window.innerHeight) {
|
||||||
|
readerWordPopup.classList.add('hidden');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
positionWordPopup(popupCurrentData.targetElement);
|
||||||
|
}
|
||||||
|
|
||||||
|
let popupScrollRaf = null;
|
||||||
|
function schedulePopupUpdate() {
|
||||||
|
if (!readerWordPopup || readerWordPopup.classList.contains('hidden')) return;
|
||||||
|
if (popupScrollRaf) return;
|
||||||
|
popupScrollRaf = requestAnimationFrame(() => {
|
||||||
|
popupScrollRaf = null;
|
||||||
|
updatePopupPositionOnScroll();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
window.addEventListener('scroll', schedulePopupUpdate, { passive: true, capture: true });
|
||||||
|
window.addEventListener('resize', schedulePopupUpdate, { passive: true });
|
||||||
|
|
||||||
if (popupSaveBtn) {
|
if (popupSaveBtn) {
|
||||||
popupSaveBtn.addEventListener('click', async () => {
|
popupSaveBtn.addEventListener('click', async () => {
|
||||||
if (!popupCurrentData) return;
|
if (!popupCurrentData) return;
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ services:
|
|||||||
- "127.0.0.1:8002:8002"
|
- "127.0.0.1:8002:8002"
|
||||||
volumes:
|
volumes:
|
||||||
- ./data:/app/data
|
- ./data:/app/data
|
||||||
|
- ./app:/app/app
|
||||||
environment:
|
environment:
|
||||||
- HOST=0.0.0.0
|
- HOST=0.0.0.0
|
||||||
- PORT=8002
|
- PORT=8002
|
||||||
|
|||||||
Reference in New Issue
Block a user