hexo-theme-stellar 1.30.3 → 1.31.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 (36) hide show
  1. package/_config.yml +17 -5
  2. package/_data/widgets.yml +0 -1
  3. package/layout/_partial/head.ejs +9 -2
  4. package/layout/_partial/main/navbar/ghinfo.ejs +1 -1
  5. package/layout/_partial/scripts/defines.ejs +9 -0
  6. package/layout/_partial/widgets/ghissues.ejs +1 -1
  7. package/layout/_partial/widgets/ghrepo.ejs +2 -2
  8. package/layout/_partial/widgets/ghuser.ejs +1 -1
  9. package/layout/layout.ejs +2 -0
  10. package/package.json +1 -1
  11. package/scripts/events/index.js +8 -3
  12. package/scripts/events/lib/version-check.js +84 -0
  13. package/scripts/helpers/json_ld.js +114 -0
  14. package/scripts/tags/index.js +1 -0
  15. package/scripts/tags/lib/albums.js +2 -1
  16. package/scripts/tags/lib/friends.js +2 -1
  17. package/scripts/tags/lib/ghcard.js +3 -2
  18. package/scripts/tags/lib/posters.js +2 -1
  19. package/scripts/tags/lib/quote.js +22 -0
  20. package/scripts/tags/lib/sites.js +2 -1
  21. package/scripts/tags/lib/video.js +2 -2
  22. package/source/css/_common/canonical.styl +32 -0
  23. package/source/css/_common/device.styl +8 -6
  24. package/source/css/_components/layout.styl +3 -3
  25. package/source/css/_components/pages/article-story.styl +20 -12
  26. package/source/css/_components/sidebar/sidebar.styl +62 -18
  27. package/source/css/_components/tag-plugins/media.styl +1 -1
  28. package/source/css/_components/tag-plugins/quote.styl +7 -0
  29. package/source/css/_components/widgets/list.styl +1 -3
  30. package/source/css/_custom.styl +3 -0
  31. package/source/css/_defines/const.styl +5 -1
  32. package/source/css/_defines/func.styl +5 -5
  33. package/source/css/_defines/theme.styl +2 -2
  34. package/source/js/main.js +50 -0
  35. package/source/js/plugins/download-file.js +1 -1
  36. package/source/js/services/mdrender.js +0 -1
package/_config.yml CHANGED
@@ -1,6 +1,6 @@
1
1
  ######## Stellar info ########
2
2
  stellar:
3
- version: '1.30.3'
3
+ version: '1.31.0'
4
4
  homepage: 'https://xaoxuu.com/wiki/stellar/'
5
5
  repo: 'https://github.com/xaoxuu/hexo-theme-stellar'
6
6
  main_css: /css/main.css
@@ -13,10 +13,20 @@ preconnect:
13
13
  # - https://gcore.jsdelivr.net
14
14
  # - https://unpkg.com
15
15
 
16
+ # 一旦设置源站地址,非源站地址将不会被SEO收录,并且访问时弹出提示
17
+ # 如果访问地址不在备用站主机列表,则警告信息为非法克隆
18
+ canonical:
19
+ originalHost: # 主站点域名主机,例如 xaoxuu.com
20
+ officialHosts: # 官方主机列表,每行一个,例如 xaoxuu.vercel.app
21
+ - localhost
22
+
16
23
  open_graph:
17
24
  enable: true
18
25
  twitter_id: # for open_graph meta
19
26
 
27
+ structured_data:
28
+ links: [] # your social links.
29
+
20
30
 
21
31
  ######## Sidebar ########
22
32
  # 左上角显示的 logo 区域,包含图标、大标题、副标题
@@ -439,7 +449,7 @@ data_services:
439
449
  siteinfo:
440
450
  js: /js/services/siteinfo.js
441
451
  # 设置 api 可以自动提取网页标题、图标,服务部署方法:https://github.com/xaoxuu/site-info-api/
442
- # 接口测试通过后,把按钮的 href 部分替换成 {href} 之后填写到下方,例如:https://api.vlts.cc/site_info/v1?url={href}
452
+ # 接口测试通过后,把按钮的 href 部分替换成 {href} 之后填写到下方,例如:https://api.xaox.cc/site_info/v1?url={href}
443
453
  api:
444
454
  ghinfo:
445
455
  js: /js/services/ghinfo.js
@@ -673,6 +683,8 @@ default:
673
683
 
674
684
 
675
685
  api_host:
676
- ghapi: https://api.github.com
677
- ghraw: https://raw.githubusercontent.com
678
- gist: https://gist.github.com
686
+ ghapi: api.github.com
687
+ ghraw: raw.githubusercontent.com
688
+ gist: gist.github.com
689
+ # https://github.com/anuraghazra/github-readme-stats
690
+ ghcard: github-readme-stats.vercel.app
package/_data/widgets.yml CHANGED
@@ -68,7 +68,6 @@ timeline:
68
68
  title: 近期动态
69
69
  api: # https://api.github.com/repos/xaoxuu/hexo-theme-stellar/issues
70
70
  user: # 默认显示所有人的数据,设置名称可过滤为仅显示某人的数据,多个名称用英文逗号隔开,不要加空格
71
- type: # 默认不用写,如果是友链朋友圈数据请写 fcircle
72
71
  limit: # 默认通过 api 上增加 per_page 来设置,如果是友链朋友圈,可通过这个设置数量
73
72
 
74
73
  latest_comment:
@@ -47,6 +47,10 @@ function generate_keywords() {
47
47
  }
48
48
 
