feat: 首页由 navigation 首项决定
- 移除 navigation.active 配置项,默认页以 navigation[0] 为准(生成端/前端一致) - 注入 homePageId;首页渲染用 profile.title/profile.subtitle 覆盖 title/subtitle - 模板按 homePageId 切换首页/非首页标题 DOM 与 data-editable,避免样式错位 - 更新默认配置与文档;书签导入不再写入 active 字段 - 新增/更新单测覆盖首页规则与 profile 覆盖 BREAKING CHANGE: 不再支持 navigation[].active;通过调整 navigation 顺序设置默认页/首页
This commit is contained in:
@@ -121,7 +121,7 @@ MeNav 配置系统采用“完全替换”策略(不合并),按以下优
|
|||||||
|
|
||||||
5. **导航**
|
5. **导航**
|
||||||
- `navigation[]`:页面入口列表,`id` 需唯一,并与 `pages/` 中配置文件名对应(例如 `id: home` 对应 `pages/home.yml`)
|
- `navigation[]`:页面入口列表,`id` 需唯一,并与 `pages/` 中配置文件名对应(例如 `id: home` 对应 `pages/home.yml`)
|
||||||
- 只允许一个导航项 `active: true` 作为默认页
|
- 默认首页由 `navigation` 数组顺序决定:**第一项即为首页(默认打开页)**,不再使用 `active` 字段
|
||||||
- 图标使用 Font Awesome 类名字符串(例如 `fas fa-home`、`fab fa-github`)
|
- 图标使用 Font Awesome 类名字符串(例如 `fas fa-home`、`fab fa-github`)
|
||||||
- 导航显示顺序与数组顺序一致,可通过调整数组顺序改变导航顺序
|
- 导航显示顺序与数组顺序一致,可通过调整数组顺序改变导航顺序
|
||||||
|
|
||||||
@@ -230,7 +230,6 @@ navigation:
|
|||||||
- name: "首页"
|
- name: "首页"
|
||||||
icon: "fas fa-home"
|
icon: "fas fa-home"
|
||||||
id: "home"
|
id: "home"
|
||||||
active: true
|
|
||||||
- name: "项目"
|
- name: "项目"
|
||||||
icon: "fas fa-project-diagram"
|
icon: "fas fa-project-diagram"
|
||||||
id: "projects"
|
id: "projects"
|
||||||
|
|||||||
@@ -50,12 +50,11 @@ social:
|
|||||||
url: https://steam.com
|
url: https://steam.com
|
||||||
icon: fab fa-steam
|
icon: fab fa-steam
|
||||||
|
|
||||||
# 导航配置
|
# 导航配置(顺序第一项即首页/默认打开页)
|
||||||
navigation:
|
navigation:
|
||||||
- name: 首页 # 菜单名称
|
- name: 常用 # 菜单名称
|
||||||
icon: fas fa-home # Font Awesome 图标类
|
icon: fas fa-star # Font Awesome 图标类
|
||||||
id: home # 页面标识符(唯一,需与 pages/home.yml 对应)
|
id: home # 页面标识符(唯一,需与 pages/<id>.yml 对应)
|
||||||
active: true # 是否默认激活(全局仅一个 true)
|
|
||||||
- name: 项目
|
- name: 项目
|
||||||
icon: fas fa-project-diagram
|
icon: fas fa-project-diagram
|
||||||
id: projects
|
id: projects
|
||||||
|
|||||||
@@ -794,8 +794,7 @@ function updateNavigationFile(filePath) {
|
|||||||
navConfig.push({
|
navConfig.push({
|
||||||
name: '书签',
|
name: '书签',
|
||||||
icon: 'fas fa-bookmark',
|
icon: 'fas fa-bookmark',
|
||||||
id: 'bookmarks',
|
id: 'bookmarks'
|
||||||
active: false
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// 更新文件
|
// 更新文件
|
||||||
|
|||||||
@@ -462,13 +462,6 @@ function prepareRenderData(config) {
|
|||||||
// 移除警告日志,数据处理逻辑保留
|
// 移除警告日志,数据处理逻辑保留
|
||||||
}
|
}
|
||||||
|
|
||||||
// 添加序列化的配置数据,用于浏览器扩展
|
|
||||||
renderData.configJSON = JSON.stringify({
|
|
||||||
version: process.env.npm_package_version || '1.0.0',
|
|
||||||
timestamp: new Date().toISOString(),
|
|
||||||
data: renderData // 使用经过处理的renderData而不是原始config
|
|
||||||
});
|
|
||||||
|
|
||||||
// 添加导航项的活动状态标记和子菜单
|
// 添加导航项的活动状态标记和子菜单
|
||||||
if (Array.isArray(renderData.navigation)) {
|
if (Array.isArray(renderData.navigation)) {
|
||||||
renderData.navigation = renderData.navigation.map((item, index) => {
|
renderData.navigation = renderData.navigation.map((item, index) => {
|
||||||
@@ -476,7 +469,7 @@ function prepareRenderData(config) {
|
|||||||
...item,
|
...item,
|
||||||
isActive: index === 0, // 默认第一项为活动项
|
isActive: index === 0, // 默认第一项为活动项
|
||||||
id: item.id || `nav-${index}`,
|
id: item.id || `nav-${index}`,
|
||||||
active: index === 0 // 兼容原有逻辑
|
active: index === 0 // 保持旧模板兼容(由顺序决定,不读取配置的 active 字段)
|
||||||
};
|
};
|
||||||
|
|
||||||
// 使用辅助函数获取子菜单
|
// 使用辅助函数获取子菜单
|
||||||
@@ -489,6 +482,16 @@ function prepareRenderData(config) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 首页(默认页)规则:navigation 顺序第一项即首页
|
||||||
|
renderData.homePageId = renderData.navigation && renderData.navigation[0] ? renderData.navigation[0].id : null;
|
||||||
|
|
||||||
|
// 添加序列化的配置数据,用于浏览器扩展(确保包含 homePageId 等处理结果)
|
||||||
|
renderData.configJSON = JSON.stringify({
|
||||||
|
version: process.env.npm_package_version || '1.0.0',
|
||||||
|
timestamp: new Date().toISOString(),
|
||||||
|
data: renderData // 使用经过处理的renderData而不是原始config
|
||||||
|
});
|
||||||
|
|
||||||
// 为Handlebars模板特别准备navigationData数组
|
// 为Handlebars模板特别准备navigationData数组
|
||||||
renderData.navigationData = renderData.navigation;
|
renderData.navigationData = renderData.navigation;
|
||||||
|
|
||||||
@@ -814,6 +817,17 @@ function renderPage(pageId, config) {
|
|||||||
Object.assign(data, config[pageId]);
|
Object.assign(data, config[pageId]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 首页标题规则:使用 site.yml 的 profile 覆盖首页(导航第一项)的 title/subtitle 显示
|
||||||
|
const homePageId = config.homePageId
|
||||||
|
|| (Array.isArray(config.navigation) && config.navigation[0] ? config.navigation[0].id : null)
|
||||||
|
|| 'home';
|
||||||
|
// 供模板判断“当前是否首页”
|
||||||
|
data.homePageId = homePageId;
|
||||||
|
if (pageId === homePageId && config.profile) {
|
||||||
|
if (config.profile.title !== undefined) data.title = config.profile.title;
|
||||||
|
if (config.profile.subtitle !== undefined) data.subtitle = config.profile.subtitle;
|
||||||
|
}
|
||||||
|
|
||||||
// 检查页面配置中是否指定了模板
|
// 检查页面配置中是否指定了模板
|
||||||
let templateName = pageId;
|
let templateName = pageId;
|
||||||
if (config[pageId] && config[pageId].template) {
|
if (config[pageId] && config[pageId].template) {
|
||||||
@@ -844,11 +858,6 @@ function generateAllPagesHTML(config) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// 确保首页存在
|
|
||||||
if (!pages.home) {
|
|
||||||
pages.home = renderPage('home', config);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 确保搜索结果页存在
|
// 确保搜索结果页存在
|
||||||
if (!pages['search-results']) {
|
if (!pages['search-results']) {
|
||||||
pages['search-results'] = renderPage('search-results', config);
|
pages['search-results'] = renderPage('search-results', config);
|
||||||
@@ -989,7 +998,9 @@ function main() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
main();
|
if (require.main === module) {
|
||||||
|
main();
|
||||||
|
}
|
||||||
|
|
||||||
// 导出供测试使用的函数
|
// 导出供测试使用的函数
|
||||||
module.exports = {
|
module.exports = {
|
||||||
@@ -1002,4 +1013,3 @@ module.exports = {
|
|||||||
renderTemplate,
|
renderTemplate,
|
||||||
generateAllPagesHTML
|
generateAllPagesHTML
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -641,7 +641,41 @@ function extractNestedData(element) {
|
|||||||
document.addEventListener('DOMContentLoaded', () => {
|
document.addEventListener('DOMContentLoaded', () => {
|
||||||
// 先声明所有状态变量
|
// 先声明所有状态变量
|
||||||
let isSearchActive = false;
|
let isSearchActive = false;
|
||||||
let currentPageId = 'home';
|
// 首页不再固定为 "home":以导航顺序第一项为准
|
||||||
|
const homePageId = (() => {
|
||||||
|
// 1) 优先从生成端注入的配置数据读取(保持与实际导航顺序一致)
|
||||||
|
try {
|
||||||
|
const config = window.MeNav && typeof window.MeNav.getConfig === 'function'
|
||||||
|
? window.MeNav.getConfig()
|
||||||
|
: null;
|
||||||
|
const injectedHomePageId = config && config.data && config.data.homePageId
|
||||||
|
? String(config.data.homePageId).trim()
|
||||||
|
: '';
|
||||||
|
if (injectedHomePageId) return injectedHomePageId;
|
||||||
|
const nav = config && config.data && Array.isArray(config.data.navigation)
|
||||||
|
? config.data.navigation
|
||||||
|
: null;
|
||||||
|
const firstId = nav && nav[0] && nav[0].id ? String(nav[0].id).trim() : '';
|
||||||
|
if (firstId) return firstId;
|
||||||
|
} catch (error) {
|
||||||
|
// 忽略解析错误,继续使用 DOM 推断
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2) 回退到 DOM:取首个导航项的 data-page
|
||||||
|
const firstNavItem = document.querySelector('.nav-item[data-page]');
|
||||||
|
if (firstNavItem) {
|
||||||
|
const id = String(firstNavItem.getAttribute('data-page') || '').trim();
|
||||||
|
if (id) return id;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3) 最后兜底:取首个页面容器 id
|
||||||
|
const firstPage = document.querySelector('.page[id]');
|
||||||
|
if (firstPage && firstPage.id) return firstPage.id;
|
||||||
|
|
||||||
|
return 'home';
|
||||||
|
})();
|
||||||
|
|
||||||
|
let currentPageId = homePageId;
|
||||||
let isInitialLoad = true;
|
let isInitialLoad = true;
|
||||||
let isSidebarOpen = false;
|
let isSidebarOpen = false;
|
||||||
let isSearchOpen = false;
|
let isSearchOpen = false;
|
||||||
@@ -1257,9 +1291,9 @@ document.addEventListener('DOMContentLoaded', () => {
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// 如果没有激活的导航项,默认显示首页
|
// 如果没有激活的导航项,默认显示首页
|
||||||
currentPageId = 'home';
|
currentPageId = homePageId;
|
||||||
pages.forEach(page => {
|
pages.forEach(page => {
|
||||||
page.classList.toggle('active', page.id === 'home');
|
page.classList.toggle('active', page.id === homePageId);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
} catch (resetError) {
|
} catch (resetError) {
|
||||||
@@ -1489,7 +1523,7 @@ document.addEventListener('DOMContentLoaded', () => {
|
|||||||
|
|
||||||
// 立即执行初始化,不再使用requestAnimationFrame延迟
|
// 立即执行初始化,不再使用requestAnimationFrame延迟
|
||||||
// 显示首页
|
// 显示首页
|
||||||
showPage('home');
|
showPage(homePageId);
|
||||||
|
|
||||||
// 添加载入动画
|
// 添加载入动画
|
||||||
categories.forEach((category, index) => {
|
categories.forEach((category, index) => {
|
||||||
|
|||||||
@@ -1,7 +1,14 @@
|
|||||||
|
{{#ifEquals pageId @root.homePageId}}
|
||||||
|
<div class="welcome-section">
|
||||||
|
<h2 data-editable="profile-title">{{title}}</h2>
|
||||||
|
<h3 data-editable="profile-subtitle">{{subtitle}}</h3>
|
||||||
|
</div>
|
||||||
|
{{else}}
|
||||||
<div class="welcome-section">
|
<div class="welcome-section">
|
||||||
<h2 data-editable="page-title">{{title}}</h2>
|
<h2 data-editable="page-title">{{title}}</h2>
|
||||||
<p class="subtitle" data-editable="page-subtitle">{{subtitle}}</p>
|
<p class="subtitle" data-editable="page-subtitle">{{subtitle}}</p>
|
||||||
</div>
|
</div>
|
||||||
|
{{/ifEquals}}
|
||||||
{{#each categories}}
|
{{#each categories}}
|
||||||
{{> category}}
|
{{> category}}
|
||||||
{{/each}}
|
{{/each}}
|
||||||
|
|||||||
@@ -1,7 +1,14 @@
|
|||||||
|
{{#ifEquals pageId @root.homePageId}}
|
||||||
|
<div class="welcome-section">
|
||||||
|
<h2 data-editable="profile-title">{{title}}</h2>
|
||||||
|
<h3 data-editable="profile-subtitle">{{subtitle}}</h3>
|
||||||
|
</div>
|
||||||
|
{{else}}
|
||||||
<div class="welcome-section">
|
<div class="welcome-section">
|
||||||
<h2 data-editable="page-title">{{title}}</h2>
|
<h2 data-editable="page-title">{{title}}</h2>
|
||||||
<p class="subtitle" data-editable="page-subtitle">{{subtitle}}</p>
|
<p class="subtitle" data-editable="page-subtitle">{{subtitle}}</p>
|
||||||
</div>
|
</div>
|
||||||
|
{{/ifEquals}}
|
||||||
{{#each categories}}
|
{{#each categories}}
|
||||||
{{> category}}
|
{{> category}}
|
||||||
{{/each}}
|
{{/each}}
|
||||||
|
|||||||
@@ -1,7 +1,14 @@
|
|||||||
|
{{#ifEquals pageId @root.homePageId}}
|
||||||
|
<div class="welcome-section">
|
||||||
|
<h2 data-editable="profile-title">{{title}}</h2>
|
||||||
|
<h3 data-editable="profile-subtitle">{{subtitle}}</h3>
|
||||||
|
</div>
|
||||||
|
{{else}}
|
||||||
<div class="welcome-section">
|
<div class="welcome-section">
|
||||||
<h2 data-editable="page-title">{{title}}</h2>
|
<h2 data-editable="page-title">{{title}}</h2>
|
||||||
<p class="subtitle" data-editable="page-subtitle">{{subtitle}}</p>
|
<p class="subtitle" data-editable="page-subtitle">{{subtitle}}</p>
|
||||||
</div>
|
</div>
|
||||||
|
{{/ifEquals}}
|
||||||
{{#each categories}}
|
{{#each categories}}
|
||||||
{{> category}}
|
{{> category}}
|
||||||
{{/each}}
|
{{/each}}
|
||||||
|
|||||||
@@ -1,7 +1,14 @@
|
|||||||
|
{{#ifEquals pageId @root.homePageId}}
|
||||||
<div class="welcome-section">
|
<div class="welcome-section">
|
||||||
<h2 data-editable="profile-title">{{profile.title}}</h2>
|
<h2 data-editable="profile-title">{{title}}</h2>
|
||||||
<h3 data-editable="profile-subtitle">{{profile.subtitle}}</h3>
|
<h3 data-editable="profile-subtitle">{{subtitle}}</h3>
|
||||||
</div>
|
</div>
|
||||||
|
{{else}}
|
||||||
|
<div class="welcome-section">
|
||||||
|
<h2 data-editable="page-title">{{title}}</h2>
|
||||||
|
<p class="subtitle" data-editable="page-subtitle">{{subtitle}}</p>
|
||||||
|
</div>
|
||||||
|
{{/ifEquals}}
|
||||||
{{#each categories}}
|
{{#each categories}}
|
||||||
{{> category}}
|
{{> category}}
|
||||||
{{/each}}
|
{{/each}}
|
||||||
|
|||||||
@@ -1,9 +1,16 @@
|
|||||||
<div class="page" id="{{pageId}}">
|
<div class="page" id="{{pageId}}">
|
||||||
|
{{#ifEquals pageId @root.homePageId}}
|
||||||
|
<div class="welcome-section">
|
||||||
|
<h2 data-editable="profile-title">{{title}}</h2>
|
||||||
|
<h3 data-editable="profile-subtitle">{{subtitle}}</h3>
|
||||||
|
</div>
|
||||||
|
{{else}}
|
||||||
<div class="welcome-section">
|
<div class="welcome-section">
|
||||||
<h2 data-editable="page-title">{{title}}</h2>
|
<h2 data-editable="page-title">{{title}}</h2>
|
||||||
<p class="subtitle" data-editable="page-subtitle">{{subtitle}}</p>
|
<p class="subtitle" data-editable="page-subtitle">{{subtitle}}</p>
|
||||||
</div>
|
</div>
|
||||||
|
{{/ifEquals}}
|
||||||
{{#each categories}}
|
{{#each categories}}
|
||||||
{{> category}}
|
{{> category}}
|
||||||
{{/each}}
|
{{/each}}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,7 +1,14 @@
|
|||||||
|
{{#ifEquals pageId @root.homePageId}}
|
||||||
|
<div class="welcome-section">
|
||||||
|
<h2 data-editable="profile-title">{{title}}</h2>
|
||||||
|
<h3 data-editable="profile-subtitle">{{subtitle}}</h3>
|
||||||
|
</div>
|
||||||
|
{{else}}
|
||||||
<div class="welcome-section">
|
<div class="welcome-section">
|
||||||
<h2 data-editable="page-title">{{title}}</h2>
|
<h2 data-editable="page-title">{{title}}</h2>
|
||||||
<p class="subtitle" data-editable="page-subtitle">{{subtitle}}</p>
|
<p class="subtitle" data-editable="page-subtitle">{{subtitle}}</p>
|
||||||
</div>
|
</div>
|
||||||
|
{{/ifEquals}}
|
||||||
{{#each categories}}
|
{{#each categories}}
|
||||||
{{> category}}
|
{{> category}}
|
||||||
{{/each}}
|
{{/each}}
|
||||||
|
|||||||
55
test/home-page-profile-override.node-test.js
Normal file
55
test/home-page-profile-override.node-test.js
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
const test = require('node:test');
|
||||||
|
const assert = require('node:assert/strict');
|
||||||
|
const path = require('node:path');
|
||||||
|
|
||||||
|
const { loadHandlebarsTemplates, generateAllPagesHTML } = require('../src/generator.js');
|
||||||
|
|
||||||
|
test('首页(navigation 第一项)应使用 profile 覆盖 title/subtitle 显示', () => {
|
||||||
|
const originalCwd = process.cwd();
|
||||||
|
process.chdir(path.join(__dirname, '..'));
|
||||||
|
|
||||||
|
try {
|
||||||
|
loadHandlebarsTemplates();
|
||||||
|
|
||||||
|
const config = {
|
||||||
|
site: { title: 'Test Site', description: '', author: '', favicon: '', logo_text: 'Test' },
|
||||||
|
profile: { title: 'PROFILE_TITLE', subtitle: 'PROFILE_SUBTITLE' },
|
||||||
|
social: [],
|
||||||
|
navigation: [
|
||||||
|
{ id: 'bookmarks', name: '书签', icon: 'fas fa-bookmark' },
|
||||||
|
{ id: 'home', name: '首页', icon: 'fas fa-home' },
|
||||||
|
{ id: 'projects', name: '项目', icon: 'fas fa-project-diagram' },
|
||||||
|
],
|
||||||
|
bookmarks: { title: '书签页标题', subtitle: '书签页副标题', template: 'bookmarks', categories: [] },
|
||||||
|
home: { title: 'HOME_PAGE_TITLE', subtitle: 'HOME_PAGE_SUBTITLE', template: 'home', categories: [] },
|
||||||
|
projects: { title: '项目页标题', subtitle: '项目页副标题', template: 'projects', categories: [] },
|
||||||
|
};
|
||||||
|
|
||||||
|
const pages = generateAllPagesHTML(config);
|
||||||
|
|
||||||
|
assert.ok(typeof pages.bookmarks === 'string' && pages.bookmarks.length > 0);
|
||||||
|
assert.ok(pages.bookmarks.includes('PROFILE_TITLE'));
|
||||||
|
assert.ok(pages.bookmarks.includes('PROFILE_SUBTITLE'));
|
||||||
|
assert.ok(pages.bookmarks.includes('data-editable="profile-title"'));
|
||||||
|
assert.ok(pages.bookmarks.includes('data-editable="profile-subtitle"'));
|
||||||
|
assert.ok(pages.bookmarks.includes('<h3'));
|
||||||
|
assert.ok(!pages.bookmarks.includes('书签页标题'));
|
||||||
|
assert.ok(!pages.bookmarks.includes('书签页副标题'));
|
||||||
|
assert.ok(!pages.bookmarks.includes('data-editable="page-title"'));
|
||||||
|
|
||||||
|
assert.ok(typeof pages.home === 'string' && pages.home.length > 0);
|
||||||
|
assert.ok(pages.home.includes('HOME_PAGE_TITLE'));
|
||||||
|
assert.ok(pages.home.includes('HOME_PAGE_SUBTITLE'));
|
||||||
|
assert.ok(pages.home.includes('data-editable="page-title"'));
|
||||||
|
assert.ok(pages.home.includes('data-editable="page-subtitle"'));
|
||||||
|
assert.ok(pages.home.includes('<p class="subtitle"'));
|
||||||
|
assert.ok(!pages.home.includes('PROFILE_TITLE'));
|
||||||
|
|
||||||
|
assert.ok(typeof pages.projects === 'string' && pages.projects.length > 0);
|
||||||
|
assert.ok(pages.projects.includes('项目页标题'));
|
||||||
|
assert.ok(pages.projects.includes('项目页副标题'));
|
||||||
|
assert.ok(pages.projects.includes('<p class="subtitle"'));
|
||||||
|
} finally {
|
||||||
|
process.chdir(originalCwd);
|
||||||
|
}
|
||||||
|
});
|
||||||
Reference in New Issue
Block a user