chore(tracker): убрать отладку — console.log, debug-бейдж, server-лог
Прогресс работает, отладочная обвязка больше не нужна: - 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.
This commit is contained in:
@@ -214,8 +214,6 @@ router.get('/:slug', (req, res) => {
|
|||||||
|
|
||||||
/* POST /api/textbooks/:slug/progress — update progress */
|
/* POST /api/textbooks/:slug/progress — update progress */
|
||||||
router.post('/:slug/progress', (req, res) => {
|
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);
|
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: 'Учебник не найден' });
|
if (!t) return res.status(404).json({ error: 'Учебник не найден' });
|
||||||
|
|
||||||
|
|||||||
@@ -31,32 +31,26 @@
|
|||||||
let syncPending = false;
|
let syncPending = false;
|
||||||
let pendingExtra = null;
|
let pendingExtra = null;
|
||||||
function syncToServer(extra) {
|
function syncToServer(extra) {
|
||||||
if (typeof LS === 'undefined' || !LS.getToken) { console.warn('[tracker] LS не загружен — пропускаем sync'); return; }
|
if (typeof LS === 'undefined' || !LS.getToken || !LS.getToken()) return;
|
||||||
if (!LS.getToken()) { console.warn('[tracker] нет токена в localStorage — пользователь не залогинен'); return; }
|
|
||||||
if (syncPending) {
|
if (syncPending) {
|
||||||
pendingExtra = Object.assign(pendingExtra || {}, extra || {});
|
pendingExtra = Object.assign(pendingExtra || {}, extra || {});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
syncPending = true;
|
syncPending = true;
|
||||||
const body = JSON.stringify({ last_para: localState.last, ...(extra || {}) });
|
|
||||||
console.log('[tracker]', slug, '→ POST', body);
|
|
||||||
fetch('/api/textbooks/' + slug + '/progress', {
|
fetch('/api/textbooks/' + slug + '/progress', {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: {
|
headers: {
|
||||||
'Content-Type': 'application/json',
|
'Content-Type': 'application/json',
|
||||||
'Authorization': 'Bearer ' + LS.getToken(),
|
'Authorization': 'Bearer ' + LS.getToken(),
|
||||||
},
|
},
|
||||||
body,
|
body: JSON.stringify({ last_para: localState.last, ...(extra || {}) }),
|
||||||
}).then(r => {
|
|
||||||
console.log('[tracker]', slug, '← HTTP', r.status);
|
|
||||||
if (!r.ok) r.text().then(t => console.warn('[tracker] ошибка:', t));
|
|
||||||
}).finally(() => {
|
}).finally(() => {
|
||||||
syncPending = false;
|
syncPending = false;
|
||||||
if (pendingExtra) {
|
if (pendingExtra) {
|
||||||
const next = pendingExtra; pendingExtra = null;
|
const next = pendingExtra; pendingExtra = null;
|
||||||
syncToServer(next);
|
syncToServer(next);
|
||||||
}
|
}
|
||||||
}).catch(e => console.warn('[tracker] fetch упал:', e));
|
}).catch(() => {});
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ── 2. Initial load: merge server data into local state + push back ──
|
/* ── 2. Initial load: merge server data into local state + push back ──
|
||||||
@@ -235,100 +229,63 @@
|
|||||||
refreshCheckUI(key);
|
refreshCheckUI(key);
|
||||||
persist();
|
persist();
|
||||||
syncToServer({ mark_read: key });
|
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 =
|
|
||||||
'<div><b>tracker debug</b></div>' +
|
|
||||||
'<div>slug: ' + slug + '</div>' +
|
|
||||||
'<div>active pill: ' + active + '</div>' +
|
|
||||||
'<div>localState.last: ' + (localState.last || '?') + '</div>' +
|
|
||||||
'<div>read [' + localState.read.length + ']: ' + (localState.read.slice(-8).join(',') || '—') + '</div>' +
|
|
||||||
'<div style="opacity:.6;margin-top:4px">Кликай пилюли — должно меняться</div>';
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function wirePillTracking() {
|
function wirePillTracking() {
|
||||||
// Hook 1: всплытие click до body. Работает в обычном HTML.
|
// Hook 1: всплытие click → body. Работает в обычном HTML.
|
||||||
document.body.addEventListener('click', e => {
|
document.body.addEventListener('click', e => {
|
||||||
const pill = e.target && e.target.closest && e.target.closest('.para-pill[data-para]');
|
const pill = e.target && e.target.closest && e.target.closest('.para-pill[data-para]');
|
||||||
if (!pill) return;
|
if (!pill) return;
|
||||||
console.log('[tracker] клик по пилюле (bubble)', pill.dataset.para);
|
|
||||||
recordParaVisit(pill.dataset.para);
|
recordParaVisit(pill.dataset.para);
|
||||||
});
|
});
|
||||||
// Hook 2: capture-фаза (ловит до того, как кто-то остановит propagation).
|
// Hook 2: capture-фаза — ловит до того, как кто-то остановит propagation.
|
||||||
document.addEventListener('click', e => {
|
document.addEventListener('click', e => {
|
||||||
const pill = e.target && e.target.closest && e.target.closest('.para-pill[data-para]');
|
const pill = e.target && e.target.closest && e.target.closest('.para-pill[data-para]');
|
||||||
if (!pill) return;
|
if (!pill) return;
|
||||||
// Защита от двойного срабатывания — отметим pill сразу.
|
|
||||||
if (pill.__tbVisited) return;
|
if (pill.__tbVisited) return;
|
||||||
pill.__tbVisited = true;
|
pill.__tbVisited = true;
|
||||||
setTimeout(() => { pill.__tbVisited = false; }, 100);
|
setTimeout(() => { pill.__tbVisited = false; }, 100);
|
||||||
console.log('[tracker] клик по пилюле (capture)', pill.dataset.para);
|
|
||||||
recordParaVisit(pill.dataset.para);
|
recordParaVisit(pill.dataset.para);
|
||||||
}, true);
|
}, true);
|
||||||
// Hook 3: monkey-patch setParaTab — кликом по пилюле химия/физика 9 вызывают
|
// Hook 3: monkey-patch setParaTab. chemistry-9 / physics-9 зовут её inline
|
||||||
// inline onclick="setParaTab('pN')". Перехват напрямую = работает даже если
|
// через onclick="setParaTab('pN')" — перехват на уровне JS-функции
|
||||||
// event-bubbling сломан расширением браузера или CSS overlay.
|
// работает даже если event-стек сломан расширением или overlay.
|
||||||
function patchSetParaTab() {
|
function patchSetParaTab() {
|
||||||
if (typeof window.setParaTab !== 'function' || window.setParaTab.__tbPatched) return;
|
if (typeof window.setParaTab !== 'function' || window.setParaTab.__tbPatched) return;
|
||||||
const orig = window.setParaTab;
|
const orig = window.setParaTab;
|
||||||
const wrapped = function (para) {
|
const wrapped = function (para) {
|
||||||
try {
|
try {
|
||||||
if (para && /^p\d+$/i.test(String(para))) {
|
if (para && /^p\d+$/i.test(String(para))) recordParaVisit(String(para));
|
||||||
console.log('[tracker] setParaTab перехвачен', para);
|
} catch (e) {}
|
||||||
recordParaVisit(String(para));
|
|
||||||
}
|
|
||||||
} catch (e) { console.warn('[tracker] patch error:', e); }
|
|
||||||
return orig.apply(this, arguments);
|
return orig.apply(this, arguments);
|
||||||
};
|
};
|
||||||
wrapped.__tbPatched = true;
|
wrapped.__tbPatched = true;
|
||||||
window.setParaTab = wrapped;
|
window.setParaTab = wrapped;
|
||||||
console.log('[tracker] setParaTab успешно обёрнут');
|
|
||||||
}
|
}
|
||||||
patchSetParaTab();
|
patchSetParaTab();
|
||||||
// Если страница определяет setParaTab позже — поймаем через короткие опросы.
|
|
||||||
let tries = 0;
|
let tries = 0;
|
||||||
const ivl = setInterval(() => {
|
const ivl = setInterval(() => {
|
||||||
patchSetParaTab();
|
patchSetParaTab();
|
||||||
if (typeof window.setParaTab === 'function' && window.setParaTab.__tbPatched) clearInterval(ivl);
|
if (typeof window.setParaTab === 'function' && window.setParaTab.__tbPatched) clearInterval(ivl);
|
||||||
if (++tries > 20) clearInterval(ivl);
|
if (++tries > 20) clearInterval(ivl);
|
||||||
}, 100);
|
}, 100);
|
||||||
// Hook 4b: боковая панель-справочник в chemistry-9 / physics-9 использует
|
// Hook 4: справочник в chemistry-9 / physics-9 использует .tab[data-tab=refN]
|
||||||
// .tab[data-tab="refN"] вместо .para-pill. Маппим ref<N> → p<N> и фиксируем.
|
// (отдельная панель). Маппим ref<N> → p<N> и фиксируем как просмотр параграфа.
|
||||||
document.addEventListener('click', e => {
|
document.addEventListener('click', e => {
|
||||||
const tab = e.target && e.target.closest && e.target.closest('.tab[data-tab]');
|
const tab = e.target && e.target.closest && e.target.closest('.tab[data-tab]');
|
||||||
if (!tab) return;
|
if (!tab) return;
|
||||||
const m = String(tab.dataset.tab || '').match(/^ref(\d+)$/);
|
const m = String(tab.dataset.tab || '').match(/^ref(\d+)$/);
|
||||||
if (!m) return;
|
if (!m) return;
|
||||||
const para = 'p' + m[1];
|
recordParaVisit('p' + m[1]);
|
||||||
console.log('[tracker] клик по справ. табу', tab.dataset.tab, '→', para);
|
|
||||||
recordParaVisit(para);
|
|
||||||
}, true);
|
}, true);
|
||||||
// Hook 4: polling — наблюдаем за классом .active на пилюлях.
|
// Hook 5: polling — наблюдаем за классом .active на пилюлях.
|
||||||
// Если кто-то поменял активный параграф (через клик, через JS вызов
|
// Срабатывает на любую программную смену активного параграфа.
|
||||||
// setParaTab, через любой механизм) — мы это поймаем за 500мс и зафиксируем.
|
|
||||||
// Самый robust способ; не зависит ни от событий, ни от наличия функций.
|
|
||||||
let lastActivePara = null;
|
let lastActivePara = null;
|
||||||
setInterval(() => {
|
setInterval(() => {
|
||||||
const active = document.querySelector('.para-pill.active[data-para]');
|
const active = document.querySelector('.para-pill.active[data-para]');
|
||||||
if (!active) return;
|
if (!active) return;
|
||||||
const para = active.dataset.para;
|
const para = active.dataset.para;
|
||||||
if (para && para !== lastActivePara) {
|
if (para && para !== lastActivePara) {
|
||||||
if (lastActivePara !== null) console.log('[tracker] активный параграф изменился на', para);
|
|
||||||
lastActivePara = para;
|
lastActivePara = para;
|
||||||
recordParaVisit(para);
|
recordParaVisit(para);
|
||||||
}
|
}
|
||||||
@@ -566,9 +523,6 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
function boot() {
|
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();
|
injectStyles();
|
||||||
installBackButton();
|
installBackButton();
|
||||||
installBookmarksBtn();
|
installBookmarksBtn();
|
||||||
|
|||||||
Reference in New Issue
Block a user