diff --git a/assets/style.css b/assets/style.css index a8d5c1d..6464246 100644 --- a/assets/style.css +++ b/assets/style.css @@ -892,6 +892,205 @@ body .content.expanded { font-size: 1.3rem; } +/* 多层级嵌套样式 */ +/* 层级2: 子分类 */ +.category-level-2 { + margin: 1.5rem 0 1.5rem 2rem; + padding: 1.5rem; + background: linear-gradient(145deg, + rgba(var(--card-bg-rgb), 0.8), + rgba(var(--card-bg-rgb), 0.6)); + border-left: 3px solid var(--accent-color); + border-radius: 12px; + width: calc(100% - 3.5rem); +} + +.category-level-2 .category-header h3 { + font-size: 1.1rem; + margin-bottom: 1rem; + color: var(--text-bright); + display: flex; + align-items: center; + gap: 0.6rem; + letter-spacing: 0.2px; +} + +.category-level-2 .category-header h3 i { + color: var(--accent-color); + font-size: 1.2rem; +} + +/* 层级3: 分组 */ +.group-level-3, .category-level-3 { + margin: 1rem 0 1rem 1.5rem; + padding: 1rem; + background: rgba(var(--card-bg-rgb), 0.4); + border-left: 2px solid var(--secondary-color); + border-radius: 8px; + width: calc(100% - 2.5rem); +} + +.group-level-3 .group-header h4, +.category-level-3 .category-header h4 { + font-size: 1rem; + margin-bottom: 0.8rem; + color: var(--text-bright); + display: flex; + align-items: center; + gap: 0.5rem; + letter-spacing: 0.2px; +} + +.group-level-3 .group-header h4 i, +.category-level-3 .category-header h4 i { + color: var(--secondary-color); + font-size: 1.1rem; +} + +/* 层级4: 更深层次的分组 */ +.group-level-4, .category-level-4 { + margin: 0.8rem 0 0.8rem 1rem; + padding: 0.8rem; + background: rgba(var(--card-bg-rgb), 0.2); + border-left: 1px solid var(--tertiary-color); + border-radius: 6px; + width: calc(100% - 1.8rem); +} + +.group-level-4 .group-header h5, +.category-level-4 .category-header h5 { + font-size: 0.95rem; + margin-bottom: 0.6rem; + color: var(--text-bright); + display: flex; + align-items: center; + gap: 0.4rem; + letter-spacing: 0.1px; +} + +.group-level-4 .group-header h5 i, +.category-level-4 .category-header h5 i { + color: var(--tertiary-color); + font-size: 1rem; +} + +/* 切换图标样式 */ +.toggle-icon { + display: inline-block; + margin-left: auto; + transition: transform 0.3s ease; + color: var(--text-muted); + font-size: 0.9rem; +} + +.category-header:hover .toggle-icon, +.group-header:hover .toggle-icon { + color: var(--accent-color); +} + +/* 展开/折叠动画 */ +.category-content, .group-content { + overflow: hidden; + transition: max-height 0.3s ease, opacity 0.3s ease; + max-height: 9999px; + opacity: 1; +} + +.category.collapsed .category-content, +.group.collapsed .group-content { + max-height: 0; + opacity: 0; +} + +.category.collapsed .toggle-icon i, +.group.collapsed .toggle-icon i { + transform: rotate(-90deg); +} + +/* 空内容提示 */ +.empty-content { + color: var(--text-muted); + font-style: italic; + text-align: center; + padding: 1rem; + font-size: 0.9rem; +} + +/* 子容器样式 */ +.subcategories-container, +.groups-container { + width: 100%; +} + +/* 确保嵌套的网站网格正确显示 */ +.category-level-2 .sites-grid, +.group-level-3 .sites-grid, +.category-level-3 .sites-grid, +.group-level-4 .sites-grid, +.category-level-4 .sites-grid { + margin-top: 0.8rem; + gap: 0.8rem; + grid-template-columns: repeat(auto-fill, minmax(140px, 1fr)); +} + +/* 响应式设计 - 嵌套结构 */ +@media (max-width: 768px) { + .category-level-2 { + margin-left: 1rem; + padding: 1rem; + width: calc(100% - 2rem); + } + + .group-level-3, .category-level-3 { + margin-left: 0.5rem; + padding: 0.8rem; + width: calc(100% - 1.3rem); + } + + .group-level-4, .category-level-4 { + margin-left: 0.3rem; + padding: 0.6rem; + width: calc(100% - 0.9rem); + } + + .category-level-2 .sites-grid, + .group-level-3 .sites-grid, + .category-level-3 .sites-grid, + .group-level-4 .sites-grid, + .category-level-4 .sites-grid { + grid-template-columns: repeat(2, minmax(0, 1fr)); + gap: 0.6rem; + } +} + +@media (max-width: 480px) { + .category { + margin-left: 1rem; + padding: 1.5rem; + } + + .category-level-2, .group-level-3, .category-level-3 { + margin-left: 0.5rem; + padding: 0.8rem; + width: calc(100% - 1rem); + } + + .group-level-4, .category-level-4 { + margin-left: 0.3rem; + padding: 0.6rem; + width: calc(100% - 0.6rem); + } + + .category-level-2 .sites-grid, + .group-level-3 .sites-grid, + .category-level-3 .sites-grid, + .group-level-4 .sites-grid, + .category-level-4 .sites-grid { + grid-template-columns: repeat(2, minmax(0, 1fr)); + gap: 0.5rem; + } +} + /* 网站卡片网格 */ .sites-grid { display: grid; diff --git a/config/_default/pages/bookmarks-four-level.yml b/config/_default/pages/bookmarks-four-level.yml new file mode 100644 index 0000000..2b4333c --- /dev/null +++ b/config/_default/pages/bookmarks-four-level.yml @@ -0,0 +1,313 @@ +# 四层级嵌套书签配置示例 +# 展示分类 -> 子分类 -> 分组 -> 网站的四层结构 +# 说明:该页面通常由“书签导入工具”自动生成,手工修改时请保持字段结构一致。 +title: 我的书签 +subtitle: 从浏览器导入的书签收藏 + +# 指定使用的模板文件名,现有页面模板可见 templates/pages(不含 .hbs) +template: bookmarks + +categories: + - name: '技术资源' + icon: 'fas fa-laptop-code' + subcategories: + - name: '前端开发' + icon: 'fas fa-code' + groups: + - name: '框架库' + icon: 'fas fa-cube' + sites: + - name: 'React' + url: 'https://reactjs.org/' + icon: 'fab fa-react' + description: 'React官方文档' + - name: 'Vue.js' + url: 'https://vuejs.org/' + icon: 'fab fa-vuejs' + description: 'Vue.js官方文档' + - name: 'Angular' + url: 'https://angular.io/' + icon: 'fab fa-angular' + description: 'Angular官方文档' + - name: 'Svelte' + url: 'https://svelte.dev/' + icon: 'fas fa-fire' + description: 'Svelte官方文档' + - name: '状态管理' + icon: 'fas fa-database' + sites: + - name: 'Redux' + url: 'https://redux.js.org/' + icon: 'fas fa-database' + description: 'Redux状态管理' + - name: 'Vuex' + url: 'https://vuex.vuejs.org/' + icon: 'fas fa-database' + description: 'Vue状态管理' + - name: 'MobX' + url: 'https://mobx.js.org/' + icon: 'fas fa-react' + description: '响应式状态管理' + - name: '构建工具' + icon: 'fas fa-tools' + sites: + - name: 'Webpack' + url: 'https://webpack.js.org/' + icon: 'fas fa-cube' + description: '模块打包工具' + - name: 'Vite' + url: 'https://vitejs.dev/' + icon: 'fas fa-bolt' + description: '下一代前端构建工具' + - name: 'Rollup' + url: 'https://rollupjs.org/' + icon: 'fas fa-compress' + description: '模块打包器' + - name: '后端开发' + icon: 'fas fa-server' + groups: + - name: 'Node.js生态' + icon: 'fab fa-node-js' + sites: + - name: 'Express' + url: 'https://expressjs.com/' + icon: 'fas fa-server' + description: 'Node.js Web框架' + - name: 'Koa' + url: 'https://koajs.com/' + icon: 'fas fa-leaf' + description: '下一代Node.js框架' + - name: 'NestJS' + url: 'https://nestjs.com/' + icon: 'fas fa-home' + description: 'Node.js企业级框架' + - name: 'Python框架' + icon: 'fab fa-python' + sites: + - name: 'Django' + url: 'https://www.djangoproject.com/' + icon: 'fas fa-python' + description: 'Python Web框架' + - name: 'Flask' + url: 'https://flask.palletsprojects.com/' + icon: 'fas fa-flask' + description: 'Python微框架' + - name: 'FastAPI' + url: 'https://fastapi.tiangolo.com/' + icon: 'fas fa-bolt' + description: '现代Python Web框架' + - name: '数据库ORM' + icon: 'fas fa-database' + sites: + - name: 'Prisma' + url: 'https://www.prisma.io/' + icon: 'fas fa-database' + description: '下一代数据库工具' + - name: 'TypeORM' + url: 'https://typeorm.io/' + icon: 'fas fa-database' + description: 'TypeScript ORM' + - name: 'SQLAlchemy' + url: 'https://www.sqlalchemy.org/' + icon: 'fas fa-database' + description: 'Python SQL工具包' + + - name: '设计资源' + icon: 'fas fa-palette' + subcategories: + - name: 'UI设计工具' + icon: 'fas fa-paint-brush' + groups: + - name: '原型设计' + icon: 'fas fa-drafting-compass' + sites: + - name: 'Figma' + url: 'https://www.figma.com/' + icon: 'fab fa-figma' + description: '协作式UI设计工具' + - name: 'Sketch' + url: 'https://www.sketch.com/' + icon: 'fab fa-sketch' + description: 'Mac平台UI设计工具' + - name: 'Adobe XD' + url: 'https://www.adobe.com/products/xd.html' + icon: 'fab fa-adobe' + description: 'Adobe UI设计工具' + - name: '设计系统' + icon: 'fas fa-th-large' + sites: + - name: 'Ant Design' + url: 'https://ant.design/' + icon: 'fas fa-th' + description: '企业级UI设计语言' + - name: 'Material Design' + url: 'https://material.io/design' + icon: 'fas fa-cube' + description: 'Google设计系统' + - name: 'Bootstrap' + url: 'https://getbootstrap.com/' + icon: 'fab fa-bootstrap' + description: '响应式CSS框架' + - name: '交互设计' + icon: 'fas fa-hand-pointer' + sites: + - name: 'Principle' + url: 'https://principleformac.com/' + icon: 'fas fa-magic' + description: '交互设计工具' + - name: 'Framer' + url: 'https://www.framer.com/' + icon: 'fas fa-mobile-alt' + description: '交互式设计工具' + - name: 'ProtoPie' + url: 'https://www.protopie.io/' + icon: 'fas fa-chart-pie' + description: '原型制作工具' + - name: '视觉资源' + icon: 'fas fa-image' + groups: + - name: '图标库' + icon: 'fas fa-icons' + sites: + - name: 'Font Awesome' + url: 'https://fontawesome.com/' + icon: 'fab fa-font-awesome' + description: '图标库' + - name: 'Iconfont' + url: 'https://www.iconfont.cn/' + icon: 'fas fa-icons' + description: '阿里巴巴图标库' + - name: 'Feather Icons' + url: 'https://feathericons.com/' + icon: 'fas fa-feather' + description: '简洁的图标库' + - name: '插画资源' + icon: 'fas fa-paint-brush' + sites: + - name: 'UnDraw' + url: 'https://undraw.co/' + icon: 'fas fa-palette' + description: '开源插画库' + - name: 'Illustrations.co' + url: 'https://illustrations.co/' + icon: 'fas fa-image' + description: '插画资源' + - name: 'Storyset' + url: 'https://storyset.com/' + icon: 'fas fa-book' + description: '动画插画库' + - name: '配色方案' + icon: 'fas fa-palette' + sites: + - name: 'Coolors' + url: 'https://coolors.co/' + icon: 'fas fa-palette' + description: '在线配色方案生成器' + - name: 'Adobe Color' + url: 'https://color.adobe.com/' + icon: 'fab fa-adobe' + description: 'Adobe配色工具' + - name: 'Paletton' + url: 'https://paletton.com/' + icon: 'fas fa-palette' + description: '配色方案设计工具' + + - name: '开发工具' + icon: 'fas fa-tools' + subcategories: + - name: '编程工具' + icon: 'fas fa-code' + groups: + - name: '代码编辑器' + icon: 'fas fa-code' + sites: + - name: 'Visual Studio Code' + url: 'https://code.visualstudio.com/' + icon: 'fas fa-code' + description: '微软代码编辑器' + - name: 'Sublime Text' + url: 'https://www.sublimetext.com/' + icon: 'fas fa-file-code' + description: '轻量级代码编辑器' + - name: 'WebStorm' + url: 'https://www.jetbrains.com/webstorm/' + icon: 'fab fa-js' + description: 'JetBrains前端IDE' + - name: '版本控制' + icon: 'fas fa-code-branch' + sites: + - name: 'GitHub' + url: 'https://github.com/' + icon: 'fab fa-github' + description: '代码托管平台' + - name: 'GitLab' + url: 'https://gitlab.com/' + icon: 'fab fa-gitlab' + description: 'Git代码管理平台' + - name: 'Bitbucket' + url: 'https://bitbucket.org/' + icon: 'fab fa-bitbucket' + description: 'Atlassian代码托管' + - name: '调试工具' + icon: 'fas fa-bug' + sites: + - name: 'Chrome DevTools' + url: 'https://developers.google.com/web/tools/chrome-devtools' + icon: 'fab fa-chrome' + description: 'Chrome开发者工具' + - name: 'Firefox DevTools' + url: 'https://developer.mozilla.org/en-US/docs/Tools' + icon: 'fab fa-firefox' + description: 'Firefox开发者工具' + - name: 'Postman' + url: 'https://www.postman.com/' + icon: 'fas fa-paper-plane' + description: 'API测试工具' + - name: '部署运维' + icon: 'fas fa-server' + groups: + - name: '云服务' + icon: 'fas fa-cloud' + sites: + - name: 'AWS' + url: 'https://aws.amazon.com/' + icon: 'fab fa-aws' + description: '亚马逊云服务' + - name: 'Azure' + url: 'https://azure.microsoft.com/' + icon: 'fab fa-microsoft' + description: '微软云服务' + - name: 'Google Cloud' + url: 'https://cloud.google.com/' + icon: 'fab fa-google' + description: '谷歌云服务' + - name: '容器化' + icon: 'fas fa-cube' + sites: + - name: 'Docker' + url: 'https://www.docker.com/' + icon: 'fab fa-docker' + description: '容器化平台' + - name: 'Kubernetes' + url: 'https://kubernetes.io/' + icon: 'fas fa-dharmachakra' + description: '容器编排系统' + - name: 'Podman' + url: 'https://podman.io/' + icon: 'fas fa-cube' + description: '无守护进程容器引擎' + - name: 'CI/CD' + icon: 'fas fa-infinity' + sites: + - name: 'GitHub Actions' + url: 'https://github.com/features/actions' + icon: 'fab fa-github' + description: 'GitHub自动化工具' + - name: 'Jenkins' + url: 'https://www.jenkins.io/' + icon: 'fas fa-infinity' + description: '开源自动化服务器' + - name: 'GitLab CI' + url: 'https://docs.gitlab.com/ee/ci/' + icon: 'fab fa-gitlab' + description: 'GitLab CI/CD工具' \ No newline at end of file diff --git a/config/_default/site.yml b/config/_default/site.yml index d822cfd..fc119bb 100644 --- a/config/_default/site.yml +++ b/config/_default/site.yml @@ -69,3 +69,6 @@ navigation: - name: 书签 icon: fas fa-bookmark id: bookmarks + - name: 书签(多层级) + icon: fas fa-bookmark + id: bookmarks-four-level diff --git a/src/bookmark-processor.js b/src/bookmark-processor.js index 1ee33cd..e42853d 100644 --- a/src/bookmark-processor.js +++ b/src/bookmark-processor.js @@ -103,11 +103,10 @@ function getLatestBookmarkFile() { } } -// 解析书签HTML内容 +// 解析书签HTML内容,支持2-4层级嵌套结构 function parseBookmarks(htmlContent) { - // 简单的正则表达式匹配方法解析书签文件 - // 注意:这是一个简化实现,可能不适用于所有浏览器的书签格式 - const folderRegex = /
]*>(.*?)<\/H3>/g; + // 正则表达式匹配文件夹和书签 + const folderRegex = /
]*)>(.*?)<\/H3>/g; const bookmarkRegex = /
]*>(.*?)<\/A>/g; // 储存解析结果 @@ -115,31 +114,89 @@ function parseBookmarks(htmlContent) { categories: [] }; - // 提取文件夹 - let match; - let folderMatches = []; - while ((match = folderRegex.exec(htmlContent)) !== null) { - folderMatches.push({ - index: match.index, - name: match[1].trim(), - end: match.index + match[0].length - }); + // 递归解析嵌套文件夹 + function parseNestedFolder(htmlContent, parentPath = [], level = 1) { + const folders = []; + let match; + + // 使用正则表达式匹配文件夹 + folderRegex.lastIndex = 0; + + while ((match = folderRegex.exec(htmlContent)) !== null) { + const folderName = match[2].trim(); + const folderStart = match.index; + const folderEnd = match.index + match[0].length; + + // 查找文件夹的结束位置 + let folderContentEnd = htmlContent.length; + let depth = 1; + let pos = folderEnd; + + while (pos < htmlContent.length && depth > 0) { + const dlStart = htmlContent.substring(pos).match(/

