hexo-theme-stellar 1.33.0 → 1.34.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 (83) hide show
  1. package/CLAUDE.md +217 -0
  2. package/README.md +2 -0
  3. package/_config.yml +35 -26
  4. package/docs/audits/2026-08-08-stellar-analysis.md +267 -0
  5. package/docs/release-process.md +59 -0
  6. package/layout/_partial/comments/artalk/script.ejs +56 -41
  7. package/layout/_partial/comments/beaudar/script.ejs +35 -20
  8. package/layout/_partial/comments/giscus/script.ejs +34 -19
  9. package/layout/_partial/comments/twikoo/script.ejs +29 -14
  10. package/layout/_partial/comments/utterances/script.ejs +35 -20
  11. package/layout/_partial/comments/waline/script.ejs +40 -28
  12. package/layout/_partial/cover/wiki_cover.ejs +1 -1
  13. package/layout/_partial/head.ejs +53 -4
  14. package/layout/_partial/scripts/defines.ejs +21 -3
  15. package/layout/_partial/scripts/lazyload.ejs +2 -2
  16. package/layout/_partial/scripts/services.ejs +101 -41
  17. package/layout/_partial/scripts/utils.ejs +144 -5
  18. package/layout/_partial/sidebar/index_leftbar.ejs +3 -0
  19. package/layout/_partial/sidebar/search.ejs +3 -0
  20. package/layout/_partial/widgets/ghuser.ejs +18 -4
  21. package/layout/_partial/widgets/linklist.ejs +1 -1
  22. package/layout/_partial/widgets/timeline.ejs +36 -33
  23. package/layout/_plugins/copycode.ejs +9 -5
  24. package/layout/_plugins/mathjax.ejs +47 -18
  25. package/layout/_plugins/mermaid.ejs +28 -24
  26. package/layout/_plugins/pjax.ejs +8 -0
  27. package/layout/_plugins/scrollreveal.ejs +14 -13
  28. package/layout/_plugins/swiper.ejs +2 -2
  29. package/layout/index.ejs +4 -2
  30. package/layout/index_topic.ejs +4 -2
  31. package/layout/index_wiki.ejs +6 -4
  32. package/layout/layout.ejs +3 -2
  33. package/package.json +2 -1
  34. package/scripts/commands/new-note.js +49 -0
  35. package/scripts/filters/lib/img_onerror.js +0 -2
  36. package/scripts/helpers/json_ld.js +11 -8
  37. package/scripts/helpers/pretty_url.js +5 -0
  38. package/scripts/tags/lib/chat.js +16 -9
  39. package/scripts/tags/lib/frame.js +2 -1
  40. package/scripts/tags/lib/image.js +6 -5
  41. package/scripts/tags/lib/link.js +6 -4
  42. package/scripts/tags/lib/timeline.js +101 -79
  43. package/source/css/_common/canonical.styl +11 -6
  44. package/source/css/_common/highlight.styl +4 -1
  45. package/source/css/_common/html.styl +4 -0
  46. package/source/css/_common/loading.styl +5 -1
  47. package/source/css/_components/md.styl +3 -0
  48. package/source/css/_components/pages/article-story.styl +2 -0
  49. package/source/css/_components/partial/article-footer.styl +1 -0
  50. package/source/css/_components/partial/navbar.styl +4 -2
  51. package/source/css/_components/sidebar/menu.styl +1 -0
  52. package/source/css/_components/sidebar/search.styl +4 -0
  53. package/source/css/_components/sidebar/sidebar.styl +12 -5
  54. package/source/css/_components/tag-plugins/chat.styl +6 -8
  55. package/source/css/_components/tag-plugins/folding.styl +1 -0
  56. package/source/css/_components/tag-plugins/gallery.styl +2 -0
  57. package/source/css/_components/tag-plugins/grid.styl +1 -0
  58. package/source/css/_components/tag-plugins/image.styl +3 -0
  59. package/source/css/_components/tag-plugins/link.styl +1 -0
  60. package/source/css/_components/tag-plugins/navbar.styl +15 -13
  61. package/source/css/_components/tag-plugins/note.styl +1 -0
  62. package/source/css/_components/tag-plugins/okr.styl +1 -0
  63. package/source/css/_components/tag-plugins/sites.styl +2 -0
  64. package/source/css/_components/tag-plugins/timeline.styl +9 -0
  65. package/source/css/_components/tag-plugins/vote.styl +2 -0
  66. package/source/css/_components/widgets/components.styl +1 -0
  67. package/source/css/_components/widgets/ghuser.styl +19 -3
  68. package/source/css/_components/widgets/list.styl +1 -0
  69. package/source/css/_components/widgets/toc.styl +3 -0
  70. package/source/css/_components/widgets/widgets.styl +4 -0
  71. package/source/css/_custom.styl +6 -3
  72. package/source/css/_defines/func.styl +24 -8
  73. package/source/css/_defines/theme_base.styl +1 -1
  74. package/source/css/_plugins/comments/artalk.styl +6 -2
  75. package/source/css/_plugins/comments/twikoo.styl +2 -0
  76. package/source/css/_plugins/index.styl +2 -0
  77. package/source/css/_plugins/pjax.styl +61 -0
  78. package/source/js/main.js +80 -26
  79. package/source/js/plugins/copycode.js +34 -27
  80. package/source/js/plugins/pjax.js +646 -0
  81. package/source/js/search/local-search.js +5 -2
  82. package/source/js/services/memos.js +42 -4
  83. package/source/js/services/rss.js +200 -0
