forked from Gitlink/gitlink_help_center
464 lines
18 KiB
JavaScript
464 lines
18 KiB
JavaScript
import React, { useState, useRef, useEffect } from 'react';
|
||
import BrowserOnly from '@docusaurus/BrowserOnly';
|
||
import styles from './TableOfContentsButton.module.css';
|
||
|
||
export default function TableOfContentsButton() {
|
||
return (
|
||
<BrowserOnly>
|
||
{() => {
|
||
const [isOpen, setIsOpen] = useState(false);
|
||
const [searchQuery, setSearchQuery] = useState('');
|
||
const dropdownRef = useRef(null);
|
||
const searchInputRef = useRef(null);
|
||
const useHistory = require('@docusaurus/router').useHistory;
|
||
const history = useHistory();
|
||
|
||
// 路径前缀
|
||
const pathPrefix = '/next';
|
||
|
||
// 硬编码的目录结构,基于实际docs文件夹结构
|
||
// 带有子目录支持
|
||
const directories = [
|
||
{
|
||
name: '首页',
|
||
path: `${pathPrefix}/`,
|
||
column: 1,
|
||
isMainCategory: true
|
||
},
|
||
{
|
||
name: '视频教程',
|
||
path: `${pathPrefix}/视频教程`,
|
||
column: 1,
|
||
isMainCategory: true
|
||
},
|
||
{
|
||
name: '快速开始',
|
||
path: `${pathPrefix}/快速开始`,
|
||
column: 1,
|
||
isMainCategory: true,
|
||
subItems: [
|
||
{ name: '注册GitLink账号', path: `${pathPrefix}/快速开始/注册GitLink账号` },
|
||
{ name: '创建第一个开源项目', path: `${pathPrefix}/快速开始/创建第一个开源项目` },
|
||
{ name: '提交第一行代码', path: `${pathPrefix}/快速开始/提交第一行代码` },
|
||
{ name: '导入GitHub等第三方Git项目', path: `${pathPrefix}/快速开始/导入GitHub等第三方Git项目` }
|
||
]
|
||
},
|
||
{
|
||
name: '代码库管理',
|
||
path: `${pathPrefix}/代码库管理`,
|
||
column: 1,
|
||
isMainCategory: true,
|
||
subItems: [
|
||
{ name: '仓库创建', path: `${pathPrefix}/代码库管理/仓库创建` },
|
||
{ name: '仓库设置', path: `${pathPrefix}/代码库管理/仓库设置` },
|
||
{ name: '代码提交', path: `${pathPrefix}/代码库管理/代码提交` },
|
||
{ name: '分支管理', path: `${pathPrefix}/代码库管理/分支管理` },
|
||
{ name: '文件管理', path: `${pathPrefix}/代码库管理/文件管理` },
|
||
{ name: '成员管理', path: `${pathPrefix}/代码库管理/成员管理` },
|
||
{ name: 'Webhook', path: `${pathPrefix}/代码库管理/Webhook` },
|
||
{ name: 'WebIDE', path: `${pathPrefix}/代码库管理/WebIDE` },
|
||
{ name: '标签和发行版管理', path: `${pathPrefix}/代码库管理/标签和发行版管理` }
|
||
]
|
||
},
|
||
{
|
||
name: '合并请求(PR)',
|
||
path: `${pathPrefix}/合并请求`,
|
||
column: 2,
|
||
isMainCategory: true,
|
||
subItems: [
|
||
{ name: '合并请求简介', path: `${pathPrefix}/合并请求/合并请求简介` },
|
||
{ name: '创建合并请求', path: `${pathPrefix}/合并请求/创建合并请求` },
|
||
{ name: '代码评审', path: `${pathPrefix}/合并请求/代码评审` },
|
||
{ name: '合并模式简介', path: `${pathPrefix}/合并请求/合并模式简介` },
|
||
{ name: '合并请求关联疑修', path: `${pathPrefix}/合并请求/合并请求关联疑修` }
|
||
]
|
||
},
|
||
{
|
||
name: '疑修(Issue)',
|
||
path: `${pathPrefix}/疑修`,
|
||
column: 3,
|
||
isMainCategory: true,
|
||
subItems: [
|
||
{ name: '疑修简介', path: `${pathPrefix}/疑修/疑修简介` },
|
||
{ name: '疑修创建', path: `${pathPrefix}/疑修/疑修创建` },
|
||
{ name: '疑修状态变更', path: `${pathPrefix}/疑修/疑修状态变更` },
|
||
{ name: '疑修列表', path: `${pathPrefix}/疑修/疑修列表` },
|
||
{ name: '评论及操作记录', path: `${pathPrefix}/疑修/评论及操作记录` },
|
||
{ name: '标记管理', path: `${pathPrefix}/疑修/标记管理` },
|
||
{ name: '里程碑管理', path: `${pathPrefix}/疑修/里程碑管理` }
|
||
]
|
||
},
|
||
{
|
||
name: '机器人(Bot)',
|
||
path: `${pathPrefix}/Bot市场/Bot简介`,
|
||
column: 2,
|
||
isMainCategory: true,
|
||
subItems: [
|
||
{ name: 'bot安装', path: `${pathPrefix}/Bot市场/bot安装` },
|
||
{ name: 'bot配置', path: `${pathPrefix}/Bot市场/bot配置` },
|
||
{ name: 'bot开发', path: `${pathPrefix}/Bot市场/bot开发` }
|
||
]
|
||
},
|
||
{
|
||
name: '第三方服务',
|
||
path: `${pathPrefix}/第三方服务`,
|
||
column: 2,
|
||
isMainCategory: true,
|
||
subItems: [
|
||
{ name: 'WebIDE', path: `${pathPrefix}/第三方服务/WebIDE` },
|
||
{ name: '重睛鸟代码溯源', path: `${pathPrefix}/第三方服务/重睛鸟代码溯源` },
|
||
{ name: '跨平台代码同步服务', path: `${pathPrefix}/第三方服务/跨平台代码同步` }
|
||
]
|
||
},
|
||
{
|
||
name: '组织管理',
|
||
path: `${pathPrefix}/组织管理`,
|
||
column: 2,
|
||
isMainCategory: true,
|
||
subItems: [
|
||
{ name: '组织简介', path: `${pathPrefix}/组织管理/组织简介` },
|
||
{ name: '组织创建及设置', path: `${pathPrefix}/组织管理/组织创建及设置` },
|
||
{ name: '组织团队管理', path: `${pathPrefix}/组织管理/组织团队管理` },
|
||
{ name: '组织成员管理', path: `${pathPrefix}/组织管理/组织成员管理` },
|
||
{ name: '组织项目管理', path: `${pathPrefix}/组织管理/组织项目管理` }
|
||
]
|
||
},
|
||
{
|
||
name: '维基',
|
||
path: `${pathPrefix}/维基`,
|
||
column: 2,
|
||
isMainCategory: true,
|
||
subItems: [
|
||
{ name: '维基页面管理', path: `${pathPrefix}/维基/维基页面管理` },
|
||
{ name: '模板导入及导出', path: `${pathPrefix}/维基/模板导入及导出` }
|
||
]
|
||
},
|
||
{
|
||
name: 'DevOps引擎',
|
||
path: `${pathPrefix}/DevOps引擎`,
|
||
column: 3,
|
||
isMainCategory: true,
|
||
subItems: [
|
||
{ name: '引擎简介', path: `${pathPrefix}/DevOps引擎/引擎简介` },
|
||
{ name: '代码流水线', path: `${pathPrefix}/DevOps引擎/代码流水线` },
|
||
{ name: '图形流水线', path: `${pathPrefix}/DevOps引擎/图形流水线` },
|
||
{ name: '参数配置', path: `${pathPrefix}/DevOps引擎/参数配置` },
|
||
{ name: '密钥设置', path: `${pathPrefix}/DevOps引擎/密钥设置` },
|
||
{ name: '执行记录查询', path: `${pathPrefix}/DevOps引擎/执行记录查询` }
|
||
]
|
||
},
|
||
{
|
||
name: '通知',
|
||
path: `${pathPrefix}/通知`,
|
||
column: 3,
|
||
isMainCategory: true,
|
||
subItems: [
|
||
{ name: '通知简介', path: `${pathPrefix}/通知/通知简介` },
|
||
{ name: '通知设置', path: `${pathPrefix}/通知/通知设置` }
|
||
]
|
||
},
|
||
{
|
||
name: '个人主页建站',
|
||
path: `${pathPrefix}/个人主页建站`,
|
||
column: 3,
|
||
isMainCategory: true,
|
||
subItems: [
|
||
{ name: '建站流程', path: `${pathPrefix}/个人主页建站/站点创建流程` },
|
||
{ name: '建站工具', path: `${pathPrefix}/个人主页建站/建站工具` }
|
||
]
|
||
},
|
||
{
|
||
name: '平台公告',
|
||
path: `${pathPrefix}/平台公告`,
|
||
column: 3,
|
||
isMainCategory: true
|
||
},
|
||
{
|
||
name: '服务协议',
|
||
path: `${pathPrefix}/服务协议`,
|
||
column: 3,
|
||
isMainCategory: true,
|
||
subItems: [
|
||
{ name: 'GitLink服务协议', path: `${pathPrefix}/服务协议/GitLink服务协议` }
|
||
]
|
||
}
|
||
];
|
||
|
||
// 按列分组目录
|
||
// 重新分配各目录到三列,确保各列内容均衡
|
||
const column1 = directories.filter(dir =>
|
||
dir.name === '首页' ||
|
||
dir.name === '视频教程' ||
|
||
dir.name === '快速开始' ||
|
||
dir.name === '代码库管理' ||
|
||
dir.name === '合并请求(PR)'
|
||
);
|
||
|
||
const column2 = directories.filter(dir =>
|
||
dir.name === '疑修(Issue)' ||
|
||
dir.name === '机器人(Bot)' ||
|
||
dir.name === '维基' ||
|
||
dir.name === '第三方服务' ||
|
||
dir.name === '组织管理'
|
||
);
|
||
|
||
const column3 = directories.filter(dir =>
|
||
dir.name === 'DevOps引擎' ||
|
||
dir.name === '通知' ||
|
||
dir.name === '个人主页建站' ||
|
||
dir.name === '平台公告' ||
|
||
dir.name === '服务协议'
|
||
);
|
||
|
||
// 移除列4,改为三列布局
|
||
const column4 = [];
|
||
|
||
// 搜索功能
|
||
// 过滤目录项
|
||
const filterDirectories = (query) => {
|
||
if (!query) return directories;
|
||
|
||
const normalizedQuery = query.toLowerCase().trim();
|
||
|
||
return directories.filter(dir => {
|
||
// 检查主目录名称是否匹配
|
||
const dirNameMatch = dir.name.toLowerCase().includes(normalizedQuery);
|
||
|
||
// 如果主目录匹配,直接返回
|
||
if (dirNameMatch) return true;
|
||
|
||
// 检查子目录是否有匹配
|
||
if (dir.subItems && dir.subItems.length > 0) {
|
||
const hasMatchingSubItem = dir.subItems.some(
|
||
subItem => subItem.name.toLowerCase().includes(normalizedQuery)
|
||
);
|
||
return hasMatchingSubItem;
|
||
}
|
||
|
||
return false;
|
||
});
|
||
};
|
||
|
||
// 获取经过搜索过滤的目录列表
|
||
const filteredDirectories = filterDirectories(searchQuery);
|
||
|
||
// 过滤后的目录分组
|
||
const filteredColumn1 = filteredDirectories.filter(dir => column1.some(c => c.name === dir.name));
|
||
const filteredColumn2 = filteredDirectories.filter(dir => column2.some(c => c.name === dir.name));
|
||
const filteredColumn3 = filteredDirectories.filter(dir => column3.some(c => c.name === dir.name));
|
||
|
||
// 将搜索结果重新分配到各列,实现更均衡的布局
|
||
const distributeSearchResults = (results) => {
|
||
if (!searchQuery) {
|
||
// 不搜索时使用原始布局
|
||
return {
|
||
col1: filteredColumn1,
|
||
col2: filteredColumn2,
|
||
col3: filteredColumn3,
|
||
col4: []
|
||
};
|
||
}
|
||
|
||
// 搜索时根据数量动态分配
|
||
const totalCount = results.length;
|
||
|
||
if (totalCount <= 3) {
|
||
// 结果很少时,全部放在第一列
|
||
return {
|
||
col1: results,
|
||
col2: [],
|
||
col3: [],
|
||
col4: []
|
||
};
|
||
} else if (totalCount <= 6) {
|
||
// 结果较少时,放在两列
|
||
const midPoint = Math.ceil(totalCount / 2);
|
||
return {
|
||
col1: results.slice(0, midPoint),
|
||
col2: results.slice(midPoint),
|
||
col3: [],
|
||
col4: []
|
||
};
|
||
} else {
|
||
// 结果较多时,放在三列
|
||
const colSize = Math.ceil(totalCount / 3);
|
||
return {
|
||
col1: results.slice(0, colSize),
|
||
col2: results.slice(colSize, colSize * 2),
|
||
col3: results.slice(colSize * 2),
|
||
col4: []
|
||
};
|
||
}
|
||
};
|
||
|
||
// 获取重新分配后的各列内容
|
||
const {
|
||
col1: displayColumn1,
|
||
col2: displayColumn2,
|
||
col3: displayColumn3,
|
||
col4: displayColumn4
|
||
} = distributeSearchResults(filteredDirectories);
|
||
|
||
// 处理搜索框变化
|
||
const handleSearchChange = (e) => {
|
||
setSearchQuery(e.target.value);
|
||
};
|
||
|
||
// 清除搜索
|
||
const clearSearch = () => {
|
||
setSearchQuery('');
|
||
if (searchInputRef.current) {
|
||
searchInputRef.current.focus();
|
||
}
|
||
};
|
||
|
||
// 处理点击目录链接
|
||
const handleDirectoryClick = (dir) => {
|
||
// 如果有子目录,则跳转到第一个子目录
|
||
if (dir.subItems && dir.subItems.length > 0) {
|
||
history.push(dir.subItems[0].path);
|
||
} else {
|
||
history.push(dir.path);
|
||
}
|
||
setIsOpen(false);
|
||
};
|
||
|
||
// 点击外部关闭下拉菜单
|
||
useEffect(() => {
|
||
function handleClickOutside(event) {
|
||
if (dropdownRef.current && !dropdownRef.current.contains(event.target)) {
|
||
setIsOpen(false);
|
||
}
|
||
}
|
||
|
||
document.addEventListener('mousedown', handleClickOutside);
|
||
return () => {
|
||
document.removeEventListener('mousedown', handleClickOutside);
|
||
};
|
||
}, []);
|
||
|
||
// 当目录打开时聚焦到搜索框
|
||
useEffect(() => {
|
||
if (isOpen && searchInputRef.current) {
|
||
setTimeout(() => {
|
||
searchInputRef.current.focus();
|
||
}, 100);
|
||
} else {
|
||
setSearchQuery(''); // 关闭目录时清空搜索
|
||
}
|
||
}, [isOpen]);
|
||
|
||
// 渲染目录项
|
||
const renderDirectoryItem = (dir) => {
|
||
// 当有搜索词时,只显示匹配的子项
|
||
let subItems = dir.subItems;
|
||
if (searchQuery && dir.subItems) {
|
||
const normalizedQuery = searchQuery.toLowerCase().trim();
|
||
subItems = dir.subItems.filter(item =>
|
||
item.name.toLowerCase().includes(normalizedQuery)
|
||
);
|
||
}
|
||
|
||
return (
|
||
<div key={dir.name} className={styles.directoryItem}>
|
||
<a
|
||
onClick={() => handleDirectoryClick(dir)}
|
||
className={`${styles.docLink} ${styles.mainCategory}`}
|
||
>
|
||
{dir.name}
|
||
</a>
|
||
|
||
{subItems && subItems.length > 0 && (
|
||
<div className={styles.subItemsContainer}>
|
||
{subItems.map(subItem => (
|
||
<a
|
||
key={subItem.name}
|
||
onClick={() => {
|
||
history.push(subItem.path);
|
||
setIsOpen(false);
|
||
}}
|
||
className={`${styles.docLink} ${styles.subCategory}`}
|
||
>
|
||
{subItem.name}
|
||
</a>
|
||
))}
|
||
</div>
|
||
)}
|
||
</div>
|
||
);
|
||
};
|
||
|
||
return (
|
||
<div className={styles.tocButtonContainer} ref={dropdownRef}>
|
||
<button
|
||
className={styles.tocButton}
|
||
onClick={() => setIsOpen(!isOpen)}
|
||
aria-label="文档目录"
|
||
title="浏览文档目录"
|
||
>
|
||
<span className={styles.tocIcon}>
|
||
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||
<path d="M3 5H21" stroke="currentColor" strokeWidth="2" strokeLinecap="round"/>
|
||
<path d="M3 12H21" stroke="currentColor" strokeWidth="2" strokeLinecap="round"/>
|
||
<path d="M3 19H21" stroke="currentColor" strokeWidth="2" strokeLinecap="round"/>
|
||
</svg>
|
||
</span>
|
||
</button>
|
||
|
||
{isOpen && (
|
||
<div className={styles.tocDropdown}>
|
||
<h2 className={styles.tocTitle}>文档目录</h2>
|
||
|
||
<div className={styles.searchContainer}>
|
||
<input
|
||
ref={searchInputRef}
|
||
type="text"
|
||
value={searchQuery}
|
||
onChange={handleSearchChange}
|
||
placeholder="搜索文档..."
|
||
className={styles.searchInput}
|
||
aria-label="搜索文档"
|
||
/>
|
||
{searchQuery && (
|
||
<button
|
||
className={styles.clearSearchButton}
|
||
onClick={clearSearch}
|
||
aria-label="清除搜索"
|
||
>
|
||
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||
<path d="M18 6L6 18" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"/>
|
||
<path d="M6 6L18 18" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"/>
|
||
</svg>
|
||
</button>
|
||
)}
|
||
</div>
|
||
|
||
<div className={styles.tocContent}>
|
||
{filteredDirectories.length === 0 ? (
|
||
<div className={styles.emptyMessage}>未找到匹配的文档</div>
|
||
) : (
|
||
<div className={styles.multiColumnLayout}>
|
||
<div className={styles.column}>
|
||
{displayColumn1.map(renderDirectoryItem)}
|
||
</div>
|
||
{displayColumn2.length > 0 && (
|
||
<div className={styles.column}>
|
||
{displayColumn2.map(renderDirectoryItem)}
|
||
</div>
|
||
)}
|
||
{displayColumn3.length > 0 && (
|
||
<div className={styles.column}>
|
||
{displayColumn3.map(renderDirectoryItem)}
|
||
</div>
|
||
)}
|
||
</div>
|
||
)}
|
||
</div>
|
||
</div>
|
||
)}
|
||
</div>
|
||
);
|
||
}}
|
||
</BrowserOnly>
|
||
);
|
||
}
|