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 ![img](../../static/img/建站工具/1.png) -点击左测面板中 个人建站 - 我的站点 +点击左侧面板中 个人建站 - 我的站点 ![img](../../static/img/建站工具/3.png) ### 创建站点 -在我的站点界面点击 新建站点 按钮,进入新建站点界面 +在**我的站点**界面点击**新建站点**按钮,进入新建站点界面 -在新建站点界面输入站点名称,此名称将在我的站点列表中展示 +在新建站点界面输入站点名称,此名称将在**我的站点**的**站点列表**中展示 并且选择对应的建站工具与主题,我们为您提供了3种不同的工具,每个工具10种主题,一共30种供您挑选 ![img](../../static/img/建站工具/4.png) -填写完毕后,点击页面下方蓝色按钮: 创建站点 +填写完毕后,点击页面下方蓝色按钮**创建站点** ![img](../../static/img/建站工具/5.png) 这样您就拥有了一个网站,并且有了一个代码仓库。 -在仓库的 服务 一栏中 个人建站服务的操作界面,这里您可以查看一些您的站点状态、站点名称、网站地址、建站工具、建站时间 +在仓库的**服务**一栏中**个人建站**服务的操作界面,这里您可以查看一些您的站点状态、站点名称、网站地址、建站工具、建站时间。 ### 部署站点 ![img](../../static/img/建站工具/6.png) -点击去部署按钮,选择想要部署的分支后点击确定 +点击**去部署**按钮,选择想要部署的分支后点击**确定** ![img](../../static/img/建站工具/7.png) -等待程序运行一会儿后,会返回一些服务器部署信息给您 +等待程序运行一会儿后,会返回一些服务器部署信息给您,部署成功后您就可以访问站点了。 -部署成功后您就可以访问站点了。点击网站地址就可以跳转 +点击网站地址就可以跳转 ![img](../../static/img/建站工具/8.png) 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. 仓库创建入口** -用户可以通过平台主页右上角的按键以及个人主页参与项目模块的“新建”按键来创建仓库。 +用户可以通过平台主页右上角的按键以及个人主页参与项目模块的“新建”按钮来创建仓库。 ![](../../static/img/代码库管理/仓库创建/仓库创建.png)
### **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. 成员管理入口** -在仓库主页,点击“仓库设置”按键,再点击”成员管理“按键,即可进入成员管理模块,如下图所示。 +在仓库主页,点击**仓库设置**按键,再点击**成员管理**按键,即可进入成员管理模块,如下图所示。 ![](../../static/img/代码库管理/成员管理/成员管理入口.png)
### **2. 筛选、搜索项目成员** -进入成员管理模块后,可以通过”角色筛选“按键来筛选项目成员类型,通过”搜索“按键可以检索具体的项目成员,如下图所示。 +进入成员管理模块后,可以通过**角色筛选**按键来筛选项目成员类型,通过**搜索**按键可以检索具体的项目成员,如下图所示。 ![](../../static/img/代码库管理/成员管理/筛选、搜索项目成员.png)
-### **3. 筛选、搜索项目成员** -进入成员管理模块后,可以通过”添加成员“按键来添加项目成员,左侧搜索框检索到具体用户并选中后,单击”添加成员“按键即可成功添加项目成员。 +### **3. 添加成员** +进入成员管理模块后,可以通过**添加成员**按键来添加项目成员,左侧搜索框检索到具体用户并选中后,单击**添加成员**按键即可成功添加项目成员。 ![](../../static/img/代码库管理/成员管理/添加成员.png)
### **4. 项目成员权限管理** @@ -19,5 +19,5 @@ sidebar_position: 7 ![](../../static/img/代码库管理/成员管理/项目成员权限管理.png)
### **5. 删除项目成员** -进入成员管理模块后,单击项目成员右侧的”删除“按键,可以删除改名项目成员,如下图所示。 +进入成员管理模块后,单击项目成员右侧的**删除**按键,可以删除改名项目成员,如下图所示。 ![](../../static/img/代码库管理/成员管理/删除项目成员.png)
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配置方式为:个人头像→设置→私人令牌→生成新令牌→ ![5.配置webhook](../../static/img/reposyncer/5.配置webhook.jpg) -②访问点击前往按钮前往目标仓库webhook页新建一个webhook,以github为例 +②访问“点击前往”按钮前往目标仓库webhook页新建一个webhook,以github为例 ![6.webhook示例](../../static/img/reposyncer/6.webhook示例.png) 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 页面点击顶部导航栏的“+”符号可以进行组织新建操作。 ![](/img/Org/imageOrg1.png) - 在新建页面中输入**组织账号**、**组织名称**、**组织描述**、**所在地区**、**可见性**以及**组织头像**等信息后,点击“**创建组织**”按钮完成组织的创建。 + 在新建页面中输入组织账号、组织名称、组织描述、所在地区、可见性以及组织头像等信息后,点击**创建组织**按钮完成组织的创建。 ![](/img/Org/imageOrg2.png) 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”团队,成员为组织的创建者)。 -在团队新建页面,输入团队标识、团队名称、团队描述、项目权限以及版本库权限等信息后,点击“新建团队”完成团队的创建。 +在团队新建页面,输入团队标识、团队名称、团队描述、项目权限以及版本库权限等信息后,点击 **新建团队** 按钮完成团队的创建。 ![](/img/Org/imageOrg9.png) @@ -19,7 +19,7 @@ sidebar_position: 3 ## 管理组织团队 -点击团队信息页面中的“团队设置”按钮可以对团队进行管理 +点击团队信息页面中的 **团队设置** 按钮可以对团队进行管理。 - 基本设置:修改项目的基本信息,如名称和描述等。 ![](/img/Org/imageOrg10.png) 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) +# 成员管理 - 在 个人所管理的项目当中的**仓库设置**当中的**成员管理**可以进入成员管理界面 + 在个人所管理的项目当中的**仓库设置**当中的**成员管理**可以进入成员管理界面 ![](/img/Org/imageOrg14.png) 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)界面**来展示项目的一些情况。 +![](/img/wiki/img1.png) #### 2.创建维基内容 -在初始化的维基界面当中点击**创建Wiki文档**或者**导入模板**即可进行Wiki文档的编辑 +在初始化的维基界面当中点击**创建Wiki文档**或者**导入模板**即可进行Wiki文档的编辑。
+![](/img/wiki/img4.png) + #### 3.进行Wiki文档的编辑 点击**创建Wiki文档**后即跳转到编辑界面,初始内容为空白的文档 -[](/img/wiki/img3.png) -也可以通过勾选*添加模板*来进行模板的导入,平台提供的模板包括*周报*和*月报*的格式 -[](/img/wiki/img2.png) -完成编辑后点击左下角的的保存即可 \ No newline at end of file +![](/img/wiki/img3.png) +也可以通过勾选**添加模板**来进行模板的导入,平台提供的模板包括*周报*和*月报*的格式 +![](/img/wiki/img2.png) +完成编辑后点击左下角的的**保存**即可 \ 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将通知分为“系统通知”和“@我”的两种类型:
-点击通知按钮可以进入消息通知界面,“我的通知”界面可以进行“进查看未读消息”和“所有消息一键已读”的选择。 +点击“通知”按钮可以进入消息通知界面,“我的通知”界面可以进行“仅看未读消息”和“所有系统通知一键已读”的选择。 ![](/img/notice/my_notice2.png) \ 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.进入通知设置界面 -通过点击首页的通知按钮进入通知设置界面; +通过点击首页的通知按钮进入通知设置界面 ![](/img/notice/home_notice.png)
-或在头像下拉列表中选择设置可以进入消息通知设置界面; +或在头像下拉列表中选择设置可以进入消息通知设置界面 ![](/img/notice/into_notice.png) #### 2.进行通知设置 -通过“通知管理”可以对接受通知的方式进行设置,默认所有通知都是通过站内信的方式接受,可以通过勾选为重要的通知类型增加邮件接受方式。 +通过**通知管理**可以对接受通知的方式进行设置,默认所有通知都是通过站内信的方式接受,可以通过勾选为重要的通知类型增加邮件接受方式。 ![](/img/notice/notice_set.png) \ 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() - 清除所有记录'); + +})();