@@ -1,29 +1,33 @@
1
1
  <script defer type="text/javascript" src="<%- conf.js %>"></script>
2
2
  <script>
3
- window.addEventListener('DOMContentLoaded', (event) => {
4
- var mermaid_config = {
5
- startOnLoad: true,
6
- theme:
7
- "<%- theme.style.prefers_theme %>" == "auto" &&
8
- window.matchMedia("(prefers-color-scheme: dark)").matches
9
- ? "dark"
10
- : "<%- conf.theme %>",
11
- logLevel: 3,
12
- themeVariables: {
13
- darkMode: true
14
- },
15
- flowchart: {
16
- useMaxWidth: false,
17
- htmlLabels: true,
18
- curve: "linear"
19
- },
20
- gantt: {
21
- axisFormat: "%Y/%m/%d"
22
- },
23
- sequence: {
24
- actorMargin: 50
3
+ utils.initPlugin(() => {
4
+ const els = document.querySelectorAll('.mermaid');
5
+ if (els.length > 0) {
6
+ const mermaid_config = {
7
+ startOnLoad: false,
8
+ theme:
9
+ "<%- theme.style.prefers_theme %>" == "auto" &&
10
+ window.matchMedia("(prefers-color-scheme: dark)").matches
11
+ ? "dark"
12
+ : "<%- conf.theme %>",
13
+ logLevel: 3,
14
+ themeVariables: {
15
+ darkMode: true
16
+ },
17
+ flowchart: {
18
+ useMaxWidth: false,
19
+ htmlLabels: true,
20
+ curve: "linear"
21
+ },
22
+ gantt: {
23
+ axisFormat: "%Y/%m/%d"
24
+ },
25
+ sequence: {
26
+ actorMargin: 50
27
+ }
25
28
  }
29
+ mermaid.initialize(mermaid_config);
30
+ mermaid.run();
26
31
  }
27
- mermaid.initialize(mermaid_config);
28
- });
32
+ }, 'mermaid');
29
33
  </script>
@@ -0,0 +1,8 @@
1
+ <script>
2
+ window.StellarPjaxConfig = {
3
+ selectors: <%- JSON.stringify((conf.selectors || ['title', '#l_cover', '.l_body']).includes('#l_cover') ? (conf.selectors || ['title', '#l_cover', '.l_body']) : (conf.selectors || ['title', '#l_cover', '.l_body']).concat('#l_cover')) %>,
4
+ timeout: <%= conf.timeout || 10000 %>,
5
+ cacheBust: <%= conf.cacheBust || false %>
6
+ };
7
+ </script>
8
+ <script src="<%- url_for('/js/plugins/pjax.js') %>" defer></script>
@@ -1,16 +1,17 @@
1
1
  <script defer src="<%- conf.js %>"></script>
2
2
  <script>
3
- window.addEventListener('DOMContentLoaded', (event) => {
4
- const slideUp = {
5
- distance: `<%- conf.distance %>`,
6
- duration: `<%- conf.duration %>`,
7
- interval: `<%- conf.interval %>`,
8
- scale: `<%- conf.scale %>`,
9
- opacity: 0,
10
- easing: "ease-out"
11
- };
12
- ScrollReveal({ container: '.widgets' }).reveal('.slide-up', { ...slideUp });
13
- ScrollReveal({ container: '.l_main' }).reveal('.slide-up', { ...slideUp });
14
- ScrollReveal({ container: '.l_right' }).reveal('.slide-up', { ...slideUp });
15
- });
3
+ utils.initPlugin(() => {
4
+ const els = document.querySelectorAll('.slide-up');
5
+ if (els.length > 0) {
6
+ const slideUp = {
7
+ distance: `<%- conf.distance %>`,
8
+ duration: `<%- conf.duration %>`,
9
+ interval: `<%- conf.interval %>`,
10
+ scale: `<%- conf.scale %>`,
11
+ opacity: 0,
12
+ easing: "ease-out"
13
+ };
14
+ ScrollReveal().reveal('.slide-up', { ...slideUp });
15
+ }
16
+ }, 'scrollreveal');
16
17
  </script>
@@ -1,5 +1,5 @@
1
1
  <script>
2
- window.addEventListener('DOMContentLoaded', (event) => {
2
+ utils.initPlugin(() => {
3
3
  const swiper_api = document.getElementById('swiper-api');
4
4
  if (swiper_api != undefined) {
5
5
  utils.css(`<%- conf.css %>`);
@@ -22,5 +22,5 @@
22
22
  });
23
23
  })
24
24
  }
25
- });
25
+ }, 'swiper');
26
26
  </script>
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,13 +62,14 @@ 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>`
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>`
72
73
  html += `</body>`
