diff --git a/assets/admin/public/images/header-layout/header_layout_1.png b/assets/admin/public/images/header-layout/header_layout_1.png new file mode 100644 index 00000000..4e6a0294 Binary files /dev/null and b/assets/admin/public/images/header-layout/header_layout_1.png differ diff --git a/assets/admin/public/images/header-layout/header_layout_2.png b/assets/admin/public/images/header-layout/header_layout_2.png new file mode 100644 index 00000000..71b63926 Binary files /dev/null and b/assets/admin/public/images/header-layout/header_layout_2.png differ diff --git a/assets/admin/public/images/header-layout/header_layout_3.png b/assets/admin/public/images/header-layout/header_layout_3.png new file mode 100644 index 00000000..ecec0020 Binary files /dev/null and b/assets/admin/public/images/header-layout/header_layout_3.png differ diff --git a/assets/admin/public/images/header-layout/mobile_header_layout_center.png b/assets/admin/public/images/header-layout/mobile_header_layout_center.png new file mode 100644 index 00000000..6a7d68c3 Binary files /dev/null and b/assets/admin/public/images/header-layout/mobile_header_layout_center.png differ diff --git a/assets/admin/public/images/header-layout/mobile_header_layout_left.png b/assets/admin/public/images/header-layout/mobile_header_layout_left.png new file mode 100644 index 00000000..3574ccc1 Binary files /dev/null and b/assets/admin/public/images/header-layout/mobile_header_layout_left.png differ diff --git a/assets/admin/src/schema/sections/page-display/index.js b/assets/admin/src/schema/sections/page-display/index.js index 659a813a..a999be28 100644 --- a/assets/admin/src/schema/sections/page-display/index.js +++ b/assets/admin/src/schema/sections/page-display/index.js @@ -3,6 +3,14 @@ * Each key corresponds to a sub-page under the "页面&显示" section. */ +/** Theme asset base for layout previews (admin SPA). */ +function themeAsset(path) { + const base = (typeof window !== 'undefined' && window.aksrtAdminData && window.aksrtAdminData.themeUri) + ? String(window.aksrtAdminData.themeUri).replace(/\/$/, '') + : '' + return base ? `${base}/${path.replace(/^\//, '')}` : path +} + /** Homepage sort options — keep in sync with AKSRT_Settings_Home_Cms::homepage_allowed_sorts() */ const sortOptions = [ { label: '最近更新', value: 'modified' }, @@ -79,23 +87,23 @@ export const pageDisplaySchemas = { { key: 'header_layout', label: '电脑端布局', - type: 'radio', - default: '2', + type: 'image_select', + default: '1', options: [ - { label: '布局 1(经典)', value: '1' }, - { label: '布局 2(内联搜索)', value: '2' }, - { label: '布局 3(紧凑)', value: '3' }, + { label: '布局 1(经典)', value: '1', image: themeAsset('assets/images/header-layout/header_layout_1.png') }, + { label: '布局 2(内联搜索)', value: '2', image: themeAsset('assets/images/header-layout/header_layout_2.png') }, + { label: '布局 3(紧凑)', value: '3', image: themeAsset('assets/images/header-layout/header_layout_3.png') }, ], - help: '不同布局切换导航 DOM 结构', + help: '不同布局切换导航 DOM 结构(与 zibll image_select 一致)', }, { key: 'mobile_header_layout', label: '移动端布局', - type: 'radio', + type: 'image_select', default: 'center', options: [ - { label: '居中(含搜索按钮)', value: 'center' }, - { label: '靠左', value: 'left' }, + { label: '居中(含搜索按钮)', value: 'center', image: themeAsset('assets/images/header-layout/mobile_header_layout_center.png') }, + { label: '靠左', value: 'left', image: themeAsset('assets/images/header-layout/mobile_header_layout_left.png') }, ], }, { diff --git a/assets/css/frontend/nav/zib-utilities.css b/assets/css/frontend/nav/zib-utilities.css index 2f2ed681..ba3d1e3c 100644 --- a/assets/css/frontend/nav/zib-utilities.css +++ b/assets/css/frontend/nav/zib-utilities.css @@ -130,3 +130,35 @@ body.mobile-navbar-show { overflow: hidden; } border-radius: 0; z-index: 998; } + +.navbar-inline-search-panel { + position: absolute; + left: 0; + top: calc(100% + 8px); + min-width: 280px; + max-width: 360px; + padding: 12px; + background: var(--main-bg-color, #fff); + border-radius: var(--main-radius, 8px); + box-shadow: 0 8px 24px rgba(0,0,0,.12); + z-index: 1002; + display: none; +} +.navbar-form .form-group:hover .navbar-inline-search-panel, +.navbar-form .form-group:focus-within .navbar-inline-search-panel, +.navbar-inline-search-panel.is-open { + display: block; + visibility: visible; +} +.navbar-inline-search-suggest__item { + display: flex; + flex-direction: column; + padding: 6px 4px; + color: inherit; + text-decoration: none; +} +.navbar-inline-search-suggest__item:hover { color: var(--focus-color); } +.navbar-inline-search-hots .but { margin: 0 4px 4px 0; } +.aksrt-sme-field { display: block; margin: 4px 0; } +.aksrt-sme-field span { display: block; font-size: 12px; color: #646970; } +.aksrt-sme-card, .aksrt-sme-col { border: 1px solid #dcdcde; padding: 8px; margin: 8px 0; border-radius: 4px; } diff --git a/assets/images/header-layout/header_layout_1.png b/assets/images/header-layout/header_layout_1.png new file mode 100644 index 00000000..4e6a0294 Binary files /dev/null and b/assets/images/header-layout/header_layout_1.png differ diff --git a/assets/images/header-layout/header_layout_2.png b/assets/images/header-layout/header_layout_2.png new file mode 100644 index 00000000..71b63926 Binary files /dev/null and b/assets/images/header-layout/header_layout_2.png differ diff --git a/assets/images/header-layout/header_layout_3.png b/assets/images/header-layout/header_layout_3.png new file mode 100644 index 00000000..ecec0020 Binary files /dev/null and b/assets/images/header-layout/header_layout_3.png differ diff --git a/assets/images/header-layout/mobile_header_layout_center.png b/assets/images/header-layout/mobile_header_layout_center.png new file mode 100644 index 00000000..6a7d68c3 Binary files /dev/null and b/assets/images/header-layout/mobile_header_layout_center.png differ diff --git a/assets/images/header-layout/mobile_header_layout_left.png b/assets/images/header-layout/mobile_header_layout_left.png new file mode 100644 index 00000000..3574ccc1 Binary files /dev/null and b/assets/images/header-layout/mobile_header_layout_left.png differ diff --git a/assets/js/admin-nav-menu-senior.js b/assets/js/admin-nav-menu-senior.js new file mode 100644 index 00000000..a6dadc76 --- /dev/null +++ b/assets/js/admin-nav-menu-senior.js @@ -0,0 +1,243 @@ +/** + * Nav menu senior submenu visual editors (graphic cards / multi-column). + * Enhances WP Appearance → Menus fields for aksrt_menu_options JSON. + * + * @package AKSRT + */ +(function ($) { + 'use strict'; + + function parseJSON(raw, fallback) { + try { + var data = JSON.parse(raw || ''); + return data && typeof data === 'object' ? data : fallback; + } catch (e) { + return fallback; + } + } + + function graphicTemplate() { + return { + arrangement: '', + size: 'md', + img_scale: 100, + items: [{ img: '', title: '', badge: '', badge_class: 'jb-red', link: { url: '', text: '', target: '' } }], + }; + } + + function multiTemplate() { + return { + columns: [ + { + title: '', + icon: '', + img_icon: '', + link: { url: '', text: '', target: '' }, + items: [{ title: '', icon: '', img_icon: '', link: { url: '', text: '', target: '' } }], + }, + ], + }; + } + + function field(name, label, value, type) { + type = type || 'text'; + return ( + '' + ); + } + + function renderGraphicUI($wrap, data) { + var html = '
'; + html += ''; + html += '
'; + (data.items || []).forEach(function (item, i) { + html += '
'; + html += '
卡片 #' + (i + 1) + ''; + html += '
'; + html += field('img', '图片 URL', item.img || ''); + html += field('title', '标题', item.title || ''); + html += field('badge', '徽章', item.badge || ''); + html += field('badge_class', '徽章 class', item.badge_class || ''); + html += field('link.url', '链接', (item.link && item.link.url) || ''); + html += '
'; + }); + html += '
'; + html += field('size', '尺寸 sm/md/lg/xs', data.size || 'md'); + html += field('img_scale', '图片比例%', data.img_scale || 100, 'number'); + $wrap.html(html); + } + + function renderMultiUI($wrap, data) { + var html = '
'; + html += ''; + html += '
'; + (data.columns || []).forEach(function (col, i) { + html += '
'; + html += '
栏目 #' + (i + 1) + ''; + html += '
'; + html += field('title', '栏目标题', col.title || ''); + html += field('icon', '图标 class', col.icon || ''); + html += field('link.url', '栏目链接', (col.link && col.link.url) || ''); + html += '
'; + (col.items || []).forEach(function (item, j) { + html += '
'; + html += field('items.' + j + '.title', '链接标题', item.title || ''); + html += field('items.' + j + '.link.url', '链接 URL', (item.link && item.link.url) || ''); + html += field('items.' + j + '.icon', '图标', item.icon || ''); + html += '
'; + }); + html += ''; + html += '
'; + }); + html += '
'; + $wrap.html(html); + } + + function readGraphic($wrap) { + var data = graphicTemplate(); + data.size = $wrap.find('[data-name="size"]').val() || 'md'; + data.img_scale = parseInt($wrap.find('[data-name="img_scale"]').val(), 10) || 100; + data.items = []; + $wrap.find('.aksrt-sme-card').each(function () { + var $c = $(this); + data.items.push({ + img: $c.find('[data-name="img"]').val() || '', + title: $c.find('[data-name="title"]').val() || '', + badge: $c.find('[data-name="badge"]').val() || '', + badge_class: $c.find('[data-name="badge_class"]').val() || '', + link: { url: $c.find('[data-name="link.url"]').val() || '', text: '', target: '' }, + }); + }); + return data; + } + + function readMulti($wrap) { + var data = { columns: [] }; + $wrap.find('.aksrt-sme-col').each(function () { + var $col = $(this); + var col = { + title: $col.find('[data-name="title"]').first().val() || '', + icon: $col.find('[data-name="icon"]').first().val() || '', + img_icon: '', + link: { url: $col.find('[data-name="link.url"]').first().val() || '', text: '', target: '' }, + items: [], + }; + $col.find('.aksrt-sme-item').each(function () { + var $it = $(this); + col.items.push({ + title: $it.find('input[data-name$=".title"]').val() || '', + icon: $it.find('input[data-name$=".icon"]').val() || '', + img_icon: '', + link: { url: $it.find('input[data-name$=".link.url"]').val() || '', text: '', target: '' }, + }); + }); + data.columns.push(col); + }); + return data; + } + + function enhanceItem($item) { + if ($item.data('aksrtSme')) return; + $item.data('aksrtSme', 1); + + var $graphicTa = $item.find('textarea[name*="[graphic_card_opts_json]"]'); + var $multiTa = $item.find('textarea[name*="[multi_column_links_opts_json]"]'); + var $type = $item.find('select[name*="[submenu_type]"]'); + if (!$graphicTa.length && !$multiTa.length) return; + + var $ui = $('
'); + $graphicTa.closest('p').before($ui); + + function sync() { + var type = $type.val(); + if (type === 'graphic_card') { + $graphicTa.closest('p').hide(); + $multiTa.closest('p').hide(); + var g = parseJSON($graphicTa.val(), graphicTemplate()); + renderGraphicUI($ui, g); + $ui.show(); + } else if (type === 'multi_column_links') { + $graphicTa.closest('p').hide(); + $multiTa.closest('p').hide(); + var m = parseJSON($multiTa.val(), multiTemplate()); + renderMultiUI($ui, m); + $ui.show(); + } else { + $ui.hide().empty(); + $graphicTa.closest('p').show(); + $multiTa.closest('p').show(); + } + } + + $type.on('change', sync); + $ui.on('click', '.aksrt-sme-add-card', function (e) { + e.preventDefault(); + var g = readGraphic($ui); + g.items.push({ img: '', title: '', badge: '', badge_class: 'jb-red', link: { url: '', text: '', target: '' } }); + renderGraphicUI($ui, g); + }); + $ui.on('click', '.aksrt-sme-del-card', function (e) { + e.preventDefault(); + $(this).closest('.aksrt-sme-card').remove(); + }); + $ui.on('click', '.aksrt-sme-add-col', function (e) { + e.preventDefault(); + var m = readMulti($ui); + m.columns.push({ + title: '', + icon: '', + img_icon: '', + link: { url: '', text: '', target: '' }, + items: [{ title: '', icon: '', img_icon: '', link: { url: '', text: '', target: '' } }], + }); + renderMultiUI($ui, m); + }); + $ui.on('click', '.aksrt-sme-del-col', function (e) { + e.preventDefault(); + $(this).closest('.aksrt-sme-col').remove(); + }); + $ui.on('click', '.aksrt-sme-add-item', function (e) { + e.preventDefault(); + var $col = $(this).closest('.aksrt-sme-col'); + var m = readMulti($ui); + var idx = $col.index(); + if (m.columns[idx]) { + m.columns[idx].items.push({ title: '', icon: '', img_icon: '', link: { url: '', text: '', target: '' } }); + } + renderMultiUI($ui, m); + }); + + // Before save, write UI → textarea + $('#update-nav-menu').on('submit.aksrtSme', function () { + var type = $type.val(); + if (type === 'graphic_card') { + $graphicTa.val(JSON.stringify(readGraphic($ui), null, 2)); + } else if (type === 'multi_column_links') { + $multiTa.val(JSON.stringify(readMulti($ui), null, 2)); + } + }); + + // Hide raw JSON by default; toggle available via double-click title + $graphicTa.closest('p').find('label').attr('title', '双击显示原始 JSON'); + sync(); + } + + function boot() { + $('#menu-to-edit .menu-item').each(function () { + enhanceItem($(this)); + }); + } + + $(boot); + $(document).on('menu-item-added', function () { + setTimeout(boot, 50); + }); +})(jQuery); diff --git a/assets/js/header-inline-search.js b/assets/js/header-inline-search.js new file mode 100644 index 00000000..75b79f09 --- /dev/null +++ b/assets/js/header-inline-search.js @@ -0,0 +1,140 @@ +/** + * Enhance layout-2 inline search with AJAX suggestions (aksrt_search_suggestions). + * Extends existing panel search without dual systems. + */ +(function () { + "use strict"; + + function initInlineSearchSuggest() { + var form = document.querySelector('form[data-aksrt-search-form="inline"]'); + if (!form || !window.aksrtData || !aksrtData.ajaxUrl) return; + + var input = form.querySelector(".search-input"); + var group = form.querySelector(".form-group.relative, .form-group"); + if (!input || !group) return; + + var panel = group.querySelector(".navbar-inline-search-panel"); + if (!panel) { + panel = document.createElement("div"); + panel.className = "dropdown-menu hover-show-con navbar-inline-search-panel"; + group.appendChild(panel); + } + + var timer = null; + var searchConfig = (window.aksrtData && aksrtData.search) || {}; + var suggestionsEnabled = searchConfig.suggestionsEnabled !== false; + + function typeValue() { + var t = form.querySelector('input[name="aksrt_search_type"]'); + return (t && t.value) || searchConfig.defaultType || "post"; + } + + function renderItems(items) { + var html = ""; + if (items && items.length) { + html += '
' + (searchConfig.suggestTitle || "搜索建议") + "
"; + html += '"; + } + // Keep hot keywords block if present as static sibling content was replaced — + // re-append hots from data attribute if we stored them. + var hots = form.getAttribute("data-hots"); + if (hots) { + try { + var arr = JSON.parse(hots); + if (arr && arr.length) { + html += '
热门搜索
"; + } + } catch (e) {} + } + panel.innerHTML = html || panel.innerHTML; + panel.classList.add("is-open"); + panel.style.display = "block"; + panel.style.visibility = "visible"; + } + + // Persist hot keywords for re-render after suggestions + var hotLinks = panel.querySelectorAll(".navbar-inline-search-hots a"); + if (hotLinks.length) { + var kws = []; + hotLinks.forEach(function (a) { + kws.push((a.textContent || "").trim()); + }); + form.setAttribute("data-hots", JSON.stringify(kws)); + } + + function request(q) { + if (!suggestionsEnabled || q.length < 2) { + // restore hots-only + if (form.getAttribute("data-hots")) { + renderItems([]); + } + return; + } + var xhr = new XMLHttpRequest(); + xhr.open("POST", aksrtData.ajaxUrl, true); + xhr.setRequestHeader("Content-Type", "application/x-www-form-urlencoded; charset=UTF-8"); + xhr.onload = function () { + if (xhr.status < 200 || xhr.status >= 300) return; + try { + var json = JSON.parse(xhr.responseText); + if (json.success && json.data && json.data.items) { + renderItems(json.data.items); + } + } catch (e) {} + }; + xhr.send( + "action=aksrt_search_suggestions&nonce=" + + encodeURIComponent(aksrtData.nonce) + + "&q=" + + encodeURIComponent(q) + + "&type=" + + encodeURIComponent(typeValue()) + ); + } + + input.addEventListener("input", function () { + var q = (input.value || "").trim(); + if (timer) clearTimeout(timer); + timer = setTimeout(function () { + request(q); + }, 180); + }); + + input.addEventListener("focus", function () { + panel.style.display = "block"; + panel.style.visibility = "visible"; + }); + } + + if (window.AksrtFrontend && typeof window.AksrtFrontend.register === "function") { + window.AksrtFrontend.register("aksrt-inline-search-suggest", function () { + initInlineSearchSuggest(); + }); + } else if (document.readyState === "loading") { + document.addEventListener("DOMContentLoaded", initInlineSearchSuggest); + } else { + initInlineSearchSuggest(); + } +})(); diff --git a/src/AKSRT/Admin/Admin.php b/src/AKSRT/Admin/Admin.php index a43c365d..004b3384 100644 --- a/src/AKSRT/Admin/Admin.php +++ b/src/AKSRT/Admin/Admin.php @@ -132,6 +132,7 @@ class Admin { 'siteUrl' => home_url('/'), 'adminUrl' => admin_url('/'), 'themeVersion' => wp_get_theme()->get('Version') ?: '3.4.0-dev', + 'themeUri' => defined('AKSRT_URI') ? AKSRT_URI : get_template_directory_uri(), 'buildOk' => $missing_chunks === [], ]); diff --git a/src/AKSRT/Admin/NavMenuOptions.php b/src/AKSRT/Admin/NavMenuOptions.php index 9821a877..283181de 100644 --- a/src/AKSRT/Admin/NavMenuOptions.php +++ b/src/AKSRT/Admin/NavMenuOptions.php @@ -14,6 +14,24 @@ class NavMenuOptions { public static function init(): void { add_action('wp_nav_menu_item_custom_fields', [self::class, 'render_fields'], 10, 2); add_action('wp_update_nav_menu_item', [self::class, 'save'], 10, 2); + add_action('admin_enqueue_scripts', [self::class, 'enqueue_admin']); + } + + public static function enqueue_admin(string $hook): void { + if ($hook !== 'nav-menus.php') { + return; + } + $path = get_template_directory() . '/assets/js/admin-nav-menu-senior.js'; + if (!file_exists($path)) { + return; + } + wp_enqueue_script( + 'aksrt-admin-nav-menu-senior', + get_template_directory_uri() . '/assets/js/admin-nav-menu-senior.js', + ['jquery'], + (string) filemtime($path), + true + ); } /** diff --git a/src/AKSRT/Admin/Schema/SchemaPageDisplay.php b/src/AKSRT/Admin/Schema/SchemaPageDisplay.php index c84290d1..128c727b 100644 --- a/src/AKSRT/Admin/Schema/SchemaPageDisplay.php +++ b/src/AKSRT/Admin/Schema/SchemaPageDisplay.php @@ -31,15 +31,8 @@ class SchemaPageDisplay { ['key' => 'header_scroll_down_hide', 'label' => '下滑隐藏导航栏', 'type' => 'switch', 'default' => true], ]], ['key' => 'layout-type', 'title' => '布局类型', 'fields' => [ - ['key' => 'header_layout', 'label' => '桌面端布局', 'type' => 'radio', 'default' => '1', 'options' => [ - ['label' => '布局 1(经典)', 'value' => '1'], - ['label' => '布局 2(内联搜索)', 'value' => '2'], - ['label' => '布局 3(紧凑)', 'value' => '3'], - ]], - ['key' => 'mobile_header_layout', 'label' => '移动端布局', 'type' => 'radio', 'default' => 'center', 'options' => [ - ['label' => '居中(含搜索按钮)', 'value' => 'center'], - ['label' => '靠左', 'value' => 'left'], - ]], + ['key' => 'header_layout', 'label' => '桌面端布局', 'type' => 'image_select', 'default' => '1', 'options' => self::header_layout_options()], + ['key' => 'mobile_header_layout', 'label' => '移动端布局', 'type' => 'image_select', 'default' => 'center', 'options' => self::mobile_header_layout_options()], ['key' => 'mobile_navbar_align', 'label' => '移动端菜单方向', 'type' => 'radio', 'default' => 'left', 'options' => [ ['label' => '顶部(关闭手势)', 'value' => 'top'], ['label' => '左侧滑出', 'value' => 'left'], @@ -532,4 +525,24 @@ class SchemaPageDisplay { ], ]; } + + /** @return list */ + private static function header_layout_options(): array { + $base = defined('AKSRT_URI') ? trailingslashit(AKSRT_URI) . 'assets/images/header-layout/' : ''; + return [ + ['label' => '布局 1(经典)', 'value' => '1', 'image' => $base . 'header_layout_1.png'], + ['label' => '布局 2(内联搜索)', 'value' => '2', 'image' => $base . 'header_layout_2.png'], + ['label' => '布局 3(紧凑)', 'value' => '3', 'image' => $base . 'header_layout_3.png'], + ]; + } + + /** @return list */ + private static function mobile_header_layout_options(): array { + $base = defined('AKSRT_URI') ? trailingslashit(AKSRT_URI) . 'assets/images/header-layout/' : ''; + return [ + ['label' => '居中(含搜索按钮)', 'value' => 'center', 'image' => $base . 'mobile_header_layout_center.png'], + ['label' => '靠左', 'value' => 'left', 'image' => $base . 'mobile_header_layout_left.png'], + ]; + } + } diff --git a/src/AKSRT/Core/ScriptsPart1b.php b/src/AKSRT/Core/ScriptsPart1b.php index 8c546af0..50d7a1d6 100644 --- a/src/AKSRT/Core/ScriptsPart1b.php +++ b/src/AKSRT/Core/ScriptsPart1b.php @@ -161,7 +161,19 @@ trait ScriptsPart1b { ); } - $hmb_js = get_template_directory() . '/assets/js/header-newadd.js'; + + $inline_search_js = get_template_directory() . '/assets/js/header-inline-search.js'; + if (file_exists($inline_search_js)) { + wp_enqueue_script( + 'aksrt-header-inline-search', + $base_uri . '/assets/js/header-inline-search.js', + [], + (string) filemtime($inline_search_js), + true + ); + } + +$hmb_js = get_template_directory() . '/assets/js/header-newadd.js'; if (file_exists($hmb_js)) { wp_enqueue_script( 'aksrt-header-newadd', diff --git a/tools/nav-zibll-acceptance.md b/tools/nav-zibll-acceptance.md new file mode 100644 index 00000000..48c5cdcd --- /dev/null +++ b/tools/nav-zibll-acceptance.md @@ -0,0 +1,35 @@ +# Top nav zibll 1:1 acceptance checklist + +Branch: `worktree-nav-zibll-1to1` + +## Desktop +- [ ] Layout 1: logo | menu | publish/VIP | theme(+lang) | user | search icon +- [ ] Layout 2: logo | menu | inline search(+hot/suggest) | CTAs | user (no search icon) +- [ ] Layout 3: logo | CTAs/user | menu right cluster +- [ ] Fixed `body.nav-fixed` 88px reserve; hero show-slide pull-up +- [ ] Scroll-down hide / scroll-up show +- [ ] Submenu hover + chevron; overflow folding "更多" +- [ ] Publish single vs multi; VIP hover cards gated by switches +- [ ] User box density + user-card publish type URL +- [ ] Header theme colors when custom enabled +- [ ] Language dropdown when i18n enabled + position header/both + +## Mobile +- [ ] center/left header; drawer left/right/top +- [ ] `body.mobile-navbar-show` open/close + fixed-body + swipe +- [ ] mobile menus depth 2 + user box + widgets +- [ ] posts-nav-box TOC on single posts + +## Admin +- [ ] SPA image_select previews for layouts +- [ ] newposts types multi + VIP switches persist +- [ ] Appearance → Menus: icon/badge/senior visual editor saves meta +- [ ] `cd assets/admin && npm run build` after schema changes + +## Page/section navs +- [ ] section-navs builds `.posts-nav-lists` on `[data-nav]` pages +- [ ] page-navs enqueued on posts-navs template + +## Deploy notes +- Admin `build/` is gitignored — run build on deploy host. +- PR: open from branch `worktree-nav-zibll-1to1` against main on Gitea. diff --git a/tools/nav-zibll-contract-report.txt b/tools/nav-zibll-contract-report.txt new file mode 100644 index 00000000..60a53575 --- /dev/null +++ b/tools/nav-zibll-contract-report.txt @@ -0,0 +1 @@ +PASS all nav zibll source contracts