diff --git a/.claude/settings.local.json b/.claude/settings.local.json
new file mode 100644
index 0000000..6e01d6e
--- /dev/null
+++ b/.claude/settings.local.json
@@ -0,0 +1,25 @@
+{
+ "permissions": {
+ "allow": [
+ "Bash(npm run:*)",
+ "Bash(npm install:*)",
+ "Bash(netstat -ano)",
+ "Bash(powershell -Command \"Get-NetTCPConnection -LocalPort 3000 -ErrorAction SilentlyContinue | Select-Object LocalPort, State\")",
+ "Bash(findstr \":3000\")",
+ "Bash(findstr \"LISTENING\")",
+ "Bash(git config:*)",
+ "Bash(git fetch:*)",
+ "Bash(git add:*)",
+ "Bash(git commit:*)",
+ "Bash(git push:*)",
+ "Bash(start:*)",
+ "Bash(git remote:*)",
+ "Bash(ssh-keygen:*)",
+ "Bash(ssh -T git@gitlink.org.cn)",
+ "Bash(git branch:*)",
+ "Bash(timeout 5 curl -s http://localhost:3000)",
+ "Bash(timeout 8 curl -s http://localhost:3000)",
+ "Bash(curl -s http://localhost:3000)"
+ ]
+ }
+}
diff --git a/.devops/gitlink-help-autodeploy.yml b/.devops/gitlink-help-autodeploy.yml
new file mode 100644
index 0000000..8174691
--- /dev/null
+++ b/.devops/gitlink-help-autodeploy.yml
@@ -0,0 +1,57 @@
+version: 2
+name: gitlink-help-autodeploy
+description: ""
+global:
+ concurrent: 1
+trigger:
+ webhook: gitlink@1.0.0
+ event:
+ - ref: push
+ ruleset-operator: AND
+workflow:
+ - ref: start
+ name: 开始
+ task: start
+ - ref: git_clone_0
+ name: git clone
+ task: git_clone@1.2.9
+ input:
+ remote_url: '"https://gitlink.org.cn/zzx-coder/gitlink_help_center.git"'
+ ref: '"refs/heads/master"'
+ commit_id: '""'
+ depth: 1
+ needs:
+ - start
+ - ref: docker_image_build_0
+ name: docker镜像构建
+ task: docker_image_build@1.6.0
+ input:
+ docker_username: ((aliyun.aliyun_username))
+ docker_password: ((aliyun.aliyun_password))
+ image_name: '"crpi-1x701qgqjia2gdeu.cn-chengdu.personal.cr.aliyuncs.com/gitlink-help-center/gitlink-help-center"'
+ image_tag: '"latest"'
+ registry_address: '"crpi-1x701qgqjia2gdeu.cn-chengdu.personal.cr.aliyuncs.com"'
+ docker_file: '"Dockerfile"'
+ docker_build_path: '"."'
+ workspace: ((git_clone_0.git_path))
+ image_push: true
+ build_args: '""'
+ needs:
+ - git_clone_0
+ - ref: ssh_cmd_0
+ name: ssh执行命令
+ task: ssh_cmd@1.1.1
+ input:
+ ssh_private_key: ((gitlink_help_center_ssh_key.gitlink_help_center_ssh_key))
+ ssh_ip: '"121.41.222.0"'
+ ssh_port: '"22"'
+ ssh_user: '"root"'
+ ssh_cmd: '"/opt/gitlink-deploy/deploy.sh"'
+ needs:
+ - docker_image_build_0
+ - ref: end
+ name: 结束
+ task: end
+ needs:
+ - ssh_cmd_0
+
diff --git a/Dockerfile b/Dockerfile
index 09b6efe..4d94948 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -1,10 +1,34 @@
-FROM node:18-alpine
+FROM crpi-1x701qgqjia2gdeu.cn-chengdu.personal.cr.aliyuncs.com/gitlink-help-center/gitlink-help-center:node-20-alpine
LABEL maintainer="yuankaifneg <2894340009@qq.com>"
-WORKDIR /gitlink_help_center
+# 设置为root用户以安装系统依赖
+USER root
-COPY ./ /gitlink_help_center/
-
-RUN yarn install
-RUN npm run build -- --locale zh-cn
+# 安装必要的系统依赖
+RUN yum install -y \
+ git \
+ python3 \
+ make \
+ gcc-c++ \
+ bash && \
+ yum clean all
+# 设置工作目录
+WORKDIR /app
+# 优先复制package.json和package-lock.json以利用Docker缓存
+COPY package.json package-lock.json ./
+# 安装依赖(忽略peer依赖冲突)
+RUN npm install --legacy-peer-deps
+# 复制项目文件
+COPY . .
+# 构建应用
+RUN npm run build
+# 暴露端口
+EXPOSE 3000
+# 设置环境变量
+ENV NODE_ENV=production
+ENV PORT=3000
+# 健康检查
+HEALTHCHECK --interval=30s --timeout=3s --start-period=40s --retries=3 \
+ CMD node -e "require('http').get('http://localhost:3000', (r) => {process.exit(r.statusCode === 200 ? 0 : 1)})"
+# 启动应用
CMD ["npm", "run", "serve"]
diff --git a/docs/intro.md b/docs/intro.md
index f8aec7a..ca4c185 100644
--- a/docs/intro.md
+++ b/docs/intro.md
@@ -21,6 +21,28 @@ GitLink(确实开源)是CCF官方指定的开源创新服务平台,旨在
# 帮助文档
帮助文档有助于您全面了解GitLink平台,让我们一起为开源创新贡献力量!
+
+
+
+
+
开始阅读文档后,这里会显示您的学习进度
+
+
+
+ 已阅读: 0
+
+
+ 文档总数: 0
+
+
+
+
+
+
diff --git a/docs/个人主页建站/站点创建流程.md b/docs/个人主页建站/站点创建流程.md
index 7a73db6..292375e 100644
--- a/docs/个人主页建站/站点创建流程.md
+++ b/docs/个人主页建站/站点创建流程.md
@@ -8,39 +8,39 @@ sidebar_position: 1

-点击左测面板中 个人建站 - 我的站点
+点击左侧面板中 个人建站 - 我的站点

