diff --git a/backend/src/routes/textbooks.js b/backend/src/routes/textbooks.js
index 664b315..3ca22ca 100644
--- a/backend/src/routes/textbooks.js
+++ b/backend/src/routes/textbooks.js
@@ -214,8 +214,6 @@ router.get('/:slug', (req, res) => {
/* POST /api/textbooks/:slug/progress — update progress */
router.post('/:slug/progress', (req, res) => {
- // DEBUG: log incoming progress writes
- console.log('[progress]', new Date().toISOString(), 'user', req.user && req.user.id, '| slug=', req.params.slug, '| body=', JSON.stringify(req.body || {}));
const t = db.prepare('SELECT id FROM textbooks WHERE slug=? AND is_active=1').get(req.params.slug);
if (!t) return res.status(404).json({ error: 'Учебник не найден' });
diff --git a/frontend/js/textbook-tracker.js b/frontend/js/textbook-tracker.js
index fc0b847..a9990fe 100644
--- a/frontend/js/textbook-tracker.js
+++ b/frontend/js/textbook-tracker.js
@@ -31,32 +31,26 @@
let syncPending = false;
let pendingExtra = null;
function syncToServer(extra) {
- if (typeof LS === 'undefined' || !LS.getToken) { console.warn('[tracker] LS не загружен — пропускаем sync'); return; }
- if (!LS.getToken()) { console.warn('[tracker] нет токена в localStorage — пользователь не залогинен'); return; }
+ if (typeof LS === 'undefined' || !LS.getToken || !LS.getToken()) return;
if (syncPending) {
pendingExtra = Object.assign(pendingExtra || {}, extra || {});
return;
}
syncPending = true;
- const body = JSON.stringify({ last_para: localState.last, ...(extra || {}) });
- console.log('[tracker]', slug, '→ POST', body);
fetch('/api/textbooks/' + slug + '/progress', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': 'Bearer ' + LS.getToken(),
},
- body,
- }).then(r => {
- console.log('[tracker]', slug, '← HTTP', r.status);
- if (!r.ok) r.text().then(t => console.warn('[tracker] ошибка:', t));
+ body: JSON.stringify({ last_para: localState.last, ...(extra || {}) }),
}).finally(() => {
syncPending = false;
if (pendingExtra) {
const next = pendingExtra; pendingExtra = null;
syncToServer(next);
}
- }).catch(e => console.warn('[tracker] fetch упал:', e));
+ }).catch(() => {});
}
/* ── 2. Initial load: merge server data into local state + push back ──
@@ -235,100 +229,63 @@
refreshCheckUI(key);
persist();
syncToServer({ mark_read: key });
- updateDebugBadge();
- }
-
- // DEBUG: визуальный бейдж прогресса в правом нижнем углу.
- function ensureDebugBadge() {
- if (document.getElementById('tb-debug-badge')) return;
- const b = document.createElement('div');
- b.id = 'tb-debug-badge';
- b.style.cssText = 'position:fixed;bottom:12px;right:12px;z-index:99999;background:rgba(15,23,42,.92);color:#fff;padding:10px 14px;border-radius:10px;font-family:monospace;font-size:12px;line-height:1.5;box-shadow:0 4px 14px rgba(0,0,0,.3);max-width:260px';
- document.body.appendChild(b);
- updateDebugBadge();
- }
- function updateDebugBadge() {
- const b = document.getElementById('tb-debug-badge');
- if (!b) return;
- const activeParaEl = document.querySelector('.para-pill.active[data-para]');
- const active = activeParaEl ? activeParaEl.dataset.para : '?';
- b.innerHTML =
- '
tracker debug
' +
- 'slug: ' + slug + '
' +
- 'active pill: ' + active + '
' +
- 'localState.last: ' + (localState.last || '?') + '
' +
- 'read [' + localState.read.length + ']: ' + (localState.read.slice(-8).join(',') || '—') + '
' +
- 'Кликай пилюли — должно меняться
';
}
function wirePillTracking() {
- // Hook 1: всплытие click до body. Работает в обычном HTML.
+ // Hook 1: всплытие click → body. Работает в обычном HTML.
document.body.addEventListener('click', e => {
const pill = e.target && e.target.closest && e.target.closest('.para-pill[data-para]');
if (!pill) return;
- console.log('[tracker] клик по пилюле (bubble)', pill.dataset.para);
recordParaVisit(pill.dataset.para);
});
- // Hook 2: capture-фаза (ловит до того, как кто-то остановит propagation).
+ // Hook 2: capture-фаза — ловит до того, как кто-то остановит propagation.
document.addEventListener('click', e => {
const pill = e.target && e.target.closest && e.target.closest('.para-pill[data-para]');
if (!pill) return;
- // Защита от двойного срабатывания — отметим pill сразу.
if (pill.__tbVisited) return;
pill.__tbVisited = true;
setTimeout(() => { pill.__tbVisited = false; }, 100);
- console.log('[tracker] клик по пилюле (capture)', pill.dataset.para);
recordParaVisit(pill.dataset.para);
}, true);
- // Hook 3: monkey-patch setParaTab — кликом по пилюле химия/физика 9 вызывают
- // inline onclick="setParaTab('pN')". Перехват напрямую = работает даже если
- // event-bubbling сломан расширением браузера или CSS overlay.
+ // Hook 3: monkey-patch setParaTab. chemistry-9 / physics-9 зовут её inline
+ // через onclick="setParaTab('pN')" — перехват на уровне JS-функции
+ // работает даже если event-стек сломан расширением или overlay.
function patchSetParaTab() {
if (typeof window.setParaTab !== 'function' || window.setParaTab.__tbPatched) return;
const orig = window.setParaTab;
const wrapped = function (para) {
try {
- if (para && /^p\d+$/i.test(String(para))) {
- console.log('[tracker] setParaTab перехвачен', para);
- recordParaVisit(String(para));
- }
- } catch (e) { console.warn('[tracker] patch error:', e); }
+ if (para && /^p\d+$/i.test(String(para))) recordParaVisit(String(para));
+ } catch (e) {}
return orig.apply(this, arguments);
};
wrapped.__tbPatched = true;
window.setParaTab = wrapped;
- console.log('[tracker] setParaTab успешно обёрнут');
}
patchSetParaTab();
- // Если страница определяет setParaTab позже — поймаем через короткие опросы.
let tries = 0;
const ivl = setInterval(() => {
patchSetParaTab();
if (typeof window.setParaTab === 'function' && window.setParaTab.__tbPatched) clearInterval(ivl);
if (++tries > 20) clearInterval(ivl);
}, 100);
- // Hook 4b: боковая панель-справочник в chemistry-9 / physics-9 использует
- // .tab[data-tab="refN"] вместо .para-pill. Маппим ref → p и фиксируем.
+ // Hook 4: справочник в chemistry-9 / physics-9 использует .tab[data-tab=refN]
+ // (отдельная панель). Маппим ref → p и фиксируем как просмотр параграфа.
document.addEventListener('click', e => {
const tab = e.target && e.target.closest && e.target.closest('.tab[data-tab]');
if (!tab) return;
const m = String(tab.dataset.tab || '').match(/^ref(\d+)$/);
if (!m) return;
- const para = 'p' + m[1];
- console.log('[tracker] клик по справ. табу', tab.dataset.tab, '→', para);
- recordParaVisit(para);
+ recordParaVisit('p' + m[1]);
}, true);
- // Hook 4: polling — наблюдаем за классом .active на пилюлях.
- // Если кто-то поменял активный параграф (через клик, через JS вызов
- // setParaTab, через любой механизм) — мы это поймаем за 500мс и зафиксируем.
- // Самый robust способ; не зависит ни от событий, ни от наличия функций.
+ // Hook 5: polling — наблюдаем за классом .active на пилюлях.
+ // Срабатывает на любую программную смену активного параграфа.
let lastActivePara = null;
setInterval(() => {
const active = document.querySelector('.para-pill.active[data-para]');
if (!active) return;
const para = active.dataset.para;
if (para && para !== lastActivePara) {
- if (lastActivePara !== null) console.log('[tracker] активный параграф изменился на', para);
lastActivePara = para;
recordParaVisit(para);
}
@@ -566,9 +523,6 @@
}
function boot() {
- console.log('[tracker] boot, slug =', slug, '| LS:', typeof LS !== 'undefined', '| token:', typeof LS !== 'undefined' && LS.getToken && !!LS.getToken());
- ensureDebugBadge();
- setInterval(updateDebugBadge, 1000);
injectStyles();
installBackButton();
installBookmarksBtn();