Compare commits
53 Commits
master
...
local-chan
| Author | SHA1 | Date |
|---|---|---|
|
|
c36ae092a0 | |
|
|
907b40b3d7 | |
|
|
c0231e0562 | |
|
|
6a196f1925 | |
|
|
d03d01d20f | |
|
|
2471344b5f | |
|
|
877416efcc | |
|
|
6e6a753b27 | |
|
|
a81a27b5b8 | |
|
|
31d0fed7b1 | |
|
|
b572d5cc02 | |
|
|
fb590caf65 | |
|
|
8636b658bd | |
|
|
392dcd1194 | |
|
|
858cc50a24 | |
|
|
9f84857c78 | |
|
|
43bd26d83a | |
|
|
293675001a | |
|
|
0d20ee0ed2 | |
|
|
a1b1f5a98b | |
|
|
5313d0a92d | |
|
|
b938837b2c | |
|
|
9042c56474 | |
|
|
581b4874a1 | |
|
|
ca4191549f | |
|
|
49cd589dd0 | |
|
|
218036d855 | |
|
|
a8693d16c5 | |
|
|
c947a39409 | |
|
|
a71e88b167 | |
|
|
f7760e738b | |
|
|
2bfbd34bfc | |
|
|
1da78bc2de | |
|
|
eb7eec5c56 | |
|
|
0020bf468d | |
|
|
79db184e3e | |
|
|
2108ec3e3b | |
|
|
a7cda42e59 | |
|
|
5603bde83c | |
|
|
4404ead1c2 | |
|
|
5e697cbb51 | |
|
|
640dac5ecd | |
|
|
acbca0caee | |
|
|
3d3d6167a0 | |
|
|
cd145221a2 | |
|
|
631a55a43a | |
|
|
bde3dc87ac | |
|
|
a6490b7bb7 | |
|
|
bc2b1d99c1 | |
|
|
ac21ca3e67 | |
|
|
30aa2573e8 | |
|
|
9d1fc94dd6 | |
|
|
a28bcc64b9 |
|
|
@ -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)"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
|
||||
36
Dockerfile
36
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"]
|
||||
|
|
|
|||
|
|
@ -8,39 +8,39 @@ sidebar_position: 1
|
|||
|
||||

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

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

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

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

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

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

|
||||
|
||||
|
|
|
|||
|
|
@ -3,15 +3,15 @@ sidebar_label: '成员管理'
|
|||
sidebar_position: 7
|
||||
---
|
||||
### **1. 成员管理入口**
|
||||
在仓库主页,点击“仓库设置”按键,再点击”成员管理“按键,即可进入成员管理模块,如下图所示。
|
||||
在仓库主页,点击**仓库设置**按键,再点击**成员管理**按键,即可进入成员管理模块,如下图所示。
|
||||
<br/>
|
||||
|
||||
### **2. 筛选、搜索项目成员**
|
||||
进入成员管理模块后,可以通过”角色筛选“按键来筛选项目成员类型,通过”搜索“按键可以检索具体的项目成员,如下图所示。
|
||||
进入成员管理模块后,可以通过**角色筛选**按键来筛选项目成员类型,通过**搜索**按键可以检索具体的项目成员,如下图所示。
|
||||
<br/>
|
||||
|
||||
### **3. 筛选、搜索项目成员**
|
||||
进入成员管理模块后,可以通过”添加成员“按键来添加项目成员,左侧搜索框检索到具体用户并选中后,单击”添加成员“按键即可成功添加项目成员。
|
||||
### **3. 添加成员**
|
||||
进入成员管理模块后,可以通过**添加成员**按键来添加项目成员,左侧搜索框检索到具体用户并选中后,单击**添加成员**按键即可成功添加项目成员。
|
||||
<br/>
|
||||
|
||||
### **4. 项目成员权限管理**
|
||||
|
|
@ -19,5 +19,5 @@ sidebar_position: 7
|
|||
<br/>
|
||||
|
||||
### **5. 删除项目成员**
|
||||
进入成员管理模块后,单击项目成员右侧的”删除“按键,可以删除改名项目成员,如下图所示。
|
||||
进入成员管理模块后,单击项目成员右侧的**删除**按键,可以删除改名项目成员,如下图所示。
|
||||
<br/>
|
||||
|
|
|
|||
|
|
@ -3,4 +3,6 @@ sidebar_label: '合并请求关联疑修'
|
|||
sidebar_position: 5
|
||||
---
|
||||
|
||||
# 合并请求关联疑修
|
||||
# 合并请求关联疑修
|
||||
|
||||
该功能还在开发完善中,敬请期待......
|
||||
|
|
@ -1,4 +1,12 @@
|
|||
---
|
||||
sidebar_label: '平台公告'
|
||||
sidebar_position: 1
|
||||
---
|
||||
---
|
||||
|
||||
# 平台公告
|
||||
|
||||
GitLink帮助中心致力于帮助开发者快速熟悉平台功能,掌握开源协作的正确方法。在这里,您可以找到从账号注册到项目管理的完整指南,也可以通过实际使用中发现平台存在的问题并提出改进建议。我们希望通过文档与社区的共同努力,让GitLink成为更好的开源创新服务平台。如果您在使用过程中遇到任何问题,欢迎在平台中提交疑修(Issue)获得帮助。
|
||||
|
||||
帮助中心项目地址是:https://gitlink.org.cn/Gitlink/gitlink_help_center/
|
||||
|
||||
开发者可以先复刻帮助中心项目,然后在本地进行开发。
|
||||
|
|
@ -33,7 +33,7 @@ Gitee配置方式为:个人头像→设置→私人令牌→生成新令牌→
|
|||

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

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

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

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

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