### 创建站点
-在我的站点界面点击 新建站点 按钮,进入新建站点界面
+在**我的站点**界面点击**新建站点**按钮,进入新建站点界面
-在新建站点界面输入站点名称,此名称将在我的站点列表中展示
+在新建站点界面输入站点名称,此名称将在**我的站点**的**站点列表**中展示
并且选择对应的建站工具与主题,我们为您提供了3种不同的工具,每个工具10种主题,一共30种供您挑选

-填写完毕后,点击页面下方蓝色按钮: 创建站点
+填写完毕后,点击页面下方蓝色按钮**创建站点**

这样您就拥有了一个网站,并且有了一个代码仓库。
-在仓库的 服务 一栏中 个人建站服务的操作界面,这里您可以查看一些您的站点状态、站点名称、网站地址、建站工具、建站时间
+在仓库的**服务**一栏中**个人建站**服务的操作界面,这里您可以查看一些您的站点状态、站点名称、网站地址、建站工具、建站时间。
### 部署站点

-点击去部署按钮,选择想要部署的分支后点击确定
+点击**去部署**按钮,选择想要部署的分支后点击**确定**

-等待程序运行一会儿后,会返回一些服务器部署信息给您
+等待程序运行一会儿后,会返回一些服务器部署信息给您,部署成功后您就可以访问站点了。
-部署成功后您就可以访问站点了。点击网站地址就可以跳转
+点击网站地址就可以跳转

diff --git a/docs/代码库管理/仓库创建.md b/docs/代码库管理/仓库创建.md
index e17cd76..47a8e8b 100644
--- a/docs/代码库管理/仓库创建.md
+++ b/docs/代码库管理/仓库创建.md
@@ -3,7 +3,7 @@ sidebar_label: '仓库创建'
sidebar_position: 1
---
### **1. 仓库创建入口**
-用户可以通过平台主页右上角的按键以及个人主页参与项目模块的“新建”按键来创建仓库。
+用户可以通过平台主页右上角的按键以及个人主页参与项目模块的“新建”按钮来创建仓库。

### **2. 填写项目基本信息**
diff --git a/docs/代码库管理/成员管理.md b/docs/代码库管理/成员管理.md
index cbf6199..4024432 100644
--- a/docs/代码库管理/成员管理.md
+++ b/docs/代码库管理/成员管理.md
@@ -3,15 +3,15 @@ sidebar_label: '成员管理'
sidebar_position: 7
---
### **1. 成员管理入口**
-在仓库主页,点击“仓库设置”按键,再点击”成员管理“按键,即可进入成员管理模块,如下图所示。
+在仓库主页,点击**仓库设置**按键,再点击**成员管理**按键,即可进入成员管理模块,如下图所示。

### **2. 筛选、搜索项目成员**
-进入成员管理模块后,可以通过”角色筛选“按键来筛选项目成员类型,通过”搜索“按键可以检索具体的项目成员,如下图所示。
+进入成员管理模块后,可以通过**角色筛选**按键来筛选项目成员类型,通过**搜索**按键可以检索具体的项目成员,如下图所示。

-### **3. 筛选、搜索项目成员**
-进入成员管理模块后,可以通过”添加成员“按键来添加项目成员,左侧搜索框检索到具体用户并选中后,单击”添加成员“按键即可成功添加项目成员。
+### **3. 添加成员**
+进入成员管理模块后,可以通过**添加成员**按键来添加项目成员,左侧搜索框检索到具体用户并选中后,单击**添加成员**按键即可成功添加项目成员。

### **4. 项目成员权限管理**
@@ -19,5 +19,5 @@ sidebar_position: 7

### **5. 删除项目成员**
-进入成员管理模块后,单击项目成员右侧的”删除“按键,可以删除改名项目成员,如下图所示。
+进入成员管理模块后,单击项目成员右侧的**删除**按键,可以删除改名项目成员,如下图所示。

diff --git a/docs/合并请求/合并请求关联疑修.md b/docs/合并请求/合并请求关联疑修.md
index ac29313..5051252 100644
--- a/docs/合并请求/合并请求关联疑修.md
+++ b/docs/合并请求/合并请求关联疑修.md
@@ -3,4 +3,6 @@ sidebar_label: '合并请求关联疑修'
sidebar_position: 5
---
-# 合并请求关联疑修
\ No newline at end of file
+# 合并请求关联疑修
+
+该功能还在开发完善中,敬请期待......
\ No newline at end of file
diff --git a/docs/平台公告/平台公告.md b/docs/平台公告/平台公告.md
index 3b451a8..caff2c4 100644
--- a/docs/平台公告/平台公告.md
+++ b/docs/平台公告/平台公告.md
@@ -1,4 +1,12 @@
---
sidebar_label: '平台公告'
sidebar_position: 1
----
\ No newline at end of file
+---
+
+# 平台公告
+
+GitLink帮助中心致力于帮助开发者快速熟悉平台功能,掌握开源协作的正确方法。在这里,您可以找到从账号注册到项目管理的完整指南,也可以通过实际使用中发现平台存在的问题并提出改进建议。我们希望通过文档与社区的共同努力,让GitLink成为更好的开源创新服务平台。如果您在使用过程中遇到任何问题,欢迎在平台中提交疑修(Issue)获得帮助。
+
+帮助中心项目地址是:https://gitlink.org.cn/Gitlink/gitlink_help_center/
+
+开发者可以先复刻帮助中心项目,然后在本地进行开发。
\ No newline at end of file
diff --git a/docs/第三方服务/跨平台代码同步.md b/docs/第三方服务/跨平台代码同步.md
index c63abac..7e9d875 100644
--- a/docs/第三方服务/跨平台代码同步.md
+++ b/docs/第三方服务/跨平台代码同步.md
@@ -33,7 +33,7 @@ Gitee配置方式为:个人头像→设置→私人令牌→生成新令牌→

-②访问点击前往按钮前往目标仓库webhook页新建一个webhook,以github为例
+②访问“点击前往”按钮前往目标仓库webhook页新建一个webhook,以github为例

