/* ===== YCat 全站统一左侧导航 Rail ===== * 用法:页面 body 内放
,并在 前引入本文件。 * 本文件会:读取 window.APP_RAIL_NAV(分组导航数据)→ 渲染 rail → 绑定交互。 * 若不提供 window.APP_RAIL_NAV,使用内置默认(6 组约 22 项)。 */ (function () { 'use strict'; // 默认分组导航(emoji 图标,href 为站内页面) var DEFAULT_NAV = [ { group: '创作', items: [ { label: '写文章', href: 'editor.html', emoji: '✍️', key: '1', hint: '图文 / 视频创作与发布', active: ['editor.html'] }, { label: '快速起草', href: 'quick-draft.html', emoji: '⚡', key: '0', hint: '随手记录灵感碎片' }, { label: '模板库', href: 'templates.html', emoji: '📋', hint: '可复用的内容模板' }, { label: '灵感库', href: 'inspiration.html', emoji: '💡', hint: '写作灵感与选题参考' } ]}, { group: '内容', items: [ { label: '文章管理', href: 'articles.html', emoji: '📄', key: '2', hint: '草稿、已发布、回收站', active: ['articles.html', 'article-diff.html', 'versions.html'] }, { label: '媒体库', href: 'media.html', emoji: '🖼️', hint: '图片与素材管理' }, { label: '标签管理', href: 'tag-manager.html', emoji: '🏷️', hint: '标签体系与归类' }, { label: '专题合集', href: 'series.html', emoji: '📚', key: '5', hint: '系列内容整理归档' }, { label: '回收站', href: 'trash.html', emoji: '🗑️', hint: '已删除内容恢复' } ]}, { group: '分发', items: [ { label: '内容分发', href: 'distribute.html', emoji: '🚀', key: '3', hint: '定时与多渠道推送', active: ['distribute.html', 'scheduled.html'] }, { label: '账号授权', href: 'accounts.html', emoji: '🔑', hint: '平台账号与 OAuth 授权' }, { label: '内容日历', href: 'calendar.html', emoji: '📅', hint: '发布计划与排期' } ]}, { group: '数据', items: [ { label: '数据看板', href: 'dashboard.html', emoji: '📊', key: '4', hint: '阅读、互动、趋势概览', active: ['dashboard.html'] }, { label: '文章分析', href: 'analytics.html', emoji: '📈', hint: '内容表现与读者画像', active: ['analytics.html', 'content-analysis.html', 'readability-analysis.html'] }, { label: '质量评分', href: 'quality-score.html', emoji: '⭐', hint: 'SEO 与内容质量诊断' }, { label: '原创检测', href: 'originality.html', emoji: '🔍', hint: '查重与原创度核验' } ]}, { group: '运营', items: [ { label: '评论互动', href: 'comments.html', emoji: '💬', key: '6', hint: '读者留言与回复管理', active: ['comments.html', 'comments-admin.html'] }, { label: '通知中心', href: 'notifications.html', emoji: '🔔', hint: '系统与应用消息' }, { label: '活动', href: 'activity.html', emoji: '🎉', hint: '运营活动与激励' } ]}, { group: '系统', items: [ { label: '帮助中心', href: 'help.html', emoji: '❓', hint: '使用指南与常见问题' }, { label: '卓越中心', href: 'excellence.html', emoji: '🏆', hint: '能力标杆与最佳实践' }, { label: '设置', href: 'settings.html', emoji: '⚙️', hint: '账户、授权与偏好', active: ['settings.html'] } ]} ]; var NAV = (typeof window.APP_RAIL_NAV !== 'undefined') ? window.APP_RAIL_NAV : DEFAULT_NAV; var STORE_KEY = 'ycat_rail_expanded'; var COLLAPSE_KEY = 'ycat_rail_collapsed'; // 存数组:已折叠的 group 名 function currentPage() { var path = location.pathname.split('/').pop(); return path || 'index.html'; } function isActive(item) { var page = currentPage(); if (item.active && item.active.indexOf(page) >= 0) return true; return item.href === page; } function esc(s) { return String(s).replace(/[&<>"]/g, function (c) { return { '&': '&', '<': '<', '>': '>', '"': '"' }[c]; }); } function getCollapsed() { try { return JSON.parse(localStorage.getItem(COLLAPSE_KEY) || '[]'); } catch (e) { return []; } } function setCollapsed(arr) { try { localStorage.setItem(COLLAPSE_KEY, JSON.stringify(arr)); } catch (e) {} } function buildItem(it) { var a = document.createElement('a'); a.className = 'rail-item' + (isActive(it) ? ' rail-item--active' : ''); a.href = it.href; a.setAttribute('data-tip', it.label); if (it.key) a.setAttribute('data-key', it.key); if (isActive(it)) a.setAttribute('data-active', '1'); if (it.emoji) { var ic = document.createElement('span'); ic.className = 'rail-emoji'; ic.textContent = it.emoji; a.appendChild(ic); } var txt = document.createElement('span'); txt.className = 'rail-text'; var lab = document.createElement('span'); lab.className = 'rail-label'; lab.textContent = it.label; txt.appendChild(lab); if (it.hint) { var h = document.createElement('span'); h.className = 'rail-hint'; h.textContent = it.hint; txt.appendChild(h); } a.appendChild(txt); return a; } function render(mount) { mount.innerHTML = ''; mount.className = 'app-rail'; var logo = document.createElement('a'); logo.className = 'rail-logo'; logo.href = 'index.html'; logo.title = 'YCat'; logo.innerHTML = 'YCat'; mount.appendChild(logo); var toggle = document.createElement('button'); toggle.className = 'rail-toggle'; toggle.type = 'button'; toggle.setAttribute('aria-label', '展开/收起导航'); toggle.innerHTML = '收起'; toggle.addEventListener('click', toggleRail); mount.appendChild(toggle); var scroll = document.createElement('div'); scroll.className = 'rail-scroll'; var collapsed = getCollapsed(); NAV.forEach(function (grp) { var g = document.createElement('div'); g.className = 'rail-group' + (collapsed.indexOf(grp.group) >= 0 ? ' collapsed' : ''); // 可点击收缩的组标题(仅展开态显示) var head = document.createElement('button'); head.type = 'button'; head.className = 'rail-group-head'; head.innerHTML = '▶' + esc(grp.group) + ''; head.addEventListener('click', function () { var arr = getCollapsed(); var i = arr.indexOf(grp.group); if (i >= 0) arr.splice(i, 1); else arr.push(grp.group); setCollapsed(arr); g.classList.toggle('collapsed'); }); g.appendChild(head); var items = document.createElement('div'); items.className = 'rail-group-items'; grp.items.forEach(function (it) { items.appendChild(buildItem(it)); }); g.appendChild(items); scroll.appendChild(g); }); mount.appendChild(scroll); } function applyRailPref() { try { if (localStorage.getItem(STORE_KEY) === '1') { document.body.classList.add('rail-expanded'); var lbl = document.querySelector('#app-rail .rt-label'); if (lbl) lbl.textContent = '收起'; } } catch (e) {} } function toggleRail() { var expanded = document.body.classList.toggle('rail-expanded'); var lbl = document.querySelector('#app-rail .rt-label'); if (lbl) lbl.textContent = expanded ? '收起' : '展开'; try { localStorage.setItem(STORE_KEY, expanded ? '1' : '0'); } catch (e) {} } window.toggleRail = toggleRail; // 悬停浮现介绍(2.8s 后自动淡出) function bindHintPeek() { function show(el) { var h = el.querySelector('.rail-hint'); if (!h) return; h.classList.add('show'); if (el._peekTimer) clearTimeout(el._peekTimer); el._peekTimer = setTimeout(function () { h.classList.remove('show'); el._peekTimer = null; }, 2800); } function hide(el) { var h = el.querySelector('.rail-hint'); if (!h) return; if (el._peekTimer) { clearTimeout(el._peekTimer); el._peekTimer = null; } h.classList.remove('show'); } document.querySelectorAll('#app-rail .rail-item').forEach(function (el) { el.addEventListener('mouseenter', function () { show(el); }); el.addEventListener('mouseleave', function () { hide(el); }); }); } // 键盘快捷键:反引号切换栏;Alt+1~6 跳对应 data-key function bindKeys() { function init() { var keyMap = {}; document.querySelectorAll('#app-rail .rail-item[data-key]').forEach(function (el) { keyMap[el.getAttribute('data-key')] = el.getAttribute('href'); }); document.addEventListener('keydown', function (e) { var t = e.target; var typing = t && (t.tagName === 'INPUT' || t.tagName === 'TEXTAREA' || t.isContentEditable); if ((e.key === '`' || e.key === '~') && !e.altKey && !e.ctrlKey && !e.metaKey) { if (typing) return; e.preventDefault(); toggleRail(); return; } if (e.altKey && !e.ctrlKey && !e.metaKey) { var k = e.key; if (k >= '1' && k <= '9' && keyMap[k]) { if (typing) return; e.preventDefault(); window.location.href = keyMap[k]; } } }); } if (document.readyState === 'loading') document.addEventListener('DOMContentLoaded', init); else init(); } function boot() { var mount = document.getElementById('app-rail'); if (!mount) return; render(mount); applyRailPref(); bindHintPeek(); bindKeys(); // 标记 body:所有页面挂 has-rail(非编辑器靠 padding 联动;编辑器加 editor-page 自行管理 .main) document.body.classList.add('has-rail'); if (document.getElementById('editor-app')) document.body.classList.add('editor-page'); // 编辑器容器:toggle 时同步 --rail-w 与 rail-expanded 类 function syncEditor() { var on = document.body.classList.contains('rail-expanded'); var ea = document.getElementById('editor-app'); if (ea) { ea.classList.toggle('rail-expanded', on); ea.style.setProperty('--rail-w', on ? '216px' : '66px'); } } syncEditor(); var obs = new MutationObserver(syncEditor); obs.observe(document.body, { attributes: true, attributeFilter: ['class'] }); } if (document.readyState === 'loading') document.addEventListener('DOMContentLoaded', boot); else boot(); })();