main.min.js 在 body 上委托 [data-action]/[ajax-action]/.wp-ajax-submit/ .initiate-checkin/.oauth-untying 的点击并向 admin-ajax 发 zibll 原生 action (comment_like/like/follow_user…),AKSRT 无对应处理器,WP 一律返回 400 "0", 即控制台「ajax请求错误」的来源;评论点赞(data-action=comment_like)、文章 点赞/收藏(data-action=like|favorite)、VIP 购买等均会触发,且与 §10.11 自有 处理重复。main.min.js 由 loader.js 异步加载,解绑以幂等 .off 每 200ms 重试至多 50 次覆盖加载窗口。vendor 运行时仅保留 UI 行为。
574 lines
23 KiB
JavaScript
574 lines
23 KiB
JavaScript
/**
|
||
* AKSRT Runtime Infrastructure
|
||
*
|
||
* Unified core primitives for the entire frontend:
|
||
* aksrt.tbquire(deps, cb) — lazy script/CSS loader
|
||
* aksrt.confirm(msg, opts) — i18n confirm dialog
|
||
* aksrt.scrollTopTo(target, off) — smart scroll with header offset
|
||
* aksrt.loading(el, show) — element-level loading overlay
|
||
* aksrt.route(url, title) — push/replace URL + title
|
||
*
|
||
* Declarative interactions (event delegation on body):
|
||
* [data-close] — close target element
|
||
* [data-onclick] — trigger click on another element
|
||
* [data-confirm] — confirm before action
|
||
* [data-action] — universal AJAX action button (like/follow/checkin)
|
||
* [ajax-action] — general AJAX button with modal response
|
||
*
|
||
* @package AKSRT
|
||
* @since 5.0.0
|
||
*/
|
||
(function () {
|
||
'use strict';
|
||
|
||
var aksrt = window.aksrt || {};
|
||
window.aksrt = aksrt;
|
||
|
||
// ══════════════════════════════════════════════════════════════
|
||
// §10.11 — tbquire: lazy script/CSS loader
|
||
// ══════════════════════════════════════════════════════════════
|
||
|
||
var _loadedScripts = {};
|
||
var _loadingPromises = {};
|
||
|
||
function loadSingleScript(src) {
|
||
if (_loadedScripts[src]) return Promise.resolve();
|
||
if (_loadingPromises[src]) return _loadingPromises[src];
|
||
|
||
_loadingPromises[src] = new Promise(function (resolve, reject) {
|
||
var isCSS = /\.css(\?|$)/i.test(src);
|
||
var el;
|
||
|
||
if (isCSS) {
|
||
el = document.createElement('link');
|
||
el.rel = 'stylesheet';
|
||
el.href = src;
|
||
} else {
|
||
el = document.createElement('script');
|
||
el.src = src;
|
||
el.async = true;
|
||
}
|
||
|
||
el.onload = function () {
|
||
_loadedScripts[src] = true;
|
||
delete _loadingPromises[src];
|
||
resolve();
|
||
};
|
||
el.onerror = function () {
|
||
delete _loadingPromises[src];
|
||
reject(new Error('Failed to load: ' + src));
|
||
};
|
||
|
||
(document.head || document.documentElement).appendChild(el);
|
||
});
|
||
|
||
return _loadingPromises[src];
|
||
}
|
||
|
||
aksrt.tbquire = function (deps, callback) {
|
||
if (typeof deps === 'string') deps = [deps];
|
||
if (!Array.isArray(deps) || !deps.length) {
|
||
if (typeof callback === 'function') callback();
|
||
return Promise.resolve();
|
||
}
|
||
|
||
var resolvedDeps = deps.map(function (dep) {
|
||
if (/^https?:\/\/|^\//.test(dep)) return dep;
|
||
var map = (window.aksrtData && aksrtData.tbquireMap) || {};
|
||
return map[dep] || dep;
|
||
});
|
||
|
||
var p = Promise.all(resolvedDeps.map(loadSingleScript));
|
||
if (typeof callback === 'function') {
|
||
p.then(callback).catch(function (err) {
|
||
console.error('[AKSRT tbquire]', err);
|
||
});
|
||
}
|
||
return p;
|
||
};
|
||
|
||
// ══════════════════════════════════════════════════════════════
|
||
// §10.10 — confirm: i18n confirm dialog + [data-confirm]
|
||
// ══════════════════════════════════════════════════════════════
|
||
|
||
var _i18n = (window.aksrtData && aksrtData.i18n) || {};
|
||
|
||
function __(key, fallback) {
|
||
return _i18n[key] || fallback || key;
|
||
}
|
||
|
||
aksrt.__ = __;
|
||
|
||
aksrt.confirm = function (msg, opts) {
|
||
opts = opts || {};
|
||
return new Promise(function (resolve) {
|
||
var overlay = document.createElement('div');
|
||
overlay.className = 'aksrt-confirm-overlay';
|
||
|
||
var dialog = document.createElement('div');
|
||
dialog.className = 'aksrt-confirm-dialog';
|
||
dialog.setAttribute('role', 'alertdialog');
|
||
dialog.setAttribute('aria-modal', 'true');
|
||
|
||
dialog.innerHTML =
|
||
'<div class="aksrt-confirm__body">' + escapeHtml(msg) + '</div>' +
|
||
'<div class="aksrt-confirm__actions">' +
|
||
'<button type="button" class="aksrt-confirm__cancel btn-ghost">' +
|
||
escapeHtml(opts.cancelText || __('cancel', '取消')) +
|
||
'</button>' +
|
||
'<button type="button" class="aksrt-confirm__ok btn-primary">' +
|
||
escapeHtml(opts.okText || __('confirm', '确定')) +
|
||
'</button>' +
|
||
'</div>';
|
||
|
||
overlay.appendChild(dialog);
|
||
document.body.appendChild(overlay);
|
||
|
||
requestAnimationFrame(function () { overlay.classList.add('is-open'); });
|
||
|
||
function close(result) {
|
||
overlay.classList.remove('is-open');
|
||
setTimeout(function () { overlay.remove(); }, 200);
|
||
resolve(result);
|
||
}
|
||
|
||
dialog.querySelector('.aksrt-confirm__cancel').addEventListener('click', function () { close(false); });
|
||
dialog.querySelector('.aksrt-confirm__ok').addEventListener('click', function () { close(true); });
|
||
overlay.addEventListener('click', function (e) { if (e.target === overlay) close(false); });
|
||
|
||
dialog.querySelector('.aksrt-confirm__ok').focus();
|
||
});
|
||
};
|
||
|
||
// ══════════════════════════════════════════════════════════════
|
||
// §10.10 — scrollTopTo: smart scroll with header offset
|
||
// ══════════════════════════════════════════════════════════════
|
||
|
||
aksrt.scrollTopTo = function (target, extraOffset) {
|
||
var top = 0;
|
||
if (typeof target === 'number') {
|
||
top = target;
|
||
} else if (target && target.getBoundingClientRect) {
|
||
top = target.getBoundingClientRect().top + window.pageYOffset;
|
||
}
|
||
|
||
var navFixed = document.querySelector('body.nav-fixed .header, .navbar.navbar-top');
|
||
var headerOffset = navFixed ? (navFixed.offsetHeight + 20) : 0;
|
||
top = Math.max(0, top - headerOffset - (extraOffset || 0));
|
||
|
||
var current = window.pageYOffset;
|
||
if (Math.abs(top - current) < 80) return;
|
||
|
||
window.scrollTo({ top: top, behavior: 'smooth' });
|
||
};
|
||
|
||
// ══════════════════════════════════════════════════════════════
|
||
// §10.10 — loading: element-level loading overlay
|
||
// ══════════════════════════════════════════════════════════════
|
||
|
||
aksrt.loading = function (el, show) {
|
||
if (!el) return;
|
||
if (show === false) {
|
||
var mask = el.querySelector('.aksrt-loading-mask');
|
||
if (mask) {
|
||
mask.classList.add('aksrt-loading-mask--out');
|
||
setTimeout(function () { mask.remove(); }, 300);
|
||
}
|
||
return;
|
||
}
|
||
if (el.querySelector('.aksrt-loading-mask')) return;
|
||
var pos = getComputedStyle(el).position;
|
||
if (pos === 'static') el.style.position = 'relative';
|
||
|
||
var mask = document.createElement('div');
|
||
mask.className = 'aksrt-loading-mask';
|
||
mask.innerHTML = '<svg class="aksrt-loading-mask__spinner" width="36" height="36" viewBox="0 0 36 36">' +
|
||
'<circle cx="18" cy="18" r="15" fill="none" stroke="currentColor" stroke-width="3" stroke-dasharray="80 40" stroke-linecap="round">' +
|
||
'<animateTransform attributeName="transform" type="rotate" from="0 18 18" to="360 18 18" dur="0.8s" repeatCount="indefinite"/>' +
|
||
'</circle></svg>';
|
||
el.appendChild(mask);
|
||
};
|
||
|
||
// ══════════════════════════════════════════════════════════════
|
||
// §10.11 — route: tab route + popstate
|
||
// ══════════════════════════════════════════════════════════════
|
||
|
||
aksrt.route = function (url, title) {
|
||
if (!window.history || !history.pushState) return;
|
||
history.pushState({ aksrt: true }, title || '', url);
|
||
if (title) document.title = title;
|
||
};
|
||
|
||
aksrt.routeReplace = function (url, title) {
|
||
if (!window.history || !history.replaceState) return;
|
||
history.replaceState({ aksrt: true }, title || '', url);
|
||
if (title) document.title = title;
|
||
};
|
||
|
||
// Tab route: elements with [data-route] push URL on activation
|
||
window.addEventListener('popstate', function (e) {
|
||
var routeTabs = document.querySelectorAll('[data-route]');
|
||
if (!routeTabs.length) return;
|
||
|
||
var path = window.location.pathname + window.location.search;
|
||
routeTabs.forEach(function (tab) {
|
||
var tabRoute = tab.getAttribute('data-route');
|
||
var isMatch = path === tabRoute || path.indexOf(tabRoute) === 0;
|
||
tab.classList.toggle('is-active', isMatch);
|
||
var target = tab.getAttribute('data-route-target');
|
||
if (target) {
|
||
var panel = document.querySelector(target);
|
||
if (panel) panel.classList.toggle('is-active', isMatch);
|
||
}
|
||
});
|
||
});
|
||
|
||
// ══════════════════════════════════════════════════════════════
|
||
// §10.11 — Declarative interactions (event delegation)
|
||
// ══════════════════════════════════════════════════════════════
|
||
|
||
document.addEventListener('click', function (e) {
|
||
var target = e.target.closest('[data-close]');
|
||
if (target) {
|
||
e.preventDefault();
|
||
var sel2 = target.getAttribute('data-close');
|
||
var el2 = sel2 ? document.querySelector(sel2) : target.parentElement;
|
||
if (el2) {
|
||
el2.classList.remove('is-open', 'is-active', 'is-visible');
|
||
el2.hidden = true;
|
||
}
|
||
return;
|
||
}
|
||
|
||
target = e.target.closest('[data-onclick]');
|
||
if (target) {
|
||
e.preventDefault();
|
||
var sel3 = target.getAttribute('data-onclick');
|
||
var el3 = document.querySelector(sel3);
|
||
if (el3) el3.click();
|
||
return;
|
||
}
|
||
});
|
||
|
||
// [data-confirm] — intercept submission
|
||
document.addEventListener('click', function (e) {
|
||
var target = e.target.closest('[data-confirm]');
|
||
if (!target) return;
|
||
|
||
var msg = target.getAttribute('data-confirm');
|
||
if (!msg) return;
|
||
|
||
e.preventDefault();
|
||
e.stopPropagation();
|
||
|
||
aksrt.confirm(msg).then(function (ok) {
|
||
if (!ok) return;
|
||
target.removeAttribute('data-confirm');
|
||
target.click();
|
||
target.setAttribute('data-confirm', msg);
|
||
});
|
||
}, true);
|
||
|
||
// ══════════════════════════════════════════════════════════════
|
||
// §10.10a — 解绑 vendor main.min.js 的 ajax 委托
|
||
// ══════════════════════════════════════════════════════════════
|
||
// main.min.js(loader.js 异步加载)在 body 上挂了 zib_ajax 系点击委托:
|
||
// [data-action] / [ajax-action] / .wp-ajax-submit / .initiate-checkin / .oauth-untying
|
||
// 它们向 admin-ajax 发 zibll 的 action(comment_like、like、follow_user…),
|
||
// AKSRT 没有这些处理器 → WP 返回 400 "0"(控制台「ajax请求错误」),
|
||
// 且与下方 §10.11 自有处理重复。vendor 运行时只保留 UI 行为,ajax 委托统一摘除。
|
||
// main.min.js 异步加载,.off 需在其绑定后执行——幂等重试覆盖加载窗口。
|
||
function unbindVendorAjaxDelegates() {
|
||
if (!window.jQuery) return;
|
||
try {
|
||
var $body = jQuery('body');
|
||
$body.off('click', '[data-action]');
|
||
$body.off('click', '[ajax-action]');
|
||
$body.off('click', '.wp-ajax-submit,[zibajax="submit"]');
|
||
$body.off('click', '.initiate-checkin');
|
||
$body.off('click', '.oauth-untying');
|
||
} catch (e) { /* vendor 运行时未就绪时忽略 */ }
|
||
}
|
||
var unbindVendorAjaxLoop = function () {
|
||
unbindVendorAjaxDelegates();
|
||
if (++unbindVendorAjaxLoop.tries >= 50) return; // ~10s @200ms 覆盖异步加载窗口
|
||
setTimeout(unbindVendorAjaxLoop, 200);
|
||
};
|
||
unbindVendorAjaxLoop.tries = 0;
|
||
unbindVendorAjaxLoop();
|
||
|
||
// ══════════════════════════════════════════════════════════════
|
||
// §10.11 — [data-action] / [ajax-action] universal buttons
|
||
// ══════════════════════════════════════════════════════════════
|
||
|
||
// Only these data-action values are network AJAX. UI-only actions
|
||
// (toggle-theme, editor toolbar, download, etc.) must not hit admin-ajax.
|
||
var DATA_ACTION_AJAX = {
|
||
like: true,
|
||
favorite: true,
|
||
follow: true,
|
||
checkin: true
|
||
};
|
||
|
||
document.addEventListener('click', function (e) {
|
||
// [data-action] — like/follow/checkin with localStorage de-dup
|
||
var btn = e.target.closest('[data-action]');
|
||
if (btn) {
|
||
var dataAction = btn.getAttribute('data-action') || '';
|
||
if (!DATA_ACTION_AJAX[dataAction]) {
|
||
// Leave non-AJAX data-action handlers (theme toggle, toolbar, download) alone.
|
||
return;
|
||
}
|
||
e.preventDefault();
|
||
handleDataAction(btn);
|
||
return;
|
||
}
|
||
|
||
// [ajax-action] — general AJAX button
|
||
btn = e.target.closest('[ajax-action]');
|
||
if (btn) {
|
||
e.preventDefault();
|
||
handleAjaxAction(btn);
|
||
return;
|
||
}
|
||
});
|
||
|
||
function handleDataAction(btn) {
|
||
if (btn.classList.contains('is-loading')) return;
|
||
|
||
var action = btn.getAttribute('data-action');
|
||
if (!action || !DATA_ACTION_AJAX[action]) return;
|
||
|
||
var id = btn.getAttribute('data-id') || '';
|
||
var storageKey = 'aksrt_action_' + action + '_' + id;
|
||
|
||
if (action === 'like' || action === 'favorite') {
|
||
try {
|
||
if (localStorage.getItem(storageKey) && !btn.classList.contains('is-active')) {
|
||
notyf(__('already_done', '已操作'), 'warning');
|
||
return;
|
||
}
|
||
} catch (ex) { /* storage unavailable */ }
|
||
}
|
||
|
||
btn.classList.add('is-loading');
|
||
|
||
var ajaxUrl = (window.aksrtData && aksrtData.ajaxUrl) || '/wp-admin/admin-ajax.php';
|
||
if (!(window.aksrtData && aksrtData.nonce)) {
|
||
btn.classList.remove('is-loading');
|
||
notyf(__('action_failed', '操作失败'), 'danger');
|
||
return;
|
||
}
|
||
var params = new URLSearchParams();
|
||
params.set('action', 'aksrt_' + action);
|
||
params.set('nonce', aksrtData.nonce);
|
||
params.set('id', id);
|
||
|
||
var extraData = btn.getAttribute('data-action-data');
|
||
if (extraData) {
|
||
try {
|
||
var ed = JSON.parse(extraData);
|
||
Object.keys(ed).forEach(function (k) { params.set(k, ed[k]); });
|
||
} catch (ex) { /* ignore */ }
|
||
}
|
||
|
||
fetch(ajaxUrl, {
|
||
method: 'POST',
|
||
headers: { 'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8' },
|
||
credentials: 'same-origin',
|
||
body: params.toString()
|
||
})
|
||
.then(function (r) { return r.json(); })
|
||
.then(function (json) {
|
||
btn.classList.remove('is-loading');
|
||
if (json && json.success) {
|
||
var isActive = !!json.data && !!json.data.active;
|
||
btn.classList.toggle('is-active', isActive);
|
||
|
||
var countEl = btn.querySelector('[data-count]') || btn.querySelector('.action-count');
|
||
if (countEl && json.data && json.data.count !== undefined) {
|
||
countEl.textContent = String(json.data.count);
|
||
}
|
||
|
||
try {
|
||
if (isActive) localStorage.setItem(storageKey, '1');
|
||
else localStorage.removeItem(storageKey);
|
||
} catch (ex) { /* */ }
|
||
|
||
if (json.data && json.data.message) {
|
||
notyf(json.data.message, 'success');
|
||
}
|
||
|
||
if (json.data && json.data.goto) {
|
||
window.location.href = json.data.goto;
|
||
}
|
||
} else {
|
||
notyf((json.data && json.data.message) || __('action_failed', '操作失败'), 'danger');
|
||
}
|
||
})
|
||
.catch(function (err) {
|
||
btn.classList.remove('is-loading');
|
||
notyf(err.message || __('network_error', '网络错误'), 'danger');
|
||
});
|
||
}
|
||
|
||
function handleAjaxAction(btn) {
|
||
if (btn.classList.contains('is-loading')) return;
|
||
btn.classList.add('is-loading');
|
||
|
||
var action = btn.getAttribute('ajax-action');
|
||
var ajaxUrl = (window.aksrtData && aksrtData.ajaxUrl) || '/wp-admin/admin-ajax.php';
|
||
var params = new URLSearchParams();
|
||
params.set('action', action);
|
||
if (window.aksrtData && aksrtData.nonce) params.set('nonce', aksrtData.nonce);
|
||
|
||
var form = btn.closest('form');
|
||
if (form) {
|
||
new FormData(form).forEach(function (v, k) { params.set(k, v); });
|
||
}
|
||
|
||
fetch(ajaxUrl, {
|
||
method: 'POST',
|
||
headers: { 'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8' },
|
||
credentials: 'same-origin',
|
||
body: params.toString()
|
||
})
|
||
.then(function (r) { return r.json(); })
|
||
.then(function (json) {
|
||
btn.classList.remove('is-loading');
|
||
if (json && json.success) {
|
||
if (json.data && json.data.show_modal) {
|
||
window.AksrtModalRuntime.open({
|
||
html: json.data.show_modal,
|
||
bottom: true,
|
||
mobileBottom: true,
|
||
kind: 'generic',
|
||
});
|
||
} else if (json.data && json.data.message) {
|
||
notyf(json.data.message, 'success');
|
||
}
|
||
if (json.data && json.data.goto) {
|
||
window.location.href = json.data.goto;
|
||
}
|
||
if (json.data && json.data.reload) {
|
||
window.location.reload();
|
||
}
|
||
} else {
|
||
notyf((json.data && json.data.message) || __('action_failed', '操作失败'), 'danger');
|
||
}
|
||
})
|
||
.catch(function (err) {
|
||
btn.classList.remove('is-loading');
|
||
notyf(err.message || __('network_error', '网络错误'), 'danger');
|
||
});
|
||
}
|
||
|
||
// ══════════════════════════════════════════════════════════════
|
||
// §10.11 — Ripple (broad coverage)
|
||
// ══════════════════════════════════════════════════════════════
|
||
|
||
var RIPPLE_SELECTORS = '.toggle-radius,.but-ripple,.but,.btn,.btn-primary,.btn-ghost,.btn-outline,.item-thumbnail,.menu-item>a,.aksrt-ripple,.yiyan-box,.aksrt-card';
|
||
|
||
document.addEventListener('click', function (e) {
|
||
var target = e.target.closest(RIPPLE_SELECTORS);
|
||
if (!target) return;
|
||
// Markup opt-out (header publish uses class="nowave") + nav CTA chips
|
||
if (target.classList.contains('nowave') || target.closest('.nowave')) return;
|
||
if (target.classList.contains('navbar-cta') || target.closest('.navbar-cta')) return;
|
||
if (target.closest('.navbar-top .navbar-but, .navbar-top .navbar-vip, .navbar-but-cluster')) return;
|
||
if (target.querySelector('.aksrt-wave')) return;
|
||
|
||
var rect = target.getBoundingClientRect();
|
||
var size = Math.max(rect.width, rect.height) * 2;
|
||
var x = e.clientX - rect.left - size / 2;
|
||
var y = e.clientY - rect.top - size / 2;
|
||
|
||
var wave = document.createElement('span');
|
||
wave.className = 'aksrt-wave';
|
||
// Must be absolute — without it the huge span expands the button box ("变长").
|
||
wave.style.cssText = 'position:absolute;pointer-events:none;border-radius:50%;background:rgba(255,255,255,0.35);width:'
|
||
+ size + 'px;height:' + size + 'px;left:' + x + 'px;top:' + y + 'px;transform:scale(0);animation:aksrtWaveRipple .6s ease-out forwards;';
|
||
|
||
var cs = getComputedStyle(target);
|
||
if (cs.position === 'static') target.style.position = 'relative';
|
||
if (cs.overflow === 'visible') target.style.overflow = 'hidden';
|
||
|
||
target.appendChild(wave);
|
||
setTimeout(function () {
|
||
if (wave.parentNode) wave.parentNode.removeChild(wave);
|
||
}, 600);
|
||
});
|
||
|
||
// ══════════════════════════════════════════════════════════════
|
||
// §10.11 — auto_fun: extended rehydration coverage
|
||
// ══════════════════════════════════════════════════════════════
|
||
|
||
aksrt.autoFun = function (root) {
|
||
root = root || document;
|
||
if (window.AksrtFrontend) {
|
||
AksrtFrontend.rehydrate(root, 'ajax');
|
||
}
|
||
|
||
// Tooltips
|
||
root.querySelectorAll('[data-tooltip]').forEach(function (el) {
|
||
if (el.dataset.tooltipBound) return;
|
||
el.dataset.tooltipBound = '1';
|
||
el.addEventListener('mouseenter', function () {
|
||
var tip = document.createElement('div');
|
||
tip.className = 'aksrt-tooltip';
|
||
tip.textContent = el.getAttribute('data-tooltip');
|
||
document.body.appendChild(tip);
|
||
var rect = el.getBoundingClientRect();
|
||
tip.style.left = (rect.left + rect.width / 2 - tip.offsetWidth / 2) + 'px';
|
||
tip.style.top = (rect.top - tip.offsetHeight - 8 + window.pageYOffset) + 'px';
|
||
el._aksrtTooltip = tip;
|
||
});
|
||
el.addEventListener('mouseleave', function () {
|
||
if (el._aksrtTooltip) { el._aksrtTooltip.remove(); el._aksrtTooltip = null; }
|
||
});
|
||
});
|
||
|
||
// Countdowns
|
||
root.querySelectorAll('[data-countdown]').forEach(function (el) {
|
||
if (el.dataset.countdownBound) return;
|
||
el.dataset.countdownBound = '1';
|
||
var end = parseInt(el.getAttribute('data-countdown'), 10);
|
||
if (!end) return;
|
||
var iv = setInterval(function () {
|
||
var left = Math.max(0, end - Math.floor(Date.now() / 1000));
|
||
if (left <= 0) { clearInterval(iv); el.textContent = '00:00'; return; }
|
||
var m = Math.floor(left / 60);
|
||
var s = left % 60;
|
||
el.textContent = (m < 10 ? '0' : '') + m + ':' + (s < 10 ? '0' : '') + s;
|
||
}, 1000);
|
||
});
|
||
};
|
||
|
||
// ══════════════════════════════════════════════════════════════
|
||
// Helpers
|
||
// ══════════════════════════════════════════════════════════════
|
||
|
||
function escapeHtml(str) {
|
||
if (!str) return '';
|
||
return String(str)
|
||
.replace(/&/g, '&')
|
||
.replace(/</g, '<')
|
||
.replace(/>/g, '>')
|
||
.replace(/"/g, '"');
|
||
}
|
||
|
||
// ── DOMContentLoaded ──
|
||
|
||
function initRuntime() {
|
||
aksrt.autoFun(document);
|
||
}
|
||
|
||
if (document.readyState === 'loading') {
|
||
document.addEventListener('DOMContentLoaded', initRuntime);
|
||
} else {
|
||
initRuntime();
|
||
}
|
||
|
||
})();
|
||
|