/gi); + const dlEnd = htmlContent.substring(pos).match(/<\/DL>

/gi); + + if (dlStart && dlStart.index < (dlEnd ? dlEnd.index : htmlContent.length)) { + depth++; + pos += dlStart.index + dlStart[0].length; + } else if (dlEnd) { + depth--; + pos += dlEnd.index + dlEnd[0].length; + } else { + break; + } + } + + folderContentEnd = pos; + const folderContent = htmlContent.substring(folderEnd, folderContentEnd); + + // 解析文件夹内容 + const folder = { + name: folderName, + icon: 'fas fa-folder', + path: [...parentPath, folderName] + }; + + // 检查是否包含子文件夹 + const hasSubfolders = folderRegex.test(folderContent); + folderRegex.lastIndex = 0; + + if (hasSubfolders && level < 4) { + // 递归解析子文件夹 + const subfolders = parseNestedFolder(folderContent, folder.path, level + 1); + + // 根据层级深度决定数据结构 + if (level === 1) { + folder.subcategories = subfolders; + } else if (level === 2) { + folder.groups = subfolders; + } else if (level === 3) { + // 层级3直接解析书签 + folder.sites = parseSitesInFolder(folderContent); + } + } else { + // 解析书签 + folder.sites = parseSitesInFolder(folderContent); + } + + // 只添加包含内容的文件夹 + const hasContent = folder.sites && folder.sites.length > 0 || + folder.subcategories && folder.subcategories.length > 0 || + folder.groups && folder.groups.length > 0; + + if (hasContent) { + folders.push(folder); + } + } + + return folders; } - // 对每个文件夹,提取其中的书签 - for (let i = 0; i < folderMatches.length; i++) { - const folder = folderMatches[i]; - const nextFolder = folderMatches[i + 1]; - - // 确定当前文件夹的内容范围 - const folderContent = nextFolder - ? htmlContent.substring(folder.end, nextFolder.index) - : htmlContent.substring(folder.end); - - // 从文件夹内容中提取书签 + // 解析文件夹中的书签 + function parseSitesInFolder(folderContent) { const sites = []; let bookmarkMatch; - bookmarkRegex.lastIndex = 0; // 重置regex索引 + bookmarkRegex.lastIndex = 0; while ((bookmarkMatch = bookmarkRegex.exec(folderContent)) !== null) { const url = bookmarkMatch[1]; @@ -162,16 +219,12 @@ function parseBookmarks(htmlContent) { }); } - // 只添加包含书签的文件夹 - if (sites.length > 0) { - bookmarks.categories.push({ - name: folder.name, - icon: 'fas fa-folder', // 默认使用文件夹图标 - sites: sites - }); - } + return sites; } + // 开始解析 + bookmarks.categories = parseNestedFolder(htmlContent); + return bookmarks; } diff --git a/src/generator.js b/src/generator.js index 930a147..c6689b6 100644 --- a/src/generator.js +++ b/src/generator.js @@ -196,7 +196,21 @@ function safeLoadYamlConfig(filePath) { try { const fileContent = fs.readFileSync(filePath, 'utf8'); - return yaml.load(fileContent); + // 使用 loadAll 而不是 load 来支持多文档 YAML 文件 + const docs = yaml.loadAll(fileContent); + + // 如果只有一个文档,直接返回 + if (docs.length === 1) { + return docs[0]; + } + + // 如果有多个文档,返回第一个文档(忽略后面的文档) + if (docs.length > 1) { + console.warn(`Warning: Multiple documents found in ${filePath}. Using the first document only.`); + return docs[0]; + } + + return null; } catch (error) { handleConfigLoadError(filePath, error); return null; diff --git a/src/helpers/utils.js b/src/helpers/utils.js index 0bd9128..47ca280 100644 --- a/src/helpers/utils.js +++ b/src/helpers/utils.js @@ -181,6 +181,19 @@ function encodeURIComponentHelper(text) { } } +/** + * 数学加法运算助手函数 + * @param {number} a 第一个数 + * @param {number} b 第二个数 + * @returns {number} 两数之和 + * @example {{add level 1}} + */ +function add(a, b) { + const numA = parseInt(a, 10) || 0; + const numB = parseInt(b, 10) || 0; + return numA + numB; +} + // 导出所有工具类助手函数 module.exports = { slice, @@ -191,5 +204,6 @@ module.exports = { range, pick, keys, - encodeURIComponent: encodeURIComponentHelper + encodeURIComponent: encodeURIComponentHelper, + add }; diff --git a/src/script.js b/src/script.js index 7be3d4c..0ae6af9 100644 --- a/src/script.js +++ b/src/script.js @@ -342,6 +342,137 @@ window.MeNav = { } }; +// 多层级嵌套书签功能 +window.MeNav.expandAll = function() { + document.querySelectorAll('.category.collapsed, .group.collapsed').forEach(element => { + element.classList.remove('collapsed'); + saveToggleState(element, 'expanded'); + }); +}; + +window.MeNav.collapseAll = function() { + document.querySelectorAll('.category:not(.collapsed), .group:not(.collapsed)').forEach(element => { + element.classList.add('collapsed'); + saveToggleState(element, 'collapsed'); + }); +}; + +window.MeNav.toggleCategory = function(categoryName, subcategoryName = null, groupName = null) { + const selector = groupName + ? `[data-name="${categoryName}"] [data-name="${subcategoryName}"] [data-name="${groupName}"]` + : subcategoryName + ? `[data-name="${categoryName}"] [data-name="${subcategoryName}"]` + : `[data-name="${categoryName}"]`; + + const element = document.querySelector(selector); + if (element) { + toggleNestedElement(element); + } +}; + +window.MeNav.getNestedStructure = function() { + // 返回完整的嵌套结构数据 + const categories = []; + document.querySelectorAll('.category-level-1').forEach(cat => { + categories.push(extractNestedData(cat)); + }); + return categories; +}; + +// 切换嵌套元素 +function toggleNestedElement(container) { + const isCollapsed = container.classList.contains('collapsed'); + + if (isCollapsed) { + container.classList.remove('collapsed'); + saveToggleState(container, 'expanded'); + } else { + container.classList.add('collapsed'); + saveToggleState(container, 'collapsed'); + } + + // 触发自定义事件 + const event = new CustomEvent('nestedToggle', { + detail: { + element: container, + type: container.dataset.type, + name: container.dataset.name, + isCollapsed: !isCollapsed + } + }); + document.dispatchEvent(event); +} + +// 保存切换状态 +function saveToggleState(element, state) { + const type = element.dataset.type; + const name = element.dataset.name; + const level = element.dataset.level || '1'; + const key = `menav-toggle-${type}-${level}-${name}`; + localStorage.setItem(key, state); +} + +// 恢复切换状态 +function restoreToggleState(element) { + const type = element.dataset.type; + const name = element.dataset.name; + const level = element.dataset.level || '1'; + const key = `menav-toggle-${type}-${level}-${name}`; + const savedState = localStorage.getItem(key); + + if (savedState === 'collapsed') { + element.classList.add('collapsed'); + } +} + +// 初始化嵌套分类 +function initializeNestedCategories() { + // 为所有可折叠元素添加切换功能 + document.querySelectorAll('[data-toggle="category"], [data-toggle="group"]').forEach(header => { + header.addEventListener('click', function(e) { + e.stopPropagation(); + const container = this.parentElement; + toggleNestedElement(container); + }); + + // 恢复保存的状态 + restoreToggleState(header.parentElement); + }); +} + +// 提取嵌套数据 +function extractNestedData(element) { + const data = { + name: element.dataset.name, + type: element.dataset.type, + level: element.dataset.level, + isCollapsed: element.classList.contains('collapsed') + }; + + // 提取子元素数据 + const subcategories = element.querySelectorAll(':scope > .category-content > .subcategories-container > .category'); + if (subcategories.length > 0) { + data.subcategories = Array.from(subcategories).map(sub => extractNestedData(sub)); + } + + const groups = element.querySelectorAll(':scope > .category-content > .groups-container > .group'); + if (groups.length > 0) { + data.groups = Array.from(groups).map(group => extractNestedData(group)); + } + + const sites = element.querySelectorAll(':scope > .category-content > .sites-grid > .site-card, :scope > .group-content > .sites-grid > .site-card'); + if (sites.length > 0) { + data.sites = Array.from(sites).map(site => ({ + name: site.dataset.name, + url: site.dataset.url, + icon: site.dataset.icon, + description: site.dataset.description + })); + } + + return data; +} + document.addEventListener('DOMContentLoaded', () => { // 先声明所有状态变量 let isSearchActive = false; @@ -1312,6 +1443,9 @@ document.addEventListener('DOMContentLoaded', () => { }); }); + // 初始化嵌套分类功能 + initializeNestedCategories(); + // 初始化搜索索引(使用requestIdleCallback或setTimeout延迟初始化,避免影响页面加载) if ('requestIdleCallback' in window) { requestIdleCallback(() => initSearchIndex()); diff --git a/templates/components/category.hbs b/templates/components/category.hbs index c624cfd..97e305d 100644 --- a/templates/components/category.hbs +++ b/templates/components/category.hbs @@ -1,12 +1,53 @@ -

