hexo-theme-stellar 1.39.0 → 1.40.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/.claude/skills/stellar-theme-dev/SKILL.md +2 -2
- package/CHANGELOG.md +44 -0
- package/_config.yml +8 -2
- package/languages/en.yml +2 -0
- package/languages/zh-CN.yml +2 -0
- package/languages/zh-TW.yml +2 -0
- package/layout/_partial/comments/artalk/script.ejs +8 -0
- package/layout/_partial/cover/post_cover.ejs +1 -1
- package/layout/_partial/head.ejs +11 -2
- package/layout/_partial/main/article/article_footer.ejs +13 -10
- package/layout/_partial/main/navbar/article_banner.ejs +7 -0
- package/layout/_partial/main/navbar/dateinfo.ejs +1 -1
- package/layout/_partial/main/navbar/nav_tabs_blog.ejs +6 -3
- package/layout/_partial/main/navbar/nav_tabs_wiki.ejs +1 -1
- package/layout/_partial/main/pin_slider.ejs +11 -12
- package/layout/_partial/main/post_list/post_card.ejs +24 -13
- package/layout/_partial/sidebar/index_leftbar.ejs +2 -2
- package/layout/_partial/widgets/markdown.ejs +1 -1
- package/layout/_partial/widgets/toc.ejs +8 -1
- package/layout/categories.ejs +18 -8
- package/layout/index.ejs +67 -11
- package/layout/page.ejs +5 -1
- package/package.json +5 -3
- package/scripts/commands/new-note.js +2 -2
- package/scripts/helpers/category_tree.js +11 -0
- package/scripts/helpers/escape_html.js +9 -0
- package/scripts/helpers/json_ld.js +5 -0
- package/scripts/helpers/mdrender.js +29 -0
- package/scripts/helpers/reading_time.js +12 -0
- package/scripts/helpers/related_posts.js +9 -5
- package/scripts/lib/category_tree.js +26 -0
- package/scripts/lib/mdrender_html.js +97 -0
- package/scripts/lib/reading_time.js +16 -0
- package/scripts/lib/wiki_readme.js +57 -0
- package/scripts/tags/index.js +2 -0
- package/scripts/tags/lib/md.js +17 -9
- package/scripts/tags/lib/table.js +22 -0
- package/scripts/tags/lib/timeline.js +9 -9
- package/scripts/tags/lib/tip.js +23 -0
- package/source/css/_common/device.styl +3 -0
- package/source/css/_components/list.styl +11 -5
- package/source/css/_components/pages/archives.styl +7 -3
- package/source/css/_components/partial/bread-nav.styl +9 -1
- package/source/css/_components/sidebar/search.styl +6 -4
- package/source/css/_components/sidebar/sidebar.styl +10 -7
- package/source/css/_components/tag-plugins/table.styl +37 -0
- package/source/css/_components/tag-plugins/tip.styl +27 -0
- package/source/css/_custom.styl +1 -1
- package/source/css/_defines/func.styl +19 -13
- package/source/css/_defines/theme_base.styl +3 -0
- package/source/js/main.js +92 -1
- package/source/js/services/mdrender.js +116 -7
- package/source/js/services/memos.js +60 -8
- package/source/js/services/waline_latest_comment.js +3 -1
package/layout/page.ejs
CHANGED
|
@@ -44,8 +44,12 @@ function layoutDiv() {
|
|
|
44
44
|
el += partial('_partial/main/navbar/article_banner')
|
|
45
45
|
}
|
|
46
46
|
el += `<article class="${articleClass()}">`
|
|
47
|
-
if (page.content && page.content.length > 0) {
|
|
47
|
+
if (page.content && String(page.content).trim().length > 0) {
|
|
48
48
|
el += page.content
|
|
49
|
+
} else {
|
|
50
|
+
// wiki 项目首页正文为空(剪裁空白后)且配置了 repo 时,
|
|
51
|
+
// 以该仓库 README.md 作为主页正文(应用层 helper,复用底层远程 md 渲染组件)
|
|
52
|
+
el += wiki_readme_html(theme.wiki.tree[page.wiki], page)
|
|
49
53
|
}
|
|
50
54
|
if (notebook) {
|
|
51
55
|
el += partial('_partial/main/notebook/note_tags', { notebook: notebook })
|
package/package.json
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "hexo-theme-stellar",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.40.0",
|
|
4
4
|
"description": "Elegant and powerful theme for Hexo.",
|
|
5
5
|
"main": "package.json",
|
|
6
6
|
"scripts": {
|
|
7
7
|
"lint": "eslint .",
|
|
8
|
-
"test": "node --test",
|
|
8
|
+
"test": "node ci/check-require-decls.js && node --test",
|
|
9
9
|
"check": "npm run lint && npm test && python3 docs/knowledge/tools/verify.py",
|
|
10
10
|
"release": "node release.js",
|
|
11
11
|
"release:dry": "node release.js --dry-run"
|
|
@@ -29,10 +29,12 @@
|
|
|
29
29
|
"glob": "^10.4.0",
|
|
30
30
|
"hexo-renderer-ejs": "^2.0.0",
|
|
31
31
|
"hexo-renderer-stylus": "^3.0.1",
|
|
32
|
+
"hexo-pagination": "^3.0.0",
|
|
32
33
|
"probe-image-size": "^7.2.3"
|
|
33
34
|
},
|
|
34
35
|
"homepage": "https://xaoxuu.com/wiki/stellar/",
|
|
35
36
|
"devDependencies": {
|
|
36
|
-
"eslint": "^9.39.5"
|
|
37
|
+
"eslint": "^9.39.5",
|
|
38
|
+
"hexo-util": "^4.0.0"
|
|
37
39
|
}
|
|
38
40
|
}
|
|
@@ -36,7 +36,7 @@ hexo.extend.console.register('new-note', 'Create a new note', {
|
|
|
36
36
|
const frontMatter = [
|
|
37
37
|
'---',
|
|
38
38
|
`title: ${name}`,
|
|
39
|
-
`date: ${date.getFullYear()}-${String(date.getMonth()+1).padStart(2,'0')}-${String(date.getDate()).padStart(2,'0')} ${String(date.getHours()).padStart(2,'0')}:${String(date.getMinutes()).padStart(2,'0')}
|
|
39
|
+
`date: ${date.getFullYear()}-${String(date.getMonth()+1).padStart(2,'0')}-${String(date.getDate()).padStart(2,'0')} ${String(date.getHours()).padStart(2,'0')}:${String(date.getMinutes()).padStart(2,'0')}`,
|
|
40
40
|
`tags: [${tags.map(t => '"'+t+'"').join(', ')}]`,
|
|
41
41
|
`notebook: ${book}`,
|
|
42
42
|
'---',
|
|
@@ -46,4 +46,4 @@ hexo.extend.console.register('new-note', 'Create a new note', {
|
|
|
46
46
|
fs.writeFileSync(filePath, frontMatter);
|
|
47
47
|
console.log(`Created: ${filePath}`);
|
|
48
48
|
callback();
|
|
49
|
-
});
|
|
49
|
+
});
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/* global hexo */
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
const { buildCategoryTree } = require('../lib/category_tree');
|
|
5
|
+
|
|
6
|
+
hexo.extend.helper.register('category_tree', function(categories) {
|
|
7
|
+
const list = categories && typeof categories.toArray === 'function'
|
|
8
|
+
? categories.toArray()
|
|
9
|
+
: (categories || []);
|
|
10
|
+
return buildCategoryTree(list);
|
|
11
|
+
});
|
|
@@ -98,6 +98,11 @@ hexo.extend.helper.register('json_ld', function(args) {
|
|
|
98
98
|
|
|
99
99
|
if (page.excerpt || page.description) {
|
|
100
100
|
schema.description = this.strip_html(page.description || page.excerpt);
|
|
101
|
+
} else if (page.wiki) {
|
|
102
|
+
const proj = this.theme.wiki.tree[page.wiki];
|
|
103
|
+
if (proj && proj.description) {
|
|
104
|
+
schema.description = proj.description;
|
|
105
|
+
}
|
|
101
106
|
} else {
|
|
102
107
|
schema.description = util.truncate(this.strip_html(page.content), {length: 200});
|
|
103
108
|
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/* global hexo */
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
const { mdrenderHtml, PLACEHOLDER_CLASS } = require('../lib/mdrender_html');
|
|
5
|
+
const { wikiReadmeHtml, isWikiReadmePage } = require('../lib/wiki_readme');
|
|
6
|
+
|
|
7
|
+
// 远程 Markdown 渲染占位标记(底层组件服务端入口,供 EJS 模板复用)
|
|
8
|
+
hexo.extend.helper.register('mdrender_html', function (src, options = {}) {
|
|
9
|
+
if (options.ghraw == null) {
|
|
10
|
+
options.ghraw = hexo.theme.config.api_host.ghraw;
|
|
11
|
+
}
|
|
12
|
+
return mdrenderHtml(src, options);
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
// wiki 项目 README 主页占位(首页正文为空且配置 repo 时输出,否则空串)
|
|
16
|
+
hexo.extend.helper.register('wiki_readme_html', function (proj, page) {
|
|
17
|
+
return wikiReadmeHtml(proj, page, { ghraw: hexo.theme.config.api_host.ghraw });
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
// 页面是否包含客户端渲染的远程 md(供 TOC 预留空容器)
|
|
21
|
+
hexo.extend.helper.register('has_remote_md', function (page) {
|
|
22
|
+
if (!page) {
|
|
23
|
+
return false;
|
|
24
|
+
}
|
|
25
|
+
if (page.content && page.content.indexOf(PLACEHOLDER_CLASS) >= 0) {
|
|
26
|
+
return true;
|
|
27
|
+
}
|
|
28
|
+
return isWikiReadmePage(hexo.theme.config.wiki.tree[page.wiki], page);
|
|
29
|
+
});
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/* global hexo */
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
const { countWords, readingMinutes } = require('../lib/reading_time');
|
|
5
|
+
|
|
6
|
+
hexo.extend.helper.register('word_count', function(page) {
|
|
7
|
+
return countWords(page && (page.content || page._content || ''));
|
|
8
|
+
});
|
|
9
|
+
|
|
10
|
+
hexo.extend.helper.register('reading_time', function(page) {
|
|
11
|
+
return readingMinutes(page && (page.content || page._content || ''));
|
|
12
|
+
});
|
|
@@ -6,6 +6,10 @@
|
|
|
6
6
|
|
|
7
7
|
var util = require('hexo-util');
|
|
8
8
|
|
|
9
|
+
function esc(v) {
|
|
10
|
+
return util.escapeHTML(v == null ? '' : String(v))
|
|
11
|
+
}
|
|
12
|
+
|
|
9
13
|
hexo.extend.helper.register('popular_posts_wrapper', function(args){
|
|
10
14
|
const title = args.title;
|
|
11
15
|
const json = args.json.json;
|
|
@@ -18,7 +22,7 @@ hexo.extend.helper.register('popular_posts_wrapper', function(args){
|
|
|
18
22
|
var returnHTML = "";
|
|
19
23
|
var div = `
|
|
20
24
|
<section class='header'>
|
|
21
|
-
<div class='title cap theme'>${title}</div>
|
|
25
|
+
<div class='title cap theme'>${esc(title)}</div>
|
|
22
26
|
</section>
|
|
23
27
|
<section class='body'>
|
|
24
28
|
`;
|
|
@@ -28,14 +32,14 @@ hexo.extend.helper.register('popular_posts_wrapper', function(args){
|
|
|
28
32
|
|
|
29
33
|
function listItem(obj){
|
|
30
34
|
var el = '';
|
|
31
|
-
el += '<a class="item" href="' + obj.path + '" title="' + obj.title + '">';
|
|
35
|
+
el += '<a class="item" href="' + esc(obj.path) + '" title="' + esc(obj.title) + '">';
|
|
32
36
|
var p = posts.filter(p => root + p.path == obj.path)
|
|
33
37
|
if (p && p.length > 0) {
|
|
34
38
|
p = p.data[0];
|
|
35
39
|
}
|
|
36
|
-
el += '<span class="title">' + obj.title + '</span>';
|
|
40
|
+
el += '<span class="title">' + esc(obj.title) + '</span>';
|
|
37
41
|
if (obj.excerpt && obj.excerpt.length > 0) {
|
|
38
|
-
el += '<span class="excerpt">' + util.truncate(util.stripHTML(obj.excerpt), {length: 120}) + '</span>';
|
|
42
|
+
el += '<span class="excerpt">' + esc(util.truncate(util.stripHTML(obj.excerpt), {length: 120})) + '</span>';
|
|
39
43
|
}
|
|
40
44
|
el += '</a>';
|
|
41
45
|
return el;
|
|
@@ -47,7 +51,7 @@ hexo.extend.helper.register('popular_posts_wrapper', function(args){
|
|
|
47
51
|
}
|
|
48
52
|
}
|
|
49
53
|
|
|
50
|
-
if (returnHTML != "") returnHTML = "<div class=\"" + cls + "\">" + returnHTML + "</div>";
|
|
54
|
+
if (returnHTML != "") returnHTML = "<div class=\"" + esc(cls) + "\">" + returnHTML + "</div>";
|
|
51
55
|
div += returnHTML;
|
|
52
56
|
div += '</section>';
|
|
53
57
|
return div;
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
// 分类嵌套树构建(#564):基于 parent 关系递归组装,兼容 2/3/4 级
|
|
4
|
+
function buildCategoryTree(list) {
|
|
5
|
+
const items = (list || []).map(c => ({
|
|
6
|
+
_id: c._id,
|
|
7
|
+
name: c.name,
|
|
8
|
+
path: c.path,
|
|
9
|
+
parentId: c.parent ? (typeof c.parent === 'object' ? c.parent._id : c.parent) : null,
|
|
10
|
+
count: (c.posts && c.posts.length) || 0,
|
|
11
|
+
children: []
|
|
12
|
+
}));
|
|
13
|
+
const byId = {};
|
|
14
|
+
items.forEach(item => { byId[item._id] = item; });
|
|
15
|
+
const roots = [];
|
|
16
|
+
items.forEach(item => {
|
|
17
|
+
if (item.parentId != null && byId[item.parentId]) {
|
|
18
|
+
byId[item.parentId].children.push(item);
|
|
19
|
+
} else {
|
|
20
|
+
roots.push(item);
|
|
21
|
+
}
|
|
22
|
+
});
|
|
23
|
+
return roots;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
module.exports = { buildCategoryTree };
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* mdrender_html.js v2 | https://github.com/xaoxuu/hexo-theme-stellar/
|
|
3
|
+
*
|
|
4
|
+
* 远程 Markdown 渲染底层组件(通用能力,不包含应用场景逻辑):
|
|
5
|
+
* 统一生成 .data-service.ds-mdrender 占位标记。
|
|
6
|
+
*
|
|
7
|
+
* 行为:
|
|
8
|
+
* - src 为 GitHub raw 类地址时,host 替换为调用方传入的 ghraw
|
|
9
|
+
* (主题配置 api_host.ghraw,代码不设默认值);未传 ghraw 时保留 src 原 host;
|
|
10
|
+
* - 识别后输出 data-base(该 md 所在 raw 目录);
|
|
11
|
+
* - 非 GitHub src 原样输出、不带 data-base;
|
|
12
|
+
* - replace=true 输出 data-replace(客户端原地替换,无外部容器);
|
|
13
|
+
* - heading 缺省 true,输出 data-heading(客户端标题适配本地文章格式)。
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
'use strict';
|
|
17
|
+
|
|
18
|
+
// GitHub raw 规范 host:用于识别用户 src 中的规范 raw 地址(GitHub 固有事实,非配置默认值)
|
|
19
|
+
const GITHUB_RAW_HOST = 'raw.githubusercontent.com';
|
|
20
|
+
|
|
21
|
+
// 占位标记类名(组件契约:服务端生成、services 按需加载、客户端渲染共用)
|
|
22
|
+
const PLACEHOLDER_CLASS = 'ds-mdrender';
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* 解析 GitHub raw 地址(规范 raw host 或调用方传入的 ghraw 镜像 host)。
|
|
26
|
+
* 返回 { host, owner, repo, ref, rest };无法识别时返回 null。
|
|
27
|
+
*/
|
|
28
|
+
function parseGitHubRaw(url, ghrawHost) {
|
|
29
|
+
if (typeof url !== 'string' || url.length === 0) {
|
|
30
|
+
return null;
|
|
31
|
+
}
|
|
32
|
+
let u;
|
|
33
|
+
try {
|
|
34
|
+
u = new URL(url);
|
|
35
|
+
} catch (e) {
|
|
36
|
+
return null;
|
|
37
|
+
}
|
|
38
|
+
const host = u.hostname || '';
|
|
39
|
+
if (host !== GITHUB_RAW_HOST && host !== ghrawHost) {
|
|
40
|
+
return null;
|
|
41
|
+
}
|
|
42
|
+
const segs = u.pathname.split('/').filter(s => s.length > 0);
|
|
43
|
+
if (segs.length < 3) {
|
|
44
|
+
return null;
|
|
45
|
+
}
|
|
46
|
+
return {
|
|
47
|
+
host: host,
|
|
48
|
+
owner: segs[0],
|
|
49
|
+
repo: segs[1],
|
|
50
|
+
ref: segs[2],
|
|
51
|
+
rest: segs.slice(3).join('/')
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function escapeAttr(value) {
|
|
56
|
+
return String(value).replace(/&/g, '&').replace(/"/g, '"');
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* 生成远程 Markdown 渲染占位标记。
|
|
61
|
+
* options: { id, replace, heading, ghraw }
|
|
62
|
+
*/
|
|
63
|
+
function mdrenderHtml(src, options = {}) {
|
|
64
|
+
const parsed = parseGitHubRaw(src, options.ghraw);
|
|
65
|
+
let url = src;
|
|
66
|
+
let base = null;
|
|
67
|
+
if (parsed) {
|
|
68
|
+
// 未传 ghraw 时保留 src 原 host,不硬编码默认值
|
|
69
|
+
const ghraw = options.ghraw || parsed.host;
|
|
70
|
+
const dir = `${parsed.owner}/${parsed.repo}/${parsed.ref}/`;
|
|
71
|
+
url = `https://${ghraw}/${dir}${parsed.rest}`;
|
|
72
|
+
base = `https://${ghraw}/${dir}`;
|
|
73
|
+
}
|
|
74
|
+
let el = '<div class="data-service ds-mdrender"';
|
|
75
|
+
el += ` src="${escapeAttr(url)}"`;
|
|
76
|
+
if (base) {
|
|
77
|
+
el += ` data-base="${escapeAttr(base)}"`;
|
|
78
|
+
}
|
|
79
|
+
if (options.replace === true) {
|
|
80
|
+
el += ' data-replace="true"';
|
|
81
|
+
}
|
|
82
|
+
if (options.heading !== false) {
|
|
83
|
+
el += ' data-heading="true"';
|
|
84
|
+
}
|
|
85
|
+
if (options.id) {
|
|
86
|
+
el += ` id="${escapeAttr(options.id)}"`;
|
|
87
|
+
}
|
|
88
|
+
el += '></div>';
|
|
89
|
+
return el;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
module.exports = {
|
|
93
|
+
GITHUB_RAW_HOST,
|
|
94
|
+
PLACEHOLDER_CLASS,
|
|
95
|
+
parseGitHubRaw,
|
|
96
|
+
mdrenderHtml
|
|
97
|
+
};
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
// 阅读信息统计(#302):中文字符按 1 字计,英文/数字按空格分词计词
|
|
4
|
+
function countWords(content) {
|
|
5
|
+
const text = String(content || '').replace(/<[^>]*>/g, ' ');
|
|
6
|
+
const cjk = (text.match(/[\u4e00-\u9fff]/g) || []).length;
|
|
7
|
+
const latin = (text.replace(/[\u4e00-\u9fff]/g, ' ').match(/[A-Za-z0-9]+(?:[-'][A-Za-z0-9]+)*/g) || []).length;
|
|
8
|
+
return cjk + latin;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
// 混合阅读速度约 300 字/分钟,最少 1 分钟
|
|
12
|
+
function readingMinutes(content, speed = 300) {
|
|
13
|
+
return Math.max(1, Math.ceil(countWords(content) / speed));
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
module.exports = { countWords, readingMinutes };
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* wiki_readme.js v1 | https://github.com/xaoxuu/hexo-theme-stellar/
|
|
3
|
+
*
|
|
4
|
+
* Wiki 应用层:项目首页正文为空(剪裁空白后)且配置 repo 时,
|
|
5
|
+
* 以该仓库 README.md 作为主页正文(复用底层远程 md 渲染组件)。
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
'use strict';
|
|
9
|
+
|
|
10
|
+
const { mdrenderHtml } = require('./mdrender_html');
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* README 地址。host 由调用方从主题配置 api_host.ghraw 传入;
|
|
14
|
+
* branch 缺省用 GitHub HEAD(自动指向默认分支)。
|
|
15
|
+
*/
|
|
16
|
+
function readmeUrl(repo, branch, host) {
|
|
17
|
+
return `https://${host}/${repo}/${branch || 'HEAD'}/README.md`;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
// 页面正文是否为空(剪裁多余空行、空格后判断)
|
|
21
|
+
function isEmptyContent(page) {
|
|
22
|
+
return !page || !String(page.content || '').trim();
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* 判定是否 README 主页:配置了 repo 的项目首页且正文为空。
|
|
27
|
+
*/
|
|
28
|
+
function isWikiReadmePage(proj, page) {
|
|
29
|
+
if (!proj || !proj.repo || !page || !proj.homepage) {
|
|
30
|
+
return false;
|
|
31
|
+
}
|
|
32
|
+
if (!isEmptyContent(page)) {
|
|
33
|
+
return false;
|
|
34
|
+
}
|
|
35
|
+
return proj.homepage.path === page.path;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* 生成 README 主页占位(原地替换模式);不适用时返回空串。
|
|
40
|
+
* options: { ghraw }
|
|
41
|
+
*/
|
|
42
|
+
function wikiReadmeHtml(proj, page, options = {}) {
|
|
43
|
+
if (!isWikiReadmePage(proj, page)) {
|
|
44
|
+
return '';
|
|
45
|
+
}
|
|
46
|
+
return mdrenderHtml(readmeUrl(proj.repo, proj.branch, options.ghraw), {
|
|
47
|
+
ghraw: options.ghraw,
|
|
48
|
+
replace: true
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
module.exports = {
|
|
53
|
+
readmeUrl,
|
|
54
|
+
isEmptyContent,
|
|
55
|
+
isWikiReadmePage,
|
|
56
|
+
wikiReadmeHtml
|
|
57
|
+
};
|
package/scripts/tags/index.js
CHANGED
|
@@ -48,6 +48,8 @@ hexo.extend.tag.register('audio', require('./lib/audio')(hexo))
|
|
|
48
48
|
hexo.extend.tag.register('video', require('./lib/video')(hexo))
|
|
49
49
|
hexo.extend.tag.register('rating', require('./lib/rating')(hexo))
|
|
50
50
|
hexo.extend.tag.register('vote', require('./lib/vote')(hexo))
|
|
51
|
+
hexo.extend.tag.register('table', require('./lib/table')(hexo), {ends: true})
|
|
52
|
+
hexo.extend.tag.register('tip', require('./lib/tip')(hexo), {ends: true})
|
|
51
53
|
|
|
52
54
|
// read 阅读类
|
|
53
55
|
hexo.extend.tag.register('reel', require('./lib/read/reel')(hexo), true)
|
package/scripts/tags/lib/md.js
CHANGED
|
@@ -1,20 +1,28 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* md.js
|
|
3
|
-
*
|
|
4
|
-
*
|
|
2
|
+
* md.js v2 | https://github.com/xaoxuu/hexo-theme-stellar/
|
|
3
|
+
*
|
|
5
4
|
* 格式与官方标签插件一致使用空格分隔,中括号内的是可选参数(中括号不需要写出来)
|
|
6
5
|
*
|
|
7
|
-
* {% md src %}
|
|
6
|
+
* {% md src [wrap:true|false] %}
|
|
7
|
+
*
|
|
8
|
+
* wrap 默认 true:渲染结果保留在 .data-service.ds-mdrender 容器内(现状);
|
|
9
|
+
* wrap:false:渲染后占位元素被原地替换,无外部容器(复用底层 data-replace)。
|
|
8
10
|
*
|
|
11
|
+
* 复用底层组件(scripts/lib/mdrender_html.js)输出渲染占位;
|
|
12
|
+
* 容器结构与现状一致,标题默认适配本地文章格式(id + headerlink,h1 隐藏)。
|
|
9
13
|
*/
|
|
10
14
|
'use strict'
|
|
11
15
|
|
|
16
|
+
const { mdrenderHtml } = require('../../lib/mdrender_html')
|
|
17
|
+
|
|
12
18
|
var md_index = 0
|
|
13
19
|
|
|
14
20
|
module.exports = ctx => function(args) {
|
|
15
|
-
args = ctx.args.map(args, [''], ['src'])
|
|
21
|
+
args = ctx.args.map(args, ['wrap'], ['src'])
|
|
16
22
|
const md_id = "md_" + ++md_index
|
|
17
|
-
return
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
23
|
+
return mdrenderHtml(args.src, {
|
|
24
|
+
id: md_id,
|
|
25
|
+
ghraw: ctx.theme.config.api_host.ghraw,
|
|
26
|
+
replace: args.wrap === 'false'
|
|
27
|
+
})
|
|
28
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* table.js v1.0 | https://github.com/xaoxuu/hexo-theme-stellar/
|
|
3
|
+
* 格式与官方标签插件一致使用空格分隔,中括号内的是可选参数(中括号不需要写出来)
|
|
4
|
+
*
|
|
5
|
+
* {% table [style:scroll|wrap|compact] %}
|
|
6
|
+
* | 列1 | 列2 |
|
|
7
|
+
* | --- | --- |
|
|
8
|
+
* | a | b |
|
|
9
|
+
* {% endtable %}
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
'use strict'
|
|
13
|
+
|
|
14
|
+
module.exports = ctx => function(args, content = '') {
|
|
15
|
+
args = ctx.args.map(args, ['style'], [])
|
|
16
|
+
const style = ['scroll', 'wrap', 'compact'].includes(args.style) ? args.style : 'scroll'
|
|
17
|
+
var el = ''
|
|
18
|
+
el += `<div class="tag-plugin table table-${style}">`
|
|
19
|
+
el += ctx.render.renderSync({text: content, engine: 'markdown'})
|
|
20
|
+
el += '</div>'
|
|
21
|
+
return el
|
|
22
|
+
}
|
|
@@ -14,15 +14,15 @@
|
|
|
14
14
|
|
|
15
15
|
'use strict'
|
|
16
16
|
|
|
17
|
-
function layoutNodeTitle(ctx, content) {
|
|
18
|
-
var el = ''
|
|
19
|
-
el += '<div class="header">'
|
|
20
|
-
if (content && content.length > 0) {
|
|
21
|
-
el += `<span>${content}</span>`
|
|
22
|
-
}
|
|
23
|
-
el += '</div>'
|
|
24
|
-
return el
|
|
25
|
-
}
|
|
17
|
+
function layoutNodeTitle(ctx, content) {
|
|
18
|
+
var el = ''
|
|
19
|
+
el += '<div class="header">'
|
|
20
|
+
if (content && content.length > 0) {
|
|
21
|
+
el += `<span>${ctx.render.renderSync({text: content, engine: 'markdown'}).split('\n').join('').replace(/<\/?p>/g, '')}</span>`
|
|
22
|
+
}
|
|
23
|
+
el += '</div>'
|
|
24
|
+
return el
|
|
25
|
+
}
|
|
26
26
|
|
|
27
27
|
function layoutNodeContent(ctx, content) {
|
|
28
28
|
var el = ''
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* tip.js v1.0 | https://github.com/xaoxuu/hexo-theme-stellar/
|
|
3
|
+
* 格式与官方标签插件一致使用空格分隔,中括号内的是可选参数(中括号不需要写出来)
|
|
4
|
+
*
|
|
5
|
+
* {% tip text:注解内容 %}词句{% endtip %}
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
'use strict'
|
|
9
|
+
|
|
10
|
+
module.exports = ctx => function(args, content = '') {
|
|
11
|
+
args = ctx.args.map(args, ['text'], [])
|
|
12
|
+
const escapeHTML = require('hexo-util').escapeHTML
|
|
13
|
+
const text = escapeHTML(args.text || '')
|
|
14
|
+
const inner = ctx.render.renderSync({text: content, engine: 'markdown'})
|
|
15
|
+
.split('\n').join('')
|
|
16
|
+
.replace(/<\/?p>/g, '')
|
|
17
|
+
var el = ''
|
|
18
|
+
el += `<span class="tag-plugin tip" tabindex="0">`
|
|
19
|
+
el += `<span class="tip-text">${inner}</span>`
|
|
20
|
+
el += `<span class="tip-bubble" role="tooltip">${text}</span>`
|
|
21
|
+
el += '</span>'
|
|
22
|
+
return el
|
|
23
|
+
}
|
|
@@ -18,6 +18,7 @@
|
|
|
18
18
|
float: right
|
|
19
19
|
z-index: 999999
|
|
20
20
|
display: flex
|
|
21
|
+
align-items: center
|
|
21
22
|
justify-content: center
|
|
22
23
|
flex-direction: column
|
|
23
24
|
gap: $bar-item-gap
|
|
@@ -37,6 +38,8 @@
|
|
|
37
38
|
color: var(--text)
|
|
38
39
|
background: none
|
|
39
40
|
box-sizing: border-box
|
|
41
|
+
flex: 0 0 auto
|
|
42
|
+
margin: 0 auto
|
|
40
43
|
width: 36px
|
|
41
44
|
height: 36px
|
|
42
45
|
padding: 4px
|
|
@@ -13,6 +13,7 @@
|
|
|
13
13
|
position: absolute
|
|
14
14
|
left: 0
|
|
15
15
|
bottom: -2px
|
|
16
|
+
border-radius: 2px
|
|
16
17
|
z-index: -1
|
|
17
18
|
width: 100%
|
|
18
19
|
height: 0
|
|
@@ -69,11 +70,11 @@
|
|
|
69
70
|
transform: scale(1.05)
|
|
70
71
|
img
|
|
71
72
|
filter: brightness(var(--img-br)) saturate(var(--img-sat))
|
|
72
|
-
// 封面放大用
|
|
73
|
-
transition: all .2s ease-out, transform .5s ease-out
|
|
74
|
-
-moz-transition: all .2s ease-out, transform .5s ease-out
|
|
75
|
-
-webkit-transition: all .2s ease-out, transform .5s ease-out
|
|
76
|
-
-o-transition: all .2s ease-out, transform .5s ease-out
|
|
73
|
+
// 封面放大用 1.5s 缓动,其余属性(亮度/饱和度)保持主题默认 0.2s
|
|
74
|
+
transition: all .2s ease-out, transform 1.5s ease-out
|
|
75
|
+
-moz-transition: all .2s ease-out, transform 1.5s ease-out
|
|
76
|
+
-webkit-transition: all .2s ease-out, transform 1.5s ease-out
|
|
77
|
+
-o-transition: all .2s ease-out, transform 1.5s ease-out
|
|
77
78
|
.excerpt
|
|
78
79
|
margin: 1rem 0
|
|
79
80
|
@media screen and (max-width: $device-mobile)
|
|
@@ -105,6 +106,11 @@
|
|
|
105
106
|
object-fit: contain
|
|
106
107
|
svg
|
|
107
108
|
color: $c-red
|
|
109
|
+
.card-tags
|
|
110
|
+
display: flex
|
|
111
|
+
flex-wrap: wrap
|
|
112
|
+
align-items: center
|
|
113
|
+
gap: 0 .5rem
|
|
108
114
|
|
|
109
115
|
|
|
110
116
|
// common article
|
|
@@ -42,10 +42,10 @@
|
|
|
42
42
|
|
|
43
43
|
.post-list #cats
|
|
44
44
|
padding: 0
|
|
45
|
+
.cat-item
|
|
46
|
+
display: block
|
|
45
47
|
a.cat
|
|
46
48
|
display: flex
|
|
47
|
-
&.child
|
|
48
|
-
padding-left: 2rem
|
|
49
49
|
padding: .5em 1rem
|
|
50
50
|
border-radius: $border-bar
|
|
51
51
|
color: var(--text-p2)
|
|
@@ -72,6 +72,10 @@
|
|
|
72
72
|
.badge
|
|
73
73
|
opacity: 1
|
|
74
74
|
color: var(--theme)
|
|
75
|
+
.cat-children
|
|
76
|
+
margin-left: 1.25rem
|
|
77
|
+
padding-left: .75rem
|
|
78
|
+
border-left: 1px dashed var(--text-meta)
|
|
75
79
|
|
|
76
80
|
.post-list #tags
|
|
77
81
|
display: flex
|
|
@@ -103,4 +107,4 @@
|
|
|
103
107
|
|
|
104
108
|
@media screen and (min-width: $device-mobile)
|
|
105
109
|
.post-list.author #archive
|
|
106
|
-
padding: 1rem 0
|
|
110
|
+
padding: 1rem 0
|
|
@@ -4,10 +4,18 @@
|
|
|
4
4
|
display: flex
|
|
5
5
|
justify-content: space-between
|
|
6
6
|
color: var(--text-banner)
|
|
7
|
+
.left
|
|
8
|
+
flex: 1
|
|
9
|
+
min-width: 0
|
|
7
10
|
.flex-row
|
|
8
11
|
display: flex
|
|
9
12
|
align-items: baseline
|
|
10
13
|
flex-direction: row
|
|
14
|
+
#breadcrumb
|
|
15
|
+
.reading.right
|
|
16
|
+
margin-left: auto
|
|
17
|
+
white-space: nowrap
|
|
18
|
+
opacity: .8
|
|
11
19
|
a
|
|
12
20
|
color: var(--text-banner)
|
|
13
21
|
padding: 4px
|
|
@@ -50,4 +58,4 @@
|
|
|
50
58
|
span
|
|
51
59
|
margin-left: 4px
|
|
52
60
|
&:hover
|
|
53
|
-
opacity 1
|
|
61
|
+
opacity 1
|
|
@@ -29,8 +29,9 @@
|
|
|
29
29
|
background: var(--leftbar-search-line, var(--bg-a100))
|
|
30
30
|
:root[data-theme="dark"] &
|
|
31
31
|
background: var(--leftbar-search-line, var(--bg-a20))
|
|
32
|
-
|
|
33
|
-
|
|
32
|
+
:root:not([data-theme]) &
|
|
33
|
+
@media screen and (prefers-color-scheme: dark)
|
|
34
|
+
background: var(--leftbar-search-line, var(--bg-a20))
|
|
34
35
|
z-index 0
|
|
35
36
|
trans1 all
|
|
36
37
|
&:after
|
|
@@ -62,8 +63,9 @@
|
|
|
62
63
|
color: var(--text-p3)
|
|
63
64
|
:root[data-theme="dark"] &
|
|
64
65
|
color: var(--text-p2)
|
|
65
|
-
|
|
66
|
-
color:
|
|
66
|
+
:root:not([data-theme]) &
|
|
67
|
+
@media screen and (prefers-color-scheme: dark)
|
|
68
|
+
color: var(--text-p2)
|
|
67
69
|
.search-button
|
|
68
70
|
border-radius: $border-bar
|
|
69
71
|
display: flex
|