hexo-theme-stellar 1.33.1 → 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.
Files changed (82) hide show
  1. package/README.md +2 -0
  2. package/_config.yml +19 -14
  3. package/layout/_partial/comments/artalk/script.ejs +56 -41
  4. package/layout/_partial/comments/beaudar/script.ejs +35 -20
  5. package/layout/_partial/comments/giscus/script.ejs +34 -19
  6. package/layout/_partial/comments/twikoo/script.ejs +29 -14
  7. package/layout/_partial/comments/utterances/script.ejs +35 -20
  8. package/layout/_partial/comments/waline/script.ejs +40 -28
  9. package/layout/_partial/cover/wiki_cover.ejs +1 -1
  10. package/layout/_partial/head.ejs +53 -7
  11. package/layout/_partial/scripts/defines.ejs +21 -3
  12. package/layout/_partial/scripts/lazyload.ejs +2 -2
  13. package/layout/_partial/scripts/services.ejs +101 -41
  14. package/layout/_partial/scripts/utils.ejs +100 -4
  15. package/layout/_partial/sidebar/index_leftbar.ejs +3 -0
  16. package/layout/_partial/sidebar/search.ejs +3 -0
  17. package/layout/_partial/widgets/ghuser.ejs +18 -4
  18. package/layout/_partial/widgets/linklist.ejs +1 -1
  19. package/layout/_partial/widgets/timeline.ejs +36 -33
  20. package/layout/_plugins/copycode.ejs +9 -5
  21. package/layout/_plugins/mermaid.ejs +28 -24
  22. package/layout/_plugins/scrollreveal.ejs +14 -13
  23. package/layout/_plugins/swiper.ejs +2 -2
  24. package/layout/index.ejs +4 -2
  25. package/layout/index_topic.ejs +4 -2
  26. package/layout/index_wiki.ejs +6 -4
  27. package/layout/layout.ejs +2 -2
  28. package/package.json +5 -2
  29. package/scripts/commands/new-note.js +49 -0
  30. package/scripts/events/index.js +2 -1
  31. package/scripts/events/lib/doc_tree.js +7 -4
  32. package/scripts/events/lib/merge_posts.js +3 -1
  33. package/scripts/events/lib/notebooks.js +5 -3
  34. package/scripts/events/lib/path_normalize.js +22 -0
  35. package/scripts/filters/lib/img_onerror.js +0 -2
  36. package/scripts/generators/search.js +3 -1
  37. package/scripts/helpers/json_ld.js +14 -10
  38. package/scripts/helpers/pretty_url.js +12 -8
  39. package/scripts/lib/path_utils.js +24 -0
  40. package/scripts/tags/lib/chat.js +16 -9
  41. package/scripts/tags/lib/frame.js +2 -1
  42. package/scripts/tags/lib/image.js +6 -5
  43. package/scripts/tags/lib/link.js +6 -4
  44. package/scripts/tags/lib/timeline.js +101 -79
  45. package/source/css/_common/canonical.styl +11 -6
  46. package/source/css/_common/highlight.styl +4 -1
  47. package/source/css/_common/html.styl +4 -0
  48. package/source/css/_common/loading.styl +5 -1
  49. package/source/css/_components/md.styl +1 -0
  50. package/source/css/_components/pages/article-story.styl +2 -0
  51. package/source/css/_components/partial/article-footer.styl +1 -0
  52. package/source/css/_components/partial/navbar.styl +2 -0
  53. package/source/css/_components/sidebar/menu.styl +1 -0
  54. package/source/css/_components/sidebar/search.styl +4 -0
  55. package/source/css/_components/sidebar/sidebar.styl +12 -5
  56. package/source/css/_components/tag-plugins/chat.styl +6 -8
  57. package/source/css/_components/tag-plugins/folding.styl +1 -0
  58. package/source/css/_components/tag-plugins/gallery.styl +2 -0
  59. package/source/css/_components/tag-plugins/grid.styl +1 -0
  60. package/source/css/_components/tag-plugins/image.styl +3 -0
  61. package/source/css/_components/tag-plugins/link.styl +1 -0
  62. package/source/css/_components/tag-plugins/navbar.styl +15 -13
  63. package/source/css/_components/tag-plugins/note.styl +1 -0
  64. package/source/css/_components/tag-plugins/okr.styl +1 -0
  65. package/source/css/_components/tag-plugins/sites.styl +2 -0
  66. package/source/css/_components/tag-plugins/timeline.styl +9 -0
  67. package/source/css/_components/tag-plugins/vote.styl +2 -0
  68. package/source/css/_components/widgets/components.styl +1 -0
  69. package/source/css/_components/widgets/ghuser.styl +19 -3
  70. package/source/css/_components/widgets/list.styl +1 -0
  71. package/source/css/_components/widgets/toc.styl +3 -0
  72. package/source/css/_components/widgets/widgets.styl +4 -0
  73. package/source/css/_custom.styl +2 -1
  74. package/source/css/_defines/func.styl +8 -0
  75. package/source/css/_plugins/comments/artalk.styl +6 -2
  76. package/source/css/_plugins/comments/twikoo.styl +2 -0
  77. package/source/css/_plugins/index.styl +0 -1
  78. package/source/js/main.js +76 -26
  79. package/source/js/plugins/copycode.js +34 -27
  80. package/source/js/search/local-search.js +5 -2
  81. package/source/js/services/memos.js +42 -4
  82. package/source/js/services/rss.js +200 -0