|
||||
|
|
|
|||
|
|
@ -2,9 +2,9 @@
|
|||
sidebar_label: '组织成员管理'
|
||||
sidebar_position: 4
|
||||
---
|
||||
# 成员管理(Members Management)
|
||||
# 成员管理
|
||||
|
||||
在 个人所管理的项目当中的**仓库设置**当中的**成员管理**可以进入成员管理界面
|
||||
在个人所管理的项目当中的**仓库设置**当中的**成员管理**可以进入成员管理界面
|
||||
|
||||

|
||||
|
||||
|
|
|
|||
|
|
@ -3,9 +3,7 @@ sidebar_label: '模板导入及导出'
|
|||
sidebar_position: 2
|
||||
---
|
||||
|
||||
# 确实开源帮助中心
|
||||
|
||||
## 模板的导入及导出管理
|
||||
# 模板导入及导出
|
||||
#### 1.模板导入
|
||||
通过点击wiki界面的**导入模板**按钮即可导入本地的txt,markdown等格式文件进行Wiki文档的创建,当前平台此项功能尚在开发测试环节
|
||||
[](/img/wiki/img4.png)
|
||||
|
|
|
|||
|
|
@ -4,20 +4,21 @@ sidebar_position: 1
|
|||
---
|
||||
|
||||
|
||||
# 确实开源帮助中心
|
||||
# 维基页面管理
|
||||
|
||||
## 维基界面管理
|
||||
#### 1.维基界面
|
||||
GitLink项目安排了专门的**维基(Wiki)界面**来展示项目的一些情况
|
||||
[](/img/wiki/img1.png)
|
||||
GitLink项目安排了专门的**维基(Wiki)界面**来展示项目的一些情况。
|
||||

|
||||
|
||||
#### 2.创建维基内容
|
||||
在初始化的维基界面当中点击**创建Wiki文档**或者**导入模板**即可进行Wiki文档的编辑
|
||||
在初始化的维基界面当中点击**创建Wiki文档**或者**导入模板**即可进行Wiki文档的编辑。
|
||||
<br/>
|
||||

|
||||
|
||||
|
||||
#### 3.进行Wiki文档的编辑
|
||||
点击**创建Wiki文档**后即跳转到编辑界面,初始内容为空白的文档
|
||||
[](/img/wiki/img3.png)
|
||||
也可以通过勾选*添加模板*来进行模板的导入,平台提供的模板包括*周报*和*月报*的格式
|
||||
[](/img/wiki/img2.png)
|
||||
完成编辑后点击左下角的的保存即可
|
||||

|
||||
也可以通过勾选**添加模板**来进行模板的导入,平台提供的模板包括*周报*和*月报*的格式
|
||||

|
||||
完成编辑后点击左下角的的**保存**即可
|
||||
|
|
@ -3,9 +3,8 @@ sidebar_label: '通知简介'
|
|||
sidebar_position: 1
|
||||
---
|
||||
|
||||
# 确实开源帮助中心
|
||||
# 通知简介
|
||||
|
||||
## 通知简介
|
||||
#### 1.通知类型
|
||||
GitLink将通知分为“系统通知”和“@我”的两种类型:
|
||||
* 系统通知包括“我的状态”、“我创建的或负责的”、“我管理的仓库”三种类型的通知:
|
||||
|
|
@ -25,5 +24,5 @@ GitLink将通知分为“系统通知”和“@我”的两种类型:
|
|||
<br/>
|
||||
|
||||
|
||||
点击通知按钮可以进入消息通知界面,“我的通知”界面可以进行“进查看未读消息”和“所有消息一键已读”的选择。
|
||||
点击“通知”按钮可以进入消息通知界面,“我的通知”界面可以进行“仅看未读消息”和“所有系统通知一键已读”的选择。
|
||||

|
||||
|
|
@ -3,19 +3,16 @@ sidebar_label: '通知设置'
|
|||
sidebar_position: 2
|
||||
---
|
||||
|
||||
|
||||
# 确实开源帮助中心
|
||||
|
||||
## 通知设置
|
||||
# 通知设置
|
||||
#### 1.进入通知设置界面
|
||||
通过点击首页的通知按钮进入通知设置界面;
|
||||
通过点击首页的通知按钮进入通知设置界面
|
||||

|
||||
<br/>
|
||||
或在头像下拉列表中选择设置可以进入消息通知设置界面;
|
||||
或在头像下拉列表中选择设置可以进入消息通知设置界面
|
||||
|
||||

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

