From 08d259bfa2883e85de10c12a3024f2bd0a140d8b Mon Sep 17 00:00:00 2001 From: Maxim Dolgolyov Date: Wed, 27 May 2026 18:12:12 +0300 Subject: [PATCH] =?UTF-8?q?chore(tracker):=20=D1=83=D0=B1=D1=80=D0=B0?= =?UTF-8?q?=D1=82=D1=8C=20=D0=BE=D1=82=D0=BB=D0=B0=D0=B4=D0=BA=D1=83=20?= =?UTF-8?q?=E2=80=94=20console.log,=20debug-=D0=B1=D0=B5=D0=B9=D0=B4=D0=B6?= =?UTF-8?q?,=20server-=D0=BB=D0=BE=D0=B3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Прогресс работает, отладочная обвязка больше не нужна: - tracker.js: удалены все console.log/console.warn (boot, click, POST, HTTP-ответ, patch-успех), удалены ensureDebugBadge и updateDebugBadge (визуальный бейдж в правом нижнем углу), recordParaVisit больше не вызывает updateDebugBadge - 5 хуков (bubble, capture, setParaTab-patch, .tab[refN] sidebar, polling .active) сохранены в production-виде — без логов, но с теми же действиями - backend/routes/textbooks.js: убран '[progress]' console.log из POST /:slug/progress Pre-commit hook теперь проходит без --no-verify. --- backend/src/routes/textbooks.js | 2 - frontend/js/textbook-tracker.js | 76 +++++++-------------------------- 2 files changed, 15 insertions(+), 63 deletions(-) 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();