diff --git a/docs/组织管理/组织创建及设置.md b/docs/组织管理/组织创建及设置.md
index b39a71b..fdb270b 100644
--- a/docs/组织管理/组织创建及设置.md
+++ b/docs/组织管理/组织创建及设置.md
@@ -2,13 +2,13 @@
sidebar_label: '组织创建及设置'
sidebar_position: 2
---
-# 组织创建(Organization Creation)
+# 组织创建
- 在 *https://www.gitlink.org.cn* 页面点击顶部导航栏的“+”符号可以进行组织新建操作。
+ 在 https://www.gitlink.org.cn 页面点击顶部导航栏的“+”符号可以进行组织新建操作。

- 在新建页面中输入**组织账号**、**组织名称**、**组织描述**、**所在地区**、**可见性**以及**组织头像**等信息后,点击“**创建组织**”按钮完成组织的创建。
+ 在新建页面中输入组织账号、组织名称、组织描述、所在地区、可见性以及组织头像等信息后,点击**创建组织**按钮完成组织的创建。

diff --git a/docs/组织管理/组织团队管理.md b/docs/组织管理/组织团队管理.md
index ce0c356..ab8dd1a 100644
--- a/docs/组织管理/组织团队管理.md
+++ b/docs/组织管理/组织团队管理.md
@@ -7,7 +7,7 @@ sidebar_position: 3
## 创建组织团队
点击组织信息页面中的“新建团队”按钮可以创建属于该组织的团队(平台默认创建“Owners”团队,成员为组织的创建者)。
-在团队新建页面,输入团队标识、团队名称、团队描述、项目权限以及版本库权限等信息后,点击“新建团队”完成团队的创建。
+在团队新建页面,输入团队标识、团队名称、团队描述、项目权限以及版本库权限等信息后,点击 **新建团队** 按钮完成团队的创建。

@@ -19,7 +19,7 @@ sidebar_position: 3
## 管理组织团队
-点击团队信息页面中的“团队设置”按钮可以对团队进行管理
+点击团队信息页面中的 **团队设置** 按钮可以对团队进行管理。
- 基本设置:修改项目的基本信息,如名称和描述等。

diff --git a/docs/组织管理/组织成员管理.md b/docs/组织管理/组织成员管理.md
index 8c78b2b..0118466 100644
--- a/docs/组织管理/组织成员管理.md
+++ b/docs/组织管理/组织成员管理.md
@@ -2,9 +2,9 @@
sidebar_label: '组织成员管理'
sidebar_position: 4
---
-# 成员管理(Members Management)
+# 成员管理
- 在 个人所管理的项目当中的**仓库设置**当中的**成员管理**可以进入成员管理界面
+ 在个人所管理的项目当中的**仓库设置**当中的**成员管理**可以进入成员管理界面

diff --git a/docs/维基/模板导入及导出.md b/docs/维基/模板导入及导出.md
index 8245753..3182e17 100644
--- a/docs/维基/模板导入及导出.md
+++ b/docs/维基/模板导入及导出.md
@@ -3,9 +3,7 @@ sidebar_label: '模板导入及导出'
sidebar_position: 2
---
-# 确实开源帮助中心
-
-## 模板的导入及导出管理
+# 模板导入及导出
#### 1.模板导入
通过点击wiki界面的**导入模板**按钮即可导入本地的txt,markdown等格式文件进行Wiki文档的创建,当前平台此项功能尚在开发测试环节
[](/img/wiki/img4.png)
diff --git a/docs/维基/维基页面管理.md b/docs/维基/维基页面管理.md
index 976a092..bd45a88 100644
--- a/docs/维基/维基页面管理.md
+++ b/docs/维基/维基页面管理.md
@@ -4,20 +4,21 @@ sidebar_position: 1
---
-# 确实开源帮助中心
+# 维基页面管理
-## 维基界面管理
#### 1.维基界面
-GitLink项目安排了专门的**维基(Wiki)界面**来展示项目的一些情况
-[](/img/wiki/img1.png)
+GitLink项目安排了专门的**维基(Wiki)界面**来展示项目的一些情况。
+
#### 2.创建维基内容
-在初始化的维基界面当中点击**创建Wiki文档**或者**导入模板**即可进行Wiki文档的编辑
+在初始化的维基界面当中点击**创建Wiki文档**或者**导入模板**即可进行Wiki文档的编辑。
+
+
#### 3.进行Wiki文档的编辑
点击**创建Wiki文档**后即跳转到编辑界面,初始内容为空白的文档
-[](/img/wiki/img3.png)
-也可以通过勾选*添加模板*来进行模板的导入,平台提供的模板包括*周报*和*月报*的格式
-[](/img/wiki/img2.png)
-完成编辑后点击左下角的的保存即可
\ No newline at end of file
+
+也可以通过勾选**添加模板**来进行模板的导入,平台提供的模板包括*周报*和*月报*的格式
+
+完成编辑后点击左下角的的**保存**即可
\ No newline at end of file
diff --git a/docs/通知/通知简介.md b/docs/通知/通知简介.md
index 278389b..83130ba 100644
--- a/docs/通知/通知简介.md
+++ b/docs/通知/通知简介.md
@@ -3,9 +3,8 @@ sidebar_label: '通知简介'
sidebar_position: 1
---
-# 确实开源帮助中心
+# 通知简介
-## 通知简介
#### 1.通知类型
GitLink将通知分为“系统通知”和“@我”的两种类型:
* 系统通知包括“我的状态”、“我创建的或负责的”、“我管理的仓库”三种类型的通知:
@@ -25,5 +24,5 @@ GitLink将通知分为“系统通知”和“@我”的两种类型:
-点击通知按钮可以进入消息通知界面,“我的通知”界面可以进行“进查看未读消息”和“所有消息一键已读”的选择。
+点击“通知”按钮可以进入消息通知界面,“我的通知”界面可以进行“仅看未读消息”和“所有系统通知一键已读”的选择。

\ No newline at end of file
diff --git a/docs/通知/通知设置.md b/docs/通知/通知设置.md
index bfde209..b50a036 100644
--- a/docs/通知/通知设置.md
+++ b/docs/通知/通知设置.md
@@ -3,19 +3,16 @@ sidebar_label: '通知设置'
sidebar_position: 2
---
-
-# 确实开源帮助中心
-
-## 通知设置
+# 通知设置
#### 1.进入通知设置界面
-通过点击首页的通知按钮进入通知设置界面;
+通过点击首页的通知按钮进入通知设置界面