-

{{name}}

-
- {{#if sites.length}} - {{#each sites}} - {{> site-card}} - {{/each}} +
+ +
+ + + {{name}} + {{#if subcategories}} + + + + {{/if}} + {{#if groups}} + + + + {{/if}} + +
+ +
+ {{#if subcategories}} +
+ {{#each subcategories}} + {{> category level=2}} + {{/each}} +
+ {{else if groups}} +
+ {{#each groups}} + {{> group}} + {{/each}} +
+ {{else if sites}} +
+ {{#if sites.length}} + {{#each sites}} + {{> site-card}} + {{/each}} + {{else}} +

暂无网站

+ {{/if}} +
{{else}} -

暂无网站

+

暂无内容

{{/if}}
diff --git a/templates/components/group.hbs b/templates/components/group.hbs new file mode 100644 index 0000000..6b8dcf0 --- /dev/null +++ b/templates/components/group.hbs @@ -0,0 +1,32 @@ +
+ +
+ + + {{name}} + + + + +
+ +
+ {{#if sites}} +
+ {{#if sites.length}} + {{#each sites}} + {{> site-card}} + {{/each}} + {{else}} +

暂无网站

+ {{/if}} +
+ {{else}} +

暂无网站

+ {{/if}} +
+
\ No newline at end of file