/** * 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 += '
  • ' + (h.textContent || '').trim() + '
  • '; }); box.innerHTML = '
    ' + title + '
    ' + '
    ' + '
    '; 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); })();