feat(xp): physics8 + chem9 + phys9 синхронизируют XP с системной геймификацией
- js/textbook-xp-widget.js: shared модуль (monkey-patch addXp + para-pill auto-award для учебников без addXp) - physics8_thermal/electro/optics: добавлены теги /js/xp.js и /js/textbook-xp-widget.js — теперь все 74 addXp-хука пробрасываются в глобальный gamification (через self-award endpoint с дебаунсом) - chemistry_9 + physics_9: те же теги. Каждый первый клик по .para-pill даёт +5 XP в систему (без правок 23000 LOC) - Изначальный XP в учебниках не теряется — localStorage остаётся кешем, сервер — источник правды
This commit is contained in:
@@ -8,6 +8,8 @@
|
||||
<script defer src="https://cdn.jsdelivr.net/npm/katex@0.16.9/dist/katex.min.js"></script>
|
||||
<script defer src="https://cdn.jsdelivr.net/npm/katex@0.16.9/dist/contrib/auto-render.min.js"
|
||||
onload="renderMathInElement(document.body,{delimiters:[{left:'$',right:'$',display:false},{left:'\\[',right:'\\]',display:true}],throwOnError:false})"></script>
|
||||
<script src="/js/xp.js" defer></script>
|
||||
<script src="/js/textbook-xp-widget.js" defer></script>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800;900&family=JetBrains+Mono:wght@400;500;700&display=swap" rel="stylesheet">
|
||||
<style>
|
||||
:root{
|
||||
|
||||
@@ -9,6 +9,8 @@
|
||||
<link href="https://fonts.googleapis.com/css2?family=Outfit:wght@400;500;600;700;800;900&display=swap" rel="stylesheet">
|
||||
<script defer src="https://cdn.jsdelivr.net/npm/katex@0.16.9/dist/katex.min.js"></script>
|
||||
<script defer src="https://cdn.jsdelivr.net/npm/katex@0.16.9/dist/contrib/auto-render.min.js"></script>
|
||||
<script src="/js/xp.js" defer></script>
|
||||
<script src="/js/textbook-xp-widget.js" defer></script>
|
||||
<style>
|
||||
/* ═══════════ ДИЗАЙН-ТОКЕНЫ v2 ═══════════ */
|
||||
:root{
|
||||
|
||||
@@ -9,6 +9,8 @@
|
||||
<link href="https://fonts.googleapis.com/css2?family=Outfit:wght@400;500;600;700;800;900&display=swap" rel="stylesheet">
|
||||
<script defer src="https://cdn.jsdelivr.net/npm/katex@0.16.9/dist/katex.min.js"></script>
|
||||
<script defer src="https://cdn.jsdelivr.net/npm/katex@0.16.9/dist/contrib/auto-render.min.js"></script>
|
||||
<script src="/js/xp.js" defer></script>
|
||||
<script src="/js/textbook-xp-widget.js" defer></script>
|
||||
<style>
|
||||
/* ═══════════ ДИЗАЙН-ТОКЕНЫ v2 ═══════════ */
|
||||
:root{
|
||||
|
||||
@@ -9,6 +9,8 @@
|
||||
<link href="https://fonts.googleapis.com/css2?family=Outfit:wght@400;500;600;700;800;900&display=swap" rel="stylesheet">
|
||||
<script defer src="https://cdn.jsdelivr.net/npm/katex@0.16.9/dist/katex.min.js"></script>
|
||||
<script defer src="https://cdn.jsdelivr.net/npm/katex@0.16.9/dist/contrib/auto-render.min.js"></script>
|
||||
<script src="/js/xp.js" defer></script>
|
||||
<script src="/js/textbook-xp-widget.js" defer></script>
|
||||
<style>
|
||||
/* ═══════════ ДИЗАЙН-ТОКЕНЫ v2 ═══════════ */
|
||||
:root{
|
||||
|
||||
@@ -8,6 +8,8 @@
|
||||
<script defer src="https://cdn.jsdelivr.net/npm/katex@0.16.9/dist/katex.min.js"></script>
|
||||
<script defer src="https://cdn.jsdelivr.net/npm/katex@0.16.9/dist/contrib/auto-render.min.js"
|
||||
onload="renderMathInElement(document.body,{delimiters:[{left:'$',right:'$',display:false},{left:'\\[',right:'\\]',display:true}],throwOnError:false})"></script>
|
||||
<script src="/js/xp.js" defer></script>
|
||||
<script src="/js/textbook-xp-widget.js" defer></script>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Outfit:wght@400;500;600;700;800;900&family=Literata:opsz,wght@7..72,400;7..72,500;7..72,600;7..72,700;7..72,800&family=JetBrains+Mono:wght@400;500;700&display=swap" rel="stylesheet">
|
||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.1/css/all.min.css">
|
||||
<style>
|
||||
|
||||
@@ -0,0 +1,108 @@
|
||||
'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();
|
||||
}
|
||||
})();
|
||||
Reference in New Issue
Block a user