Compare commits
3 Commits
f4b1f29d39
...
0b4b79a1e6
| Author | SHA1 | Date | |
|---|---|---|---|
| 0b4b79a1e6 | |||
| b9bbcf131e | |||
| 6e577b15a2 |
62
.claude/commands/oki/extract.md
Normal file
62
.claude/commands/oki/extract.md
Normal file
@@ -0,0 +1,62 @@
|
||||
---
|
||||
name: "OKI: Extract"
|
||||
description: 基于资料,进行思考、总结、提炼,生成知识点,搭建知识链接
|
||||
category: Knowledge
|
||||
tags: [knowledge, wiki]
|
||||
---
|
||||
|
||||
基于给予资料,或现有资料,进行思考、总结、提炼,生成知识点,搭建知识链接
|
||||
|
||||
## 指令参数
|
||||
|
||||
- 可指定为资料地址,**基于资料**提炼,例如:`/oki:extract references/paper.pdf`、`/oki:summary https://www.example.com/article.html`
|
||||
- 也可以为知识点,**基于目标**从现有资料中提炼,例如:`/oki:extract 胶原蛋白`
|
||||
|
||||
若未指定,则尝试从对话上下文自动推断;若表述模糊或存在歧义,必须提示列出可用变更项
|
||||
|
||||
## 知识库结构
|
||||
|
||||
```bash
|
||||
openwiki
|
||||
├── nodes/ # 知识点 - 从「资料记录」提取到的,描述某知识点的页面
|
||||
├── public/ # 静态资源
|
||||
├── scripts/ # 执行脚本
|
||||
├── sources/ # 资料记录 - 对单篇「原始资料」的总结、记录
|
||||
├── templates/ # 页面模板
|
||||
├── config.yaml # 配置文件
|
||||
├── index.md # 主页 - 知识库内容索引页
|
||||
├── log.md # 变更日志页 - 知识库改动的记录
|
||||
└── overview.md # 总览页 - 对知识库的总结
|
||||
```
|
||||
|
||||
## 知识点页面规范
|
||||
|
||||
- 根据模板(`templates/node.md`)构建页面
|
||||
- 以中文为主,专有名词、约定俗称可用英文
|
||||
- 正文内容按描述的对象,设多级标题分块表达
|
||||
- 「知识点」的正文内容,概览页的节点描述,都至少包含一个「知识链接」
|
||||
- 流程图使用 Mermaid
|
||||
- `aliases`允许空格、特殊符号
|
||||
- `tags`大驼峰,不留空格,不用除,至少包含一个非数字字符
|
||||
- 链接使用 `[]()` ,而非`[[wiki-link]]`
|
||||
|
||||
## 执行步骤
|
||||
|
||||
严格分步骤进行,不可跳步
|
||||
|
||||
1. **根据不同情况,准备相关的「资料记录」**
|
||||
- 若基于资料,执行`/oki:summary`,为给定的原始资料创建或更新「资料记录」
|
||||
- 若基于目标,执行`/oki:query --source`,在知识库内部查找与目标相关的「资料记录」
|
||||
|
||||
2. **从「资料记录」中提取知识点,创建或更新「知识点」**
|
||||
- 知识点可以是抽象的概念,如:现象、原理、分子、基因、解剖结构、物质、物种、方法、术语、说法等
|
||||
- 也可以是具体的实体,如:某人物、某公司、某产品、某项目、某课题、某案例等
|
||||
- **不要**基于现有任何「知识点」,仅通过「资料记录」
|
||||
|
||||
3. **搭建「知识链接」**
|
||||
从正文中寻找,与已有「知识点」相关的表述,创建链接,例如`[III型胶原蛋白](concepts/胶原蛋白.md)功能的核心结构基础,是由[三条α链](concepts/三螺旋结构.md)组成`
|
||||
|
||||
4. **若有新页面,更新「总览页」**
|
||||
|
||||
5. **页面格式化**
|
||||
执行`npx prettier --write entities/ concepts/ index.md overview.md log.md`
|
||||
52
.claude/commands/oki/lint.md
Normal file
52
.claude/commands/oki/lint.md
Normal file
@@ -0,0 +1,52 @@
|
||||
---
|
||||
name: "OKI: Lint"
|
||||
description: 检查并修复知识库
|
||||
category: Knowledge
|
||||
tags: [knowledge, wiki]
|
||||
---
|
||||
|
||||
检查并修复知识库
|
||||
|
||||
## 指令参数
|
||||
|
||||
可指定范围(若不指定则全局),例如:`/oki:lint index.md`,`/oki:lint 资料记录`、`/oki:lint concepts`
|
||||
|
||||
## 知识库结构
|
||||
|
||||
```bash
|
||||
openwiki
|
||||
├── nodes/ # 知识点 - 从「资料记录」提取到的,描述某知识点的页面
|
||||
├── public/ # 静态资源
|
||||
├── scripts/ # 执行脚本
|
||||
├── sources/ # 资料记录 - 对单篇「原始资料」的总结、记录
|
||||
├── templates/ # 页面模板
|
||||
├── config.yaml # 配置文件
|
||||
├── index.md # 主页 - 知识库内容索引页
|
||||
├── log.md # 变更日志页 - 知识库改动的记录
|
||||
└── overview.md # 总览页 - 对知识库的总结
|
||||
```
|
||||
|
||||
## 执行步骤
|
||||
|
||||
1. **检查页面规范**
|
||||
逐个读取所有「资料记录」、「知识点」内容,是否符合页面规范,并修复
|
||||
- 是否根据对应模板(存于`templates/`)构建页面?
|
||||
- 是否以中文为主,英文为辅?
|
||||
- 是否正文内容设多级标题分块表达?
|
||||
- 是否「知识点」的正文内容,「总览页」的节点描述,都至少包含一个与其他页面的链接?
|
||||
- 是否「资料记录」的正文内容,没有任何链接能到其他页面?
|
||||
- 是否「资料记录」的文件名为`{类型/来源}_{正文标题}`,且不留空格,不用特殊符号?
|
||||
- 是否`tags`仅包含分类标签,而没其他标签?
|
||||
- 是否使用 Mermaid 绘制流程图?
|
||||
- 是否使用规范的`tags`(大驼峰,不留空格,不用除,至少包含一个非数字字符)?
|
||||
- 是否使用 `[]()` ,而不是`[[wiki-link]]`
|
||||
|
||||
2. **合并重复的页面**
|
||||
- 「资料记录」的`source`一致,才视为重复
|
||||
- 「知识点」描述主体相似,可视为重复
|
||||
- 需用户确认,才可执行
|
||||
|
||||
3. **检查链接和文件名**
|
||||
执行`node scripts/check-md.js sources/ nodes/ index.md overview.md log.md`,并修复
|
||||
4. **页面格式化**
|
||||
执行`npx prettier --write sources/ nodes/ index.md overview.md log.md`
|
||||
22
.claude/commands/oki/publish.md
Normal file
22
.claude/commands/oki/publish.md
Normal file
@@ -0,0 +1,22 @@
|
||||
---
|
||||
name: "OKI: Publish"
|
||||
description: 本地知识库查看、发布
|
||||
category: Knowledge
|
||||
tags: [knowledge, wiki, publish]
|
||||
---
|
||||
|
||||
本地知识库查看、发布
|
||||
|
||||
## 指令参数
|
||||
|
||||
- `--preview`、`-p`变为预览(默认发布)
|
||||
|
||||
## 发布步骤
|
||||
|
||||
1. 执行`cd openwiki && pnpm build`
|
||||
2. 若报错,修复错误
|
||||
3. 执行`bash scripts/deploy.sh`
|
||||
|
||||
## 预览步骤
|
||||
|
||||
1. 执行`cd openwiki && pnpm dev`
|
||||
105
.claude/commands/oki/query.md
Normal file
105
.claude/commands/oki/query.md
Normal file
@@ -0,0 +1,105 @@
|
||||
---
|
||||
name: "OKI: Query"
|
||||
description: 查询有关的知识点,找到直接依据,返回对应「资料记录」
|
||||
category: Knowledge
|
||||
tags: [knowledge, wiki]
|
||||
---
|
||||
|
||||
查询有关的知识点,找到直接依据,返回对应「资料记录」
|
||||
|
||||
## 知识库结构
|
||||
|
||||
```bash
|
||||
openwiki
|
||||
├── nodes/ # 知识点 - 从「资料记录」提取到的,描述某知识点的页面
|
||||
├── public/ # 静态资源
|
||||
├── scripts/ # 执行脚本
|
||||
├── sources/ # 资料记录 - 对单篇「原始资料」的总结、记录
|
||||
├── templates/ # 页面模板
|
||||
├── config.yaml # 配置文件
|
||||
├── index.md # 主页 - 知识库内容索引页
|
||||
├── log.md # 变更日志页 - 知识库改动的记录
|
||||
└── overview.md # 总览页 - 对知识库的总结
|
||||
```
|
||||
|
||||
## 输入
|
||||
|
||||
- `[entry]`,查询目标可指定为知识点,例如:`/oki:query 胶原蛋白`,也可以为自然语言,如:`/oki:query 什么是胶原蛋白?`、`/oki:query 它是什么构成的?`
|
||||
- `--from`、`-f`,则改为知识库外查询,其后填入查询来源,如:`/oki:query 胶原蛋白 --from 维基百科`,`/oki:query 胶原蛋白 --f https://pubmed.ncbi.nlm.nih.gov/`
|
||||
|
||||
若未指定任何参数,则尝试从对话上下文自动推断;若表述模糊或存在歧义,必须提示列出可用变更项。
|
||||
|
||||
## 输出
|
||||
|
||||
按以下格式输出相关的「资料记录」(**不是「知识点」**)
|
||||
|
||||
```markdown
|
||||
<!-- 报告正文 -->
|
||||
|
||||
## 相关资料
|
||||
|
||||
- [{资料记录}](../sources/{资料记录}.md):{和查询目标直接相关的原文节选}
|
||||
- ...
|
||||
```
|
||||
|
||||
## 知识库内查询步骤
|
||||
|
||||
逐层深入挖掘,查询直接证据
|
||||
|
||||
**从「知识点」中查询**
|
||||
若有和查询目标直接关联的「知识点」,则从「知识点」中查询
|
||||
|
||||
1. 返回它在`## 相关资料`中的「资料记录」
|
||||
2. 在「资料记录」中找到和查询目标直接相关的原文节选
|
||||
3. 若查到,按格式输出报告
|
||||
|
||||
**从「资料记录」中查询**
|
||||
若没直接关联的「知识点」,则从「资料记录」中查询
|
||||
|
||||
1. 找到可能相关的「资料记录」
|
||||
2. 读取「资料记录」内容,查询和目标直接相关的原文节选
|
||||
3. 若查到,按格式输出报告
|
||||
4. 若没找到,尝试**从「原始资料」中查询**
|
||||
|
||||
**从「原始资料」中查询**
|
||||
若「资料记录」内容中没有能作为直接依据的,推测可能其原始资料有,则从「原始资料」中查询
|
||||
|
||||
1. 执行`/oki:summary`重读「资料记录」`source`记录的原始资料
|
||||
2. 从「资料解析」中查询直接证据。若查到,更新「资料记录」,添加直接依据部分的内容
|
||||
3. 若查到,更新「资料记录」(添加直接依据部分的内容),按格式输出报告
|
||||
4. 若没查到,询问用户是否从外部查询
|
||||
|
||||
**重要**
|
||||
|
||||
- **不要**查询除`nodes/`、`sources/`文件夹以外的页面
|
||||
|
||||
- **优先选择**强证据来源:
|
||||
- 知识库中:「原始资料」 > 「资料记录」 > 「知识点」
|
||||
- 「资料记录」中:百科 > 论文 > 专利 > 会议 > 新闻
|
||||
|
||||
## 知识库外查询步骤
|
||||
|
||||
1. **从外部数据检索**
|
||||
- 从`--from`、`-f`后的获取意图,寻找合适的外部数据库:
|
||||
- 论文 `https://link.springer.com/search?query={查询内容}&content-type=Review`
|
||||
- 论文 `https://pubmed.ncbi.nlm.nih.gov/?term={查询内容}&filter=pubt.review`
|
||||
- 全百科 `https://en.wikipedia.org/w/index.php?search={查询内容}`
|
||||
- 医学百科 `https://www.a-hospital.com/index.php?search={查询内容}`
|
||||
- 专利 `https://patents.google.com/?q=({查询内容})`
|
||||
- 若`--from`、`-f`后为空,则根据查询目标的类型来判断:
|
||||
- 若为科学术语、专业名词,则从百科中查询
|
||||
- 若为医学概念,则从医学百科中查询
|
||||
- 若为编号`ZLxxxxx`、`CNxxxxx`,则可能为专利号
|
||||
- 若为研究相关,则从论文中查询
|
||||
|
||||
- 网站资料**优先选择**强证据来源:
|
||||
- 百科 > 论文 > 专利 > 新闻 > ~~其他~~
|
||||
|
||||
2. **从参考资料中检索**
|
||||
若检索不到直接依据,尝试从网站页面提供的参考资料中寻找,**直到找到为止**
|
||||
|
||||
3. **创建并返回「资料记录」**
|
||||
1. 若找到直接依据(可多篇),通过`/oki:summary`创建「资料记录」
|
||||
2. 按格式输出报告
|
||||
|
||||
4. 若没查到,询问用户其他外部数据库。
|
||||
72
.claude/commands/oki/summary.md
Normal file
72
.claude/commands/oki/summary.md
Normal file
@@ -0,0 +1,72 @@
|
||||
---
|
||||
name: "OKI: Summary"
|
||||
description: 读取原始资料,记录总结,返回对应「资料记录」
|
||||
category: Knowledge
|
||||
tags: [knowledge, wiki]
|
||||
---
|
||||
|
||||
读取原始资料,记录总结,返回对应「资料记录」
|
||||
|
||||
## 知识库结构
|
||||
|
||||
```bash
|
||||
openwiki
|
||||
├── nodes/ # 知识点 - 从「资料记录」提取到的,描述某知识点的页面
|
||||
├── public/ # 静态资源
|
||||
├── scripts/ # 执行脚本
|
||||
├── sources/ # 资料记录 - 对单篇「原始资料」的总结、记录
|
||||
├── templates/ # 页面模板
|
||||
├── config.yaml # 配置文件
|
||||
├── index.md # 主页 - 知识库内容索引页
|
||||
├── log.md # 变更日志页 - 知识库改动的记录
|
||||
└── overview.md # 总览页 - 对知识库的总结
|
||||
```
|
||||
|
||||
## 「资料记录」页面规范
|
||||
|
||||
- 根据模板(`templates/source.md`)构建页面
|
||||
- 以中文为主,专有名词、约定俗称可用英文
|
||||
- 正文内容按描述的对象,设多级标题分块表达
|
||||
- 文件名为`{类型/来源}_{正文标题}`,但不留空格,不用特殊符号
|
||||
- `tags`大驼峰,不留空格,不用特殊符号,至少包含一个非数字字符
|
||||
- 若原始资料存于本仓库,`source`使用相对仓库根目录的路径
|
||||
- 链接使用 `[]()` ,而非`[[wiki-link]]`
|
||||
|
||||
**不要**
|
||||
|
||||
- 正文中**不要**存在和「知识点」的链接
|
||||
- `tags`**不要**除分类标签以外的标签类型,如不要`- 胶原蛋白`
|
||||
|
||||
## 输入
|
||||
|
||||
- `[entry]`,可指定为本地文件或者网址,可批量(用空格区分),如:`/oki:summary references/paper-1.pdf paper-2.pdf`、`/oki:summary https://www.example.com/article.html`
|
||||
|
||||
若未指定任何参数,则尝试从对话上下文自动推断;若表述模糊或存在歧义,必须提示列出可用变更项。
|
||||
|
||||
## 输出
|
||||
|
||||
## 输出
|
||||
|
||||
按以下格式输出相关的「资料记录」
|
||||
|
||||
```markdown
|
||||
<!-- 报告正文 -->
|
||||
|
||||
## 资料记录
|
||||
|
||||
- [{资料记录}](../sources/{资料记录}.md):{摘要}
|
||||
- ...
|
||||
```
|
||||
|
||||
## 执行步骤
|
||||
|
||||
1. **把「原始资料」解析为「资料缓存」**
|
||||
- 若是PDF,执行`opendataloader-pdf file1.pdf file2.pdf -f markdown -o ~/.cache/oki-summary`
|
||||
- 若是网页,执行`npx defuddle parse [url] --markdown -o ~/.cache/oki-summary/[cache-name].md`
|
||||
- 若是其他格式,则尝试自行选择方式转化为 markdown 并存于`~/.cache/oki-summary`
|
||||
|
||||
2. **创建或更新「资料记录」**
|
||||
1. 执行`grep -rh "^source:" openwiki/sources/ --include="*.md" | sed 's/^source: *//'`,以获取目前所有「资料记录」的原始资料地址,进行比对
|
||||
- 若存在,则更新与之对应「资料记录」
|
||||
- 若不存在,则新建对应「资料记录」
|
||||
2. 执行`cd openwiki && npx prettier --write sources/ log.md`
|
||||
19
.claude/settings.json
Normal file
19
.claude/settings.json
Normal file
@@ -0,0 +1,19 @@
|
||||
{
|
||||
"permissions": {
|
||||
"allow": [
|
||||
"WebSearch",
|
||||
"WebFetch",
|
||||
"Read(~/.cache/**/*)",
|
||||
"Edit(~/.cache/**/*)",
|
||||
"Bash",
|
||||
"Bash(curl *)",
|
||||
"Bash(wget *)",
|
||||
"Bash(ls *)",
|
||||
"Bash(cd *)",
|
||||
"Bash(mkdir *)",
|
||||
"Bash(* --version)",
|
||||
"Bash(* --help *)",
|
||||
"mcp__MiniMax__web_search"
|
||||
]
|
||||
}
|
||||
}
|
||||
102
.claude/skills/oflow/SKILL.md
Normal file
102
.claude/skills/oflow/SKILL.md
Normal file
@@ -0,0 +1,102 @@
|
||||
---
|
||||
name: oflow
|
||||
description: 用 git 记录创作过程和团队协作的指令集,适用于创意团队。
|
||||
---
|
||||
|
||||
## 工作流程
|
||||
|
||||
采用简化版的 Github Flow 工作流,适用于创意团队的协作和创作过程记录:
|
||||
|
||||
- 仅在自己的「个人分支」工作,互不干扰
|
||||
- 通过「提交」,有选择地确认改动到个人分支
|
||||
- 不时地更新其他人在「主分支」的改动
|
||||
- 通过「合并请求」,有监督地确认改动到主分支
|
||||
|
||||
## 规范、术语
|
||||
|
||||
### 主分支
|
||||
|
||||
也就是`main`分支。
|
||||
|
||||
- 应该保持稳定和可用,不在主分支上进行创作和修改。
|
||||
|
||||
### 个人分支
|
||||
|
||||
创作者在此分支上进行创作和修改。
|
||||
|
||||
- 以创作者名字命名的分支,或斜杠加工作内容(例如`alice`、`alice/dev`、`alice/design`)
|
||||
|
||||
### 提交
|
||||
|
||||
也就是 Commit,用于确认改动,保存时间节点。
|
||||
|
||||
- 提交前询问用户哪些改动需要添加到暂存区。
|
||||
- 提交说明用中文表达为主,惯例或术语可以用中英文。
|
||||
- 提交说明第一行,不需要前缀类型(`feat`、`docs`),而是用动词开头的短句,例如
|
||||
- `refactor: 对 refs 进行了重构` 改为 `重构 refs`
|
||||
- `feat: 添加了 xx 功能` 改为 `添加 xx`
|
||||
- 如果本次改动涉及多项调整,在提交说明的后几行详述。
|
||||
|
||||
### 合并请求
|
||||
|
||||
也就是 Pull Request,简称 PR。
|
||||
|
||||
- 在 PR 前,要求先执行 `git pull origin main`,确保和`main`分支别差太远。
|
||||
|
||||
## 指令集
|
||||
|
||||
执行规则:
|
||||
|
||||
- 提交、合并请求、从主分支更新等操作前,确保当前在个人分支中。
|
||||
- 表中 git 相关的指令在正式执行前,汇总信息给用户,确认后再执行。
|
||||
- 如果 git 遇到 Authentication failed,尝试再次执行,如果依然不行,报告用户
|
||||
|
||||
### 获取仓库
|
||||
|
||||
触发词:`获取仓库`,`克隆仓库`,`下载仓库`
|
||||
|
||||
执行步骤:
|
||||
|
||||
1. 执行 `git clone [repo-url]`
|
||||
2. 若项目`.lfsconfig`中发现`fetchexclude = *`,询问是否下载 LFS 文件
|
||||
- 若需要,执行`git lfs pull --exclude= --include [folder]`来下载指定文件夹,或执行`git lfs pull --exclude= --include *`来下载所有文件
|
||||
|
||||
### 创建个人分支
|
||||
|
||||
触发词:`创建时间线`,`创建个人分支`
|
||||
|
||||
执行步骤:
|
||||
|
||||
1. 执行 `git fetch origin && git checkout -b [branch-name] origin/main`
|
||||
2. 执行 `git push -u origin [branch-name]`
|
||||
|
||||
> 其中 `[branch-name]` 为 git config 中记录的 username。如果用户有说明创建的意图,可加上后缀,例如 `alice/fix-bug`
|
||||
|
||||
### 更新
|
||||
|
||||
触发词:`从主线更新`,`从主分支同步`,`从主线同步`,`同步主分支`
|
||||
|
||||
执行步骤:
|
||||
|
||||
1. 执行 `git pull origin main`
|
||||
2. 如果遇到合并冲突,询问式地帮用户处理冲突
|
||||
3. 冲突解决提交后,若之前涉及大文件冲突,执行`git lfs pull --exclude= --include [folder]`来下载解决冲突后缺失的 LFS 文件
|
||||
|
||||
### 提交
|
||||
|
||||
触发词:`保存时间点`,`提交修改`,`确认改动`,`记录时间节点`,`创建时间节点`
|
||||
|
||||
执行步骤:
|
||||
|
||||
1. 询问用户哪些改动需要被提交(确认),执行 `git add [files]`
|
||||
2. 执行 `git commit -m [message]`
|
||||
|
||||
### 合并请求
|
||||
|
||||
触发词:`同步到主分支`,`同步给主分支`,`合并请求`,`PR`
|
||||
|
||||
执行步骤:
|
||||
|
||||
1. 执行「更新」指令
|
||||
2. 执行 `git push`
|
||||
3. 分别提供: PR 页面网址供用户访问,并提供标题、内容,可供用户复制到 PR 页面
|
||||
4
.gitattributes
vendored
4
.gitattributes
vendored
@@ -125,3 +125,7 @@
|
||||
*.ome lfs
|
||||
*.map lfs
|
||||
*.mrc lfs
|
||||
|
||||
# Others
|
||||
*.abr lfs
|
||||
*.tpl lfs
|
||||
15
.gitignore
vendored
15
.gitignore
vendored
@@ -1,6 +1,7 @@
|
||||
# Tempary files
|
||||
temp
|
||||
sandbox.*
|
||||
notes/
|
||||
|
||||
# Environments
|
||||
.env
|
||||
@@ -21,16 +22,26 @@ secrets/
|
||||
SECRETS/
|
||||
secrets.bak/
|
||||
|
||||
# Claude
|
||||
# Claude Code
|
||||
.claude/settings.local.json
|
||||
.claudian
|
||||
|
||||
# Obsidian setting
|
||||
# Obsidian
|
||||
.obsidian/*
|
||||
!.obsidian/snippets
|
||||
!.obsidian/app.json
|
||||
|
||||
*.canvas
|
||||
|
||||
# Keep odox for extract to openwiki
|
||||
!.obsidian/plugins
|
||||
.obsidian/plugins/*
|
||||
!.obsidian/plugins/odox
|
||||
|
||||
# Blender
|
||||
*.blend1
|
||||
blender_assets.cats.txt~
|
||||
blendcache_*
|
||||
|
||||
# Byte-compiled / optimized / DLL files
|
||||
__pycache__/
|
||||
|
||||
8
.obsidian/plugins/odox/data.json
vendored
Normal file
8
.obsidian/plugins/odox/data.json
vendored
Normal file
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"docsPath": "openwiki/docs",
|
||||
"assetsPath": "openwiki/public",
|
||||
"nestInFolder": false,
|
||||
"preserveFolderStructure": true,
|
||||
"transparentCanvas": true,
|
||||
"canvasEmbedTemplate": "<Viewer src=\"{url}\" />"
|
||||
}
|
||||
6
.obsidian/plugins/odox/main.js
vendored
Normal file
6
.obsidian/plugins/odox/main.js
vendored
Normal file
File diff suppressed because one or more lines are too long
10
.obsidian/plugins/odox/manifest.json
vendored
Normal file
10
.obsidian/plugins/odox/manifest.json
vendored
Normal file
@@ -0,0 +1,10 @@
|
||||
{
|
||||
"id": "odox",
|
||||
"name": "Odox",
|
||||
"version": "0.1.0",
|
||||
"minAppVersion": "0.15.0",
|
||||
"description": "Export Obsidian notes to static docs tools with asset management",
|
||||
"author": "nan",
|
||||
"authorUrl": "https://github.com/icrdr",
|
||||
"isDesktopOnly": false
|
||||
}
|
||||
30
.obsidian/plugins/odox/styles.css
vendored
Normal file
30
.obsidian/plugins/odox/styles.css
vendored
Normal file
@@ -0,0 +1,30 @@
|
||||
/*
|
||||
|
||||
This CSS file will be included with your plugin, and
|
||||
available in the app when your plugin is enabled.
|
||||
|
||||
If your plugin does not need CSS, delete this file.
|
||||
|
||||
*/
|
||||
|
||||
/* 透明导出模式:隐藏洞洞板背景 */
|
||||
.canvas-transparent-export .canvas-background {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
/* 透明导出模式:确保 canvas 容器背景透明 */
|
||||
.canvas-transparent-export {
|
||||
background-color: transparent !important;
|
||||
background-image: none !important;
|
||||
}
|
||||
|
||||
/* 截图时隐藏页面其他元素 */
|
||||
.odox-hidden {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
/* 截图时的 body 样式 */
|
||||
.odox-capturing {
|
||||
background-color: transparent !important;
|
||||
background-image: none !important;
|
||||
}
|
||||
31
AGENTS.md
Normal file
31
AGENTS.md
Normal file
@@ -0,0 +1,31 @@
|
||||
## 项目结构
|
||||
|
||||
```bash
|
||||
根目录
|
||||
├── contents/ # 资源型文件
|
||||
├── notes/ # 笔记、白板
|
||||
├── references/
|
||||
│ ├── client/ # 客户给的资料
|
||||
│ ├── papers/ # 文献资料
|
||||
│ └── visuals/ # 视觉参考
|
||||
├── subspaces/ # 工程文件、创作文件
|
||||
```
|
||||
|
||||
|
||||
|
||||
## Markdown 规范
|
||||
- 中文为主(专有名词、约定俗称可以用英文)
|
||||
- 不使用`---`分割器
|
||||
- `# Heading` 一级标题仅用于开头
|
||||
|
||||
## 代码规范
|
||||
|
||||
- 避免嵌套结构
|
||||
- 特殊的、非常规的需要写注释
|
||||
- 提交说明、文档用中文为主(专有名词、约定俗称可以用英文)
|
||||
- 注释用中文为主(专有名词、约定俗称可以用英文)
|
||||
- 变量、函数名、Docstring 用全英文
|
||||
- 同一事物用统一表达
|
||||
- 使用的依赖越少越好
|
||||
- 选用简单直接的方式实现
|
||||
- 代码、执行方式改变,调整已有的文档、计划,而非新建
|
||||
@@ -1,3 +1,3 @@
|
||||
---
|
||||
brief: An omoolab project
|
||||
description: An omoolab project
|
||||
---
|
||||
158
openwiki/.vitepress/config.mts
Normal file
158
openwiki/.vitepress/config.mts
Normal file
@@ -0,0 +1,158 @@
|
||||
import { defineConfig } from "vitepress";
|
||||
import { vitepressBeautifulMermaid } from "v-beautiful-mermaid";
|
||||
import { readdirSync, statSync, readFileSync } from "fs";
|
||||
import { join } from "path";
|
||||
import { load } from "js-yaml";
|
||||
|
||||
const siteConfig = load(
|
||||
readFileSync(join(__dirname, "../config.yaml"), "utf8"),
|
||||
) as {
|
||||
title?: string;
|
||||
description?: string;
|
||||
logo?: string;
|
||||
siteTitle?: false;
|
||||
appearance?: false;
|
||||
nav?: { text: string; link: string }[];
|
||||
footer?: { message: string; copyright: string };
|
||||
};
|
||||
|
||||
interface SidebarItem {
|
||||
text: string;
|
||||
link?: string;
|
||||
items?: SidebarItem[];
|
||||
}
|
||||
|
||||
function isDirectory(path: string): boolean {
|
||||
try {
|
||||
return statSync(path).isDirectory();
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
function buildSidebarTree(root: string, basePath: string = ""): SidebarItem[] {
|
||||
const items: SidebarItem[] = [];
|
||||
const entries = readdirSync(root);
|
||||
|
||||
// Separate files and directories
|
||||
const files: string[] = [];
|
||||
const dirs: string[] = [];
|
||||
|
||||
for (const entry of entries) {
|
||||
if (entry === "index.md") continue;
|
||||
const fullPath = join(root, entry);
|
||||
if (isDirectory(fullPath)) {
|
||||
dirs.push(entry);
|
||||
} else if (entry.endsWith(".md")) {
|
||||
files.push(entry);
|
||||
}
|
||||
}
|
||||
|
||||
// Add files first
|
||||
for (const f of files.sort()) {
|
||||
const name = f.replace(/\.md$/, "");
|
||||
const linkPath = `/${root}/${name}`;
|
||||
items.push({
|
||||
text: name,
|
||||
link: linkPath,
|
||||
});
|
||||
}
|
||||
|
||||
// Add directories as submenus
|
||||
for (const d of dirs.sort()) {
|
||||
const subRoot = join(root, d);
|
||||
const subBasePath = basePath ? `${basePath}/${d}` : d;
|
||||
const subItems = buildSidebarTree(subRoot, subBasePath);
|
||||
if (subItems.length > 0) {
|
||||
items.push({
|
||||
text: d,
|
||||
items: subItems,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return items;
|
||||
}
|
||||
|
||||
function buildSidebarItems(root: string): SidebarItem[] {
|
||||
return buildSidebarTree(root);
|
||||
}
|
||||
|
||||
const sidebar = {
|
||||
"/": [
|
||||
{
|
||||
items: [
|
||||
{
|
||||
text: "主页",
|
||||
link: "/",
|
||||
},
|
||||
{
|
||||
text: "概览",
|
||||
link: "/overview",
|
||||
},
|
||||
{
|
||||
text: "变更日志",
|
||||
link: "/log",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
items: [{}],
|
||||
},
|
||||
],
|
||||
"/docs/": [
|
||||
{
|
||||
text: "记录",
|
||||
items: [{}],
|
||||
},
|
||||
{
|
||||
text: "工作空间",
|
||||
items: [{}],
|
||||
},
|
||||
],
|
||||
"/sources/": [
|
||||
{
|
||||
items: [{}],
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
sidebar["/docs/"][0].items = buildSidebarItems("docs/notes");
|
||||
sidebar["/docs/"][1].items = buildSidebarItems("docs/subspaces");
|
||||
sidebar["/sources/"][0].items = buildSidebarItems("sources");
|
||||
sidebar["/"][1].items = buildSidebarItems("nodes");
|
||||
|
||||
// https://vitepress.dev/reference/site-config
|
||||
export default defineConfig({
|
||||
title: siteConfig.title,
|
||||
description: siteConfig.description,
|
||||
lang: "zh-CN",
|
||||
base: "/", // Replace with your repository name if deploying to GitHub Pages
|
||||
srcDir: ".",
|
||||
ignoreDeadLinks: true,
|
||||
appearance: siteConfig.appearance,
|
||||
themeConfig: {
|
||||
logo: siteConfig.logo,
|
||||
siteTitle: siteConfig.siteTitle,
|
||||
nav: [
|
||||
{ text: "知识库", link: "/overview" },
|
||||
{ text: "资料库", link: "/sources/" },
|
||||
{ text: "文档", link: "/docs/" },
|
||||
],
|
||||
sidebar,
|
||||
footer: siteConfig.footer,
|
||||
search: {
|
||||
provider: "local",
|
||||
},
|
||||
},
|
||||
markdown: {
|
||||
config: (md) => {
|
||||
md.use(vitepressBeautifulMermaid, {
|
||||
colorMode: "auto",
|
||||
renderOptions: {
|
||||
font: "LXGW WenKai Mono",
|
||||
},
|
||||
});
|
||||
},
|
||||
},
|
||||
});
|
||||
22
openwiki/.vitepress/theme/Layout.vue
Normal file
22
openwiki/.vitepress/theme/Layout.vue
Normal file
@@ -0,0 +1,22 @@
|
||||
<script setup lang="ts">
|
||||
import { computed } from 'vue'
|
||||
import { useData } from 'vitepress'
|
||||
import DefaultTheme from 'vitepress/theme-without-fonts'
|
||||
import DocTags from './components/DocTags.vue'
|
||||
import DocMetaEx from './components/DocMetaEx.vue'
|
||||
|
||||
const { frontmatter } = useData()
|
||||
const tags = computed(() => frontmatter.value?.tags ?? [])
|
||||
const hasTags = computed(() => tags.value.length > 0)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<DefaultTheme.Layout>
|
||||
<template #doc-before>
|
||||
<DocTags v-if="hasTags" style="margin-bottom: 24px;" />
|
||||
</template>
|
||||
<template #aside-outline-after>
|
||||
<DocMetaEx style="margin-top: 24px; padding-left: 16px;"/>
|
||||
</template>
|
||||
</DefaultTheme.Layout>
|
||||
</template>
|
||||
50
openwiki/.vitepress/theme/components/DocMetaEx.vue
Normal file
50
openwiki/.vitepress/theme/components/DocMetaEx.vue
Normal file
@@ -0,0 +1,50 @@
|
||||
<script setup lang="ts">
|
||||
import { computed } from "vue";
|
||||
import { useData } from "vitepress";
|
||||
|
||||
const { frontmatter } = useData();
|
||||
|
||||
const source = computed(() => frontmatter.value?.source ?? null);
|
||||
const title = computed(() => frontmatter.value?.title ?? null);
|
||||
const abstract = computed(() => frontmatter.value?.abstract ?? null);
|
||||
|
||||
const hasExtended = computed(
|
||||
() => source.value || title.value || abstract.value,
|
||||
);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div v-if="hasExtended" class="doc-meta-ex">
|
||||
<div v-if="title" class="doc-meta-title">标题:{{ title }}</div>
|
||||
<div v-if="source" class="doc-meta-source">
|
||||
来源:<a :href="source" target="_blank" rel="noopener noreferrer">{{
|
||||
source
|
||||
}}</a>
|
||||
</div>
|
||||
<div v-if="abstract" class="doc-meta-abstract">摘要:{{ abstract }}</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.doc-meta-ex {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
line-height: 1.8;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.doc-meta-source {
|
||||
font-size: 12px;
|
||||
color: var(--vp-c-text-2);
|
||||
}
|
||||
|
||||
.doc-meta-title {
|
||||
font-size: 12px;
|
||||
color: var(--vp-c-text-1);
|
||||
}
|
||||
|
||||
.doc-meta-abstract {
|
||||
font-size: 12px;
|
||||
color: var(--vp-c-text-2);
|
||||
}
|
||||
</style>
|
||||
90
openwiki/.vitepress/theme/components/DocTags.vue
Normal file
90
openwiki/.vitepress/theme/components/DocTags.vue
Normal file
@@ -0,0 +1,90 @@
|
||||
<script setup lang="ts">
|
||||
import { computed } from "vue";
|
||||
import { useData } from "vitepress";
|
||||
|
||||
const { frontmatter } = useData();
|
||||
|
||||
const tags = computed(() => frontmatter.value?.tags ?? []);
|
||||
const aliases = computed(() => frontmatter.value?.aliases ?? []);
|
||||
const createdAt = computed(() => frontmatter.value?.created_at ?? null);
|
||||
const updatedAt = computed(() => frontmatter.value?.updated_at ?? null);
|
||||
const hasTags = computed(() => tags.value.length > 0);
|
||||
const hasTime = computed(() => createdAt.value || updatedAt.value);
|
||||
|
||||
function formatDate(date: string | null): string {
|
||||
if (!date) return "";
|
||||
const d = new Date(date);
|
||||
if (isNaN(d.getTime())) return date as string;
|
||||
return d.toLocaleDateString("zh-CN", {
|
||||
year: "numeric",
|
||||
month: "2-digit",
|
||||
day: "2-digit",
|
||||
});
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="doc-tags-wrapper">
|
||||
<div v-if="hasTime" class="doc-meta-time">
|
||||
<span v-if="createdAt">创建于 {{ formatDate(createdAt) }}</span>
|
||||
<span v-if="createdAt && updatedAt"> · </span>
|
||||
<span v-if="updatedAt">更新于 {{ formatDate(updatedAt) }}</span>
|
||||
</div>
|
||||
<div v-if="hasTags" class="doc-tags">
|
||||
<span v-for="tag in tags" :key="tag" class="doc-tag"># {{ tag }}</span>
|
||||
</div>
|
||||
<div v-if="aliases.length > 0" class="doc-meta-aliases">
|
||||
<span v-for="alias in aliases" :key="alias" class="doc-meta-alias">{{
|
||||
alias
|
||||
}}</span>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.doc-tags-wrapper {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.doc-meta-time {
|
||||
font-size: 12px;
|
||||
color: var(--vp-c-text-3);
|
||||
}
|
||||
|
||||
.doc-tags {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.doc-meta-aliases {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.doc-meta-alias {
|
||||
display: inline-block;
|
||||
padding: 0 10px;
|
||||
line-height: 22px;
|
||||
font-size: 12px;
|
||||
font-weight: 500;
|
||||
border-radius: 12px;
|
||||
background-color: var(--vp-c-bg-soft);
|
||||
color: var(--vp-c-brand-1);
|
||||
border: 1px solid var(--vp-c-brand-1);
|
||||
}
|
||||
|
||||
.doc-tag {
|
||||
display: inline-block;
|
||||
padding: 0 10px;
|
||||
line-height: 22px;
|
||||
font-size: 12px;
|
||||
font-weight: 500;
|
||||
border-radius: 12px;
|
||||
background-color: var(--vp-c-bg-soft);
|
||||
color: var(--vp-c-text-2);
|
||||
}
|
||||
</style>
|
||||
1454
openwiki/.vitepress/theme/components/Viewer.vue
Normal file
1454
openwiki/.vitepress/theme/components/Viewer.vue
Normal file
File diff suppressed because it is too large
Load Diff
22
openwiki/.vitepress/theme/fonts.css
Normal file
22
openwiki/.vitepress/theme/fonts.css
Normal file
@@ -0,0 +1,22 @@
|
||||
@import url("https://fontsapi.zeoseven.com/292/main/result.css");
|
||||
@import url("https://fontsapi.zeoseven.com/292/light/result.css");
|
||||
@import url("https://fontsapi.zeoseven.com/293/main/result.css");
|
||||
@import url("https://fontsapi.zeoseven.com/2/main/result.css");
|
||||
@import url("https://fontsapi.zeoseven.com/147/main/result.css");
|
||||
|
||||
:root {
|
||||
--vp-font-family-base: "LXGW WenKai";
|
||||
--vp-font-family-mono: "LXGW WenKai Mono";
|
||||
}
|
||||
|
||||
strong {
|
||||
font-family: "LXGW ZhenKai GB";
|
||||
}
|
||||
|
||||
em {
|
||||
font-family: "LXGW WenKai Light";
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-family: "JiangChengLvDongSong";
|
||||
}
|
||||
16
openwiki/.vitepress/theme/index.js
Normal file
16
openwiki/.vitepress/theme/index.js
Normal file
@@ -0,0 +1,16 @@
|
||||
import DefaultTheme from "vitepress/theme-without-fonts";
|
||||
import { installMermaid } from "v-beautiful-mermaid/client";
|
||||
import Viewer from "./components/Viewer.vue";
|
||||
import Layout from "./Layout.vue";
|
||||
import "v-beautiful-mermaid/style.css";
|
||||
import "./fonts.css";
|
||||
|
||||
/** @type {import('vitepress').Theme} */
|
||||
export default {
|
||||
extends: DefaultTheme,
|
||||
Layout,
|
||||
enhanceApp({ app }) {
|
||||
installMermaid(app);
|
||||
app.component("Viewer", Viewer);
|
||||
},
|
||||
};
|
||||
15
openwiki/config.yaml
Normal file
15
openwiki/config.yaml
Normal file
@@ -0,0 +1,15 @@
|
||||
title: "OpenWiki"
|
||||
description: "知识库"
|
||||
logo: "/logo.svg"
|
||||
siteTitle: false
|
||||
appearance: false
|
||||
nav:
|
||||
- text: "知识库"
|
||||
link: "/overview"
|
||||
- text: "资料库"
|
||||
link: "/sources/"
|
||||
- text: "文档"
|
||||
link: "/docs/"
|
||||
footer:
|
||||
message: "Powered by OpenWiki"
|
||||
copyright: "© 2024"
|
||||
19
openwiki/index.md
Normal file
19
openwiki/index.md
Normal file
@@ -0,0 +1,19 @@
|
||||
---
|
||||
layout: home
|
||||
|
||||
hero:
|
||||
name: {{项目名称}}
|
||||
text: 知识库
|
||||
tagline: {{一句话说明}}
|
||||
image:
|
||||
src: /logo.png
|
||||
alt: VitePress
|
||||
|
||||
actions:
|
||||
- theme: brand
|
||||
text: 概览
|
||||
link: /overview
|
||||
- theme: alt
|
||||
text: 变更日志
|
||||
link: /log
|
||||
---
|
||||
1
openwiki/log.md
Normal file
1
openwiki/log.md
Normal file
@@ -0,0 +1 @@
|
||||
# 变更日志
|
||||
1
openwiki/nodes/index.md
Normal file
1
openwiki/nodes/index.md
Normal file
@@ -0,0 +1 @@
|
||||
# 知识点
|
||||
1
openwiki/overview.md
Normal file
1
openwiki/overview.md
Normal file
@@ -0,0 +1 @@
|
||||
# 概览
|
||||
15
openwiki/package.json
Normal file
15
openwiki/package.json
Normal file
@@ -0,0 +1,15 @@
|
||||
{
|
||||
"devDependencies": {
|
||||
"@types/js-yaml": "^4.0.9",
|
||||
"@types/node": "^25.6.0",
|
||||
"js-yaml": "^4.1.1",
|
||||
"v-beautiful-mermaid": "^0.0.3",
|
||||
"vitepress": "2.0.0-alpha.17"
|
||||
},
|
||||
"scripts": {
|
||||
"preinstall": "npx only-allow pnpm",
|
||||
"dev": "vitepress dev",
|
||||
"build": "vitepress build",
|
||||
"preview": "vitepress preview"
|
||||
}
|
||||
}
|
||||
1967
openwiki/pnpm-lock.yaml
generated
Normal file
1967
openwiki/pnpm-lock.yaml
generated
Normal file
File diff suppressed because it is too large
Load Diff
254
openwiki/scripts/check-md.js
Normal file
254
openwiki/scripts/check-md.js
Normal file
@@ -0,0 +1,254 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
const http = require('http');
|
||||
const https = require('https');
|
||||
|
||||
const ILLEGAL_CHARS = /[\s!@#$%^&*()+={}\[\]|\\:;"'<>~`]/;
|
||||
const EXTERNAL_LINK_REGEX = /\[([^\]]*)\]\((https?:\/\/[^\)]+)\)/g;
|
||||
const INTERNAL_LINK_REGEX = /\[([^\]]*)\]\((?!https?:\/\/)([^)]+)\)/g;
|
||||
const WIKILINK_REGEX = /\[\[([^\]]+)\]\]/g;
|
||||
|
||||
function showHelp() {
|
||||
console.log(`Usage: check-md.js [OPTIONS] PATHS...
|
||||
|
||||
Check Markdown files for broken links and invalid filenames.
|
||||
|
||||
PATHS... One or more directories or files to check
|
||||
(directories are scanned recursively for .md files)
|
||||
|
||||
OPTIONS:
|
||||
-h, --help Show this help message
|
||||
-v, --verbose Show detailed progress and statistics
|
||||
|
||||
EXAMPLES:
|
||||
check-md.js openwiki/
|
||||
check-md.js openwiki/ references/
|
||||
check-md.js openwiki/notes/file.md
|
||||
check-md.js folder-1/ file.md folder-2/`);
|
||||
}
|
||||
|
||||
function decodeUrlSpaces(url) {
|
||||
return url.replace(/%20/g, ' ');
|
||||
}
|
||||
|
||||
function validateFilename(filename, issues) {
|
||||
const name = path.basename(filename);
|
||||
|
||||
if (ILLEGAL_CHARS.test(name)) {
|
||||
const found = name.split('').filter(c => ILLEGAL_CHARS.test(c)).join(' ');
|
||||
issues.push(`filename has illegal characters: ${found}`);
|
||||
}
|
||||
|
||||
if (!filename.endsWith('.md') && name.includes('.')) {
|
||||
issues.push('filename has incorrect extension');
|
||||
}
|
||||
}
|
||||
|
||||
function extractLinks(content) {
|
||||
const externalLinks = [];
|
||||
const internalLinks = [];
|
||||
const wikilinks = [];
|
||||
|
||||
let match;
|
||||
|
||||
EXTERNAL_LINK_REGEX.lastIndex = 0;
|
||||
while ((match = EXTERNAL_LINK_REGEX.exec(content)) !== null) {
|
||||
externalLinks.push({ text: match[1], url: match[2], line: getLineNumber(content, match.index) });
|
||||
}
|
||||
|
||||
INTERNAL_LINK_REGEX.lastIndex = 0;
|
||||
while ((match = INTERNAL_LINK_REGEX.exec(content)) !== null) {
|
||||
internalLinks.push({ text: match[1], path: match[2], line: getLineNumber(content, match.index) });
|
||||
}
|
||||
|
||||
WIKILINK_REGEX.lastIndex = 0;
|
||||
while ((match = WIKILINK_REGEX.exec(content)) !== null) {
|
||||
wikilinks.push({ text: match[1], line: getLineNumber(content, match.index) });
|
||||
}
|
||||
|
||||
return { externalLinks, internalLinks, wikilinks };
|
||||
}
|
||||
|
||||
function getLineNumber(content, index) {
|
||||
return content.substring(0, index).split('\n').length;
|
||||
}
|
||||
|
||||
function checkExternalLink(url) {
|
||||
return new Promise((resolve) => {
|
||||
const protocol = url.startsWith('https') ? https : http;
|
||||
const parsed = new URL(url);
|
||||
|
||||
const req = protocol.request({
|
||||
hostname: parsed.hostname,
|
||||
port: parsed.port,
|
||||
path: parsed.pathname + parsed.search,
|
||||
method: 'HEAD',
|
||||
timeout: 5000
|
||||
}, (res) => {
|
||||
resolve({ status: res.statusCode, valid: res.statusCode >= 200 && res.statusCode < 400 });
|
||||
});
|
||||
|
||||
req.on('error', () => resolve({ status: 0, valid: false }));
|
||||
req.on('timeout', () => {
|
||||
req.destroy();
|
||||
resolve({ status: 0, valid: false, timeout: true });
|
||||
});
|
||||
|
||||
req.end();
|
||||
});
|
||||
}
|
||||
|
||||
async function checkFile(filepath, options) {
|
||||
const issues = [];
|
||||
const filenameIssues = [];
|
||||
|
||||
validateFilename(filepath, filenameIssues);
|
||||
if (filenameIssues.length > 0) {
|
||||
for (const issue of filenameIssues) {
|
||||
issues.push({ type: 'filename', message: issue });
|
||||
}
|
||||
}
|
||||
|
||||
const content = fs.readFileSync(filepath, 'utf-8');
|
||||
const { externalLinks, internalLinks, wikilinks } = extractLinks(content);
|
||||
|
||||
for (const wikilink of wikilinks) {
|
||||
issues.push({
|
||||
type: 'wikilink',
|
||||
message: `illegal wikilink format: [[${wikilink.text}]]`,
|
||||
line: wikilink.line
|
||||
});
|
||||
}
|
||||
|
||||
const fileDir = path.dirname(filepath);
|
||||
|
||||
for (const link of internalLinks) {
|
||||
const decodedPath = decodeUrlSpaces(link.path);
|
||||
const targetPath = path.isAbsolute(decodedPath)
|
||||
? decodedPath
|
||||
: path.resolve(fileDir, decodedPath);
|
||||
|
||||
if (!fs.existsSync(targetPath)) {
|
||||
issues.push({
|
||||
type: 'broken-link',
|
||||
message: `broken internal link: ${link.path}`,
|
||||
line: link.line
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
for (const link of externalLinks) {
|
||||
const result = await checkExternalLink(link.url);
|
||||
if (!result.valid) {
|
||||
const msg = result.timeout ? 'timeout' : `HTTP ${result.status}`;
|
||||
issues.push({
|
||||
type: 'broken-external',
|
||||
message: `broken external link: ${link.url} (${msg})`,
|
||||
line: link.line
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return { filepath, issues };
|
||||
}
|
||||
|
||||
async function walkDir(dir, callback) {
|
||||
if (!fs.existsSync(dir)) return;
|
||||
|
||||
const entries = fs.readdirSync(dir, { withFileTypes: true });
|
||||
for (const entry of entries) {
|
||||
const fullPath = path.join(dir, entry.name);
|
||||
if (entry.isDirectory()) {
|
||||
await walkDir(fullPath, callback);
|
||||
} else if (entry.isFile() && entry.name.endsWith('.md')) {
|
||||
await callback(fullPath);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function main() {
|
||||
const args = process.argv.slice(2);
|
||||
const options = { verbose: false, directories: [] };
|
||||
|
||||
for (let i = 0; i < args.length; i++) {
|
||||
const arg = args[i];
|
||||
if (arg === '-h' || arg === '--help') {
|
||||
showHelp();
|
||||
process.exit(0);
|
||||
} else if (arg === '-v' || arg === '--verbose') {
|
||||
options.verbose = true;
|
||||
} else if (!arg.startsWith('-')) {
|
||||
options.directories.push(arg);
|
||||
}
|
||||
}
|
||||
|
||||
if (options.directories.length === 0) {
|
||||
console.error('Error: No directories provided. Use -h for help.');
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const allResults = [];
|
||||
const fileQueue = [];
|
||||
|
||||
for (const target of options.directories) {
|
||||
if (fs.existsSync(target) && fs.statSync(target).isFile()) {
|
||||
if (target.endsWith('.md')) {
|
||||
fileQueue.push(target);
|
||||
}
|
||||
} else {
|
||||
await walkDir(target, async (filepath) => {
|
||||
fileQueue.push(filepath);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
let checkedCount = 0;
|
||||
for (const filepath of fileQueue) {
|
||||
const result = await checkFile(filepath, options);
|
||||
allResults.push(result);
|
||||
|
||||
if (result.issues.length > 0) {
|
||||
for (const issue of result.issues) {
|
||||
const lineInfo = issue.line ? `:${issue.line}` : '';
|
||||
console.log(`${filepath}${lineInfo}: ${issue.message}`);
|
||||
}
|
||||
} else {
|
||||
console.log(`[OK] ${filepath}`);
|
||||
}
|
||||
|
||||
checkedCount++;
|
||||
if (options.verbose) {
|
||||
process.stdout.write(`\rChecked: ${checkedCount}/${fileQueue.length} files...`);
|
||||
}
|
||||
}
|
||||
|
||||
if (options.verbose) {
|
||||
console.log('\n');
|
||||
}
|
||||
|
||||
const totalIssues = allResults.reduce((sum, r) => sum + r.issues.length, 0);
|
||||
console.log(`\n--- Summary ---`);
|
||||
console.log(`Total files checked: ${fileQueue.length}`);
|
||||
console.log(`Total issues found: ${totalIssues}`);
|
||||
if (totalIssues > 0) {
|
||||
console.log(`\n--- Issues ---`);
|
||||
for (const result of allResults) {
|
||||
if (result.issues.length > 0) {
|
||||
for (const issue of result.issues) {
|
||||
const lineInfo = issue.line ? `:${issue.line}` : '';
|
||||
console.log(`${result.filepath}${lineInfo}: ${issue.message}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
process.exit(1);
|
||||
} else {
|
||||
console.log(`All files passed!`);
|
||||
}
|
||||
}
|
||||
|
||||
main().catch(err => {
|
||||
console.error('Error:', err.message);
|
||||
process.exit(1);
|
||||
});
|
||||
162
openwiki/scripts/check-tag.js
Normal file
162
openwiki/scripts/check-tag.js
Normal file
@@ -0,0 +1,162 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
|
||||
function showHelp() {
|
||||
console.log(`Usage: check-tag.js [OPTIONS] PATHS...
|
||||
|
||||
Extract and count all tags from markdown files.
|
||||
|
||||
PATHS... One or more directories or files to check
|
||||
(directories are scanned recursively for .md files)
|
||||
|
||||
OPTIONS:
|
||||
-h, --help Show this help message
|
||||
-d, --detail Show which files each tag appears in
|
||||
|
||||
EXAMPLES:
|
||||
check-tag.js openwiki/concepts/
|
||||
check-tag.js openwiki/ references/
|
||||
check-tag.js openwiki/notes/file.md
|
||||
check-tag.js -d openwiki/concepts/`);
|
||||
}
|
||||
|
||||
function extractTags(content) {
|
||||
const tags = [];
|
||||
|
||||
let inFrontmatter = false;
|
||||
let frontmatterEnd = -1;
|
||||
|
||||
if (content.startsWith('---')) {
|
||||
const endMatch = content.indexOf('---', 3);
|
||||
if (endMatch !== -1) {
|
||||
inFrontmatter = true;
|
||||
frontmatterEnd = endMatch;
|
||||
}
|
||||
}
|
||||
|
||||
if (inFrontmatter) {
|
||||
const frontmatter = content.substring(0, frontmatterEnd);
|
||||
const tagsMatch = frontmatter.match(/^tags:\s*$/m);
|
||||
if (tagsMatch) {
|
||||
const afterTags = frontmatter.substring(tagsMatch.index + tagsMatch[0].length);
|
||||
const blockMatch = afterTags.match(/^(\s*-\s*.*(?:\n\s*-\s*.*)*)/m);
|
||||
if (blockMatch) {
|
||||
const tagLines = blockMatch[0].split('\n');
|
||||
for (const line of tagLines) {
|
||||
const tag = line.replace(/^\s*-\s*/, '').trim();
|
||||
if (tag) {
|
||||
tags.push(tag);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const arrayMatch = frontmatter.match(/tags:\s*\[([^\]]*)\]/);
|
||||
if (arrayMatch) {
|
||||
const tagList = arrayMatch[1].split(',').map(t => t.trim()).filter(t => t);
|
||||
for (const tag of tagList) {
|
||||
tags.push(tag);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const inlineRegex = /#([a-zA-Z0-9_-]+)/g;
|
||||
let match;
|
||||
while ((match = inlineRegex.exec(content)) !== null) {
|
||||
tags.push(match[1]);
|
||||
}
|
||||
|
||||
return tags;
|
||||
}
|
||||
|
||||
function collectFiles(targets) {
|
||||
const files = [];
|
||||
|
||||
for (const target of targets) {
|
||||
if (fs.existsSync(target)) {
|
||||
const stat = fs.statSync(target);
|
||||
if (stat.isFile() && target.endsWith('.md')) {
|
||||
files.push(target);
|
||||
} else if (stat.isDirectory()) {
|
||||
walkDir(target, files);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return files;
|
||||
}
|
||||
|
||||
function walkDir(dir, files) {
|
||||
const entries = fs.readdirSync(dir, { withFileTypes: true });
|
||||
for (const entry of entries) {
|
||||
const fullPath = path.join(dir, entry.name);
|
||||
if (entry.isDirectory()) {
|
||||
walkDir(fullPath, files);
|
||||
} else if (entry.isFile() && entry.name.endsWith('.md')) {
|
||||
files.push(fullPath);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function main() {
|
||||
const args = process.argv.slice(2);
|
||||
const targets = [];
|
||||
let showDetail = false;
|
||||
|
||||
for (let i = 0; i < args.length; i++) {
|
||||
const arg = args[i];
|
||||
if (arg === '-h' || arg === '--help') {
|
||||
showHelp();
|
||||
process.exit(0);
|
||||
} else if (arg === '-d' || arg === '--detail') {
|
||||
showDetail = true;
|
||||
} else if (!arg.startsWith('-')) {
|
||||
targets.push(arg);
|
||||
}
|
||||
}
|
||||
|
||||
if (targets.length === 0) {
|
||||
console.error('Error: No paths provided. Use -h for help.');
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const tagCount = new Map();
|
||||
const tagFiles = new Map();
|
||||
const files = collectFiles(targets);
|
||||
|
||||
for (const file of files) {
|
||||
try {
|
||||
const content = fs.readFileSync(file, 'utf-8');
|
||||
const tags = extractTags(content);
|
||||
const uniqueTags = [...new Set(tags)];
|
||||
for (const tag of uniqueTags) {
|
||||
tagCount.set(tag, (tagCount.get(tag) || 0) + 1);
|
||||
if (!tagFiles.has(tag)) {
|
||||
tagFiles.set(tag, []);
|
||||
}
|
||||
tagFiles.get(tag).push(file);
|
||||
}
|
||||
} catch (err) {
|
||||
// skip files that can't be read
|
||||
}
|
||||
}
|
||||
|
||||
const sorted = Array.from(tagCount.entries()).sort((a, b) => b[1] - a[1]);
|
||||
|
||||
if (showDetail) {
|
||||
for (const [tag, count] of sorted) {
|
||||
console.log(`${tag} ${count}`);
|
||||
for (const file of tagFiles.get(tag)) {
|
||||
console.log(` - ${file}`);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for (const [tag, count] of sorted) {
|
||||
console.log(`${tag} ${count}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
main();
|
||||
35
openwiki/scripts/deploy.sh
Normal file
35
openwiki/scripts/deploy.sh
Normal file
@@ -0,0 +1,35 @@
|
||||
#!/bin/bash
|
||||
|
||||
# 检查 wiki 目录是否有变更
|
||||
CHANGED_FILES=$(git diff --name-only . && git ls-files --others --exclude-standard .)
|
||||
|
||||
if [ -n "$CHANGED_FILES" ]; then
|
||||
echo "检测到 wiki 目录有变更,正在提交..."
|
||||
git add .
|
||||
git commit -m "Update wiki"
|
||||
else
|
||||
echo "wiki 目录没有变更,跳过提交"
|
||||
fi
|
||||
|
||||
# 自动获取当前 Git 分支名
|
||||
BRANCH_NAME=$(git rev-parse --abbrev-ref HEAD)
|
||||
|
||||
# 获取仓库名称
|
||||
REMOTE_URL=$(git remote get-url origin)
|
||||
REPO_NAME=$(basename -s .git "$REMOTE_URL")
|
||||
|
||||
# 拼接 Tag 名称:wiki/分支名
|
||||
TAG_NAME="wiki/$BRANCH_NAME"
|
||||
|
||||
echo "当前分支:$BRANCH_NAME"
|
||||
|
||||
# 创建本地 Tag
|
||||
git tag -f "$TAG_NAME"
|
||||
|
||||
# 推送到远程仓库
|
||||
if git push origin -f "$TAG_NAME"; then
|
||||
echo -e "\n✅ $BRANCH_NAME 创建并推送成功,等待部署完毕"
|
||||
else
|
||||
echo -e "\n❌ $BRANCH_NAME 推送失败!"
|
||||
exit 1
|
||||
fi
|
||||
1
openwiki/sources/index.md
Normal file
1
openwiki/sources/index.md
Normal file
@@ -0,0 +1 @@
|
||||
# 资料库
|
||||
21
openwiki/templates/node.md
Normal file
21
openwiki/templates/node.md
Normal file
@@ -0,0 +1,21 @@
|
||||
---
|
||||
name: "{知识点}"
|
||||
description: "{知识点描述}"
|
||||
aliases: # 别名、同义词、缩写、全名、翻译名等
|
||||
- {别称-1}
|
||||
- {别称-2}
|
||||
tags: # 仅分类标签,如:解剖、蛋白、现象、机制、人物、公司、产品等
|
||||
- {分类-1}
|
||||
- ...
|
||||
created_at: {YYYY-MM-DD} # 页面创建时间
|
||||
updated_at: {YYYY-MM-DD} # 页面更新时间
|
||||
---
|
||||
|
||||
# {知识点}
|
||||
|
||||
<!-- 正文内容 -->
|
||||
|
||||
## 相关资料
|
||||
|
||||
- [{资料摘要}](../sources/{资料摘要}.md)
|
||||
- ...
|
||||
21
openwiki/templates/overview.md
Normal file
21
openwiki/templates/overview.md
Normal file
@@ -0,0 +1,21 @@
|
||||
# 知识库概览
|
||||
|
||||
{知识库描述}
|
||||
|
||||
{知识结构逻辑}
|
||||
|
||||
## 知识地图
|
||||
|
||||
{mermaid流程图}
|
||||
|
||||
<!-- 图表中出现节点的描述 -->
|
||||
|
||||
- **节点-1**
|
||||
- {描述-1}
|
||||
- {描述-2}
|
||||
- ...
|
||||
|
||||
- **节点-2**
|
||||
- {描述-1}
|
||||
- {描述-2}
|
||||
- ...
|
||||
22
openwiki/templates/source.md
Normal file
22
openwiki/templates/source.md
Normal file
@@ -0,0 +1,22 @@
|
||||
---
|
||||
title: "{原始标题}" # 原始资料的原标题,不翻译。如:论文标题、网页文章标题
|
||||
abstract: "{原始摘要}" # 原始资料的原摘要、总结,不翻译。如:论文摘要、网页文章总结
|
||||
source: {原始地址} # 原始资料的文件路径或网址,并确保链接正常
|
||||
tags: # 仅分类标签,如:论文、专利、会议等
|
||||
- {分类-1}
|
||||
- ...
|
||||
created_at: {YYYY-MM-DD} # 页面创建时间
|
||||
updated_at: {YYYY-MM-DD} # 页面更新时间
|
||||
---
|
||||
|
||||
# {原始标题的中文翻译}
|
||||
|
||||
{原始摘要的中文翻译}
|
||||
|
||||
## 关键结论
|
||||
|
||||
- {结论-1}
|
||||
- {结论-2}
|
||||
- ...
|
||||
|
||||
<!-- 正文内容 -->
|
||||
Reference in New Issue
Block a user