'use strict'; /** * textbook-xp-widget.js — синхронизация XP учебников с системной геймификацией. * * Для учебников с локальным window.addXp: * monkey-patch addXp, чтобы каждый вызов также дёргал window.LS.xp.add. * * Для всех учебников (в т.ч. chemistry_9, physics_9 без addXp): * первый клик по .para-pill[data-para] за сессию/навсегда даёт +5 XP. * * Зависимость: /js/xp.js должен загрузиться раньше (defer, порядок тегов). */ (function () { /* ── Получить slug текущей страницы ── */ function _slug() { var m = location.pathname.match(/\/textbook\/([^/?#]+)/); if (m) return m[1]; return location.pathname.split('/').pop() .replace(/\.html$/i, '') .replace(/_/g, '-'); } /* ── Monkey-patch window.addXp (для учебников, где он есть) ── */ function _patchAddXp(slug) { var orig = window.addXp; if (typeof orig !== 'function') return; window.addXp = function patchedAddXp(amount, source) { var ret = orig.apply(this, arguments); // пробрасываем в глобальный XP только при успехе оригинала if (window.LS && window.LS.xp && amount > 0) { var src = slug + '-' + (source || 'misc'); window.LS.xp.add(amount, src); } return ret; }; } /* ── Para-pill auto-award ── */ function _initParaAward(slug) { var STORAGE_KEY = slug + '_xp_paras'; function _loadSeen() { try { return JSON.parse(localStorage.getItem(STORAGE_KEY) || '[]'); } catch (e) { return []; } } function _saveSeen(arr) { try { localStorage.setItem(STORAGE_KEY, JSON.stringify(arr)); } catch (e) {} } document.body.addEventListener('click', function (e) { var pill = e.target.closest('.para-pill[data-para]'); if (!pill) return; var paraId = pill.getAttribute('data-para'); if (!paraId) return; // динамически сгенерированные пилюли могут содержать шаблонные значения if (paraId.indexOf("'") !== -1 || paraId.indexOf('+') !== -1) return; // пропустить, если LS.xp недоступен (не авторизован) if (!window.LS || !window.LS.xp) return; var seen = _loadSeen(); if (seen.indexOf(paraId) !== -1) return; // уже начислено seen.push(paraId); _saveSeen(seen); window.LS.xp.add(5, slug + '-para-' + paraId); }, /* capture = */ false); } /* ── Обновление #hero-xp-badge при изменении XP (обратная совместимость) ── */ function _bindBadge() { var badge = document.getElementById('hero-xp-badge'); if (!badge) return; if (!window.LS || !window.LS.xp) return; window.LS.xp.on('change', function (state) { if (!state) return; badge.textContent = state.xp + ' XP · Ур. ' + state.level; }); } /* ── Инициализация (ждём DOM + xp.js) ── */ function _init() { var slug = _slug(); // Загрузить XP с сервера (merge локального + серверного) if (window.LS && window.LS.xp) { window.LS.xp.load(); } _patchAddXp(slug); _initParaAward(slug); _bindBadge(); } // defer гарантирует DOM готов к моменту выполнения, // но xp.js тоже defer — порядок тегов определяет порядок выполнения. // На случай гонки: если xp.js загружен первым — LS.xp уже есть. if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', _init); } else { _init(); } })();