73
74
  html += `</html>`
74
75
  %>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "hexo-theme-stellar",
3
- "version": "1.33.0",
3
+ "version": "1.34.0",
4
4
  "description": "Elegant and powerful theme for Hexo.",
5
5
  "main": "package.json",
6
6
  "scripts": {
@@ -22,6 +22,7 @@
22
22
  },
23
23
  "dependencies": {
24
24
  "cheerio": "^1.1.0",
25
+ "glob": "^10.4.0",
25
26
  "hexo-renderer-ejs": "^2.0.0",
26
27
  "hexo-renderer-stylus": "^3.0.1",
27
28
  "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
+ });
@@ -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) {
@@ -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,9 +72,9 @@ 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
-
77
+
75
78
  const url = this.is_home() ? config.url : this.pretty_url(page.permalink);
76
79
  schema = {
77
80
  '@context': 'https://schema.org',
@@ -99,9 +102,9 @@ hexo.extend.helper.register('json_ld', function(args) {
99
102
  }
100
103
 
101
104
  }
102
-
105
+
103
106
  } else {
104
-
107
+
105
108
  // default to WebPage for other layouts
106
109
  schema = {
107
110
  '@context': 'https://schema.org',
@@ -112,7 +115,7 @@ hexo.extend.helper.register('json_ld', function(args) {
112
115
  description: config.description,
113
116
  url: config.url
114
117
  };
115
-
118
+
116
119
  }
117
120
 
118
121
  return `<script type="application/ld+json">${JSON.stringify(schema)}</script>`;
@@ -1,6 +1,11 @@
1
1
  'use strict';
2
2
 
3
3
  hexo.extend.helper.register('pretty_url', function (path = '') {
4
+ if (path.startsWith('http://') || path.startsWith('https://')) {
5
+ // 如果是绝对 URL,直接返回
6
+ return path;
7
+ }
8
+
4
9
  let url = this.url_for(path);
5
10
 
6
11
  // 替换 /index.html → /
@@ -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>'