package/layout/index.ejs CHANGED
@@ -12,15 +12,17 @@ function layout_post_card(layout, post, content) {
12
12
  if (layout == 'post' && post.cover != undefined && post.poster != undefined) {
13
13
  layout += ' photo';
14
14
  }
15
- el += `<a class="post-card${scrollreveal(' ')} ${layout}" href="${pretty_url(post.link || post.path)}">`
15
+ el += `<div class="post-card-wrap${scrollreveal(' ')}">`;
16
+ el += `<a class="post-card ${layout}" href="${pretty_url(post.link || post.path)}">`
16
17
  el += content;
17
18
  el += '</a>';
19
+ el += '</div>';
18
20
  return el;
19
21
  }
20
22
 
21
23
  function layout_post_list(partial) {
22
24
  var el = '';
23
- el += '<div class="post-list post">';
25
+ el += `<div class="post-list post">`;
24
26
  page.posts.each(function(post){
25
27
  if (post.indexing != false) {
26
28
  el += layout_post_card('post', post, partial(post));
@@ -7,8 +7,8 @@ function layout_topic_list(partial) {
7
7
  if (topic == null) {
8
8
  continue
9
9
  }
10
- el += `<div class="post-list wiki topic">`
11
- el += `<a class="post-card wiki topic${scrollreveal(' ')}" href="${pretty_url(topic.homepage?.path || '/')}">`
10
+ el += `<div class="post-card-wrap${scrollreveal(' ')}">`
11
+ el += `<a class="post-card wiki topic" href="${pretty_url(topic.homepage?.path || '/')}">`
12
12
  el += partial(topic)
13
13
  el += `</a>`
14
14
  el += `</div>`
@@ -18,9 +18,11 @@ function layout_topic_list(partial) {
18
18
  function layoutDiv() {
19
19
  var el = ''
20
20
  el += partial('_partial/main/navbar/nav_tabs_blog')
21
+ el += `<div class="post-list wiki topic">`;
21
22
  el += layout_topic_list(function(topic) {
22
23
  return partial('_partial/main/post_list/topic_card', {topic: topic})
23
24
  })
25
+ el += `</div>`
24
26
  return el
25
27
  }
26
28
  %>
@@ -12,15 +12,15 @@ function layout_wiki_list(partial) {
12
12
  }
13
13
  if (page.filter === false) {
14
14
  // wikiList
15
- el += `<div class="post-list wiki">`
16
- el += `<a class="post-card wiki${scrollreveal(' ')}" href="${pretty_url(proj.homepage?.path || '/')}">`
15
+ el += `<div class="post-card-wrap${scrollreveal(' ')}">`
16
+ el += `<a class="post-card wiki" href="${pretty_url(proj.homepage?.path || '/')}">`
17
17
  el += partial(proj)
18
18
  el += `</a>`
19
19
  el += `</div>`
20
20
  } else if (proj.tags && proj.tags.includes(page.tagName) === true) {
21
21
  // filtered wikiList
22
- el += `<div class="post-list wiki filter">`
23
- el += `<a class="post-card wiki${scrollreveal(' ')}" href="${pretty_url(proj.homepage?.path || '/')}">`
22
+ el += `<div class="post-card-wrap${scrollreveal(' ')} filter">`
23
+ el += `<a class="post-card wiki" href="${pretty_url(proj.homepage?.path || '/')}">`
24
24
  el += partial(proj)
25
25
  el += `</a>`
26
26
  el += `</div>`
@@ -32,9 +32,11 @@ function layout_wiki_list(partial) {
32
32
  function layoutDiv() {
33
33
  var el = ''
34
34
  el += partial('_partial/main/navbar/nav_tabs_wiki')
35
+ el += `<div class="post-list wiki">`;
35
36
  el += layout_wiki_list(function(proj) {
36
37
  return partial('_partial/main/post_list/wiki_card', {proj: proj})
37
38
  })
39
+ el += `</div>`
38
40
  return el
39
41
  }
40
42
  %>
package/layout/layout.ejs CHANGED
@@ -47,7 +47,7 @@ if (theme.style.prefers_theme === 'auto') {
47
47
  html += partial('_partial/head')
48
48
  html += `<body>`
49
49
  html += site_background
50
- html += partial('_partial/cover/index')
50
+ html += `<div id="l_cover">${partial('_partial/cover/index').trim()}</div>`
51
51
  html += `<div class="l_body ${page_type}" id="start" layout="${page.layout}" type="${article_type}" ${indent ? 'text-indent' : ''}>`
52
52
  html += `<aside class="l_left">`
53
53
  html += `<div class="sidebg"></div>`
@@ -62,7 +62,7 @@ if (theme.style.prefers_theme === 'auto') {
62
62
  html += `<div class="main-mask" onclick="sidebar.dismiss()"></div>`
63
63
  html += `</div>`
64
64
  html += `<aside class="l_right">`
65
- html += partial('_partial/sidebar/index_rightbar')
65
+ html += partial('_partial/sidebar/index_rightbar').trim()
66
66
  html += `</aside>`
67
67
  html += partial('_partial/menubtn')
68
68
  html += `</div>`
package/package.json CHANGED
@@ -1,10 +1,12 @@
1
1
  {
2
2
  "name": "hexo-theme-stellar",
3
- "version": "1.33.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",
@@ -22,6 +24,7 @@
22
24
  },
23
25
  "dependencies": {
24
26
  "cheerio": "^1.1.0",
27
+ "glob": "^10.4.0",
25
28
  "hexo-renderer-ejs": "^2.0.0",
26
29
  "hexo-renderer-stylus": "^3.0.1",
27
30
  "probe-image-size": "^7.2.3"
@@ -0,0 +1,49 @@
1
+ const path = require('path');
2
+ const fs = require('fs');
3
+
4
+ hexo.extend.console.register('new-note', 'Create a new note', {
5
+ options: [
6
+ { name: '-n, --name', desc: 'Note name', required: true },
7
+ { name: '-bk, --book', desc: 'Notebook name', required: true },
8
+ { name: '-tag, --tag', desc: 'Tags (comma separated)', required: false }
9
+ ]
10
+ }, function(args, callback) {
11
+ const name = args.name || args.n;
12
+ const book = args.book || args.bk || args.k;
13
+ const tagStr = args.tag ||args.g ||'';
14
+ if (!name || !book) {
15
+ console.log('Usage: hexo new-note -n "name" -bk "notebook" -tag "tag1,tag2/subtag1"');
16
+ return callback();
17
+ }
18
+
19
+ const date = new Date();
20
+ const filename = `${name}.md`;
21
+ const notebookDir = path.join(hexo.source_dir, 'notebooks', book);
22
+ if (!fs.existsSync(notebookDir)) {
23
+ fs.mkdirSync(notebookDir, { recursive: true });
24
+ }
25
+ const filePath = path.join(notebookDir, filename);
26
+ if (fs.existsSync(filePath)) {
27
+ console.log(`File ${filename} already exists in ${notebookDir}.`);
28
+ return callback();
29
+ }
30
+ // 处理标签
31
+ let tags = [];
32
+ if (tagStr) {
33
+ tags = tagStr.split(',').map(t => t.trim()).filter(Boolean);
34
+ }
35
+ // 生成表头
36
+ const frontMatter = [
37
+ '---',
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')}`,
40
+ `tags: [${tags.map(t => '"'+t+'"').join(', ')}]`,
41
+ `notebook: ${book}`,
42
+ '---',
43
+ '',
44
+ ''
45
+ ].join('\n');
46
+ fs.writeFileSync(filePath, frontMatter);
47
+ console.log(`Created: ${filePath}`);
48
+ callback();
49
+ });
@@ -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.replace('.html', '')
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
- sec.pages = sec.pages.concat(sub_pages.filter(p => p.path_key == item.base_dir + key))
138
- others = others.filter(p => p.path_key != item.base_dir + key)
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.replace('.html', '')
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.replace('.html', '')
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.substring(0, notebook.base_dir.length - 1)
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
+ }
@@ -5,8 +5,6 @@
5
5
 
6
6
  'use strict';
7
7
 
8
- const fs = require('hexo-fs');
9
-
10
8
  module.exports.processSite = function(htmlContent) {
11
9
  const default_image = this.theme.config.default.image;
12
10
  return htmlContent.replace(/<img(.*?)src="(.*?)"(.*?)>/gi, function(imgTag) {
@@ -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
- temp_post.path = root + post.path
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()
@@ -6,19 +6,22 @@
6
6
  'use strict';
7
7
 
8
8
  const util = require('hexo-util');
9
-
9
+
10
10
  hexo.extend.helper.register('json_ld', function(args) {
11
11
  const page = this.page;
12
12
  const config = this.config;
13
13
  const structured_data = this.theme.structured_data;
14
14
  const authorEmail = config.email;
15
- const authorImage = config.avatar || (authorEmail ? this.gravatar(authorEmail) : null);
15
+ let authorImage = config.avatar || (authorEmail ? this.gravatar(authorEmail) : null);
16
16
  const isPage = page.layout == 'page';
17
+ if (authorImage && authorImage.startsWith("/")) {
18
+ authorImage = config.url.endsWith("/") ? config.url + authorImage.slice(1) : config.url + authorImage
19
+ }
17
20
 
18
21
  const author = {
19
22
  '@type': 'Person',
20
23
  name: config.author,
21
- sameAs: structured_data.sameAs || []
24
+ sameAs: structured_data.links || []
22
25
  };
23
26
  // Google does not accept `Person` as item type for the publisher property
24
27
  const publisher = Object.assign({}, author, {'@type': 'Organization'});
@@ -69,10 +72,11 @@ hexo.extend.helper.register('json_ld', function(args) {
69
72
 
70
73
  schema.thumbnailUrl = page.cover || page.banner;
71
74
  schema.image = images;
72
-
75
+
73
76
  } else if (isPage || this.is_home()) {
74
-
75
- const url = this.is_home() ? config.url : this.pretty_url(page.permalink);
77
+
78
+ // 首页 URL 归一为带尾斜杠形式,与 canonical 保持一致
79
+ const url = this.is_home() ? config.url.replace(/\/?$/, '/') : this.pretty_url(page.permalink);
76
80
  schema = {
77
81
  '@context': 'https://schema.org',
78
82
  '@type': 'Website',
@@ -99,9 +103,9 @@ hexo.extend.helper.register('json_ld', function(args) {
99
103
  }
100
104
 
101
105
  }
102
-
106
+
103
107
  } else {
104
-
108
+
105
109
  // default to WebPage for other layouts
106
110
  schema = {
107
111
  '@context': 'https://schema.org',
@@ -112,8 +116,8 @@ hexo.extend.helper.register('json_ld', function(args) {
112
116
  description: config.description,
113
117
  url: config.url
114
118
  };
115
-
119
+
116
120
  }
117
121
 
118
122
  return `<script type="application/ld+json">${JSON.stringify(schema)}</script>`;
119
- });
123
+ });
@@ -1,17 +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 = '') {
10
+ if (path.startsWith('http://') || path.startsWith('https://')) {
11
+ return path;
12
+ }
4
13
  let url = this.url_for(path);
5
14
 
6
- // 替换 /index.html → /
7
- url = url.replace(/\/index\.html$/, '/');
8
-
9
- // 替换 /about.html → /about/
10
- url = url.replace(/\.html$/, '/');
15
+ url = normalize_path(url);
11
16
 
12
- // 如果没有扩展名,并且不以 / 结尾,补一个 /
13
- const hasExtension = /\.[a-zA-Z0-9]+$/.test(url);
14
- if (!hasExtension && !url.endsWith('/')) {
17
+ // 添加尾 /(根路径 / 除外)
18
+ if (url !== '/') {
15
19
  url += '/';
16
20
  }
17
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 };
@@ -52,7 +52,11 @@ module.exports = ctx => function(args, content) {
52
52
  // users
53
53
  var arr = content.split(/<!--\s*([\s\S]*?)\s*-->/g).filter(item => item.trim().length > 0)
54
54
  if (arr.length > 0) {
55
- var users = merge(ctx.theme.config.chat_users, ctx.render.renderSync({ text: (arr[0] || ''), engine: 'yaml' }))
55
+ // 避免用户在没有配置chat_users.yaml时出错
56
+ if (ctx.theme.config.chat_users){
57
+ var users = merge(ctx.theme.config.chat_users, ctx.render.renderSync({ text: (arr[0] || ''), engine: 'yaml' }));
58
+ }
59
+ var users = ctx.render.renderSync({ text: (arr[0] || ''), engine: 'yaml' });
56
60
  }
57
61
 
58
62
 
@@ -286,13 +290,16 @@ module.exports = ctx => function(args, content) {
286
290
  el += '<div class="user-main">'
287
291
 
288
292
  el += '<div class="header">'
289
- if (!args.labelColorStyle || args.labelColorStyle == 'dynamic') {
290
- el += `<span class="label dynamic-color" style="color:var(--chat-${chatIndex}-label-text-color-${userColorMap[cell['user']]});background:var(--chat-${chatIndex}-label-bg-${userColorMap[cell['user']]});">`
291
- } else {
292
- el += `<span class="label hand-color" style="color:${user['label']['textColor']};background:${user['label']['bgColor']};">`
293
+ // 防止徽章为空
294
+ if (user['label']){
295
+ if (!args.labelColorStyle || args.labelColorStyle == 'dynamic') {
296
+ el += `<span class="label dynamic-color" style="color:var(--chat-${chatIndex}-label-text-color-${userColorMap[cell['user']]});background:var(--chat-${chatIndex}-label-bg-${userColorMap[cell['user']]});">`
297
+ } else {
298
+ el += `<span class="label hand-color" style="color:${user['label']['textColor']};background:${user['label']['bgColor']};">`
299
+ }
300
+ el += user['label']['text']
301
+ el += '</span>'
293
302
  }
294
- el += user['label']['text']
295
- el += '</span>'
296
303
  el += '<span class="name">'
297
304
  el += user['name']
298
305
  el += '</span>'
@@ -429,7 +436,7 @@ module.exports = ctx => function(args, content) {
429
436
  </a>
430
437
  `
431
438
  } else if (cell['file']) {
432
- el += ` file" url="${cell["file"]}">`
439
+ el += ` chat-file" url="${cell["file"]}">`
433
440
  el += `
434
441
  <div class="content">
435
442
  <div class="top">
@@ -508,4 +515,4 @@ module.exports = ctx => function(args, content) {
508
515
  el += '</div>' // tag-plugin chat
509
516
 
510
517
  return el
511
- }
518
+ }
@@ -9,11 +9,12 @@
9
9
 
10
10
  module.exports = ctx => function(args) {
11
11
  args = ctx.args.map(args, ['focus', 'img', 'video'], ['device', 'alt'])
12
+ const escapeHTML = require('hexo-util').escapeHTML
12
13
  const img = args.img || ''
13
14
  const video = args.video || ''
14
15
  const device = args.device || ''
15
16
  const focus = args.focus || ''
16
- const alt = args.alt || ''
17
+ const alt = escapeHTML(args.alt || '')
17
18
  if ((img.length == 0 && video.length == 0) || device.length == 0) {
18
19
  return
19
20
  }
@@ -37,13 +37,14 @@ module.exports = ctx => function(args) {
37
37
  }
38
38
  }
39
39
 
40
+ var safeAlt = require('hexo-util').escapeHTML(args.alt || '')
40
41
  function img(src, alt, style) {
41
42
  let a = '<a data-fancybox'
42
43
  let img = ''
43
44
  img += `<img class="lazy" src="${src}" data-src="${src}"`
44
- if (alt) {
45
- img += ` alt="${alt}"`
46
- a += ` data-caption="${alt}"`
45
+ if (safeAlt) {
46
+ img += ` alt="${safeAlt}"`
47
+ a += ` data-caption="${safeAlt}"`
47
48
  }
48
49
  if (fancybox && !fancyboxHref) {
49
50
  img += ` data-fancybox="${fancybox}"`
@@ -101,9 +102,9 @@ module.exports = ctx => function(args) {
101
102
  }
102
103
  el += '</div>'
103
104
 
104
- if (args.alt && args.alt.length > 0) {
105
+ if (safeAlt) {
105
106
  el += '<div class="image-meta">'
106
- el += '<span class="image-caption center">' + args.alt + '</span>'
107
+ el += '<span class="image-caption center">' + safeAlt + '</span>'
107
108
  el += '</div>'
108
109
  }
109
110
 
@@ -9,11 +9,13 @@
9
9
 
10
10
  module.exports = ctx => function(args) {
11
11
  const full_url_for = require('hexo-util').full_url_for.bind(ctx)
12
+ const escapeHTML = require('hexo-util').escapeHTML
12
13
  args = ctx.args.map(args, ['icon', 'desc'], ['url', 'title'])
13
14
  if (args.url == null) {
14
15
  return '';
15
16
  }
16
17
  const url = full_url_for(args.url)
18
+ const safeTitle = escapeHTML(args.title || '')
17
19
  args.api = ctx.theme.config.data_services.siteinfo?.api
18
20
  if (args.api) {
19
21
  args.api = args.api.replace('{href}', url)
@@ -25,12 +27,12 @@ module.exports = ctx => function(args) {
25
27
  if (!args.icon) {
26
28
  autofill.push('icon')
27
29
  }
28
- if (args.desc) {
30
+ if (args.desc !== 'true' && args.desc !== true && !args.desc) {
29
31
  autofill.push('desc')
30
32
  }
31
33
  var el = ''
32
34
  el += '<div class="tag-plugin link dis-select">'
33
- el += '<a class="link-card' + (args.desc ? ' rich' : ' plain') + '" title="' + (args.title || '') + '" href="' + args.url + '"'
35
+ el += '<a class="link-card' + (args.desc ? ' rich' : ' plain') + '" title="' + safeTitle + '" href="' + args.url + '"'
34
36
  if (args.url.includes('://')) {
35
37
  el += ' target="_blank" rel="external nofollow noopener noreferrer"'
36
38
  }
@@ -49,10 +51,10 @@ module.exports = ctx => function(args) {
49
51
  return el
50
52
  }
51
53
  function loadTitle() {
52
- return '<span class="title">' + (args.title || args.url) + '</span>'
54
+ return '<span class="title">' + (safeTitle || args.url) + '</span>'
53
55
  }
54
56
  function loadDesc() {
55
- return '<span class="cap desc footnote"></span>'
57
+ return `<span class="cap desc footnote">${escapeHTML(args.desc)}</span>`
56
58
  }
57
59
  function loadLink() {
58
60
  return '<span class="cap link footnote">' + full_url_for(args.url) + '</span>'