Skip to content

Commit 508d711

Browse files
committed
feat: refactor note management and add automated config generation
- Removed the old update_notes.py script and replaced it with a new version in the scripts directory that scans for Markdown notes and generates a JavaScript configuration file. - Introduced a GitHub Actions workflow to automate the regeneration of the config.js file on pushes to the main branch. - Added a new CSS file for styling the application, including themes and responsive design. - Created a new JavaScript file for handling the front-end logic, including theme toggling, note rendering, and table of contents generation. - Updated the config.js file with the latest note paths and titles.
1 parent b0b5843 commit 508d711

File tree

8 files changed

+150
-132
lines changed

8 files changed

+150
-132
lines changed

.github/workflows/update-notes.yml

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
name: Update Notes Config
2+
3+
on:
4+
push:
5+
branches:
6+
- main
7+
paths-ignore:
8+
- assets/js/config.js
9+
workflow_dispatch:
10+
11+
permissions:
12+
contents: write
13+
14+
jobs:
15+
update-config:
16+
if: github.actor != 'github-actions[bot]'
17+
runs-on: ubuntu-latest
18+
19+
steps:
20+
- name: Checkout repository
21+
uses: actions/checkout@v4
22+
23+
- name: Set up Python
24+
uses: actions/setup-python@v5
25+
with:
26+
python-version: '3.x'
27+
28+
- name: Regenerate config.js
29+
run: python scripts/update_notes.py
30+
31+
- name: Commit updated config.js
32+
shell: bash
33+
run: |
34+
if git diff --quiet -- assets/js/config.js; then
35+
echo "assets/js/config.js is already up to date"
36+
exit 0
37+
fi
38+
39+
git config user.name "github-actions[bot]"
40+
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
41+
git add assets/js/config.js
42+
git commit -m "chore: update generated notes config"
43+
git push

