Merge pull request '测试' (#17) from master into wangjing22e

This commit is contained in:
wangjing22e 2025-05-28 09:05:35 +08:00
commit 862c49c38d
5 changed files with 759 additions and 0 deletions

View File

@ -15,6 +15,16 @@ module.exports = {
projectName: 'docusaurus-luffyzh-website', // Usually your repo name.
scripts: [],
// stylesheets: ['styles/dark-mode.css'],
plugins: [
function (context, options) {
return {
name: 'custom-docusaurus-plugin',
injectHtmlTags() {
return {};
},
};
},
],
themeConfig: {
docs:{
@ -75,6 +85,10 @@ module.exports = {
// position: 'right',
// className: 'header-github-link',
// },
{
type: 'custom-table-of-contents',
position: 'right'
},
{
type: 'custom-pdf-export',
position: 'right'

View File

@ -0,0 +1,464 @@
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>
);
}

View File

@ -0,0 +1,245 @@
.tocButtonContainer {
position: relative;
display: inline-block;
}
/* 搜索框样式 */
.searchContainer {
position: relative;
margin-bottom: 1rem;
width: 100%;
}
.searchInput {
width: 100%;
padding: 0.75rem 2.5rem 0.75rem 1rem;
border-radius: 6px;
border: 1px solid var(--ifm-color-emphasis-300);
background-color: var(--ifm-background-color);
color: var(--ifm-font-color-base);
font-size: 0.95rem;
outline: none;
transition: all 0.2s;
}
.searchInput:focus {
border-color: var(--ifm-color-primary);
box-shadow: 0 0 0 2px rgba(var(--ifm-color-primary-rgb), 0.25);
}
.clearSearchButton {
position: absolute;
right: 10px;
top: 50%;
transform: translateY(-50%);
background: none;
border: none;
padding: 4px;
cursor: pointer;
color: var(--ifm-color-emphasis-500);
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
}
.clearSearchButton:hover {
background-color: var(--ifm-color-emphasis-200);
color: var(--ifm-color-emphasis-700);
}
/* 暗色模式搜索框适配 */
html[data-theme='dark'] .searchInput {
background-color: var(--ifm-background-surface-color);
border-color: var(--ifm-color-emphasis-300);
}
html[data-theme='dark'] .searchInput:focus {
border-color: var(--ifm-color-primary);
}
.tocButton {
display: flex;
align-items: center;
justify-content: center;
background-color: transparent;
border: none;
color: var(--ifm-navbar-link-color);
cursor: pointer;
padding: 0.5rem;
border-radius: 50%;
transition: all 0.2s;
}
.tocButton:hover {
background-color: var(--ifm-color-emphasis-200);
color: var(--ifm-navbar-link-hover-color);
}
.tocIcon {
display: flex;
align-items: center;
}
.tocDropdown {
position: absolute;
top: calc(100% + 5px);
right: 0;
width: 800px;
max-width: 98vw;
max-height: 90vh;
overflow-y: auto;
background-color: var(--ifm-background-surface-color);
border-radius: 8px;
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.16);
z-index: 1000;
padding: 1.5rem;
border: 1px solid var(--ifm-color-emphasis-200);
}
.tocTitle {
font-size: 1.25rem;
margin-top: 0;
margin-bottom: 1rem;
padding-bottom: 0.5rem;
border-bottom: 1px solid var(--ifm-color-emphasis-200);
}
.tocContent {
display: flex;
flex-direction: column;
}
.multiColumnLayout {
display: flex;
flex-direction: row;
gap: 1.5rem;
width: 100%;
align-items: flex-start;
}
.column {
display: flex;
flex-direction: column;
gap: 0.85rem;
flex: 1;
min-width: 160px;
}
.column:nth-child(1),
.column:nth-child(2),
.column:nth-child(3) {
align-self: stretch;
height: 100%;
justify-content: space-between;
}
.column:nth-child(4) {
align-self: flex-start;
}
.directoryItem {
display: flex;
flex-direction: column;
margin-bottom: 0.2rem;
}
.mainCategory {
font-weight: 600;
color: var(--ifm-color-primary);
padding: 0.5rem 0.75rem;
font-size: 1rem;
border-radius: 4px;
}
.subItemsContainer {
display: flex;
flex-direction: column;
padding-left: 1.25rem;
margin-top: 0.15rem;
margin-bottom: 0.25rem;
gap: 0.25rem;
}
.subCategory {
font-weight: normal;
color: var(--ifm-font-color-base);
padding: 0.3rem 0.5rem;
font-size: 0.92rem;
position: relative;
}
.subCategory:before {
content: '';
position: absolute;
left: -8px;
top: 50%;
width: 4px;
height: 4px;
border-radius: 50%;
background-color: var(--ifm-color-emphasis-400);
transform: translateY(-50%);
}
.docLink {
display: block;
text-decoration: none;
cursor: pointer;
transition: all 0.2s;
}
.docLink:hover {
background-color: var(--ifm-color-emphasis-100);
color: var(--ifm-color-primary);
}
.activeDoc .docLink {
background-color: var(--ifm-color-emphasis-100);
color: var(--ifm-color-primary);
font-weight: 500;
}
/* 响应式样式 */
@media (max-width: 768px) {
.tocDropdown {
width: 640px;
right: -8px;
}
}
/* 暗色模式适配 */
html[data-theme='dark'] .tocDropdown {
background-color: var(--ifm-background-surface-color);
border-color: var(--ifm-color-emphasis-300);
}
/* 滚动条样式 */
.tocDropdown::-webkit-scrollbar {
width: 6px;
}
.tocDropdown::-webkit-scrollbar-track {
background: transparent;
}
.tocDropdown::-webkit-scrollbar-thumb {
background-color: var(--ifm-color-emphasis-300);
border-radius: 3px;
}
.tocDropdown::-webkit-scrollbar-thumb:hover {
background-color: var(--ifm-color-emphasis-400);
}
.loadingMessage,
.errorMessage,
.emptyMessage {
padding: 1rem;
text-align: center;
color: var(--ifm-color-emphasis-600);
font-style: italic;
}
.errorMessage {
color: #ff4d4f;
}

View File

@ -0,0 +1,6 @@
import React from 'react';
import TableOfContentsButton from './TableOfContentsButton';
export default function TableOfContentsNavbarItem() {
return <TableOfContentsButton />;
}

View File

@ -0,0 +1,30 @@
import DefaultNavbarItem from '@theme/NavbarItem/DefaultNavbarItem';
import DocNavbarItem from '@theme/NavbarItem/DocNavbarItem';
import DocSidebarNavbarItem from '@theme/NavbarItem/DocSidebarNavbarItem';
import DocsVersionNavbarItem from '@theme/NavbarItem/DocsVersionNavbarItem';
import DocsVersionDropdownNavbarItem from '@theme/NavbarItem/DocsVersionDropdownNavbarItem';
import DropdownNavbarItem from '@theme/NavbarItem/DropdownNavbarItem';
import LocaleDropdownNavbarItem from '@theme/NavbarItem/LocaleDropdownNavbarItem';
import SearchNavbarItem from '@theme/NavbarItem/SearchNavbarItem';
import PdfExportNavbarItem from '../../components/PdfExportNavbarItem';
import PdfBackToTopNavbarItem from '../../components/PdfBackToTopNavbarItem';
import QAButtonNavbarItem from '../../components/QAButtonNavbarItem';
import TableOfContentsNavbarItem from '../../components/TableOfContentsNavbarItem';
const ComponentTypes = {
default: DefaultNavbarItem,
localeDropdown: LocaleDropdownNavbarItem,
search: SearchNavbarItem,
dropdown: DropdownNavbarItem,
docsVersionDropdown: DocsVersionDropdownNavbarItem,
docsVersion: DocsVersionNavbarItem,
doc: DocNavbarItem,
docSidebar: DocSidebarNavbarItem,
'custom-pdf-export': PdfExportNavbarItem,
'custom-back-to-top': PdfBackToTopNavbarItem,
'custom-qa-button': QAButtonNavbarItem,
'custom-table-of-contents': TableOfContentsNavbarItem,
};
export default ComponentTypes;