|
||||
|
|
@ -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:{
|
||||
|
|
@ -176,6 +181,18 @@ module.exports = {
|
|||
// sidebarPath: require.resolve('./sidebars.js'),
|
||||
editUrl:'https://www.gitlink.org.cn/Gitlink/gitlink_help_center/tree/master/',
|
||||
routeBasePath: "/",
|
||||
remarkPlugins: [
|
||||
async () => {
|
||||
const { visit } = await import('unist-util-visit');
|
||||
return (tree) => {
|
||||
visit(tree, 'mdxJsxFlowElement', (node) => {
|
||||
if (node.name === 'ProgressPanel') {
|
||||
node.attributes = node.attributes || [];
|
||||
}
|
||||
});
|
||||
};
|
||||
},
|
||||
],
|
||||
},
|
||||
theme: {
|
||||
customCss: require.resolve('./src/css/custom.css'),
|
||||
|
|
@ -193,7 +210,6 @@ module.exports = {
|
|||
hashed: true,
|
||||
language: ["en", "zh"],
|
||||
highlightSearchTermsOnTargetPage: true,
|
||||
blogRouteBasePath: "/",
|
||||
explicitSearchResultPath: true,
|
||||
// For Docs using Chinese, The `language` is recommended to set to:
|
||||
// ```
|
||||
|
|
|
|||
|
|
@ -0,0 +1 @@
|
|||
Subproject commit 293675001acfe7c74caa0bcf78765ec29034c715
|
||||
|
|
@ -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",
|
||||
|
|
|
|||
11
sidebars.js
11
sidebars.js
|
|
@ -1,9 +1,8 @@
|
|||
module.exports = {
|
||||
DocsSidebar: [
|
||||
docs: [
|
||||
{
|
||||
type: 'doc',
|
||||
id: 'index',
|
||||
label: 'Docs',
|
||||
}
|
||||
type: 'autogenerated',
|
||||
dirName: '.', // 自动生成 docs/ 下所有文档的侧边栏
|
||||
},
|
||||
],
|
||||
};
|
||||
};
|
||||
|
|
@ -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 (
|
||||
<div className="vote-container">
|
||||
<span className="vote-label">这篇文档有帮助吗?</span>
|
||||
<div className="vote-buttons">
|
||||
<button
|
||||
onClick={() => handleVote('up')}
|
||||
className={`vote-btn vote-btn-up ${userVote === 'up' ? 'vote-btn-active-up' : ''}`}
|
||||
title={userVote === 'up' ? '取消投票' : '有帮助'}
|
||||
>
|
||||
<span className="vote-emoji">👍</span>
|
||||
<span className="vote-count">{upvotes}</span>
|
||||
</button>
|
||||
<button
|
||||
onClick={() => handleVote('down')}
|
||||
className={`vote-btn vote-btn-down ${userVote === 'down' ? 'vote-btn-active-down' : ''}`}
|
||||
title={userVote === 'down' ? '取消投票' : '没帮助'}
|
||||
>
|
||||
<span className="vote-emoji">👎</span>
|
||||
<span className="vote-count">{downvotes}</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
@ -148,4 +148,208 @@ html[data-theme='dark'] .docusaurus-highlight-code-line {
|
|||
.widget{
|
||||
display: none!important;
|
||||
opacity: 0;
|
||||
}
|
||||
}
|
||||
/* 帮助文档卡片背景 */
|
||||
.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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 ? <DocItemTOCMobile /> : undefined;
|
||||
const desktop =
|
||||
canRender && (windowSize === 'desktop' || windowSize === 'ssr') ? (
|
||||
<DocItemTOCDesktop />
|
||||
) : undefined;
|
||||
return {
|
||||
hidden,
|
||||
mobile,
|
||||
desktop,
|
||||
};
|
||||
}
|
||||
|
||||
export default function DocItemLayout({children}) {
|
||||
const docTOC = useDocTOC();
|
||||
const {metadata} = useDoc();
|
||||
return (
|
||||
<div className="row">
|
||||
<div className={clsx('col', !docTOC.hidden && styles.docItemCol)}>
|
||||
<ContentVisibility metadata={metadata} />
|
||||
<DocVersionBanner />
|
||||
<div className={styles.docItemContainer}>
|
||||
<article>
|
||||
<DocBreadcrumbs />
|
||||
<DocVersionBadge />
|
||||
{docTOC.mobile}
|
||||
<DocItemContent>{children}</DocItemContent>
|
||||
<DocItemFooter />
|
||||
<VoteButtons />
|
||||
</article>
|
||||
<DocItemPaginator />
|
||||
</div>
|
||||
</div>
|
||||
{docTOC.desktop && <div className="col col--3">{docTOC.desktop}</div>}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
.docItemContainer header + *,
|
||||
.docItemContainer article > *:first-child {
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
@media (min-width: 997px) {
|
||||
.docItemCol {
|
||||
max-width: 75% !important;
|
||||
}
|
||||
}
|
||||
|
|
@ -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 (
|
||||
<>
|
||||
<OriginalFooter {...props} />
|
||||
<VoteButtons />
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
|
@ -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() - 清除所有记录');
|
||||
|
||||
})();
|
||||
Loading…
Reference in New Issue