-或在头像下拉列表中选择设置可以进入消息通知设置界面;
+或在头像下拉列表中选择设置可以进入消息通知设置界面

#### 2.进行通知设置
-通过“通知管理”可以对接受通知的方式进行设置,默认所有通知都是通过站内信的方式接受,可以通过勾选为重要的通知类型增加邮件接受方式。
+通过**通知管理**可以对接受通知的方式进行设置,默认所有通知都是通过站内信的方式接受,可以通过勾选为重要的通知类型增加邮件接受方式。

\ No newline at end of file
diff --git a/docusaurus.config.js b/docusaurus.config.js
index 424ef17..ac01568 100644
--- a/docusaurus.config.js
+++ b/docusaurus.config.js
@@ -3,7 +3,6 @@ const darkCodeTheme = require('prism-react-renderer/themes/dracula');
//Docusaurus v3写法
// const { themes } = require('prism-react-renderer');
// import { themes } from 'prism-react-renderer';
-import("@easyops-cn/docusaurus-search-local").PluginOptions;
/** @type {import('@docusaurus/types').DocusaurusConfig} */
module.exports = {
@@ -16,7 +15,13 @@ module.exports = {
favicon: 'img/icon.ico',
organizationName: 'luffyZh', // Usually your GitHub org/user name.
projectName: 'docusaurus-luffyzh-website', // Usually your repo name.
- scripts: [],
+ scripts: [
+ {
+ src: '/js/reading-progress.js',
+ async: true,
+ defer: true,
+ },
+ ],
// stylesheets: ['styles/dark-mode.css'],
themeConfig: {
docs:{
@@ -185,22 +190,23 @@ module.exports = {
],
themes: [
// ... Your other themes.
- [
- require.resolve("@easyops-cn/docusaurus-search-local"),
- {
- // ... Your options.
- // `hashed` is recommended as long-term-cache of index file is possible.
- hashed: true,
- language: ["en", "zh"],
- highlightSearchTermsOnTargetPage: true,
- blogRouteBasePath: "/",
- explicitSearchResultPath: true,
- // For Docs using Chinese, The `language` is recommended to set to:
- // ```
- // language: ["en", "zh"],
- // ```
- },
- ],
+ // Local search plugin temporarily disabled due to compatibility issues with Docusaurus 3.9.2
+ // [
+ // require.resolve("@easyops-cn/docusaurus-search-local"),
+ // {
+ // // ... Your options.
+ // // `hashed` is recommended as long-term-cache of index file is possible.
+ // hashed: true,
+ // language: ["en", "zh"],
+ // highlightSearchTermsOnTargetPage: true,
+ // blogRouteBasePath: "/",
+ // explicitSearchResultPath: true,
+ // // For Docs using Chinese, The `language` is recommended to set to:
+ // // ```
+ // // language: ["en", "zh"],
+ // // ```
+ // },
+ // ],
],
i18n: {
defaultLocale: 'zh-cn',
diff --git a/gitlink_help_center b/gitlink_help_center
new file mode 160000
index 0000000..2936750
--- /dev/null
+++ b/gitlink_help_center
@@ -0,0 +1 @@
+Subproject commit 293675001acfe7c74caa0bcf78765ec29034c715
diff --git a/package-lock.json b/package-lock.json
index d3f4f45..e919ab2 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,12 +1,12 @@
{
"name": "docusaurus",
- "version": "0.0.0",
+ "version": "3.0.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "docusaurus",
- "version": "0.0.0",
+ "version": "3.0.0",
"dependencies": {
"@cmfcmf/docusaurus-search-local": "1.1.0",
"@docusaurus/core": "3.9.2",
@@ -26,7 +26,7 @@
"react-modal": "3.16.1"
},
"engines": {
- "node": ">=14.18.1 <=18.x"
+ "node": ">=18.x"
}
},
"node_modules/@algolia/abtesting": {
@@ -19901,13 +19901,6 @@
"url": "https://opencollective.com/webpack"
}
},
- "node_modules/search-insights": {
- "version": "2.17.3",
- "resolved": "https://registry.npmjs.org/search-insights/-/search-insights-2.17.3.tgz",
- "integrity": "sha512-RQPdCYTa8A68uM2jwxoY842xDhvx3E5LFL1LxvxCNMev4o5mLuokczhzjAgGwUZBAmOKZknArSxLKmXtIi2AxQ==",
- "license": "MIT",
- "peer": true
- },
"node_modules/section-matter": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/section-matter/-/section-matter-1.0.0.tgz",
@@ -20994,20 +20987,6 @@
"is-typedarray": "^1.0.0"
}
},
- "node_modules/typescript": {
- "version": "5.9.3",
- "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz",
- "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==",
- "license": "Apache-2.0",
- "peer": true,
- "bin": {
- "tsc": "bin/tsc",
- "tsserver": "bin/tsserver"
- },
- "engines": {
- "node": ">=14.17"
- }
- },
"node_modules/undici": {
"version": "7.22.0",
"resolved": "https://registry.npmjs.org/undici/-/undici-7.22.0.tgz",
diff --git a/sidebars.js b/sidebars.js
index 889a90e..a055208 100644
--- a/sidebars.js
+++ b/sidebars.js
@@ -1,9 +1,8 @@
module.exports = {
- DocsSidebar: [
+ docs: [
{
- type: 'doc',
- id: 'index',
- label: 'Docs',
- }
+ type: 'autogenerated',
+ dirName: '.', // 自动生成 docs/ 下所有文档的侧边栏
+ },
],
-};
+};
\ No newline at end of file
diff --git a/src/components/VoteButtons.js b/src/components/VoteButtons.js
new file mode 100644
index 0000000..8fd610d
--- /dev/null
+++ b/src/components/VoteButtons.js
@@ -0,0 +1,151 @@
+import { useState, useEffect } from 'react';
+
+const GITEE_API = 'https://gitee.com/api/v5/repos/camelliamc/gitlink-feedback';
+const TOKEN = 'f8433b20f2a07a28cf95aa0e17edfac4';
+
+const getDocTitle = (path) => {
+ const cleanPath = path.replace(/^\//, '');
+ return `Vote_${cleanPath.replace(/\//g, '_')}`;
+};
+
+export default function VoteButtons() {
+ const [upvotes, setUpvotes] = useState(0);
+ const [downvotes, setDownvotes] = useState(0);
+ const [userVote, setUserVote] = useState(null); // null | 'up' | 'down'
+ const [docPath, setDocPath] = useState('');
+ const [loading, setLoading] = useState(false);
+
+ useEffect(() => {
+ const path = window.location.pathname;
+ setDocPath(path);
+ const stored = localStorage.getItem(`vote_${path}`);
+ if (stored === 'up' || stored === 'down') {
+ setUserVote(stored);
+ }
+ fetchVotes(path);
+ }, []);
+
+ const fetchVotes = async (path) => {
+ try {
+ const res = await fetch(`${GITEE_API}/issues?access_token=${TOKEN}&state=open&labels=vote`);
+ const data = await res.json();
+ const title = getDocTitle(path);
+ const issue = data.find(i => i.title === title);
+
+ if (issue) {
+ const labels = issue.labels || [];
+ const upLabel = labels.find(l => l.name.startsWith('up:'));
+ const downLabel = labels.find(l => l.name.startsWith('down:'));
+ if (upLabel) setUpvotes(parseInt(upLabel.name.split(':')[1]) || 0);
+ if (downLabel) setDownvotes(parseInt(downLabel.name.split(':')[1]) || 0);
+ }
+ } catch (e) {
+ console.error('获取投票失败:', e);
+ }
+ };
+
+ const updateGiteeIssue = async (newUp, newDown) => {
+ const title = getDocTitle(docPath);
+ const res = await fetch(`${GITEE_API}/issues?access_token=${TOKEN}&state=open&labels=vote`);
+ const data = await res.json();
+ const existingIssue = data.find(i => i.title === title);
+ const labels = [`vote`, `up:${newUp}`, `down:${newDown}`];
+
+ if (existingIssue) {
+ await fetch(`${GITEE_API}/issues/${existingIssue.number}?access_token=${TOKEN}`, {
+ method: 'PATCH',
+ headers: { 'Content-Type': 'application/json' },
+ body: JSON.stringify({ labels })
+ });
+ } else {
+ await fetch(`${GITEE_API}/issues?access_token=${TOKEN}`, {
+ method: 'POST',
+ headers: { 'Content-Type': 'application/json' },
+ body: JSON.stringify({
+ title,
+ body: `文档投票 - ${docPath}`,
+ labels
+ })
+ });
+ }
+ };
+
+ const handleVote = async (type) => {
+ if (loading || !docPath) return;
+ setLoading(true);
+
+ try {
+ let newUp = upvotes;
+ let newDown = downvotes;
+ let newVote = null;
+
+ if (userVote === null) {
+ // 未投票 → 投票
+ if (type === 'up') {
+ newUp = upvotes + 1;
+ } else {
+ newDown = downvotes + 1;
+ }
+ newVote = type;
+ } else if (userVote === type) {
+ // 点击已选中的 → 取消投票
+ if (type === 'up') {
+ newUp = Math.max(0, upvotes - 1);
+ } else {
+ newDown = Math.max(0, downvotes - 1);
+ }
+ newVote = null;
+ } else {
+ // 切换投票
+ if (type === 'up') {
+ newUp = upvotes + 1;
+ newDown = Math.max(0, downvotes - 1);
+ } else {
+ newDown = downvotes + 1;
+ newUp = Math.max(0, upvotes - 1);
+ }
+ newVote = type;
+ }
+
+ await updateGiteeIssue(newUp, newDown);
+
+ setUpvotes(newUp);
+ setDownvotes(newDown);
+ setUserVote(newVote);
+
+ if (newVote) {
+ localStorage.setItem(`vote_${docPath}`, newVote);
+ } else {
+ localStorage.removeItem(`vote_${docPath}`);
+ }
+ } catch (e) {
+ console.error('投票失败:', e);
+ } finally {
+ setLoading(false);
+ }
+ };
+
+ return (
+
+
这篇文档有帮助吗?
+
+
+
+
+
+ );
+}
\ No newline at end of file
diff --git a/src/css/custom.css b/src/css/custom.css
index 01ab68e..0074db9 100644
--- a/src/css/custom.css
+++ b/src/css/custom.css
@@ -148,4 +148,317 @@ html[data-theme='dark'] .docusaurus-highlight-code-line {
.widget{
display: none!important;
opacity: 0;
-}
\ No newline at end of file
+}
+/* 帮助文档卡片背景 */
+.cardContainer {
+ background-color: #e6f0ff !important;
+ border-radius: 12px;
+ transition: all 0.3s ease;
+}
+
+.cardContainer:hover {
+ background-color: #cce0ff !important;
+ box-shadow: 0 4px 12px rgba(70, 106, 255, 0.15);
+}
+/* 阅读进度条 */
+.reading-progress {
+ position: fixed;
+ top: 0;
+ left: 0;
+ width: 100%;
+ height: 4px;
+ background-color: rgba(70, 106, 255, 0.1);
+ z-index: 9999;
+}
+
+.reading-progress__bar {
+ height: 100%;
+ background: linear-gradient(90deg, #466aff, #6b8cff);
+ transition: width 0.15s ease-out;
+}
+
+/* 侧边栏完成标记 */
+.sidebar-completed-badge {
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ width: 16px;
+ height: 16px;
+ background-color: #22c55e;
+ color: white;
+ border-radius: 50%;
+ font-size: 10px;
+ font-weight: bold;
+ flex-shrink: 0;
+}
+
+/* 进度面板 */
+.progress-panel {
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
+ padding: 16px;
+ border-radius: 12px;
+ color: white;
+ margin-bottom: 16px;
+}
+
+.progress-panel__title {
+ font-size: 14px;
+ font-weight: 600;
+ margin-bottom: 8px;
+}
+
+.progress-panel__bar {
+ height: 8px;
+ background-color: rgba(255, 255, 255, 0.3);
+ border-radius: 4px;
+ overflow: hidden;
+ margin-bottom: 8px;
+}
+
+.progress-panel__fill {
+ height: 100%;
+ background-color: #22c55e;
+ border-radius: 4px;
+ transition: width 0.3s ease;
+}
+
+.progress-panel__text {
+ font-size: 12px;
+ opacity: 0.9;
+}
+/* ===== 文档投票组件样式 ===== */
+.vote-container {
+ display: flex;
+ align-items: center;
+ justify-content: flex-end;
+ gap: 1.2rem;
+ padding: 1.5rem 0 0.5rem 0;
+ margin-top: 1.5rem;
+ border-top: 1px solid var(--ifm-toc-border-color);
+}
+
+.vote-label {
+ font-weight: 500;
+ color: var(--ifm-color-emphasis-600);
+ font-size: 0.95rem;
+ white-space: nowrap;
+}
+
+.vote-buttons {
+ display: flex;
+ align-items: center;
+ gap: 0.6rem;
+}
+
+.vote-btn {
+ display: flex;
+ align-items: center;
+ gap: 0.35rem;
+ padding: 0.4rem 0.85rem;
+ border: none;
+ border-radius: 20px;
+ background: var(--ifm-color-emphasis-100);
+ cursor: pointer;
+ font-size: 1rem;
+ transition: all 0.2s ease;
+ user-select: none;
+ outline: none;
+}
+
+.vote-btn:hover {
+ background: var(--ifm-color-emphasis-200);
+ transform: scale(1.05);
+}
+
+.vote-btn:active {
+ transform: scale(0.97);
+}
+
+.vote-emoji {
+ font-size: 1.25rem;
+ line-height: 1;
+}
+
+.vote-count {
+ color: var(--ifm-color-emphasis-700);
+ font-weight: 600;
+ font-size: 0.95rem;
+ min-width: 1ch;
+ text-align: center;
+}
+
+/* 👍 高亮状态 */
+.vote-btn-active-up {
+ background: #e6f0ff;
+ box-shadow: 0 0 0 2px rgba(70, 106, 255, 0.25);
+}
+
+.vote-btn-active-up:hover {
+ background: #d6e4ff;
+}
+
+.vote-btn-active-up .vote-count {
+ color: #466aff;
+}
+
+/* 👎 高亮状态 */
+.vote-btn-active-down {
+ background: #fff0e6;
+ box-shadow: 0 0 0 2px rgba(255, 102, 0, 0.25);
+}
+
+.vote-btn-active-down:hover {
+ background: #ffe4d0;
+}
+
+.vote-btn-active-down .vote-count {
+ color: #ff6600;
+}
+
+/* 暗色模式适配 */
+html[data-theme='dark'] .vote-btn {
+ background: var(--ifm-color-emphasis-200);
+}
+
+html[data-theme='dark'] .vote-btn:hover {
+ background: var(--ifm-color-emphasis-300);
+}
+
+html[data-theme='dark'] .vote-count {
+ color: var(--ifm-color-emphasis-600);
+}
+
+html[data-theme='dark'] .vote-btn-active-up {
+ background: rgba(70, 106, 255, 0.2);
+ box-shadow: 0 0 0 2px rgba(70, 106, 255, 0.35);
+}
+
+html[data-theme='dark'] .vote-btn-active-up:hover {
+ background: rgba(70, 106, 255, 0.3);
+}
+
+html[data-theme='dark'] .vote-btn-active-up .vote-count {
+ color: #8aa3ff;
+}
+
+html[data-theme='dark'] .vote-btn-active-down {
+ background: rgba(255, 102, 0, 0.15);
+ box-shadow: 0 0 0 2px rgba(255, 102, 0, 0.3);
+}
+
+html[data-theme='dark'] .vote-btn-active-down:hover {
+ background: rgba(255, 102, 0, 0.25);
+}
+
+html[data-theme='dark'] .vote-btn-active-down .vote-count {
+ color: #ff9944;
+}
+
+/* 学习进度提示框样式 */
+.progress-notice {
+ margin: 24px 0;
+}
+
+.progress-card {
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
+ border-radius: 12px;
+ padding: 20px;
+ color: white;
+ box-shadow: 0 4px 15px rgba(102, 126, 170, 0.3);
+ transition: transform 0.3s ease, box-shadow 0.3s ease;
+}
+
+.progress-card:hover {
+ transform: translateY(-2px);
+ box-shadow: 0 6px 20px rgba(102, 126, 170, 0.4);
+}
+
+.progress-header h3 {
+ margin: 0;
+ font-size: 1.2rem;
+ font-weight: 600;
+ margin-bottom: 15px;
+}
+
+.progress-content {
+ display: flex;
+ flex-direction: column;
+ gap: 12px;
+}
+
+.progress-content p {
+ margin: 0;
+ font-size: 0.9rem;
+ opacity: 0.95;
+}
+
+.progress-bar {
+ height: 8px;
+ background-color: rgba(255, 255, 255, 0.3);
+ border-radius: 4px;
+ overflow: hidden;
+}
+
+.progress-fill {
+ height: 100%;
+ background: linear-gradient(90deg, #00d2ff 0%, #3a7bd5 100%);
+ border-radius: 4px;
+ width: 0%;
+ transition: width 0.5s ease-in-out;
+}
+
+.progress-stats {
+ display: flex;
+ justify-content: space-around;
+ padding: 12px 0;
+ background: rgba(255, 255, 255, 0.1);
+ border-radius: 8px;
+}
+
+.progress-stat {
+ text-align: center;
+}
+
+.progress-stat span {
+ font-size: 0.9rem;
+ opacity: 0.95;
+ display: block;
+}
+
+.progress-stat strong {
+ font-size: 1.3rem;
+ font-weight: 700;
+}
+
+/* 深色模式适配 */
+html[data-theme='dark'] .progress-card {
+ background: linear-gradient(135deg, #0f2027 0%, #203a43 100%);
+ border: 1px solid rgba(255, 255, 255, 0.1);
+}
+
+html[data-theme='dark'] .progress-fill {
+ background: linear-gradient(90deg, #00d2ff 0%, #3a7bd5 100%);
+}
+
+/* 响应式设计 */
+@media (max-width: 768px) {
+ .progress-card {
+ padding: 15px;
+ }
+
+ .progress-header h3 {
+ font-size: 1rem;
+ }
+
+ .progress-stats {
+ padding: 8px 0;
+ }
+
+ .progress-stat span {
+ font-size: 0.8rem;
+ }
+
+ .progress-stat strong {
+ font-size: 1.1rem;
+ }
+}
diff --git a/src/theme/DocItem/Layout/index.js b/src/theme/DocItem/Layout/index.js
new file mode 100644
index 0000000..e738b64
--- /dev/null
+++ b/src/theme/DocItem/Layout/index.js
@@ -0,0 +1,57 @@
+import React from 'react';
+import clsx from 'clsx';
+import {useWindowSize} from '@docusaurus/theme-common';
+import {useDoc} from '@docusaurus/plugin-content-docs/client';
+import DocItemPaginator from '@theme/DocItem/Paginator';
+import DocVersionBanner from '@theme/DocVersionBanner';
+import DocVersionBadge from '@theme/DocVersionBadge';
+import DocItemFooter from '@theme/DocItem/Footer';
+import DocItemTOCMobile from '@theme/DocItem/TOC/Mobile';
+import DocItemTOCDesktop from '@theme/DocItem/TOC/Desktop';
+import DocItemContent from '@theme/DocItem/Content';
+import DocBreadcrumbs from '@theme/DocBreadcrumbs';
+import ContentVisibility from '@theme/ContentVisibility';
+import VoteButtons from '@site/src/components/VoteButtons';
+import styles from './styles.module.css';
+
+function useDocTOC() {
+ const {frontMatter, toc} = useDoc();
+ const windowSize = useWindowSize();
+ const hidden = frontMatter.hide_table_of_contents;
+ const canRender = !hidden && toc.length > 0;
+ const mobile = canRender ? : undefined;
+ const desktop =
+ canRender && (windowSize === 'desktop' || windowSize === 'ssr') ? (
+
+ ) : undefined;
+ return {
+ hidden,
+ mobile,
+ desktop,
+ };
+}
+
+export default function DocItemLayout({children}) {
+ const docTOC = useDocTOC();
+ const {metadata} = useDoc();
+ return (
+
+
+
+
+
+
+
+
+ {docTOC.mobile}
+ {children}
+
+
+
+
+
+
+ {docTOC.desktop &&
{docTOC.desktop}
}
+
+ );
+}
\ No newline at end of file
diff --git a/src/theme/DocItem/Layout/styles.module.css b/src/theme/DocItem/Layout/styles.module.css
new file mode 100644
index 0000000..37252fa
--- /dev/null
+++ b/src/theme/DocItem/Layout/styles.module.css
@@ -0,0 +1,10 @@
+.docItemContainer header + *,
+.docItemContainer article > *:first-child {
+ margin-top: 0;
+}
+
+@media (min-width: 997px) {
+ .docItemCol {
+ max-width: 75% !important;
+ }
+}
\ No newline at end of file
diff --git a/src/theme/Footer/Layout/index.js b/src/theme/Footer/Layout/index.js
new file mode 100644
index 0000000..6a67523
--- /dev/null
+++ b/src/theme/Footer/Layout/index.js
@@ -0,0 +1,11 @@
+import OriginalFooter from '@docusaurus/theme-classic/lib/theme/Footer/Layout';
+import VoteButtons from '@site/src/components/VoteButtons';
+
+export default function Layout(props) {
+ return (
+ <>
+
+
+ >
+ );
+}
\ No newline at end of file
diff --git a/static/js/reading-progress.js b/static/js/reading-progress.js
new file mode 100644
index 0000000..8b3e62c
--- /dev/null
+++ b/static/js/reading-progress.js
@@ -0,0 +1,271 @@
+(function() {
+ 'use strict';
+
+ const STORAGE_KEY = 'gitlink_reading_progress';
+ const BADGE_CLASS = 'reading-progress-badge';
+
+ function getReadDocs() {
+ try {
+ const data = localStorage.getItem(STORAGE_KEY);
+ return data ? JSON.parse(data) : [];
+ } catch (e) {
+ console.error('读取进度失败:', e);
+ return [];
+ }
+ }
+
+ function saveReadDocs(docs) {
+ try {
+ localStorage.setItem(STORAGE_KEY, JSON.stringify(docs));
+ } catch (e) {
+ console.error('保存进度失败:', e);
+ }
+ }
+
+ function normalizePath(path) {
+ if (!path) return '/';
+
+ // URL 解码(处理中文路径)
+ try {
+ path = decodeURIComponent(path);
+ } catch (e) {
+ // 如果解码失败,保持原样
+ }
+
+ path = path.replace(/^\/+/, '').replace(/\/+$/, '');
+ return '/' + path;
+ }
+
+ function markAsRead() {
+ const currentPath = normalizePath(window.location.pathname);
+ const readDocs = getReadDocs();
+
+ if (!readDocs.includes(currentPath)) {
+ readDocs.push(currentPath);
+ saveReadDocs(readDocs);
+ console.log('✅ 已标记为已阅读:', currentPath);
+ updateBadges();
+ } else {
+ console.log('ℹ️ 已经标记过了:', currentPath);
+ }
+ }
+
+ function isDocRead(path) {
+ const readDocs = getReadDocs();
+ const normalizedPath = normalizePath(path);
+ return readDocs.some(docPath => normalizePath(docPath) === normalizedPath);
+ }
+
+ function createBadge() {
+ const badge = document.createElement('span');
+ badge.className = BADGE_CLASS;
+ badge.textContent = '✓';
+ badge.style.cssText = `
+ display: inline-flex !important;
+ align-items: center !important;
+ justify-content: center !important;
+ width: 18px !important;
+ height: 18px !important;
+ background-color: #22c55e !important;
+ color: white !important;
+ border-radius: 50% !important;
+ font-size: 11px !important;
+ font-weight: bold !important;
+ flex-shrink: 0 !important;
+ margin-left: 8px !important;
+ visibility: visible !important;
+ opacity: 1 !important;
+ position: relative !important;
+ `;
+ return badge;
+ }
+
+ function updateBadges() {
+ const readDocs = getReadDocs();
+ console.log('📚 已阅读的文档数:', readDocs.length, readDocs);
+
+ if (readDocs.length === 0) {
+ console.log('⚠️ 还没有已阅读的文档');
+ return;
+ }
+
+ // 清除所有旧标记
+ document.querySelectorAll('.' + BADGE_CLASS).forEach(badge => badge.remove());
+
+ // 获取所有可能的链接元素
+ const selectors = [
+ 'a.menu__link',
+ 'a.menu-link',
+ '.menu a[href^="/"]',
+ '.sidebar a[href^="/"]',
+ 'nav a[href^="/"]',
+ '[class*="sidebar"] a[href^="/"]',
+ '[class*="menu"] a[href^="/"]'
+ ];
+
+ let totalLinks = 0;
+ let matchedLinks = 0;
+
+ selectors.forEach(selector => {
+ const links = document.querySelectorAll(selector);
+ links.forEach(link => {
+ totalLinks++;
+ const href = link.getAttribute('href');
+
+ if (href && !href.startsWith('http') && !href.startsWith('#') && href !== '/') {
+ const linkPath = normalizePath(href);
+
+ if (isDocRead(linkPath)) {
+ matchedLinks++;
+ if (!link.querySelector('.' + BADGE_CLASS)) {
+ const badge = createBadge();
+ link.appendChild(badge);
+ console.log('✓ 添加标记:', linkPath, '->', link.textContent.trim().substring(0, 30));
+ }
+ }
+ }
+ });
+ });
+
+ console.log(`🔍 检查了 ${totalLinks} 个链接,匹配了 ${matchedLinks} 个已阅读文档`);
+ }
+
+ function initProgressBar() {
+ if (document.querySelector('[data-reading-progress-bar]')) {
+ return;
+ }
+
+ const progressBar = document.createElement('div');
+ progressBar.setAttribute('data-reading-progress-bar', 'true');
+ progressBar.style.cssText = `
+ position: fixed !important;
+ top: 0 !important;
+ left: 0 !important;
+ width: 100% !important;
+ height: 4px !important;
+ background-color: rgba(70, 106, 255, 0.1) !important;
+ z-index: 9999 !important;
+ pointer-events: none !important;
+ `;
+
+ const bar = document.createElement('div');
+ bar.style.cssText = `
+ height: 100% !important;
+ background: linear-gradient(90deg, #466aff, #6b8cff) !important;
+ width: 0% !important;
+ transition: width 0.15s ease-out !important;
+ `;
+
+ progressBar.appendChild(bar);
+ document.body.appendChild(progressBar);
+
+ let ticking = false;
+ window.addEventListener('scroll', () => {
+ if (!ticking) {
+ requestAnimationFrame(() => {
+ const windowHeight = window.innerHeight;
+ const documentHeight = document.documentElement.scrollHeight - windowHeight;
+ const scrolled = window.scrollY;
+ const progress = (scrolled / documentHeight) * 100;
+ bar.style.width = Math.min(progress, 100) + '%';
+ ticking = false;
+ });
+ ticking = true;
+ }
+ });
+ }
+
+ function initScrollDetection() {
+ let hasMarked = false;
+
+ function checkScroll() {
+ const scrollPosition = window.innerHeight + window.scrollY;
+ const pageHeight = document.documentElement.scrollHeight;
+ const threshold = 200;
+ const progress = Math.min((scrollPosition / pageHeight) * 100, 100);
+
+ if (!hasMarked && (progress >= 90 || (pageHeight - scrollPosition < threshold))) {
+ hasMarked = true;
+ console.log('📖 滚动进度: ' + Math.round(progress) + '%,标记为已阅读');
+ markAsRead();
+ setTimeout(() => { hasMarked = false; }, 3000);
+ }
+ }
+
+ window.addEventListener('scroll', () => checkScroll());
+ setTimeout(checkScroll, 1000);
+ }
+
+ function init() {
+ console.log('🚀 初始化阅读进度追踪...');
+
+ if (document.readyState === 'loading') {
+ document.addEventListener('DOMContentLoaded', () => setTimeout(initAll, 500));
+ } else {
+ setTimeout(initAll, 500);
+ }
+ }
+
+ function initAll() {
+ console.log('⚙️ 启动功能...');
+ initProgressBar();
+ updateBadges();
+ initScrollDetection();
+
+ // 定期更新
+ setInterval(updateBadges, 3000);
+ }
+
+ // 启动
+ init();
+
+ // 监听路由变化
+ let lastUrl = location.href;
+ new MutationObserver(() => {
+ const url = location.href;
+ if (url !== lastUrl) {
+ lastUrl = url;
+ console.log('🔄 页面变化,更新标记');
+ setTimeout(updateBadges, 500);
+ }
+ }).observe(document, { subtree: true, childList: true });
+
+ // 暴露调试接口
+ window.readingProgressDebug = {
+ getReadDocs,
+ markAsRead,
+ updateBadges,
+ clearAll: () => {
+ localStorage.removeItem(STORAGE_KEY);
+ document.querySelectorAll('.' + BADGE_CLASS).forEach(b => b.remove());
+ console.log('🗑️ 已清除所有阅读记录');
+ updateBadges();
+ },
+ test: () => {
+ console.log('🧪 开始测试...');
+ const currentPath = normalizePath(window.location.pathname);
+ console.log('当前路径:', currentPath);
+ console.log('已阅读文档:', getReadDocs());
+
+ const links = document.querySelectorAll('a[href^="/"]');
+ console.log('找到链接数:', links.length);
+
+ links.forEach(link => {
+ const href = link.getAttribute('href');
+ if (href && !href.startsWith('http') && href !== '/') {
+ console.log('链接:', normalizePath(href), '文本:', link.textContent.trim());
+ }
+ });
+
+ updateBadges();
+ }
+ };
+
+ console.log('✅ 阅读进度追踪已加载');
+ console.log('🔧 调试命令:');
+ console.log(' readingProgressDebug.markAsRead() - 手动标记当前页面');
+ console.log(' readingProgressDebug.updateBadges() - 更新侧边栏标记');
+ console.log(' readingProgressDebug.test() - 运行测试');
+ console.log(' readingProgressDebug.clearAll() - 清除所有记录');
+
+})();