README.md

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,4 +5,11 @@
55
[![GitHub Pages](https://img.shields.io/badge/GitHub-Pages-blue?logo=github)](https://luffydod.github.io/ConfigNote)
66

77
## 🔄 维护与更新
8-
本项目由个人持续维护中^_^
8+
本项目由个人持续维护中^_^
9+
10+
### 自动更新 config.js
11+
仓库已支持 GitHub Actions 自动执行 scripts/update_notes.py。
12+
13+
当 main 分支收到代码推送时,工作流会自动运行脚本并检查 assets/js/config.js 是否有变化;如果有变化,会由 Actions 自动提交回仓库。
14+
15+
工作流文件见 [.github/workflows/update-notes.yml](.github/workflows/update-notes.yml)

style.css renamed to assets/css/style.css

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -470,15 +470,15 @@ a:hover {
470470
.container {
471471
padding: 1rem;
472472
}
473-
473+
474474
.hero {
475475
padding: 2rem 0;
476476
}
477-
477+
478478
.hero-title {
479479
font-size: 2rem;
480480
}
481-
481+
482482
.glass-panel {
483483
padding: 1.5rem;
484484
}
@@ -545,12 +545,12 @@ a:hover {
545545
.note-container {
546546
flex-direction: column;
547547
}
548-
548+
549549
.toc-sidebar {
550550
width: 100%;
551551
position: static;
552552
max-height: none;
553553
margin-top: 1.5rem;
554-
order: -1;
554+
order: -1;
555555
}
556-
}
556+
}

app.js renamed to assets/js/app.js

Lines changed: 20 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ document.addEventListener('DOMContentLoaded', () => {
1515
function initTheme() {
1616
const savedTheme = localStorage.getItem('theme');
1717
const systemDark = window.matchMedia('(prefers-color-scheme: dark)').matches;
18-
18+
1919
if (savedTheme === 'dark' || (!savedTheme && systemDark)) {
2020
document.documentElement.setAttribute('data-theme', 'dark');
2121
moonIcon.classList.add('hidden');
@@ -30,10 +30,10 @@ document.addEventListener('DOMContentLoaded', () => {
3030
themeToggle.addEventListener('click', () => {
3131
const currentTheme = document.documentElement.getAttribute('data-theme');
3232
const newTheme = currentTheme === 'dark' ? 'light' : 'dark';
33-
33+
3434
document.documentElement.setAttribute('data-theme', newTheme);
3535
localStorage.setItem('theme', newTheme);
36-
36+
3737
if (newTheme === 'dark') {
3838
moonIcon.classList.add('hidden');
3939
sunIcon.classList.remove('hidden');
@@ -48,7 +48,7 @@ document.addEventListener('DOMContentLoaded', () => {
4848
if (!notesConfig || notesConfig.length === 0) {
4949
notesGrid.innerHTML = `
5050
<div style="grid-column: 1 / -1; text-align: center; color: var(--text-secondary); padding: 2rem;">
51-
没有发现笔记。请运行 <code>python3 update_notes.py</code> 来生成配置。
51+
没有发现笔记。请运行 <code>python3 scripts/update_notes.py</code> 来生成配置。
5252
</div>`;
5353
return;
5454
}
@@ -104,23 +104,23 @@ document.addEventListener('DOMContentLoaded', () => {
104104
<div class="spinner"></div>
105105
<p>加载笔记中...</p>
106106
</div>`;
107-
107+
108108
githubEditLink.href = `${GITHUB_REPO_URL}${notePath}`;
109109

110110
try {
111111
// Append a unique query param to bypass cache during development
112112
const resp = await fetch(notePath + '?v=' + new Date().getTime());
113113
if (!resp.ok) throw new Error(`HTTP error! status: ${resp.status}`);
114114
const text = await resp.text();
115-
115+
116116
// Parse Markdown to HTML
117117
const html = marked.parse(text);
118118
markdownContent.innerHTML = html;
119-
119+
120120
markdownContent.querySelectorAll('pre code').forEach((block) => {
121121
hljs.highlightElement(block);
122122
});
123-
123+
124124
buildTableOfContents();
125125
addCopyButtons();
126126
} catch (error) {
@@ -129,7 +129,7 @@ document.addEventListener('DOMContentLoaded', () => {
129129
<div style="color: #ef4444; padding: 2rem; text-align: center;">
130130
<h2>无法加载笔记 😢</h2>
131131
<p style="margin-top: 1rem;">找不到文件: <code>${notePath}</code></p>
132-
<p style="margin-top: 0.5rem; font-size: 0.9em; color: var(--text-secondary);">请检查路径是否正确,或重新生成 config.js。</p>
132+
<p style="margin-top: 0.5rem; font-size: 0.9em; color: var(--text-secondary);">请检查路径是否正确,或重新生成 assets/js/config.js。</p>
133133
</div>
134134
`;
135135
}
@@ -139,10 +139,10 @@ document.addEventListener('DOMContentLoaded', () => {
139139
function buildTableOfContents() {
140140
const tocList = document.getElementById('toc-list');
141141
if (!tocList) return;
142-
142+
143143
tocList.innerHTML = '';
144144
const headings = markdownContent.querySelectorAll('h1, h2, h3, h4');
145-
145+
146146
if (headings.length === 0) {
147147
tocList.innerHTML = '<li style="color: var(--text-secondary); font-size: 0.875rem;">无目录结构</li>';
148148
return;
@@ -155,13 +155,13 @@ document.addEventListener('DOMContentLoaded', () => {
155155
if (!id) id = `heading-${index}`;
156156
let uniqueId = id;
157157
let counter = 1;
158-
while(document.getElementById(uniqueId)) {
158+
while (document.getElementById(uniqueId)) {
159159
uniqueId = `${id}-${counter}`;
160160
counter++;
161161
}
162162
heading.id = uniqueId;
163163
}
164-
164+
165165
const level = parseInt(heading.tagName.substring(1));
166166
tocHTML += `<li class="toc-h${level}"><a href="#${heading.id}">${heading.textContent}</a></li>`;
167167
});
@@ -177,15 +177,14 @@ document.addEventListener('DOMContentLoaded', () => {
177177
const targetId = e.target.getAttribute('href').substring(1);
178178
const targetElement = document.getElementById(targetId);
179179
if (targetElement) {
180-
const yOffset = -80;
180+
const yOffset = -80;
181181
const y = targetElement.getBoundingClientRect().top + window.pageYOffset + yOffset;
182-
window.scrollTo({top: y, behavior: 'smooth'});
182+
window.scrollTo({ top: y, behavior: 'smooth' });
183183
}
184184
}
185185
});
186186
}
187187

188-
189188
// --- Code Copy ---
190189
function addCopyButtons() {
191190
const preBlocks = markdownContent.querySelectorAll('pre');
@@ -194,16 +193,16 @@ document.addEventListener('DOMContentLoaded', () => {
194193
button.className = 'copy-btn';
195194
button.title = 'Copy code';
196195
button.innerHTML = '<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="9" y="9" width="13" height="13" rx="2" ry="2"></rect><path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"></path></svg>';
197-
196+
198197
button.addEventListener('click', async () => {
199198
const codeBlock = pre.querySelector('code');
200199
if (!codeBlock) return;
201-
200+
202201
try {
203202
await navigator.clipboard.writeText(codeBlock.innerText);
204203
button.classList.add('copied');
205204
button.innerHTML = '<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="20 6 9 17 4 12"></polyline></svg>';
206-
205+
207206
setTimeout(() => {
208207
button.classList.remove('copied');
209208
button.innerHTML = '<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="9" y="9" width="13" height="13" rx="2" ry="2"></rect><path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"></path></svg>';
@@ -212,7 +211,7 @@ document.addEventListener('DOMContentLoaded', () => {
212211
console.error('Failed to copy text: ', err);
213212
}
214213
});
215-
214+
216215
pre.appendChild(button);
217216
});
218217
}
@@ -237,4 +236,4 @@ document.addEventListener('DOMContentLoaded', () => {
237236
// Bootstrap
238237
initTheme();
239238
handleRoute();
240-
});
239+
});

config.js renamed to assets/js/config.js

Lines changed: 1 addition & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,9 @@
1-
// DO NOT EDIT manually. Generated by update_notes.py
1+
// DO NOT EDIT manually. Generated by scripts/update_notes.py
22
const notesConfig = [
33
{
44
"title": "agent rules",
55
"path": "agent_rules/README.md"
66
},
7-
{
8-
"title": "OpenClaw Codebase Patterns",
9-
"path": "agent_rules/openclaw/copilot.instructions.md"
10-
},
117
{
128
"title": "😽 Docker",
139
"path": "docker/README.md"
@@ -16,10 +12,6 @@ const notesConfig = [
1612
"title": "👿 novnc",
1713
"path": "docker/novnc/README.md"
1814
},
19-
{
20-
"title": "😨 1. test",
21-
"path": "emoji4md/test.md"
22-
},
2315
{
2416
"title": "😻 git",
2517
"path": "git/README.md"
@@ -44,14 +36,6 @@ const notesConfig = [
4436
"title": "Agent Skills",
4537
"path": "skills/README.md"
4638
},
47-
{
48-
"title": "ROS2 Development Skill",
49-
"path": "skills/ros2_base.md"
50-
},
51-
{
52-
"title": "ROS2 Web Integration Skill",
53-
"path": "skills/ros2_web_integration_skill.md"
54-
},
5539
{
5640
"title": "😣 SSH",
5741
"path": "ssh/README.md"

index.html

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
<link rel="stylesheet"
1616
href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/styles/dracula.min.css">
1717
<!-- Custom CSS -->
18-
<link rel="stylesheet" href="./style.css">
18+
<link rel="stylesheet" href="./assets/css/style.css">
1919
</head>
2020

2121
<body>
@@ -104,8 +104,8 @@ <h1 class="hero-title">📝 ConfigNote</h1>
104104
<!-- Scripts -->
105105
<script src="https://cdnjs.cloudflare.com/ajax/libs/marked/12.0.1/marked.min.js"></script>
106106
<script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/highlight.min.js"></script>
107-
<script src="./config.js"></script>
108-
<script src="./app.js"></script>
107+
<script src="./assets/js/config.js"></script>
108+
<script src="./assets/js/app.js"></script>
109109
</body>
110110

111111
</html>

scripts/update_notes.py

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
#!/usr/bin/env python3
2+
import json
3+
import os
4+
from pathlib import Path
5+
6+
# 忽略的目录和文件
7+
IGNORE_DIRS = {'.git', 'docs', 'js', 'css', 'assets', 'public', 'scripts'}
8+
README_FILENAME = 'README.md'
9+
REPO_ROOT = Path(__file__).resolve().parent.parent
10+
CONFIG_OUTPUT_PATH = REPO_ROOT / 'assets' / 'js' / 'config.js'
11+
12+
13+
def get_markdown_title(filepath):
14+
"""尝试从 markdown 文件中读取第一个一级标题作为标题。"""
15+
try:
16+
with open(filepath, 'r', encoding='utf-8') as file:
17+
for line in file:
18+
if line.startswith('# '):
19+
return line[2:].strip()
20+
except Exception as error:
21+
print(f"Error reading {filepath}: {error}")
22+
23+
return Path(filepath).parent.name or Path(filepath).name
24+
25+
26+
def scan_notes(root_dir):
27+
notes = []
28+
29+
for dirpath, dirnames, filenames in os.walk(root_dir):
30+
current_dir = Path(dirpath)
31+
dirnames[:] = [name for name in dirnames if not name.startswith('.') and name not in IGNORE_DIRS]
32+
33+
if current_dir == root_dir:
34+
continue
35+
36+
if README_FILENAME not in filenames:
37+
continue
38+
39+
filepath = current_dir / README_FILENAME
40+
rel_path = filepath.relative_to(root_dir).as_posix()
41+
42+
notes.append({
43+
'title': get_markdown_title(filepath),
44+
'path': rel_path,
45+
})
46+
47+
notes.sort(key=lambda note: note['path'])
48+
return notes
49+
50+
51+
def generate_config_js(notes, output_path=CONFIG_OUTPUT_PATH):
52+
"""生成给前端使用的 JS 配置文件。"""
53+
output_path.parent.mkdir(parents=True, exist_ok=True)
54+
config_content = (
55+
"// DO NOT EDIT manually. Generated by scripts/update_notes.py\n"
56+
f"const notesConfig = {json.dumps(notes, ensure_ascii=False, indent=4)};\n"
57+
)
58+
59+
with open(output_path, 'w', encoding='utf-8') as file:
60+
file.write(config_content)
61+
62+
print(f"✅ 成功生成配置,共找到 {len(notes)} 篇笔记。")
63+
print(f"数据已写入到 {output_path}")
64+
65+
66+
if __name__ == '__main__':
67+
print('开始扫描 Markdown 笔记...')
68+
notes = scan_notes(REPO_ROOT)
69+
generate_config_js(notes)

0 commit comments

Comments
 (0)