49
49
  function generate_robots() {
50
+ const IS_BACKUP = process.env.IS_BACKUP === 'true';
51
+ if (IS_BACKUP) {
52
+ return '<meta name="robots" content="noindex, nofollow">';
53
+ }
50
54
  if (is_home() == true) {
51
55
  return '';
52
56
  }
@@ -62,6 +66,7 @@ function og_args() {
62
66
  if (page.layout == 'post' && page.cover) {
63
67
  args.twitter_card = 'summary_large_image';
64
68
  }
69
+ args.image = config.avatar || (config.email ? gravatar(config.email) : null);
65
70
  return Object.assign(args, page.open_graph);
66
71
  }
67
72
 
@@ -100,11 +105,13 @@ function custom_inject() {
100
105
  <meta name="force-rendering" content="webkit">
101
106
  <meta http-equiv="X-UA-Compatible" content="IE=Edge,chrome=1">
102
107
  <meta name="HandheldFriendly" content="True" >
103
- <meta name="apple-mobile-web-app-capable" content="yes">
108
+ <meta name="mobile-web-app-capable" content="yes">
104
109
  <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
105
110
  <meta name="theme-color" media="(prefers-color-scheme: dark)" content="#000">
106
111
  <meta name="theme-color" content="#f9fafb">
107
-
112
+ <% if (theme.canonical.originalHost) { %>
113
+ <link rel="canonical" href="https://<%= theme.canonical.originalHost + "/" + page.path %>">
114
+ <% } %>
108
115
  <title><%- generate_title() %></title>
109
116
 
110
117
  <% if (theme.open_graph && theme.open_graph.enable) { %>
@@ -5,7 +5,7 @@ function layoutDiv() {
5
5
  return ''
6
6
  }
7
7
  return `
8
- <div class="right ghrepo ds-ghinfo" api="${theme.api_host.ghapi}/repos/${repo}">
8
+ <div class="right ghrepo ds-ghinfo" api="https://${theme.api_host.ghapi}/repos/${repo}">
9
9
  <a class="repo-link bold" href="https://github.com/${repo}">
10
10
  ${icon('github:repo')}
11
11
  <span type="text">${repo}</span>
@@ -1,4 +1,13 @@
1
+ <%
2
+ const canonical = theme.canonical || {}
3
+ const encoded = Buffer.from(canonical.originalHost || '').toString('base64');
4
+ const canonicalWithEncoded = Object.assign({}, canonical, {
5
+ encoded: encoded
6
+ });
7
+ %>
8
+
1
9
  <script type="text/javascript">
10
+ window.canonical = <%- JSON.stringify(canonicalWithEncoded) %>;
2
11
  const ctx = {
3
12
  date_suffix: {
4
13
  just: `<%- __('meta.date_suffix.just') %>`,
@@ -15,7 +15,7 @@ function layoutDiv() {
15
15
  if (repo == undefined) {
16
16
  return el;
17
17
  }
18
- item.api = theme.api_host.ghapi + '/repos/' + repo + '/issues?per_page=' + item.limit;
18
+ item.api = `https://${theme.api_host.ghapi}/repos/${repo}/issues?per_page=${item.limit}`;
19
19
  if (item.labels) {
20
20
  item.api += '&labels=' + item.labels;
21
21
  }
@@ -19,7 +19,7 @@ function layoutDiv() {
19
19
  el += `<widget class="widget-wrapper${scrollreveal(' ')} ghrepo">`
20
20
  // body
21
21
  el += '<div class="widget-body">';
22
- el += '<div class="items ds-ghinfo" api="' + theme.api_host.ghapi + '/repos/' + repo + '">';
22
+ el += '<div class="items ds-ghinfo" api="https://' + theme.api_host.ghapi + '/repos/' + repo + '">';
23
23
  el += '<a class="repo" href="https://github.com/' + repo + '" target="_blank" rel="external nofollow noopener noreferrer">';
24
24
  el += '<div class="repo-name flex-row">';
25
25
  el += icon('github:repo')
@@ -35,7 +35,7 @@ function layoutDiv() {
35
35
  el += icon('github:fork')
36
36
  el += '<span type="text" id="forks_count"></span>';
37
37
  el += '</div>';
38
- el += '<div class="flex-row ds-ghinfo" index="0" api="' + theme.api_host.ghapi + '/repos/' + repo + '/tags">';
38
+ el += '<div class="flex-row ds-ghinfo" index="0" api="https://' + theme.api_host.ghapi + '/repos/' + repo + '/tags">';
39
39
  el += icon('github:tag')
40
40
  el += '<span type="text" id="latest-tag-name">0</span>';
41
41
  el += '</div>';
@@ -24,7 +24,7 @@ function layoutDiv() {
24
24
 
25
25
  el += `<widget class="widget-wrapper${scrollreveal(' ')} user-card ghuser">`
26
26
  // body
27
- el += '<div class="widget-body ds-ghinfo" api="' + theme.api_host.ghapi + '/users/' + item.username + '">';
27
+ el += '<div class="widget-body ds-ghinfo" api="https://' + theme.api_host.ghapi + '/users/' + item.username + '">';
28
28
  if (item.avatar) {
29
29
  el += '<div class="avatar" ><img no-lazy type="img" id="avatar_url" src="' + config.avatar + '"></div>';
30
30
  }
package/layout/layout.ejs CHANGED
@@ -48,6 +48,7 @@ if (theme.style.prefers_theme === 'auto') {
48
48
  html += partial('_partial/cover/index')
49
49
  html += `<div class="l_body ${page_type}" id="start" layout="${page.layout}" type="${article_type}" ${indent ? 'text-indent' : ''}>`
50
50
  html += `<aside class="l_left">`
51
+ html += `<div class="sidebg"></div>`
51
52
  html += `<div class="leftbar-container${theme.style.leftbar?.blur ? ' leftbar-blur' : ''}">`
52
53
  html += partial('_partial/sidebar/index_leftbar')
53
54
  html += `</div>`
@@ -64,6 +65,7 @@ if (theme.style.prefers_theme === 'auto') {
64
65
  html += partial('_partial/menubtn')
65
66
  html += `</div>`
66
67
  html += `<div class="scripts">`
68
+ html += json_ld()
67
69
  html += partial('_partial/scripts')
68
70
  html += `</div>`
69
71
  html += `</body>`
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "hexo-theme-stellar",
3
- "version": "1.30.3",
3
+ "version": "1.31.0",
4
4
  "description": "Elegant and powerful theme for Hexo.",
5
5
  "main": "package.json",
6
6
  "scripts": {
@@ -18,9 +18,14 @@ hexo.on('generateAfter', () => {
18
18
  });
19
19
 
20
20
  hexo.on('ready', () => {
21
+ if (process.env.HEXO_READY === '1') return;
22
+ process.env.HEXO_READY = '1';
21
23
  const { version, homepage, repository } = require('../../package.json');
22
- hexo.log.info(`Welcome to Stellar ${version}
23
- DOCS ${homepage}
24
- REPO ${repository.url}
24
+ hexo.log.info(`Welcome to \x1b[33mStellar ${version}\x1b[0m
25
+ \x1b[32mDOCS\x1b[0m ${homepage}
26
+ \x1b[32mREPO\x1b[0m ${repository.url}
25
27
  `);
28
+ // version check
29
+ const checkVersion = require('./lib/version-check');
30
+ checkVersion(hexo, { useCache: true });
26
31
  });
@@ -0,0 +1,84 @@
1
+ const https = require('https');
2
+ const path = require('path');
3
+ const fs = require('fs');
4
+ const packageName = 'hexo-theme-stellar';
5
+ const cacheFile = path.join(__dirname, '../../.cache/stellar-version.json');
6
+
7
+ function getLocalVersion() {
8
+ try {
9
+ return require(path.join(__dirname, '../../../package.json')).version;
10
+ } catch {
11
+ return '0.0.0';
12
+ }
13
+ }
14
+
15
+ function fetchLatestVersion(callback) {
16
+ const options = {
17
+ hostname: 'registry.npmjs.org',
18
+ path: `/${encodeURIComponent(packageName)}`,
19
+ headers: {
20
+ 'User-Agent': 'Hexo-Theme-Version-Check'
21
+ }
22
+ };
23
+
24
+ https.get(options, (res) => {
25
+ let rawData = '';
26
+ res.on('data', chunk => rawData += chunk);
27
+ res.on('end', () => {
28
+ try {
29
+ const data = JSON.parse(rawData);
30
+ const latest = data['dist-tags'].latest;
31
+ callback(null, latest);
32
+ } catch (err) {
33
+ callback(err);
34
+ }
35
+ });
36
+ }).on('error', err => callback(err));
37
+ }
38
+
39
+ function shouldCheck() {
40
+ if (!fs.existsSync(cacheFile)) return true;
41
+ try {
42
+ const cache = JSON.parse(fs.readFileSync(cacheFile, 'utf8'));
43
+ const time = new Date().toISOString().slice(0, 16); // 精确到分钟,例如 "2025-06-21T15:08"
44
+ return cache.time !== time;
45
+ } catch {
46
+ return true;
47
+ }
48
+ }
49
+
50
+ function writeCache(latestVersion) {
51
+ try {
52
+ fs.mkdirSync(path.dirname(cacheFile), { recursive: true });
53
+ fs.writeFileSync(cacheFile, JSON.stringify({
54
+ time: new Date().toISOString().slice(0, 16),
55
+ latest: latestVersion
56
+ }));
57
+ } catch {
58
+ // 忽略错误
59
+ }
60
+ }
61
+
62
+ module.exports = (ctx, options = { useCache: true }) => {
63
+ if (process.env.STELLAR_VERSION_CHECKED === '1') return;
64
+ process.env.STELLAR_VERSION_CHECKED = '1';
65
+
66
+ const localVersion = getLocalVersion();
67
+ if (options.useCache && !shouldCheck()) return;
68
+
69
+ fetchLatestVersion((err, latest) => {
70
+ if (err) return;
71
+ if (localVersion !== latest) {
72
+ const line = '------------------------------------------------';
73
+ ctx.log.warn(`\x1b[33m${line}\x1b[0m`);
74
+ ctx.log.warn(``);
75
+ ctx.log.warn(` 本地版本: \x1b[33m${localVersion}\x1b[0m >>>> 最新版本: \x1b[32m${latest}\x1b[0m`);
76
+ ctx.log.warn(``);
77
+ ctx.log.warn(` 请尽快升级: npm i ${packageName}@latest`);
78
+ ctx.log.warn(``);
79
+ ctx.log.warn(`\x1b[33m${line}\x1b[0m`);
80
+ }
81
+ if (options.useCache) writeCache(latest);
82
+ });
83
+
84
+ };
@@ -0,0 +1,114 @@
1
+ /**
2
+ * Builds JSON-LD structured data for current page according to its type (page or post).
3
+ *
4
+ * @returns {string} - JSON-LD structured data
5
+ */
6
+ 'use strict';
7
+
8
+ const util = require('hexo-util');
9
+
10
+ hexo.extend.helper.register('json_ld', function(args) {
11
+ const page = this.page;
12
+ const config = this.config;
13
+ const structured_data = this.theme.structured_data;
14
+ const authorEmail = config.email;
15
+ const authorImage = config.avatar || (authorEmail ? this.gravatar(authorEmail) : null);
16
+
17
+ const author = {
18
+ '@type': 'Person',
19
+ name: config.author,
20
+ sameAs: structured_data.sameAs || []
21
+ };
22
+ // Google does not accept `Person` as item type for the publisher property
23
+ const publisher = Object.assign({}, author, {'@type': 'Organization'});
24
+ let schema = {};
25
+
26
+ if (authorImage) {
27
+ author.image = authorImage;
28
+ publisher.image = authorImage;
29
+ publisher.logo = {
30
+ '@type': 'ImageObject',
31
+ url: authorImage
32
+ };
33
+ }
34
+
35
+ if (this.is_post()) {
36
+ schema = {
37
+ '@context': 'https://schema.org',
38
+ '@type': 'BlogPosting',
39
+ author: author,
40
+ // articleBody: this.strip_html(page.content),
41
+ dateCreated: page.date.format(),
42
+ dateModified: page.updated.format(),
43
+ datePublished: page.date.format(),
44
+ description: this.strip_html(page.excerpt),
45
+ headline: page.title,
46
+ mainEntityOfPage: {
47
+ '@type': 'WebPage',
48
+ '@id': this.url_for(page.permalink)
49
+ },
50
+ publisher,
51
+ url: this.url_for(page.permalink)
52
+ };
53
+
54
+ if (page.tags && page.tags.length > 0) {
55
+ schema.keywords = page.tags.map((tag) => tag.name).join(', ');
56
+ }
57
+
58
+ let images = [];
59
+ if (page.photos && page.photos.length > 0) {
60
+ images = images.concat(page.photos);
61
+ }
62
+
63
+ if (page.cover?.length > 0) {
64
+ images = images.unshift(page.cover);
65
+ } else if (page.banner?.length > 0) {
66
+ images = images.unshift(page.banner);
67
+ }
68
+
69
+ schema.thumbnailUrl = page.cover || page.banner;
70
+ schema.image = images;
71
+
72
+ } else if (this.is_page() || this.is_home()) {
73
+
74
+ const url = this.is_home() ? config.url : this.url_for(page.permalink);
75
+ schema = {
76
+ '@context': 'https://schema.org',
77
+ '@type': 'Website',
78
+ '@id': url,
79
+ author: author,
80
+ name: page.title || config.title,
81
+ description: config.description,
82
+ url: url
83
+ };
84
+
85
+ if (config.keywords && config.keywords.length) {
86
+ schema.keywords = config.keywords.join(', ');
87
+ }
88
+ if (!this.is_home()) {
89
+
90
+ if (page.excerpt || page.description) {
91
+ schema.description = this.strip_html(page.description || page.excerpt);
92
+ } else {
93
+ schema.description = util.truncate(this.strip_html(page.content), {length: 200});
94
+ }
95
+
96
+ }
97
+
98
+ } else {
99
+
100
+ // default to WebPage for other layouts
101
+ schema = {
102
+ '@context': 'https://schema.org',
103
+ '@type': 'Website',
104
+ '@id': config.url,
105
+ author: author,
106
+ name: config.title,
107
+ description: config.description,
108
+ url: config.url
109
+ };
110
+
111
+ }
112
+
113
+ return `<script type="application/ld+json">${JSON.stringify(schema)}</script>`;
114
+ });
@@ -40,6 +40,7 @@ hexo.extend.tag.register('navbar', require('./lib/navbar')(hexo))
40
40
  hexo.extend.tag.register('note', require('./lib/note')(hexo))
41
41
  hexo.extend.tag.register('poetry', require('./lib/poetry')(hexo), true)
42
42
  hexo.extend.tag.register('quot', require('./lib/quot')(hexo))
43
+ hexo.extend.tag.register('quote', require('./lib/quote')(hexo), true)
43
44
  hexo.extend.tag.register('hashtag', require('./lib/hashtag')(hexo))
44
45
  hexo.extend.tag.register('okr', require('./lib/okr')(hexo), {ends: true})
45
46
  hexo.extend.tag.register('audio', require('./lib/audio')(hexo))
@@ -9,6 +9,7 @@
9
9
 
10
10
  module.exports = ctx => function(args) {
11
11
  var args = ctx.args.map(args, ['repo', 'api', 'size'], ['group'])
12
+ const host = ctx.theme.config.api_host.ghraw
12
13
  if (args.size == null) {
13
14
  args.size = 's'
14
15
  }
@@ -16,7 +17,7 @@ module.exports = ctx => function(args) {
16
17
  if (args.api) {
17
18
  api = args.api
18
19
  } else if (args.repo) {
19
- api = 'https://api.vlts.cc/output_data/v2/' + args.repo
20
+ api = `https://${host}/${args.repo}/output/v2/data.json`
20
21
  }
21
22
 
22
23
  var el = ''
@@ -9,11 +9,12 @@
9
9
 
10
10
  module.exports = ctx => function(args) {
11
11
  args = ctx.args.map(args, ['repo', 'api', 'posts'], ['group'])
12
+ const host = ctx.theme.config.api_host.ghraw
12
13
  var api
13
14
  if (args.api) {
14
15
  api = args.api
15
16
  } else if (args.repo) {
16
- api = 'https://api.vlts.cc/output_data/v2/' + args.repo
17
+ api = `https://${host}/${args.repo}/output/v2/data.json`
17
18
  }
18
19
 
19
20
  var el = `<div class="tag-plugin ${args.posts ? 'users-posts-wrap' : 'users-wrap'}">`
@@ -17,6 +17,7 @@ module.exports = ctx => function(args) {
17
17
  var params = ['show_owner', 'theme', 'title_color', 'text_color', 'icon_color', 'bg_color', 'hide_border', 'cache_seconds', 'locale']
18
18
  args = ctx.args.map(args, params, ['repo'])
19
19
  const path = args.repo
20
+ const host = ctx.theme.config.api_host.ghcard
20
21
  var el = ''
21
22
  el += '<div class="tag-plugin ghcard">'
22
23
  el += '<a class="ghcard" rel="external nofollow noopener noreferrer" href="https://github.com/' + path + '">'
@@ -24,10 +25,10 @@ module.exports = ctx => function(args) {
24
25
  if (path.includes('/')) {
25
26
  // is repo
26
27
  const ps = path.split('/')
27
- url += 'https://github-readme-stats.xaoxuu.com/api/pin/?username=' + ps[0] + '&repo=' + ps[1]
28
+ url = `https://${host}/api/pin/?username=${ps[0]}&repo=${ps[1]}`
28
29
  } else {
29
30
  // is user
30
- url += 'https://github-readme-stats.xaoxuu.com/api/?username=' + path
31
+ url = `https://${host}/api/?username=${path}`
31
32
  }
32
33
  url += '&' + ctx.args.joinURLParams(args, params)
33
34
  if (!url.includes('&show_owner=')) {
@@ -9,6 +9,7 @@
9
9
 
10
10
  module.exports = ctx => function(args) {
11
11
  var args = ctx.args.map(args, ['repo', 'api', 'size'], ['group'])
12
+ const host = ctx.theme.config.api_host.ghraw
12
13
  if (args.size == null) {
13
14
  args.size = 'm'
14
15
  }
@@ -16,7 +17,7 @@ module.exports = ctx => function(args) {
16
17
  if (args.api) {
17
18
  api = args.api
18
19
  } else if (args.repo) {
19
- api = 'https://api.vlts.cc/output_data/v2/' + args.repo
20
+ api = `https://${host}/${args.repo}/output/v2/data.json`
20
21
  }
21
22
 
22
23
  var el = ''
@@ -0,0 +1,22 @@
1
+ /**
2
+ * quote.js v1.1 | https://github.com/xaoxuu/hexo-theme-stellar/
3
+ * 格式与官方标签插件一致使用空格分隔,中括号内的是可选参数(中括号不需要写出来)
4
+ *
5
+ * quote:
6
+ * {% quote [indent:true/false] %}
7
+ * body
8
+ * {% endquote %}
9
+ *
10
+ */
11
+
12
+ 'use strict'
13
+
14
+ module.exports = ctx => function(args, content) {
15
+ var el = ''
16
+ args = ctx.args.map(args, ['indent'], [])
17
+
18
+ el += `<div class="tag-plugin quote" indent="${args.indent}">`
19
+ el += ctx.render.renderSync({text: content, engine: 'markdown'}).split('\n').join('')
20
+ el += `</div>`
21
+ return el
22
+ }
@@ -9,11 +9,12 @@
9
9
 
10
10
  module.exports = ctx => function(args) {
11
11
  args = ctx.args.map(args, ['repo', 'api'], ['group'])
12
+ const host = ctx.theme.config.api_host.ghraw
12
13
  var api
13
14
  if (args.api) {
14
15
  api = args.api
15
16
  } else if (args.repo) {
16
- api = 'https://api.vlts.cc/output_data/v2/' + args.repo
17
+ api = `https://${host}/${args.repo}/output/v2/data.json`
17
18
  }
18
19
 
19
20
  var el = '<div class="tag-plugin sites-wrap">'
@@ -32,8 +32,8 @@ module.exports = ctx => function (args) {
32
32
  </div>
33
33
  `
34
34
  }
35
- return `<div class="tag-plugin video" style="max-width:${args.width};">
36
- <video controls preload>
35
+ return `<div class="tag-plugin video-player" style="max-width:${args.width};">
36
+ <video controls preload playsinline webkit-playsinline>
37
37
  <source src="${args.src}" type="${args.type || 'video/mp4'}">Your browser does not support the video tag.
38
38
  </video>
39
39
  </div>
@@ -0,0 +1,32 @@
1
+ .canonical-tip
2
+ position: fixed
3
+ text-align: center
4
+ bottom: 4rem
5
+ left: 50%
6
+ transform: translateX(-50%)
7
+ max-width: 90%
8
+ background-color: var(--block-hover)
9
+ color: var(--text-p1)
10
+ font-size: 1rem
11
+ font-family: #fff8e1
12
+ z-index: 9999999
13
+ box-shadow: 0 2px 8px rgba(black, .1), 0 4px 16px rgba(black, .1), 0 8px 32px rgba(black, .1)
14
+ border-radius: 6px
15
+ .headline
16
+ font-size: $fsh2
17
+ a
18
+ display: block
19
+ color: inherit
20
+ padding: 1rem 2rem
21
+
22
+ .canonical-tip.unofficial
23
+ background-color: hsl(5, 100%, 60%)
24
+ color: white
25
+ animation: breathe 3s ease-in-out infinite
26
+ @keyframes breathe
27
+ 0%
28
+ background-color: hsl(5, 100%, 60%)
29
+ 50%
30
+ background-color: hsl(5, 100%, 30%)
31
+ 100%
32
+ background-color: hsl(5, 100%, 60%)
@@ -10,14 +10,13 @@
10
10
  .float-panel
11
11
  position: sticky
12
12
  grid-column-end: span 3
13
- padding: 4px
14
13
  --inset: 2rem
15
14
  right: 0
16
15
  bottom: calc(var(--inset) * 2)
17
16
  margin-left: auto
18
17
  margin-right: var(--inset)
19
18
  float: right
20
- z-index: 10
19
+ z-index: 999999
21
20
  display: flex
22
21
  justify-content: center
23
22
  flex-direction: column
@@ -26,6 +25,9 @@
26
25
  margin-right: 3rem
27
26
  overflow: hidden
28
27
  trans1: all
28
+ &:before,&:after
29
+ border-radius: 64px
30
+
29
31
  newblur()
30
32
 
31
33
 
@@ -34,8 +36,8 @@
34
36
  cursor: pointer
35
37
  color: var(--text)
36
38
  background: none
37
- width: 40px
38
- height: 40px
39
+ width: 48px
40
+ height: 48px
39
41
  line-height: 0
40
42
  font-size: 28px
41
43
  margin: 0
@@ -49,10 +51,11 @@
49
51
  // 侧边栏弹出后
50
52
  .l_body[leftbar] .float-panel, .l_body[rightbar] .float-panel
51
53
  box-shadow: 0 0 4px -1px $color-theme, 0 0 16px -4px $color-theme, 0 0 32px -12px $color-theme, 0 0 128px -32px $color-theme
54
+ &:before
55
+ background: var(--alpha50)
52
56
 
53
57
  .l_body[leftbar] .float-panel button.leftbar-toggle
54
58
  color: $color-theme
55
- border-color: var(--block-border)
56
59
  svg g
57
60
  fill: currentColor
58
61
  fill-opacity: 0.3
@@ -66,7 +69,6 @@
66
69
 
67
70
  .l_body[rightbar] .float-panel button.rightbar-toggle
68
71
  color: $color-theme
69
- border-color: var(--block-border)
70
72
  svg g
71
73
  fill: currentColor
72
74
  fill-opacity: 0.3
@@ -41,7 +41,7 @@
41
41
  --inset: 8px
42
42
  right: var(--inset)
43
43
  top: var(--inset)
44
- max-height: 'calc(%s - 96px)' % (100vh)
44
+ max-height: 'calc(%s - %s)' % (100vh $rightbar-bottom-margin)
45
45
  box-shadow: $boxshadow-card-float
46
46
  z-index: 10
47
47
  background: var(--site-bg)
@@ -71,10 +71,10 @@
71
71
  --inset: 8px
72
72
  left: var(--inset)
73
73
  top: var(--inset)
74
- max-height: 'calc(%s - 96px)' % (100vh)
74
+ max-height: 'calc(%s - %s)' % (100vh $leftbar-bottom-margin)
75
75
  .leftbar-container
76
76
  --inset: 8px
77
- height: 'calc(%s - 96px)' % (100vh)
77
+ height: 'calc(%s - %s)' % (100vh $leftbar-bottom-margin)
78
78
  box-shadow: $boxshadow-card-float
79
79
  z-index: 10
80
80
  .l_main
@@ -33,17 +33,23 @@
33
33
  position: relative
34
34
 
35
35
  h3:not([class])
36
+ position: relative
36
37
  &:before,&:after
37
- color: hsla($color-theme, 0.5)
38
- margin: 0 8px
38
+ position absolute
39
+ font-size: 1.2em
40
+ background-color: hsla($color-theme, 0.5)
41
+ width: 1em
42
+ height: 1em
43
+ content: ''
44
+ opacity 1
45
+ display: inline-block
39
46
  trans1 all
40
- &:before
41
- content: '⏵'
47
+ vertical-align: middle
48
+ &:before
49
+ margin-left: -1em
50
+ svg-mask-icon($h3Left)
42
51
  &:after
43
- content: '⏴'
44
- &:hover
45
- &:before,&:after
46
- color: hsla($color-theme, 1)
52
+ svg-mask-icon($h3Right)
47
53
 
48
54
  h4:not([class])
49
55
  position: relative
@@ -56,12 +62,14 @@
56
62
  margin-top: -12px
57
63
  background: hsla($color-theme, 0.3)
58
64
  border-radius: 50%
59
- filter: blur(12px)
65
+ --blur-px: 8px
66
+ filter: blur(var(--blur-px))
60
67
  pointer-events: none
61
68
  z-index: -1
62
- trans1 all
69
+ trans1 all 0.5s
63
70
  &:hover:before
64
- background: hsla($color-theme, 0.6)
71
+ --blur-px: 24px
72
+ background: hsla($color-theme, 1)
65
73
 
66
74
  ol,ul
67
75
  margin-left: 'calc(%s * 2)' % var(--fsp)
@@ -71,7 +79,7 @@
71
79
  >a:first-child
72
80
  display: none
73
81
  // 修正缩进效果
74
- >blockquote
82
+ >blockquote,.tag-plugin.quote
75
83
  text-align: center
76
84
  --inset: calc(var(--fsp) * 2)
77
85
  margin-left: var(--inset)
@@ -2,9 +2,6 @@
2
2
  margin: 8px
3
3
  max-height: 'calc(%s - 16px)' % 100vh
4
4
  border-radius: $border-card-l
5
- overflow: hidden
6
- overflow: clip
7
- overflow-clip-margin: .5px
8
5
  .header
9
6
  margin: var(--gap-max) var(--gap-margin) 0
10
7
 
@@ -20,23 +17,70 @@
20
17
  margin-right: auto
21
18
 
22
19
  .l_left
23
- background-position: center
24
- background-size: cover
25
- if hexo-config('style.leftbar') && hexo-config('style.leftbar.background-image')
26
- background-image: convert(hexo-config('style.leftbar.background-image'))
27
- else if hexo-config('style.leftbar') && hexo-config('style.leftbar.background')
28
- background: convert(hexo-config('style.leftbar.background'))
20
+ .sidebg
21
+ pointer-events: none
22
+ position: absolute
23
+ --inset: 0
24
+ top: var(--inset)
25
+ bottom: var(--inset)
26
+ left: var(--inset)
27
+ right: var(--inset)
28
+ background-position: center
29
+ background-size: cover
30
+ border-radius: $border-card-l
31
+ --saturate: 300%
32
+ --blur-px: convert(hexo-config('style.leftbar.blur-px'))
33
+ --brightness: 100%
34
+ if hexo-config('style.leftbar') && hexo-config('style.leftbar.background-image')
35
+ if hexo-config('style.leftbar.blur-px')
36
+ --inset: 32px
37
+ background-image: convert(hexo-config('style.leftbar.background-image'))
38
+ filter: saturate(var(--saturate)) brightness(var(--brightness)) blur(var(--blur-px))
39
+ @media screen and (prefers-color-scheme: dark)
40
+ --blur-px: 'calc(%s * 0.75)' % convert(hexo-config('style.leftbar.blur-px'))
41
+ --saturate: 200%
42
+ --brightness: 75%
43
+ else if hexo-config('style.leftbar') && hexo-config('style.leftbar.background')
44
+ background: convert(hexo-config('style.leftbar.background'))
45
+
29
46
  .leftbar-container
30
- height: 'calc(%s - 16px)' % 100vh
47
+ height: 'calc(%s - 80px)' % 100vh
31
48
  display: flex
32
49
  flex-direction: column
33
50
  word-break: break-all
34
51
  text-align: justify
35
- if hexo-config('style.leftbar') && hexo-config('style.leftbar.blur-px')
36
- --blur-px: convert(hexo-config('style.leftbar.blur-px'))
37
- @supports ((-webkit-backdrop-filter:blur(var(--blur-px))) or (backdrop-filter:blur(var(--blur-px))))
38
- background: var(--blur-bg)
39
- backdrop-filter: saturate(240%) blur(var(--blur-px))
40
- -webkit-backdrop-filter: saturate(240%) blur(var(--blur-px))
41
- if hexo-config('style.leftbar') && hexo-config('style.leftbar.blur-bg')
42
- --blur-bg: convert(hexo-config('style.leftbar.blur-bg'))
52
+ border-radius: $border-card-l
53
+ overflow: hidden
54
+ >.widgets
55
+ mask: linear-gradient(transparent, white, 10%, white, 90%, transparent)
56
+ >*
57
+ z-index 1
58
+ &:before,&:after
59
+ position: absolute
60
+ pointer-events: none
61
+ content: ''
62
+ top: 0
63
+ bottom: 0
64
+ left: 0
65
+ right: 0
66
+ border-radius: $border-card-l
67
+ &:before
68
+ @media screen and (min-width: $device-mobile-max)
69
+ background: rgba(white, 0.5)
70
+ box-shadow: inset 0 0 32px 1px rgba(white, 0.5)
71
+ backdrop-filter: saturate(300%)
72
+ mask: linear-gradient(black, rgba(black, 0.5), 70%, transparent, 90%, transparent)
73
+
74
+ @media screen and (prefers-color-scheme: dark)
75
+ background: rgba(white, 0.05)
76
+ box-shadow: inset 0 0 32px 1px rgba(white, 0.2)
77
+
78
+ @media screen and (max-width: $device-mobile-max)
79
+ .l_left
80
+ overflow: hidden
81
+ background: var(--alpha100)
82
+ .sidebg
83
+ --inset: 0
84
+ --saturate: 300%
85
+ .leftbar-container:after
86
+ background: var(--alpha20)
@@ -1,4 +1,4 @@
1
- .tag-plugin.video
1
+ .tag-plugin.video-player
2
2
  line-height: 0
3
3
  margin: auto
4
4
  video, iframe
@@ -0,0 +1,7 @@
1
+ .tag-plugin.quote
2
+ position: relative
3
+ p:not([class])
4
+ text-indent: 0 !important
5
+ &[indent='true']
6
+ p:not([class])
7
+ text-indent: 'calc(%s * 2)' % var(--fsp) !important
@@ -6,9 +6,7 @@
6
6
  border-radius: $border-bar
7
7
  color: var(--text-p1)
8
8
  trans1 background
9
- &.active
10
- background: var(--alpha50)
11
- &:hover
9
+ &.active,&:hover
12
10
  background: var(--alpha100)
13
11
  a+a
14
12
  margin-top: 2px
@@ -21,6 +21,9 @@ $ff-logo = $ff-body
21
21
  $iQuoteLeft = "data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='1em' height='1em' viewBox='0 0 24 24'%3E%3C!-- Icon from BoxIcons Solid by Atisa - https://creativecommons.org/licenses/by/4.0/ --%3E%3Cpath fill='currentColor' d='M3.691 6.292C5.094 4.771 7.217 4 10 4h1v2.819l-.804.161c-1.37.274-2.323.813-2.833 1.604A2.9 2.9 0 0 0 6.925 10H10a1 1 0 0 1 1 1v7c0 1.103-.897 2-2 2H3a1 1 0 0 1-1-1v-5l.003-2.919c-.009-.111-.199-2.741 1.688-4.789M20 20h-6a1 1 0 0 1-1-1v-5l.003-2.919c-.009-.111-.199-2.741 1.688-4.789C16.094 4.771 18.217 4 21 4h1v2.819l-.804.161c-1.37.274-2.323.813-2.833 1.604A2.9 2.9 0 0 0 17.925 10H21a1 1 0 0 1 1 1v7c0 1.103-.897 2-2 2'/%3E%3C/svg%3E"
22
22
  $iQuoteRight = "data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='1em' height='1em' viewBox='0 0 24 24'%3E%3C!-- Icon from BoxIcons Solid by Atisa - https://creativecommons.org/licenses/by/4.0/ --%3E%3Cpath fill='currentColor' d='M20.309 17.708C22.196 15.66 22.006 13.03 22 13V5a1 1 0 0 0-1-1h-6c-1.103 0-2 .897-2 2v7a1 1 0 0 0 1 1h3.078a2.9 2.9 0 0 1-.429 1.396c-.508.801-1.465 1.348-2.846 1.624l-.803.16V20h1c2.783 0 4.906-.771 6.309-2.292m-11.007 0C11.19 15.66 10.999 13.03 10.993 13V5a1 1 0 0 0-1-1h-6c-1.103 0-2 .897-2 2v7a1 1 0 0 0 1 1h3.078a2.9 2.9 0 0 1-.429 1.396c-.508.801-1.465 1.348-2.846 1.624l-.803.16V20h1c2.783 0 4.906-.771 6.309-2.292'/%3E%3C/svg%3E"
23
23
 
24
+ $h3Left = "data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='1em' height='1em' viewBox='0 0 24 24'%3E%3C!-- Icon from Solar by 480 Design - https://creativecommons.org/licenses/by/4.0/ --%3E%3Cpath fill='currentColor' fill-rule='evenodd' d='M13.488 4.43a.75.75 0 0 1 .081 1.058L7.988 12l5.581 6.512a.75.75 0 1 1-1.138.976l-6-7a.75.75 0 0 1 0-.976l6-7a.75.75 0 0 1 1.057-.081' clip-rule='evenodd'/%3E%3Cpath fill='currentColor' d='M17.75 5a.75.75 0 0 0-1.32-.488l-6 7a.75.75 0 0 0 0 .976l6 7A.75.75 0 0 0 17.75 19z'/%3E%3C/svg%3E"
25
+ $h3Right = "data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='1em' height='1em' viewBox='0 0 24 24'%3E%3C!-- Icon from Solar by 480 Design - https://creativecommons.org/licenses/by/4.0/ --%3E%3Cpath fill='currentColor' fill-rule='evenodd' d='M10.512 4.43a.75.75 0 0 0-.081 1.058L16.012 12l-5.581 6.512a.75.75 0 1 0 1.138.976l6-7a.75.75 0 0 0 0-.976l-6-7a.75.75 0 0 0-1.057-.081' clip-rule='evenodd'/%3E%3Cpath fill='currentColor' d='M6.25 5a.75.75 0 0 1 1.32-.488l6 7a.75.75 0 0 1 0 .976l-6 7A.75.75 0 0 1 6.25 19z'/%3E%3C/svg%3E"
26
+
24
27
  if hexo-config('style.font-family.body')
25
28
  $ff-body = convert(hexo-config('style.font-family.body'))
26
29
 
@@ -51,4 +51,8 @@ $c-blue = #2196f3
51
51
  $c-purple = #9c27b0
52
52
 
53
53
  $hue-yellow = 50
54
- $cDeepOrange = hsl(14 100% 57%)
54
+ $cDeepOrange = hsl(14 100% 57%)
55
+
56
+
57
+ $leftbar-bottom-margin = 128px
58
+ $rightbar-bottom-margin = 96px
@@ -134,7 +134,7 @@ hoverable-card()
134
134
  ondark()
135
135
 
136
136
  newblur()
137
- box-shadow: 0 0 2px rgba(black, .05), 0 0 8px rgba(black, .05)
137
+ box-shadow: 0 0 2px rgba(black, .02), 0 2px 8px rgba(black, .03), 0 4px 16px rgba(black, .05)
138
138
  trans1: all
139
139
  &:before,&:after
140
140
  z-index -1
@@ -149,12 +149,12 @@ newblur()
149
149
  &:before
150
150
  backdrop-filter: blur(8px)
151
151
  &:after
152
- --blur-px: 16px
152
+ --blur-px: 32px
153
153
  --blur-sat: 500%
154
- background: var(--alpha50)
154
+ background: var(--alpha20)
155
155
  backdrop-filter: saturate(var(--blur-sat)) blur(var(--blur-px))
156
- mask: linear-gradient(rgba(white, 1), 10%, rgba(white, 0.2), 90%, rgba(white, 1)), linear-gradient(90deg, rgba(white, 1), 10%, rgba(white, 0.2), 90%, rgba(white, 1))
157
- box-shadow: inset 0 0 8px 1px white
156
+ mask: linear-gradient(rgba(white, 1), 10%, rgba(white, 0.1), 90%, rgba(white, 1)), linear-gradient(90deg, rgba(white, 1), 10%, rgba(white, 0.1), 90%, rgba(white, 1))
157
+ box-shadow: inset 0 0 4px 1px rgba(white, 0.5)
158
158
  trans1 all
159
159
  @media (prefers-color-scheme: dark)
160
160
  opacity 0.4
@@ -121,11 +121,11 @@ _common_root()
121
121
  _dark_root()
122
122
 
123
123
  @media (prefers-color-scheme: dark)
124
- :root:not([color-scheme])
124
+ :root:not([data-theme])
125
125
  img,
126
126
  .md-text .tag-plugin.chat>.content .chatcell .user-main .talk.file>.content>.top>.right>svg.icon>path
127
127
  filter: brightness(75%)!important
128
- [color-scheme='dark']
128
+ [data-theme='dark']
129
129
  img,
130
130
  .md-text .tag-plugin.chat>.content .chatcell .user-main .talk.file>.content>.top>.right>svg.icon>path
131
131
  filter: brightness(75%)!important
package/source/js/main.js CHANGED
@@ -200,6 +200,55 @@ const init = {
200
200
  window.dispatchEvent(new Event('tabs:register'));
201
201
  },
202
202
 
203
+ canonicalCheck: () => {
204
+ const canonical = window.canonical;
205
+ function showTip(isOfficial = false) {
206
+ const meta = document.createElement('meta');
207
+ meta.name = 'robots';
208
+ meta.content = 'noindex, nofollow';
209
+ document.head.appendChild(meta);
210
+ const notice = document.createElement('div');
211
+ const originalURL = `https://${canonical.originalHost}`;
212
+ if (isOfficial) {
213
+ notice.className = 'canonical-tip official';
214
+ notice.innerHTML = `
215
+ <a href="${originalURL}" target="_self" rel="noopener noreferrer">
216
+ 本站为官方备用站,仅供应急。主站:${originalURL}
217
+ </a>
218
+ `;
219
+ } else {
220
+ notice.className = 'canonical-tip unofficial';
221
+ notice.innerHTML = `
222
+ <a href="${originalURL}" target="_self" rel="noopener noreferrer">
223
+ <div class="headline icon">☠️</div>
224
+ 本站为非法克隆站,请前往官方源站访问。<br>
225
+ 源站:${originalURL}
226
+ </a>
227
+ `;
228
+ }
229
+ document.body.appendChild(notice);
230
+ }
231
+ if (!canonical.originalHost) return;
232
+ const canonicalTag = document.querySelector('link[rel="canonical"]');
233
+ if (!canonicalTag) {
234
+ showTip(false);
235
+ return;
236
+ }
237
+ const canonicalURL = new URL(canonicalTag.href);
238
+ const currentURL = new URL(window.location.href);
239
+ const canonicalHost = canonicalURL.hostname.replace(/^www\./, '');
240
+ const currentHost = currentURL.hostname.replace(/^www\./, '');
241
+ if (currentHost == 'localhost') return;
242
+ const encodedCanonicalHost = window.btoa(canonicalHost);
243
+ const encodedCurrentHost = window.btoa(currentHost);
244
+ const isCanonicalHostValid = canonical.encoded === encodedCanonicalHost;
245
+ const isCurrentHostValid = canonical.encoded === encodedCurrentHost;
246
+ if (isCanonicalHostValid && isCurrentHostValid) {
247
+ return;
248
+ }
249
+ showTip(canonical.officialHosts?.includes(currentHost));
250
+ }
251
+
203
252
  }
204
253
 
205
254
 
@@ -208,3 +257,4 @@ init.toc()
208
257
  init.sidebar()
209
258
  init.relativeDate(document.querySelectorAll('#post-meta time'))
210
259
  init.registerTabsTag()
260
+ init.canonicalCheck()
@@ -167,7 +167,7 @@ function extToMimes(ext) {
167
167
  function downloadFileEvent(fileDoms) {
168
168
  for (let i = 0; i < fileDoms.length; ++i) {
169
169
  let fileDom = fileDoms[i];
170
- const api = ctx.tag_plugins.chat.api + '?type=file&url=' || 'https://api.vlts.cc/site_info/v1?type=file&url=';
170
+ const api = ctx.tag_plugins.chat.api + '?type=file&url=';
171
171
  const obverser = new IntersectionObserver((entries, observer) => {
172
172
  utils.requestAnimationFrame(()=>{
173
173
  entries.filter((entry)=>{return entry.isIntersecting}).sort((a,b)=>a.intersectionRect.y !== b.intersectionRect.y ? a.intersectionRect.y - b.intersectionRect.y : a.intersectionRect.x - b.intersectionRect.x).forEach((entry, index) => {
@@ -3,7 +3,6 @@ utils.jq(() => {
3
3
  for (var i = 0; i < els.length; i++) {
4
4
  const el = els[i];
5
5
  const src = `${el.getAttribute('src')}?t=${new Date().getTime()}`;
6
- console.log('src', src);
7
6
 
8
7
  utils.request(el, src, async resp => {
9
8
  const data = await resp.text();