gitlink_help_center/src/components/TableOfContentsButton.js

464 lines
18 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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>
);
}