hexo-theme-stellar 1.34.0 → 1.35.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/_config.yml +1 -10
- package/layout/_partial/head.ejs +0 -3
- package/layout/_partial/scripts/utils.ejs +1 -44
- package/layout/layout.ejs +0 -1
- package/package.json +4 -2
- package/scripts/events/index.js +2 -1
- package/scripts/events/lib/doc_tree.js +7 -4
- package/scripts/events/lib/merge_posts.js +3 -1
- package/scripts/events/lib/notebooks.js +5 -3
- package/scripts/events/lib/path_normalize.js +22 -0
- package/scripts/generators/search.js +3 -1
- package/scripts/helpers/json_ld.js +3 -2
- package/scripts/helpers/pretty_url.js +9 -10
- package/scripts/lib/path_utils.js +24 -0
- package/source/css/_plugins/index.styl +0 -3
- package/source/js/main.js +0 -4
- package/CLAUDE.md +0 -217
- package/docs/audits/2026-08-08-stellar-analysis.md +0 -267
- package/docs/release-process.md +0 -59
- package/layout/_plugins/pjax.ejs +0 -8
- package/source/css/_plugins/pjax.styl +0 -61
- package/source/js/plugins/pjax.js +0 -646
package/_config.yml
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
######## Stellar info ########
|
|
2
2
|
stellar:
|
|
3
|
-
version: '1.
|
|
3
|
+
version: '1.35.0'
|
|
4
4
|
homepage: 'https://xaoxuu.com/wiki/stellar/'
|
|
5
5
|
repo: 'https://github.com/xaoxuu/hexo-theme-stellar'
|
|
6
6
|
main_css: /css/main.css
|
|
@@ -537,15 +537,6 @@ plugins:
|
|
|
537
537
|
service: flying_pages # flying_pages
|
|
538
538
|
flying_pages: https://gcore.jsdelivr.net/npm/flying-pages@2/flying-pages.min.js
|
|
539
539
|
|
|
540
|
-
# pjax - 无刷新加载
|
|
541
|
-
# 启用后点击链接将使用 AJAX 加载内容,无需整页刷新
|
|
542
|
-
pjax:
|
|
543
|
-
enable: false # 还在测试中,可能存在兼容问题
|
|
544
|
-
selectors: ['title', '#l_cover', '.l_body'] # 需要替换的元素选择器
|
|
545
|
-
timeout: 10000 # 请求超时时间(毫秒)
|
|
546
|
-
cacheBust: false # 是否添加时间戳防止缓存
|
|
547
|
-
translate-distance: 8px # 动画
|
|
548
|
-
|
|
549
540
|
# https://fancyapps.com/docs/ui/fancybox/
|
|
550
541
|
# available for {% image xxx %}
|
|
551
542
|
fancybox:
|
package/layout/_partial/head.ejs
CHANGED
|
@@ -139,9 +139,6 @@ function generate_canonical() {
|
|
|
139
139
|
if (path.startsWith('/404') || path.startsWith('404')) {
|
|
140
140
|
return '';
|
|
141
141
|
}
|
|
142
|
-
if (path.endsWith('.html') && path.length > 5) {
|
|
143
|
-
path = path.split('.html')[0];
|
|
144
|
-
}
|
|
145
142
|
return `<link rel="canonical" href="https://${originalHost}${path}">`;
|
|
146
143
|
}
|
|
147
144
|
return '';
|
|
@@ -146,29 +146,12 @@
|
|
|
146
146
|
document.head.appendChild(script)
|
|
147
147
|
}),
|
|
148
148
|
|
|
149
|
-
|
|
150
|
-
_pjaxListeners: new Set(),
|
|
151
|
-
|
|
152
|
-
jq: (fn, pjax = true) => {
|
|
149
|
+
jq: (fn) => {
|
|
153
150
|
if (typeof jQuery === 'undefined') {
|
|
154
151
|
utils.js(deps.jquery).then(fn)
|
|
155
152
|
} else {
|
|
156
153
|
fn()
|
|
157
154
|
}
|
|
158
|
-
if (pjax && !fn._pjax_registered) {
|
|
159
|
-
document.addEventListener('pjax:complete', fn);
|
|
160
|
-
fn._pjax_registered = true;
|
|
161
|
-
utils._pjaxListeners.add(fn); // 追踪监听器以便清理
|
|
162
|
-
}
|
|
163
|
-
},
|
|
164
|
-
|
|
165
|
-
// 清理所有 PJAX 事件监听器
|
|
166
|
-
cleanupPjaxListeners: () => {
|
|
167
|
-
utils._pjaxListeners.forEach(fn => {
|
|
168
|
-
document.removeEventListener('pjax:complete', fn);
|
|
169
|
-
fn._pjax_registered = false;
|
|
170
|
-
});
|
|
171
|
-
utils._pjaxListeners.clear();
|
|
172
155
|
},
|
|
173
156
|
|
|
174
157
|
onLoading: (el) => {
|
|
@@ -334,20 +317,6 @@
|
|
|
334
317
|
}
|
|
335
318
|
},
|
|
336
319
|
|
|
337
|
-
// 重新初始化所有插件(用于 PJAX 等场景)
|
|
338
|
-
reinitPlugins: () => {
|
|
339
|
-
utils._pluginInitializers.forEach(plugin => {
|
|
340
|
-
// 跳过标记为不需要重新初始化的插件
|
|
341
|
-
if (plugin.options.skipReinit) return;
|
|
342
|
-
|
|
343
|
-
try {
|
|
344
|
-
plugin.fn();
|
|
345
|
-
} catch (error) {
|
|
346
|
-
console.error(`[Plugin ${plugin.name}] 重新初始化失败:`, error);
|
|
347
|
-
}
|
|
348
|
-
});
|
|
349
|
-
},
|
|
350
|
-
|
|
351
320
|
// 清理所有插件资源
|
|
352
321
|
cleanupPlugins: () => {
|
|
353
322
|
utils._pluginCleanups.forEach((cleanup, name) => {
|
|
@@ -363,21 +332,9 @@
|
|
|
363
332
|
// 清理所有资源(用于页面卸载或重置)
|
|
364
333
|
cleanupAll: () => {
|
|
365
334
|
utils.cleanupPlugins();
|
|
366
|
-
utils.cleanupPjaxListeners();
|
|
367
335
|
},
|
|
368
336
|
};
|
|
369
337
|
|
|
370
|
-
// 自动处理 PJAX 事件
|
|
371
|
-
document.addEventListener('pjax:before', () => {
|
|
372
|
-
// 在导航前清理旧的 PJAX 监听器(如果需要重新注册)
|
|
373
|
-
// 注意:这里不清理,因为监听器应该是持久的
|
|
374
|
-
// utils.cleanupPjaxListeners();
|
|
375
|
-
});
|
|
376
|
-
|
|
377
|
-
document.addEventListener('pjax:complete', () => {
|
|
378
|
-
utils.reinitPlugins();
|
|
379
|
-
});
|
|
380
|
-
|
|
381
338
|
// utils.dark.mode 当前模式 dark or light
|
|
382
339
|
// utils.dark.toggle() 暗黑模式触发器
|
|
383
340
|
// utils.dark.push(callBack[,"callBackName"]) 传入触发器回调函数
|
package/layout/layout.ejs
CHANGED
|
@@ -69,7 +69,6 @@ if (theme.style.prefers_theme === 'auto') {
|
|
|
69
69
|
html += `<div class="scripts">`
|
|
70
70
|
html += partial('_partial/scripts')
|
|
71
71
|
html += `</div>`
|
|
72
|
-
html += `<div class="page-loading-bar"><img src="${theme.default.loading}"></div>`
|
|
73
72
|
html += `</body>`
|
|
74
73
|
html += `</html>`
|
|
75
74
|
%>
|
package/package.json
CHANGED
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "hexo-theme-stellar",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.35.0",
|
|
4
4
|
"description": "Elegant and powerful theme for Hexo.",
|
|
5
5
|
"main": "package.json",
|
|
6
6
|
"scripts": {
|
|
7
|
-
"test": "echo test"
|
|
7
|
+
"test": "echo test",
|
|
8
|
+
"release": "node release.js",
|
|
9
|
+
"release:dry": "node release.js --dry-run"
|
|
8
10
|
},
|
|
9
11
|
"repository": {
|
|
10
12
|
"type": "git",
|
package/scripts/events/index.js
CHANGED
|
@@ -3,6 +3,8 @@
|
|
|
3
3
|
'use strict';
|
|
4
4
|
|
|
5
5
|
hexo.on('generateBefore', () => {
|
|
6
|
+
// 页面路径归一化:xxx.html → xxx/,必须先于所有读取 page.path 的逻辑
|
|
7
|
+
require('./lib/path_normalize')(hexo);
|
|
6
8
|
// Merge config.
|
|
7
9
|
require('./lib/config')(hexo);
|
|
8
10
|
require('./lib/links')(hexo);
|
|
@@ -65,4 +67,3 @@ hexo.extend.filter.register('before_generate', async () => {
|
|
|
65
67
|
}
|
|
66
68
|
});
|
|
67
69
|
|
|
68
|
-
|
|
@@ -4,13 +4,15 @@
|
|
|
4
4
|
|
|
5
5
|
'use strict';
|
|
6
6
|
|
|
7
|
+
const { normalize_path } = require('../../lib/path_utils');
|
|
8
|
+
|
|
7
9
|
class WikiPage {
|
|
8
10
|
constructor(page) {
|
|
9
11
|
this.id = page._id
|
|
10
12
|
this.wiki = page.wiki
|
|
11
13
|
this.title = page.title
|
|
12
14
|
this.path = page.path
|
|
13
|
-
this.path_key = page.path
|
|
15
|
+
this.path_key = normalize_path(page.path)
|
|
14
16
|
this.layout = page.layout
|
|
15
17
|
this.updated = page.updated
|
|
16
18
|
}
|
|
@@ -107,7 +109,7 @@ module.exports = ctx => {
|
|
|
107
109
|
for (let id of Object.keys(item.tree)) {
|
|
108
110
|
const sec = item.tree[id]
|
|
109
111
|
for (let key of sec) {
|
|
110
|
-
let hs = sub_pages.filter(p => p.path_key == item.base_dir + key)
|
|
112
|
+
let hs = sub_pages.filter(p => p.path_key == normalize_path(item.base_dir + key))
|
|
111
113
|
if (hs.length > 0) {
|
|
112
114
|
homepage = hs[0]
|
|
113
115
|
break
|
|
@@ -134,8 +136,9 @@ module.exports = ctx => {
|
|
|
134
136
|
for (let title of Object.keys(item.tree)) {
|
|
135
137
|
var sec = { title: title, pages: []}
|
|
136
138
|
for (let key of item.tree[title]) {
|
|
137
|
-
|
|
138
|
-
|
|
139
|
+
const pagePathKey = normalize_path(item.base_dir + key)
|
|
140
|
+
sec.pages = sec.pages.concat(sub_pages.filter(p => p.path_key == pagePathKey))
|
|
141
|
+
others = others.filter(p => p.path_key != pagePathKey)
|
|
139
142
|
}
|
|
140
143
|
sections.push(sec)
|
|
141
144
|
}
|
|
@@ -5,6 +5,8 @@
|
|
|
5
5
|
|
|
6
6
|
'use strict';
|
|
7
7
|
|
|
8
|
+
const { normalize_path } = require('../../lib/path_utils');
|
|
9
|
+
|
|
8
10
|
class RelatedPage {
|
|
9
11
|
constructor(page) {
|
|
10
12
|
this.id = page._id
|
|
@@ -12,7 +14,7 @@ class RelatedPage {
|
|
|
12
14
|
this.topic = page.topic
|
|
13
15
|
this.title = page.title
|
|
14
16
|
this.path = page.path
|
|
15
|
-
this.path_key = page.path
|
|
17
|
+
this.path_key = normalize_path(page.path)
|
|
16
18
|
this.layout = page.layout
|
|
17
19
|
this.date = page.date
|
|
18
20
|
this.updated = page.updated
|
|
@@ -4,6 +4,8 @@
|
|
|
4
4
|
|
|
5
5
|
'use strict'
|
|
6
6
|
|
|
7
|
+
const { normalize_path } = require('../../lib/path_utils');
|
|
8
|
+
|
|
7
9
|
class NotePage {
|
|
8
10
|
constructor(page) {
|
|
9
11
|
this.id = page._id
|
|
@@ -11,7 +13,7 @@ class NotePage {
|
|
|
11
13
|
this.title = page.title
|
|
12
14
|
this.tags = page.tags
|
|
13
15
|
this.path = page.path
|
|
14
|
-
this.path_key = page.path
|
|
16
|
+
this.path_key = normalize_path(page.path)
|
|
15
17
|
this.layout = page.layout
|
|
16
18
|
this.date = page.date
|
|
17
19
|
this.updated = page.updated || page.date
|
|
@@ -39,8 +41,8 @@ function prepareNotebook(id, info, ctx) {
|
|
|
39
41
|
if (notebook.base_dir.startsWith('/')) {
|
|
40
42
|
notebook.base_dir = notebook.base_dir.substring(1)
|
|
41
43
|
}
|
|
42
|
-
if (notebook.base_dir.endsWith('/')) {
|
|
43
|
-
notebook.base_dir = notebook.base_dir
|
|
44
|
+
if (notebook.base_dir.length > 1 && !notebook.base_dir.endsWith('/')) {
|
|
45
|
+
notebook.base_dir = notebook.base_dir + '/'
|
|
44
46
|
}
|
|
45
47
|
} else {
|
|
46
48
|
const notebooksBaseDir = ctx.theme.config.site_tree.notebooks.base_dir
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* 页面路径归一化:将 xxx.html 归一为目录形式 xxx/
|
|
5
|
+
* 必须在 generateBefore 最早阶段执行(先于 doc_tree / notebooks 等读取 page.path),
|
|
6
|
+
* 直接改写 Page model 存储,保证 page.permalink、JSON-LD、sitemap、search.json
|
|
7
|
+
* 与 canonical(尾斜杠格式)一致。
|
|
8
|
+
*/
|
|
9
|
+
module.exports = ctx => {
|
|
10
|
+
const data = ctx.model('Page').data || {}
|
|
11
|
+
Object.values(data).forEach(page => {
|
|
12
|
+
if (!page || typeof page.path !== 'string') {
|
|
13
|
+
return
|
|
14
|
+
}
|
|
15
|
+
if (page.layout === false || page.layout === 'false') {
|
|
16
|
+
return
|
|
17
|
+
}
|
|
18
|
+
if (page.path.endsWith('.html') && !page.path.endsWith('/index.html')) {
|
|
19
|
+
page.path = page.path.replace(/\.html$/, '/')
|
|
20
|
+
}
|
|
21
|
+
})
|
|
22
|
+
}
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
* https://github.com/wzpan/hexo-generator-search
|
|
3
3
|
*/
|
|
4
4
|
const { stripHTML } = require('hexo-util')
|
|
5
|
+
const { normalize_path } = require('../lib/path_utils')
|
|
5
6
|
|
|
6
7
|
hexo.extend.generator.register('search_json_generator', function (locals) {
|
|
7
8
|
if (this.theme.config.search.service != 'local_search') { return {} }
|
|
@@ -28,7 +29,8 @@ hexo.extend.generator.register('search_json_generator', function (locals) {
|
|
|
28
29
|
temp_post.title = post.title.trim()
|
|
29
30
|
}
|
|
30
31
|
if (post.path) {
|
|
31
|
-
|
|
32
|
+
const path = normalize_path(root + post.path)
|
|
33
|
+
temp_post.path = path === '/' ? '/' : path + '/'
|
|
32
34
|
}
|
|
33
35
|
if (cfg.content != false && post.content) {
|
|
34
36
|
var content = stripHTML(post.content.replace(/<span class="line">\d+<\/span>/g, '')).trim()
|
|
@@ -75,7 +75,8 @@ hexo.extend.helper.register('json_ld', function(args) {
|
|
|
75
75
|
|
|
76
76
|
} else if (isPage || this.is_home()) {
|
|
77
77
|
|
|
78
|
-
|
|
78
|
+
// 首页 URL 归一为带尾斜杠形式,与 canonical 保持一致
|
|
79
|
+
const url = this.is_home() ? config.url.replace(/\/?$/, '/') : this.pretty_url(page.permalink);
|
|
79
80
|
schema = {
|
|
80
81
|
'@context': 'https://schema.org',
|
|
81
82
|
'@type': 'Website',
|
|
@@ -119,4 +120,4 @@ hexo.extend.helper.register('json_ld', function(args) {
|
|
|
119
120
|
}
|
|
120
121
|
|
|
121
122
|
return `<script type="application/ld+json">${JSON.stringify(schema)}</script>`;
|
|
122
|
-
});
|
|
123
|
+
});
|
|
@@ -1,22 +1,21 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
+
const { normalize_path } = require('../lib/path_utils');
|
|
4
|
+
|
|
5
|
+
hexo.extend.helper.register('normalize_path', function (path = '') {
|
|
6
|
+
return normalize_path(path);
|
|
7
|
+
});
|
|
8
|
+
|
|
3
9
|
hexo.extend.helper.register('pretty_url', function (path = '') {
|
|
4
10
|
if (path.startsWith('http://') || path.startsWith('https://')) {
|
|
5
|
-
// 如果是绝对 URL,直接返回
|
|
6
11
|
return path;
|
|
7
12
|
}
|
|
8
|
-
|
|
9
13
|
let url = this.url_for(path);
|
|
10
14
|
|
|
11
|
-
|
|
12
|
-
url = url.replace(/\/index\.html$/, '/');
|
|
13
|
-
|
|
14
|
-
// 替换 /about.html → /about/
|
|
15
|
-
url = url.replace(/\.html$/, '/');
|
|
15
|
+
url = normalize_path(url);
|
|
16
16
|
|
|
17
|
-
//
|
|
18
|
-
|
|
19
|
-
if (!hasExtension && !url.endsWith('/')) {
|
|
17
|
+
// 添加尾 /(根路径 / 除外)
|
|
18
|
+
if (url !== '/') {
|
|
20
19
|
url += '/';
|
|
21
20
|
}
|
|
22
21
|
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* 统一路径规范化:去除 .html 后缀,输出无尾斜杠的干净路径(path_key 格式)
|
|
5
|
+
* 用于路径比较和匹配
|
|
6
|
+
*/
|
|
7
|
+
function normalize_path(path = '') {
|
|
8
|
+
if (path.startsWith('http://') || path.startsWith('https://')) {
|
|
9
|
+
return path;
|
|
10
|
+
}
|
|
11
|
+
// /index.html → /
|
|
12
|
+
path = path.replace(/\/index\.html$/, '/');
|
|
13
|
+
// 目录首页(无 .html 后缀形式,如 wiki/stellar/index)→ /
|
|
14
|
+
path = path.replace(/\/index$/, '/');
|
|
15
|
+
// /xxx.html → /xxx
|
|
16
|
+
path = path.replace(/\.html$/, '');
|
|
17
|
+
// 去除尾 /
|
|
18
|
+
if (path.length > 1 && path.endsWith('/')) {
|
|
19
|
+
path = path.slice(0, -1);
|
|
20
|
+
}
|
|
21
|
+
return path;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
module.exports = { normalize_path };
|
|
@@ -14,9 +14,6 @@ if hexo-config('plugins.copycode.enable')
|
|
|
14
14
|
@import 'copycode'
|
|
15
15
|
if hexo-config('plugins.tianli_gpt.enable')
|
|
16
16
|
@import 'tianli_gpt'
|
|
17
|
-
if hexo-config('plugins.pjax.enable')
|
|
18
|
-
@import 'pjax'
|
|
19
|
-
|
|
20
17
|
if hexo-config('plugins.katex.enable')
|
|
21
18
|
@import 'katex'
|
|
22
19
|
|
package/source/js/main.js
CHANGED
package/CLAUDE.md
DELETED
|
@@ -1,217 +0,0 @@
|
|
|
1
|
-
# Stellar Theme Development
|
|
2
|
-
|
|
3
|
-
这是 Hexo 主题 **Stellar** 的仓库。主题是一个独立的 npm 包,用于 [hexo-theme-stellar](https://github.com/xaoxuu/hexo-theme-stellar)。
|
|
4
|
-
|
|
5
|
-
## 技术栈
|
|
6
|
-
|
|
7
|
-
| 层级 | 技术 | 目录 |
|
|
8
|
-
|------|------|------|
|
|
9
|
-
| 模板引擎 | EJS | `layout/` |
|
|
10
|
-
| CSS 预处理 | Stylus | `source/css/` |
|
|
11
|
-
| 服务端 JS | CommonJS (ES5) | `scripts/` |
|
|
12
|
-
| 浏览器 JS | ES5 (Babel 转译) | `source/js/` |
|
|
13
|
-
| 国际化 | YAML | `languages/` |
|
|
14
|
-
| 文档 | Markdown | `docs/` |
|
|
15
|
-
|
|
16
|
-
## 目录结构
|
|
17
|
-
|
|
18
|
-
```
|
|
19
|
-
layout/ # EJS 模板
|
|
20
|
-
├── _partial/ # 可复用组件
|
|
21
|
-
│ ├── main/ # 文章列表、导航、页脚
|
|
22
|
-
│ ├── sidebar/ # 侧栏组件
|
|
23
|
-
│ ├── cover/ # 封面
|
|
24
|
-
│ ├── comments/ # 评论系统
|
|
25
|
-
│ ├── widgets/ # 小部件
|
|
26
|
-
│ └── scripts/ # 脚本注入
|
|
27
|
-
├── _plugins/ # 可选插件片段
|
|
28
|
-
├── layout.ejs # 根布局
|
|
29
|
-
├── index.ejs # 首页
|
|
30
|
-
├── page.ejs # 通用页面
|
|
31
|
-
└── archive.ejs # 归档页
|
|
32
|
-
scripts/ # Hexo 服务端脚本
|
|
33
|
-
├── tags/ # 自定义标签 `{% tag %}`
|
|
34
|
-
│ └── lib/ # 标签实现
|
|
35
|
-
├── helpers/ # EJS 辅助函数
|
|
36
|
-
├── filters/ # Hexo 过滤器
|
|
37
|
-
├── generators/ # 页面生成器
|
|
38
|
-
├── events/ # 事件处理
|
|
39
|
-
└── commands/ # CLI 命令
|
|
40
|
-
source/ # 浏览器端资源
|
|
41
|
-
├── css/ # Stylus 样式
|
|
42
|
-
│ ├── _defines/ # 变量和函数
|
|
43
|
-
│ ├── _common/ # 通用基础样式
|
|
44
|
-
│ ├── _components/ # 组件样式
|
|
45
|
-
│ └── _plugins/ # 插件样式
|
|
46
|
-
└── js/ # 浏览器 JavaScript
|
|
47
|
-
├── plugins/ # 交互插件
|
|
48
|
-
├── services/ # 数据服务
|
|
49
|
-
└── search/ # 搜索
|
|
50
|
-
```
|
|
51
|
-
|
|
52
|
-
## EJS 模板规范
|
|
53
|
-
|
|
54
|
-
- `<% %>` 逻辑控制,`<%- %>` 输出非转义 HTML
|
|
55
|
-
- 变量声明用 `var`(IE8 兼容)
|
|
56
|
-
- 2 空格缩进,HTML 属性双引号
|
|
57
|
-
- 可复用片段提取到 `_partial/`
|
|
58
|
-
- 复杂逻辑提取到 `helpers/` 辅助函数
|
|
59
|
-
|
|
60
|
-
```ejs
|
|
61
|
-
<%
|
|
62
|
-
var items = site.posts.sort('date', -1).limit(10)
|
|
63
|
-
items.forEach(function(post) {
|
|
64
|
-
%>
|
|
65
|
-
<article>
|
|
66
|
-
<%- partial('_partial/main/post_list/post_card', {post: post}) %>
|
|
67
|
-
</article>
|
|
68
|
-
<%
|
|
69
|
-
})
|
|
70
|
-
%>
|
|
71
|
-
```
|
|
72
|
-
|
|
73
|
-
## Node.js 脚本规范
|
|
74
|
-
|
|
75
|
-
- CommonJS: `require()` / `module.exports`
|
|
76
|
-
- 文件头: `/* global hexo */` + `'use strict';`
|
|
77
|
-
- 2 空格缩进,双引号,分号结尾
|
|
78
|
-
- 标签注册: `hexo.extend.tag.register(name, handler, options)`
|
|
79
|
-
- 辅助函数注册: `hexo.extend.helper.register(name, handler)`
|
|
80
|
-
|
|
81
|
-
```js
|
|
82
|
-
/* global hexo */
|
|
83
|
-
'use strict';
|
|
84
|
-
|
|
85
|
-
module.exports = function(hexo) {
|
|
86
|
-
return function(args, content) {
|
|
87
|
-
var result = '';
|
|
88
|
-
// ...
|
|
89
|
-
return result;
|
|
90
|
-
};
|
|
91
|
-
};
|
|
92
|
-
```
|
|
93
|
-
|
|
94
|
-
## Stylus 样式规范
|
|
95
|
-
|
|
96
|
-
- 文件引入顺序: `const` → `custom` → `theme_base` → `theme_colorful` → `func`
|
|
97
|
-
- 类名和文件名: `kebab-case`
|
|
98
|
-
- 变量在 `_defines/`,通用样式在 `_common/`,组件在 `_components/`
|
|
99
|
-
- 2 空格缩进,属性后空格
|
|
100
|
-
- CSS 兼容 IE8
|
|
101
|
-
|
|
102
|
-
## 浏览器 JS 规范
|
|
103
|
-
|
|
104
|
-
- ES5 语法(Gulp Babel 转译为 ES2015+)
|
|
105
|
-
- 避免直接操作 DOM,使用主题工具函数
|
|
106
|
-
- 注释: `//` 单行,`/* */` 多行
|
|
107
|
-
|
|
108
|
-
## 工作流程
|
|
109
|
-
|
|
110
|
-
每次修改必须遵循以下流程,产物保留在仓库中:
|
|
111
|
-
|
|
112
|
-
### 1. 方案
|
|
113
|
-
|
|
114
|
-
在 `docs/` 目录下创建或更新方案文档,描述:
|
|
115
|
-
- 要解决的问题或新增的能力
|
|
116
|
-
- 技术方案和实现思路
|
|
117
|
-
- 影响范围(涉及哪些文件/模块)
|
|
118
|
-
|
|
119
|
-
### 2. 执行计划
|
|
120
|
-
|
|
121
|
-
方案通过后,列出具体执行步骤,记录在对应文档中:
|
|
122
|
-
- 改动文件清单
|
|
123
|
-
- 分步实施顺序
|
|
124
|
-
- 依赖关系
|
|
125
|
-
|
|
126
|
-
### 3. 测试
|
|
127
|
-
|
|
128
|
-
变更完成后,在自己的 Hexo 项目中集成验证:
|
|
129
|
-
- **`npm run g && npx gulp minify` 全量验证**(`scripts/` 变更必须执行:`npm run g` 发现模板渲染错误,`npx gulp minify` 发现 HTML 结构错误如多余引号等)
|
|
130
|
-
- `npm run s` 启动本地服务
|
|
131
|
-
- 检查涉及的所有页面类型(首页、文章页、Wiki 页等)
|
|
132
|
-
- 验证浏览器兼容性
|
|
133
|
-
- 测试结果记录在 `docs/` 中
|
|
134
|
-
|
|
135
|
-
### 4. 文档归档
|
|
136
|
-
|
|
137
|
-
- 方案、执行计划、测试记录保存在 `docs/` 目录
|
|
138
|
-
- 文件命名: `docs/{YYYY-MM-DD}-{功能简称}.md`
|
|
139
|
-
- 涉及逻辑变更(API、配置项、行为变化)必须同步更新仓库 Wiki
|
|
140
|
-
|
|
141
|
-
### 新增功能 Checklist
|
|
142
|
-
|
|
143
|
-
新增功能必须覆盖以下维度:
|
|
144
|
-
|
|
145
|
-
1. `layout/` — EJS 模板
|
|
146
|
-
2. `scripts/` — Hexo 标签 / 辅助函数 / 过滤器
|
|
147
|
-
3. `source/css/` — Stylus 样式
|
|
148
|
-
4. `source/js/` — 浏览器脚本(如需)
|
|
149
|
-
5. `docs/` — 方案 + 执行计划 + 测试记录
|
|
150
|
-
6. `languages/` — 国际化文案(如需新增文本)
|
|
151
|
-
|
|
152
|
-
## 组件架构
|
|
153
|
-
|
|
154
|
-
### 自定义标签开发
|
|
155
|
-
|
|
156
|
-
1. 在 `scripts/tags/lib/` 下创建标签实现文件
|
|
157
|
-
2. 在 `scripts/tags/index.js` 中注册
|
|
158
|
-
3. 在 `source/css/_components/tag-plugins/` 添加对应样式
|
|
159
|
-
4. 推荐使用 `hexo` 参数传入,避免 `require('hexo')`
|
|
160
|
-
|
|
161
|
-
### 评论系统
|
|
162
|
-
|
|
163
|
-
- 接口目录: `layout/_partial/comments/`
|
|
164
|
-
- 每个评论系统提供 `layout.ejs` 和 `script.ejs`
|
|
165
|
-
- 遵循现有的配置驱动模式
|
|
166
|
-
|
|
167
|
-
## Git 规范
|
|
168
|
-
|
|
169
|
-
```
|
|
170
|
-
<type>(<scope>): <description>
|
|
171
|
-
```
|
|
172
|
-
|
|
173
|
-
| Type | 说明 |
|
|
174
|
-
|------|------|
|
|
175
|
-
| `feat` | 新功能 |
|
|
176
|
-
| `fix` | Bug 修复 |
|
|
177
|
-
| `refactor` | 重构 |
|
|
178
|
-
| `perf` | 性能优化 |
|
|
179
|
-
| `style` | CSS/样式修改 |
|
|
180
|
-
| `docs` | 文档更新 |
|
|
181
|
-
|
|
182
|
-
## 发版规范
|
|
183
|
-
|
|
184
|
-
发版分两步:脚本完成版本号更新和推送 → CI 自动完成 npm 发布和 tag 创建。
|
|
185
|
-
|
|
186
|
-
```
|
|
187
|
-
npm-publish.sh → push main + npm → CI → npm publish + git tag
|
|
188
|
-
```
|
|
189
|
-
|
|
190
|
-
### 使用方式
|
|
191
|
-
|
|
192
|
-
```bash
|
|
193
|
-
# 正常发版(CI 自动处理 npm publish 和 tag)
|
|
194
|
-
bash npm-publish.sh 1.33.2
|
|
195
|
-
|
|
196
|
-
# 预览模式(仅显示改动,不提交/推送,执行后自动回滚)
|
|
197
|
-
bash npm-publish.sh 1.33.2 --dry-run
|
|
198
|
-
```
|
|
199
|
-
|
|
200
|
-
### AI 调用指南
|
|
201
|
-
|
|
202
|
-
1. **分析变更确定版本号**: 查看上一版本以来的 commit,按以下规则确定版本号:
|
|
203
|
-
- `x.y.z` → `x.y.(z+1)`: 仅含 fix / perf / style(修复和优化,安全升级)
|
|
204
|
-
- `x.y.z` → `x.(y+1).0`: 含 feat / refactor / breaking change(功能增减、一般重构)
|
|
205
|
-
- `x.y.z` → `(x+1).0.0`: 大型重构,用户可感知的设计调整
|
|
206
|
-
- `x.y.z` → `x.y.z-rc.N`: 测试版本
|
|
207
|
-
2. **向用户确认**: 列出版本号和变更摘要,等待用户确认后再继续
|
|
208
|
-
3. **dry-run 预览**: `bash npm-publish.sh <version> --dry-run` 检查变更是否正确
|
|
209
|
-
4. **正式执行**: `bash npm-publish.sh <version>`
|
|
210
|
-
5. **CI 自动**: 检测 `release:` commit → npm publish + tag
|
|
211
|
-
|
|
212
|
-
## 约束
|
|
213
|
-
|
|
214
|
-
- **修改 `scripts/` 目录下任何文件后,必须运行 `npm run g && npx gulp minify` 验证构建成功。** `npm run g` 做全量渲染发现模板错误,`npx gulp minify` 做 HTML 压缩发现结构错误(如多余引号)。`npm run s` 是按需渲染,不能替代全量验证。
|
|
215
|
-
- 不引入新构建系统,保持 Hexo 原生 + Gulp 后处理
|
|
216
|
-
- 不混用 EJS 和前端框架语法
|
|
217
|
-
- CSS 兼容 IE8,JS 兼容 ES2015+
|