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.
Files changed (54) hide show
  1. package/.claude/skills/stellar-theme-dev/SKILL.md +2 -2
  2. package/CHANGELOG.md +44 -0
  3. package/_config.yml +8 -2
  4. package/languages/en.yml +2 -0
  5. package/languages/zh-CN.yml +2 -0
  6. package/languages/zh-TW.yml +2 -0
  7. package/layout/_partial/comments/artalk/script.ejs +8 -0
  8. package/layout/_partial/cover/post_cover.ejs +1 -1
  9. package/layout/_partial/head.ejs +11 -2
  10. package/layout/_partial/main/article/article_footer.ejs +13 -10
  11. package/layout/_partial/main/navbar/article_banner.ejs +7 -0
  12. package/layout/_partial/main/navbar/dateinfo.ejs +1 -1
  13. package/layout/_partial/main/navbar/nav_tabs_blog.ejs +6 -3
  14. package/layout/_partial/main/navbar/nav_tabs_wiki.ejs +1 -1
  15. package/layout/_partial/main/pin_slider.ejs +11 -12
  16. package/layout/_partial/main/post_list/post_card.ejs +24 -13
  17. package/layout/_partial/sidebar/index_leftbar.ejs +2 -2
  18. package/layout/_partial/widgets/markdown.ejs +1 -1
  19. package/layout/_partial/widgets/toc.ejs +8 -1
  20. package/layout/categories.ejs +18 -8
  21. package/layout/index.ejs +67 -11
  22. package/layout/page.ejs +5 -1
  23. package/package.json +5 -3
  24. package/scripts/commands/new-note.js +2 -2
  25. package/scripts/helpers/category_tree.js +11 -0
  26. package/scripts/helpers/escape_html.js +9 -0
  27. package/scripts/helpers/json_ld.js +5 -0
  28. package/scripts/helpers/mdrender.js +29 -0
  29. package/scripts/helpers/reading_time.js +12 -0
  30. package/scripts/helpers/related_posts.js +9 -5
  31. package/scripts/lib/category_tree.js +26 -0
  32. package/scripts/lib/mdrender_html.js +97 -0
  33. package/scripts/lib/reading_time.js +16 -0
  34. package/scripts/lib/wiki_readme.js +57 -0
  35. package/scripts/tags/index.js +2 -0
  36. package/scripts/tags/lib/md.js +17 -9
  37. package/scripts/tags/lib/table.js +22 -0
  38. package/scripts/tags/lib/timeline.js +9 -9
  39. package/scripts/tags/lib/tip.js +23 -0
  40. package/source/css/_common/device.styl +3 -0
  41. package/source/css/_components/list.styl +11 -5
  42. package/source/css/_components/pages/archives.styl +7 -3
  43. package/source/css/_components/partial/bread-nav.styl +9 -1
  44. package/source/css/_components/sidebar/search.styl +6 -4
  45. package/source/css/_components/sidebar/sidebar.styl +10 -7
  46. package/source/css/_components/tag-plugins/table.styl +37 -0
  47. package/source/css/_components/tag-plugins/tip.styl +27 -0
  48. package/source/css/_custom.styl +1 -1
  49. package/source/css/_defines/func.styl +19 -13
  50. package/source/css/_defines/theme_base.styl +3 -0
  51. package/source/js/main.js +92 -1
  52. package/source/js/services/mdrender.js +116 -7
  53. package/source/js/services/memos.js +60 -8
  54. 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.39.0",
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')}:${String(date.getSeconds()).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
+ });
@@ -0,0 +1,9 @@
1
+ /* global hexo */
2
+ 'use strict';
3
+
4
+ const { escapeHTML } = require('hexo-util');
5
+
6
+ // 供 EJS 模板使用的 HTML 转义辅助函数:属性值与文本内容均可安全使用。
7
+ hexo.extend.helper.register('escape_html', function(value) {
8
+ return escapeHTML(value == null ? '' : String(value));
9
+ });
@@ -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, '&amp;').replace(/"/g, '&quot;');
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
+ };
@@ -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)
@@ -1,20 +1,28 @@
1
1
  /**
2
- * md.js v1.0 | https://github.com/volantis-x/hexo-theme-volantis/
3
- * contributor: @MHuiG
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
- <div class="data-service ds-mdrender" src="${args.src}" id="${md_id}"></div>
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
- // 封面放大用 0.5s 缓动,其余属性(亮度/饱和度)保持主题默认 0.2s
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
- @media screen and (prefers-color-scheme: dark)
33
- background: var(--leftbar-search-line, var(--bg-a20))
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
- @media screen and (prefers-color-scheme: dark)
66
- color: var(--text-p2)
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