- rehydrate.js 新增 boot()/aksrt:ready 事件,支持 boot 后晚注册立即以 init 触发 - aksrt-runtime.js 初始加载改走 AksrtFrontend.boot,tooltip/倒计时注册为组件 - 约 30 个前端脚本(首页、评论、支付、路由、页面叶子、widgets) 从直接 DOMContentLoaded 迁移为 AksrtFrontend.register,并加幂等标记 - section-navs 依赖改为 aksrt-runtime,保证注册时序 - 清理死代码:删除孤儿 loader.js/main.js 及 runtime/page-navs 遗留副本 - loader.js 移除已迁移模块的 tbquire 路径映射,main.js 相应瘦身 - build-public-assets.mjs 增加 runtime/main.min.js 自动压缩步骤 - 新增 4 个契约测试(homepage-dom/rehydrate-runtime/global-dom-init/ public-bundle-parity),3 个既有 harness 适配注册表初始化方式
250 lines
7.9 KiB
JavaScript
250 lines
7.9 KiB
JavaScript
/**
|
|
* AKSRT 文章导航页 — admin settings modal + HTML5 drag-sort (no SortableJS dependency).
|
|
* TOC generation is handled by section-navs.js via [data-nav] + .posts-nav-box.
|
|
*
|
|
* @package AKSRT
|
|
*/
|
|
(function () {
|
|
'use strict';
|
|
|
|
function qs(sel, root) {
|
|
if (root && root.nodeType === 1 && root.matches(sel)) return root;
|
|
return (root || document).querySelector(sel);
|
|
}
|
|
|
|
function qsa(sel, root) {
|
|
var nodes = Array.prototype.slice.call((root || document).querySelectorAll(sel));
|
|
if (root && root.nodeType === 1 && root.matches(sel)) nodes.unshift(root);
|
|
return nodes;
|
|
}
|
|
|
|
function ajaxUrl() {
|
|
if (window.aksrtData && window.aksrtData.ajaxUrl) return window.aksrtData.ajaxUrl;
|
|
if (window.ajaxurl) return window.ajaxurl;
|
|
return '/wp-admin/admin-ajax.php';
|
|
}
|
|
|
|
function initModal(root) {
|
|
var modal = qs('#post-navs-sz', root);
|
|
if (!modal) return;
|
|
|
|
if (modal.dataset.aksrtPostsNavModalBound === '1') return;
|
|
modal.dataset.aksrtPostsNavModalBound = '1';
|
|
|
|
function openModal(e) {
|
|
if (e) e.preventDefault();
|
|
modal.classList.add('is-open', 'show');
|
|
modal.setAttribute('aria-hidden', 'false');
|
|
}
|
|
|
|
function closeModal(e) {
|
|
if (e) e.preventDefault();
|
|
modal.classList.remove('is-open', 'show');
|
|
modal.setAttribute('aria-hidden', 'true');
|
|
}
|
|
|
|
qsa('[data-toggle="modal"][data-target="#post-navs-sz"], a[href="#post-navs-sz"]', root).forEach(function (el) {
|
|
el.addEventListener('click', openModal);
|
|
});
|
|
// Title [设置] uses data-target.
|
|
qsa('[data-target="#post-navs-sz"]', root).forEach(function (el) {
|
|
el.addEventListener('click', openModal);
|
|
});
|
|
qsa('[data-dismiss="modal"]', modal).forEach(function (btn) {
|
|
btn.addEventListener('click', closeModal);
|
|
});
|
|
modal.addEventListener('click', function (e) {
|
|
if (e.target === modal) closeModal(e);
|
|
});
|
|
document.addEventListener('keydown', function (e) {
|
|
if (e.key === 'Escape' && modal.classList.contains('is-open')) closeModal(e);
|
|
});
|
|
}
|
|
|
|
function initDragSort(root) {
|
|
var left = qs('#cat-sortable', root);
|
|
var right = qs('#cat-sortable-ok', root);
|
|
if (!left || !right) return;
|
|
|
|
var dragEl = null;
|
|
|
|
function onDragStart(e) {
|
|
dragEl = e.currentTarget;
|
|
dragEl.classList.add('is-dragging');
|
|
try {
|
|
e.dataTransfer.effectAllowed = 'move';
|
|
e.dataTransfer.setData('text/plain', dragEl.getAttribute('data-id') || '');
|
|
} catch (err) { /* IE */ }
|
|
}
|
|
|
|
function onDragEnd() {
|
|
if (dragEl) dragEl.classList.remove('is-dragging');
|
|
dragEl = null;
|
|
qsa('.cat-group').forEach(function (g) {
|
|
g.classList.remove('is-drop-target');
|
|
});
|
|
}
|
|
|
|
function bindItem(el) {
|
|
el.setAttribute('draggable', 'true');
|
|
el.addEventListener('dragstart', onDragStart);
|
|
el.addEventListener('dragend', onDragEnd);
|
|
}
|
|
|
|
qsa('.sortable-cat-item', left).forEach(bindItem);
|
|
qsa('.sortable-cat-item', right).forEach(bindItem);
|
|
|
|
[left, right].forEach(function (group) {
|
|
group.addEventListener('dragover', function (e) {
|
|
e.preventDefault();
|
|
group.classList.add('is-drop-target');
|
|
if (!dragEl) return;
|
|
var after = getDragAfterElement(group, e.clientY);
|
|
if (after == null) {
|
|
group.appendChild(dragEl);
|
|
} else {
|
|
group.insertBefore(dragEl, after);
|
|
}
|
|
});
|
|
group.addEventListener('dragleave', function () {
|
|
group.classList.remove('is-drop-target');
|
|
});
|
|
group.addEventListener('drop', function (e) {
|
|
e.preventDefault();
|
|
group.classList.remove('is-drop-target');
|
|
});
|
|
});
|
|
|
|
function getDragAfterElement(container, y) {
|
|
var els = qsa('.sortable-cat-item:not(.is-dragging)', container);
|
|
var closest = { offset: Number.NEGATIVE_INFINITY, element: null };
|
|
els.forEach(function (child) {
|
|
var box = child.getBoundingClientRect();
|
|
var offset = y - box.top - box.height / 2;
|
|
if (offset < 0 && offset > closest.offset) {
|
|
closest = { offset: offset, element: child };
|
|
}
|
|
});
|
|
return closest.element;
|
|
}
|
|
}
|
|
|
|
function initSave(root) {
|
|
var form = qs('#aksrt-posts-navs-settings', root);
|
|
if (!form) return;
|
|
|
|
form.addEventListener('submit', function (e) {
|
|
e.preventDefault();
|
|
var ids = [];
|
|
qsa('#cat-sortable-ok .sortable-cat-item').forEach(function (item) {
|
|
var id = item.getAttribute('data-id');
|
|
if (id) ids.push(id);
|
|
});
|
|
if (!ids.length) {
|
|
notyf('请选择需要显示的分类', 'danger');
|
|
return;
|
|
}
|
|
|
|
var fd = new FormData(form);
|
|
fd.set('action', 'aksrt_post_navs_settings');
|
|
// PHP accepts array; append each.
|
|
fd.delete('navs_show_cat_id');
|
|
ids.forEach(function (id) {
|
|
fd.append('navs_show_cat_id[]', id);
|
|
});
|
|
|
|
var btn = qs('button[type="submit"]', form);
|
|
if (btn) btn.disabled = true;
|
|
|
|
fetch(ajaxUrl(), {
|
|
method: 'POST',
|
|
body: fd,
|
|
credentials: 'same-origin',
|
|
})
|
|
.then(function (r) { return r.json(); })
|
|
.then(function (data) {
|
|
if (!data || data.error) {
|
|
notyf((data && data.msg) || '保存失败', 'danger');
|
|
return;
|
|
}
|
|
notyf(data.msg || '保存成功', 'success');
|
|
if (data.reload) {
|
|
window.setTimeout(function () { window.location.reload(); }, 400);
|
|
}
|
|
})
|
|
.catch(function () {
|
|
notyf('网络错误', 'danger');
|
|
})
|
|
.finally(function () {
|
|
if (btn) btn.disabled = false;
|
|
});
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Build simple TOC into .posts-nav-box if section-navs hasn't already.
|
|
* Uses category h2 headings inside .posts-navs.
|
|
*/
|
|
function initFallbackToc(scope) {
|
|
var box = qs('.posts-nav-box', scope);
|
|
var root = qs('.posts-navs[data-nav]', scope);
|
|
if (!box || !root) return;
|
|
if (box.querySelector('.posts-nav-lists, .aksrt-section-nav, .toc-list')) return;
|
|
|
|
var headings = qsa('.title-theme h2, h2', root);
|
|
if (headings.length < 1) return;
|
|
|
|
var title = box.getAttribute('data-title') || '导航目录';
|
|
var list = '';
|
|
headings.forEach(function (h, i) {
|
|
if (!h.id) h.id = 'navs-sec-' + i;
|
|
list += '<li class="n-H2"><a class="text-ellipsis" href="#' + h.id + '">' + (h.textContent || '').trim() + '</a></li>';
|
|
});
|
|
|
|
box.innerHTML =
|
|
'<div class="box-body notop"><div class="title-theme">' + title + '</div></div>' +
|
|
'<div class="aksrt-panel"><div class="posts-nav-lists scroll-y mini-scrollbar list-unstyled">' +
|
|
'<ul class="bl relative nav">' + list + '</ul></div></div>';
|
|
|
|
qsa('a[href^="#"]', box).forEach(function (a) {
|
|
a.addEventListener('click', function (e) {
|
|
var id = a.getAttribute('href');
|
|
var el = id ? document.getElementById(id.replace(/^#/, '')) : null;
|
|
if (!el) return;
|
|
e.preventDefault();
|
|
var top = el.getBoundingClientRect().top + window.pageYOffset - 90;
|
|
window.scrollTo({ top: top, behavior: 'smooth' });
|
|
});
|
|
});
|
|
|
|
function highlight() {
|
|
var y = window.pageYOffset + 120;
|
|
var current = headings[0];
|
|
headings.forEach(function (h) {
|
|
if (h.offsetTop <= y) current = h;
|
|
});
|
|
qsa('li', box).forEach(function (li) {
|
|
var a = li.querySelector('a');
|
|
if (a && current && a.getAttribute('href') === '#' + current.id) {
|
|
li.classList.add('active');
|
|
} else {
|
|
li.classList.remove('active');
|
|
}
|
|
});
|
|
}
|
|
window.addEventListener('scroll', highlight, { passive: true });
|
|
highlight();
|
|
}
|
|
|
|
function init(root) {
|
|
root = root || document;
|
|
initModal(root);
|
|
initDragSort(root);
|
|
initSave(root);
|
|
// Defer so section-navs.js can own TOC first.
|
|
window.setTimeout(function () { initFallbackToc(root); }, 50);
|
|
}
|
|
|
|
window.AksrtFrontend.register('aksrt-posts-navs-page', init);
|
|
})();
|