hexo-component-inferno 4.2.2 → 5.0.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 (69) hide show
  1. package/README.md +5 -18
  2. package/asset/css/github-calendar.css +41 -0
  3. package/asset/js/algolia.js +18 -69
  4. package/asset/js/github-calendar.js +185 -0
  5. package/asset/js/google_cse.js +19 -68
  6. package/asset/js/insight.js +45 -208
  7. package/lib/core/commented_yaml.js +35 -0
  8. package/lib/core/default_value.js +150 -0
  9. package/lib/core/schema.js +8 -196
  10. package/lib/core/schema_util.js +14 -0
  11. package/lib/hexo/helper/cdn.js +10 -97
  12. package/lib/hexo/tag/tabs.js +4 -84
  13. package/lib/schema/comment/artalk.json +7 -1
  14. package/lib/schema/comment/facebook.json +1 -1
  15. package/lib/schema/comment/gitalk.json +23 -6
  16. package/lib/schema/comment/gitment.json +1 -1
  17. package/lib/schema/comment/twikoo.json +3 -3
  18. package/lib/schema/comment/waline.json +28 -3
  19. package/lib/schema/misc/open_graph.json +2 -1
  20. package/lib/schema/plugin/cookie_consent.json +2 -2
  21. package/lib/schema/plugin/mathjax.json +1 -1
  22. package/lib/schema/plugin/outdated_browser.json +1 -1
  23. package/lib/schema/share/bdshare.json +1 -1
  24. package/lib/schema/share/sharejs.json +1 -1
  25. package/lib/schema/share/web_share.json +13 -0
  26. package/lib/schema/widget/github_calendar.json +30 -0
  27. package/lib/schema/widget/profile.json +67 -0
  28. package/lib/util/archives.js +86 -0
  29. package/lib/util/categories.js +102 -0
  30. package/lib/util/cdn-rules.js +109 -0
  31. package/lib/util/cdn.js +116 -0
  32. package/lib/util/consent.js +122 -0
  33. package/lib/util/deprecate.js +1 -1
  34. package/lib/util/gallery.js +227 -0
  35. package/lib/util/insight-search.js +98 -0
  36. package/lib/util/searchbox.js +88 -0
  37. package/lib/util/tab-options.js +109 -0
  38. package/lib/view/comment/artalk.js +24 -6
  39. package/lib/view/comment/disqus.js +1 -1
  40. package/lib/view/comment/disqusjs.js +7 -6
  41. package/lib/view/comment/facebook.js +3 -16
  42. package/lib/view/comment/gitalk.js +18 -15
  43. package/lib/view/comment/gitment.js +2 -2
  44. package/lib/view/comment/twikoo.js +5 -3
  45. package/lib/view/comment/valine.js +4 -3
  46. package/lib/view/comment/waline.js +8 -6
  47. package/lib/view/misc/open_graph.js +3 -5
  48. package/lib/view/plugin/cookie_consent.js +8 -7
  49. package/lib/view/plugin/gallery.js +42 -19
  50. package/lib/view/plugin/katex.js +4 -4
  51. package/lib/view/plugin/mathjax.js +10 -7
  52. package/lib/view/plugin/outdated_browser.js +59 -45
  53. package/lib/view/plugin/progressbar.js +6 -1
  54. package/lib/view/plugin/waline_pageview.js +2 -2
  55. package/lib/view/search/algolia.js +8 -3
  56. package/lib/view/search/google_cse.js +13 -10
  57. package/lib/view/search/insight.js +9 -2
  58. package/lib/view/share/addthis.js +4 -9
  59. package/lib/view/share/addtoany.js +23 -5
  60. package/lib/view/share/bdshare.js +9 -30
  61. package/lib/view/share/sharejs.js +7 -34
  62. package/lib/view/share/sharethis.js +21 -6
  63. package/lib/view/share/web_share.js +153 -0
  64. package/lib/view/widget/archives.js +19 -71
  65. package/lib/view/widget/categories.js +20 -67
  66. package/lib/view/widget/github_calendar.js +94 -0
  67. package/lib/view/widget/profile.js +194 -0
  68. package/lib/view/widget/subscribe_email.js +4 -26
  69. package/package.json +6 -4
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "$schema": "http://json-schema.org/draft-07/schema#",
3
3
  "$id": "/plugin/outdated_browser.json",
4
- "description": "Enable the Outdated Browser plugin\nhttp://outdatedbrowser.com/",
4
+ "description": "Warn when required CSS features are unsupported",
5
5
  "type": "boolean",
6
6
  "default": false
7
7
  }
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "$schema": "http://json-schema.org/draft-07/schema#",
3
3
  "$id": "/share/bdshare.json",
4
- "description": "Enable Baidu share buttons",
4
+ "description": "Baidu share buttons (deprecated)",
5
5
  "type": "object",
6
6
  "properties": {
7
7
  "type": {
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "$schema": "http://json-schema.org/draft-07/schema#",
3
3
  "$id": "/share/sharejs.json",
4
- "description": "Enable Share.js share buttons\nhttps://github.com/overtrue/share.js/",
4
+ "description": "Share.js (retired; select web_share or addtoany)",
5
5
  "type": "object",
6
6
  "properties": {
7
7
  "type": {
@@ -0,0 +1,13 @@
1
+ {
2
+ "$schema": "http://json-schema.org/draft-07/schema#",
3
+ "$id": "/share/web_share.json",
4
+ "description": "Social sharing links and a native Web Share button",
5
+ "type": "object",
6
+ "properties": {
7
+ "type": {
8
+ "type": "string",
9
+ "const": "web_share"
10
+ }
11
+ },
12
+ "required": ["type"]
13
+ }
@@ -0,0 +1,30 @@
1
+ {
2
+ "$schema": "http://json-schema.org/draft-07/schema#",
3
+ "$id": "/widget/github_calendar.json",
4
+ "description": "Responsive GitHub contribution calendar. Data is loaded from the third-party github-contributions-api.jogruber.de service; no GitHub token is needed",
5
+ "type": "object",
6
+ "properties": {
7
+ "type": {
8
+ "type": "string",
9
+ "const": "github_calendar"
10
+ },
11
+ "username": {
12
+ "type": "string",
13
+ "description": "GitHub username. Leave empty to hide this widget",
14
+ "pattern": "^$|^[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,37}[a-zA-Z0-9])?$",
15
+ "nullable": true
16
+ },
17
+ "title": {
18
+ "type": "string",
19
+ "description": "Widget title. Leave empty to use the translated default",
20
+ "nullable": true
21
+ },
22
+ "color": {
23
+ "type": "string",
24
+ "description": "Optional six-digit hex color, for example '#40c463'. Leave empty to use the theme's calendar palette",
25
+ "pattern": "^$|^#?[a-fA-F0-9]{6}$",
26
+ "nullable": true
27
+ }
28
+ },
29
+ "required": ["type"]
30
+ }
@@ -0,0 +1,67 @@
1
+ {
2
+ "$schema": "http://json-schema.org/draft-07/schema#",
3
+ "$id": "/widget/profile.json",
4
+ "description": "Profile widget configurations",
5
+ "type": "object",
6
+ "properties": {
7
+ "type": {
8
+ "type": "string",
9
+ "const": "profile",
10
+ "nullable": true
11
+ },
12
+ "author": {
13
+ "type": "string",
14
+ "description": "Author name",
15
+ "examples": ["Your name"],
16
+ "nullable": true
17
+ },
18
+ "author_title": {
19
+ "type": "string",
20
+ "description": "Author title",
21
+ "examples": ["Your title"],
22
+ "nullable": true
23
+ },
24
+ "location": {
25
+ "type": "string",
26
+ "description": "Author's current location",
27
+ "examples": ["Your location"],
28
+ "nullable": true
29
+ },
30
+ "avatar": {
31
+ "type": "string",
32
+ "description": "URL or path to the avatar image",
33
+ "nullable": true
34
+ },
35
+ "avatar_rounded": {
36
+ "type": "boolean",
37
+ "description": "Whether show the rounded avatar image",
38
+ "default": false,
39
+ "nullable": true
40
+ },
41
+ "gravatar": {
42
+ "type": "string",
43
+ "description": "Email address for the Gravatar",
44
+ "nullable": true
45
+ },
46
+ "follow_link": {
47
+ "type": "string",
48
+ "description": "URL or path for the follow button",
49
+ "examples": ["https://github.com/ppoffice"],
50
+ "nullable": true
51
+ },
52
+ "social_links": {
53
+ "$ref": "/misc/poly_links.json",
54
+ "description": "Links to be shown on the bottom of the profile widget",
55
+ "examples": [
56
+ {
57
+ "Github": { "icon": "fab fa-github", "url": "https://github.com/ppoffice" },
58
+ "Facebook": { "icon": "fab fa-facebook", "url": "https://facebook.com" },
59
+ "Twitter": { "icon": "fab fa-twitter", "url": "https://twitter.com" },
60
+ "Dribbble": { "icon": "fab fa-dribbble", "url": "https://dribbble.com" },
61
+ "RSS": { "icon": "fas fa-rss", "url": "/" }
62
+ }
63
+ ]
64
+ }
65
+ },
66
+ "required": ["type"]
67
+ }
@@ -0,0 +1,86 @@
1
+ /**
2
+ * Internal option and grouping logic for the archives widget. No Hexo imports, helpers or JSX.
3
+ * @module util/archives
4
+ * @private
5
+ */
6
+
7
+ function firstSet(...values) {
8
+ return values.find(value => value !== null && typeof value !== 'undefined');
9
+ }
10
+
11
+ /**
12
+ * Resolve archive option aliases/defaults without coercing false, zero or empty strings.
13
+ * @param {Object} widget Archives widget options.
14
+ * @returns {Object} Resolved grouping, ordering, formatting, limit and count-display options.
15
+ */
16
+ function resolveArchiveOptions(widget) {
17
+ const groupBy = firstSet(widget.group_by, 'monthly');
18
+ const order = firstSet(widget.order, -1);
19
+ const {
20
+ format,
21
+ amount
22
+ } = widget;
23
+ const showCount = firstSet(widget.show_count, widget.showCount, true);
24
+ return {
25
+ groupBy,
26
+ order,
27
+ format,
28
+ amount,
29
+ showCount
30
+ };
31
+ }
32
+
33
+ // Same conversion as Hexo's toMomentLocale, without depending on Hexo's private modules.
34
+ function toMomentLocale(lang) {
35
+ if (lang === undefined) return undefined;
36
+ if (!lang || lang === 'en' || lang === 'default') return 'en';
37
+ return lang.toLowerCase().replace('_', '-');
38
+ }
39
+
40
+ /**
41
+ * Group already ordered posts by consecutive formatted names and apply the archive limit.
42
+ * The first date in each group supplies its year/month even when a custom format merges years.
43
+ * Sorting stays with the Hexo collection adapter; this function only needs forEach and date.clone.
44
+ * @param {{forEach: Function}} posts Ordered records with Moment-compatible date values.
45
+ * @param {Object} options groupBy/format/amount plus the selected language code and timezone.
46
+ * @returns {Object} Selected groups ({name, year, month, count}) and hasMore; URLs remain unresolved.
47
+ */
48
+ function groupArchives(posts, options) {
49
+ const {
50
+ groupBy,
51
+ format,
52
+ amount,
53
+ timezone
54
+ } = options;
55
+ const language = toMomentLocale(options.language);
56
+ const groups = [];
57
+ let length = 0;
58
+ posts.forEach(post => {
59
+ let date = post.date.clone();
60
+ if (timezone) date = date.tz(timezone);
61
+ if (language) date = date.locale(language);
62
+ const year = date.year();
63
+ const month = date.month() + 1;
64
+ const name = date.format(format || (groupBy === 'monthly' ? 'MMMM YYYY' : 'YYYY'));
65
+ const last = groups[length - 1];
66
+ if (!last || last.name !== name) {
67
+ length = groups.push({
68
+ name,
69
+ year,
70
+ month,
71
+ count: 1
72
+ });
73
+ } else {
74
+ last.count++;
75
+ }
76
+ });
77
+ const hasMore = amount > 0 && groups.length > amount;
78
+ return {
79
+ groups: hasMore ? groups.slice(0, amount) : groups,
80
+ hasMore
81
+ };
82
+ }
83
+ module.exports = {
84
+ resolveArchiveOptions,
85
+ groupArchives
86
+ };
@@ -0,0 +1,102 @@
1
+ /**
2
+ * Internal category option and tree preparation, independent of Hexo initialization and JSX.
3
+ * @module util/categories
4
+ * @private
5
+ */
6
+
7
+ function firstSet(...values) {
8
+ return values.find(value => value !== null && typeof value !== 'undefined');
9
+ }
10
+
11
+ /**
12
+ * Resolve aliases/defaults without coercing explicit false, zero or empty strings.
13
+ * @param {Object} widget Categories widget options.
14
+ * @returns {Object} order, orderBy, showCurrent and showCount.
15
+ */
16
+ function resolveCategoryOptions(widget) {
17
+ const order = firstSet(widget.order, 1);
18
+ const orderBy = firstSet(widget.order_by, widget.orderBy, 'name');
19
+ const showCurrent = firstSet(widget.show_current, widget.showCurrent, false);
20
+ const showCount = firstSet(widget.show_count, widget.showCount, true);
21
+ return {
22
+ order,
23
+ orderBy,
24
+ showCurrent,
25
+ showCount
26
+ };
27
+ }
28
+
29
+ /**
30
+ * Parse depth only after the caller's empty-source check, retaining parseInt and zero-as-unlimited.
31
+ * @param {*} depth Widget depth.
32
+ * @param {*} fallback Legacy top-level props.depth.
33
+ * @returns {number} Parsed depth, or zero for an unset/unparseable value.
34
+ */
35
+ function resolveCategoryDepth(depth, fallback) {
36
+ return parseInt(firstSet(depth, fallback), 10) || 0;
37
+ }
38
+ function prepareQuery(categories, parent, orderBy, order) {
39
+ const query = {};
40
+ if (parent) query.parent = parent;else query.parent = {
41
+ $exists: false
42
+ };
43
+ return categories.find(query).sort(orderBy, order).filter(cat => cat.length);
44
+ }
45
+ function currentCategory(cat, page) {
46
+ let isCurrent = false;
47
+ for (let j = 0; j < cat.length; j++) {
48
+ const post = cat.posts.data[j];
49
+ if (post && post._id === page._id) {
50
+ isCurrent = true;
51
+ break;
52
+ }
53
+ }
54
+ // Preserve both the raw prefix test and its falsey/unset result, rather than coercing to boolean.
55
+ return isCurrent || page.base && page.base.startsWith(cat.path);
56
+ }
57
+
58
+ /**
59
+ * Prepare the category tree through the collection's existing find/sort/filter/map contract.
60
+ * URLs are resolved in the original depth-first postorder, even for subsequently hidden roots.
61
+ * Keeping the URL helper at this point also preserves helper calls before later query/current errors.
62
+ * @param {Object} categories Queryable source with the existing find/sort/filter/map contract.
63
+ * @param {Object} options Resolved order/orderBy/showCurrent/depth plus raw amount.
64
+ * @param {Object} page Current page; an unset page skips current checks.
65
+ * @param {Function} url_for Existing URL helper, not a generic node projection.
66
+ * @returns {Object} Selected categories ({children, isCurrent, name, count, url}) and hasMore.
67
+ */
68
+ function prepareCategories(categories, options, page, url_for) {
69
+ const {
70
+ order,
71
+ orderBy,
72
+ showCurrent,
73
+ depth,
74
+ amount
75
+ } = options;
76
+ function hierarchicalList(level, parent) {
77
+ return prepareQuery(categories, parent, orderBy, order).map(cat => {
78
+ let children = [];
79
+ if (!depth || level + 1 < depth) children = hierarchicalList(level + 1, cat._id);
80
+ let isCurrent = false;
81
+ if (showCurrent && page) isCurrent = currentCategory(cat, page);
82
+ return {
83
+ children,
84
+ isCurrent,
85
+ name: cat.name,
86
+ count: cat.length,
87
+ url: url_for(cat.path)
88
+ };
89
+ });
90
+ }
91
+ const list = hierarchicalList(0);
92
+ const hasMore = amount > 0 && list.length > amount;
93
+ return {
94
+ categories: hasMore ? list.slice(0, amount) : list,
95
+ hasMore
96
+ };
97
+ }
98
+ module.exports = {
99
+ resolveCategoryOptions,
100
+ resolveCategoryDepth,
101
+ prepareCategories
102
+ };
@@ -0,0 +1,109 @@
1
+ /**
2
+ * Provider templates and package compatibility rules, private to the URL resolver.
3
+ * @module util/cdn-rules
4
+ * @private
5
+ */
6
+ const PROVIDERS = {
7
+ LIBRARY: {
8
+ cdnjs: '[cdnjs]https://cdnjs.cloudflare.com/ajax/libs/${ package }/${ version }/${ filename }',
9
+ loli: '[cdnjs]https://cdnjs.loli.net/ajax/libs/${ package }/${ version }/${ filename }',
10
+ jsdelivr: 'https://cdn.jsdelivr.net/npm/${ package }@${ version }/${ filename }',
11
+ unpkg: 'https://unpkg.com/${ package }@${ version }/${ filename }'
12
+ },
13
+ FONT: {
14
+ google: 'https://fonts.googleapis.com/${ type }?family=${ fontname }',
15
+ loli: 'https://fonts.loli.net/${ type }?family=${ fontname }',
16
+ fontim: 'https://fonts.font.im/${ type }?family=${ fontname }',
17
+ ustc: 'https://fonts.lug.ustc.edu.cn/${ type }?family=${ fontname }'
18
+ },
19
+ ICON: {
20
+ loli: 'https://cdnjs.loli.net/ajax/libs/font-awesome/6.0.0/css/all.min.css',
21
+ fontawesome: 'https://use.fontawesome.com/releases/v6.0.0/css/all.css'
22
+ }
23
+ };
24
+ function stripPrefix(filename, prefix) {
25
+ return filename.startsWith(prefix) ? filename.substr(prefix.length) : filename;
26
+ }
27
+ function stripMin(filename) {
28
+ return filename.indexOf('.min.') > -1 ? filename.replace(/\.min\./gi, '.') : filename;
29
+ }
30
+
31
+ // An absent mapping preserves the npm path; null means the provider lacks the package.
32
+ const PACKAGE_RULES = {
33
+ 'justified-gallery': {
34
+ cdnjs: null
35
+ },
36
+ '@mathjax/mathjax-newcm-font': {
37
+ cdnjs: null
38
+ },
39
+ lightgallery: {
40
+ // cdnjs itself serves this release, but its loli mirror lacks the CSS, core and plugins.
41
+ unavailable: [{
42
+ provider: 'loli',
43
+ version: '3.0.0'
44
+ }]
45
+ },
46
+ disqusjs: {
47
+ cdnjs: {
48
+ filename: file => stripPrefix(file, 'dist/browser/')
49
+ },
50
+ unpkg: {
51
+ filename: file => file.startsWith('dist/browser/') ? file : file.replace(/\.min\./gi, '.')
52
+ }
53
+ },
54
+ moment: {
55
+ cdnjs: {
56
+ package: 'moment.js',
57
+ filename: file => stripPrefix(file, 'min/')
58
+ }
59
+ },
60
+ outdatedbrowser: {
61
+ cdnjs: {
62
+ package: 'outdated-browser',
63
+ filename: file => stripPrefix(file, 'outdatedbrowser/')
64
+ }
65
+ },
66
+ 'highlight.js': {
67
+ cdnjs: {
68
+ filename: file => file.endsWith('.css') && file.indexOf('.min.') === -1 ? file.substr(0, file.length - 4) + '.min.css' : file
69
+ }
70
+ },
71
+ mathjax: {
72
+ cdnjs: {
73
+ filename: file => stripMin(stripPrefix(file, 'unpacked/'))
74
+ },
75
+ unpkg: {
76
+ filename: stripMin
77
+ }
78
+ },
79
+ katex: {
80
+ cdnjs: {
81
+ package: 'KaTeX'
82
+ }
83
+ },
84
+ 'pace-js': {
85
+ cdnjs: {
86
+ package: 'pace'
87
+ }
88
+ },
89
+ clipboard: {
90
+ cdnjs: {
91
+ package: 'clipboard.js'
92
+ }
93
+ },
94
+ cookieconsent: {
95
+ cdnjs: {
96
+ filename: file => stripPrefix(file, 'build/')
97
+ }
98
+ },
99
+ '@waline/client': {
100
+ cdnjs: {
101
+ package: 'waline'
102
+ }
103
+ }
104
+ };
105
+ module.exports = {
106
+ PROVIDERS,
107
+ PACKAGE_RULES,
108
+ stripPrefix
109
+ };
@@ -0,0 +1,116 @@
1
+ /**
2
+ * Pure CDN URL resolution. Hexo registration and package compatibility rules stay separate.
3
+ * @module util/cdn
4
+ * @private
5
+ */
6
+ const {
7
+ PROVIDERS,
8
+ PACKAGE_RULES,
9
+ stripPrefix
10
+ } = require('./cdn-rules');
11
+ function selectLibraryProvider(providers, name) {
12
+ const {
13
+ cdn_overrides: overrides
14
+ } = providers;
15
+ return typeof overrides === 'object' && overrides !== null && overrides[name] ? overrides[name] : providers.cdn || 'jsdelivr';
16
+ }
17
+ function packageRule(name) {
18
+ return Object.prototype.hasOwnProperty.call(PACKAGE_RULES, name) ? PACKAGE_RULES[name] : {};
19
+ }
20
+ function resolveLibraryAsset(provider, asset) {
21
+ let template = provider in PROVIDERS.LIBRARY ? PROVIDERS.LIBRARY[provider] : provider;
22
+ const unavailable = typeof asset.package === 'string' && packageRule(asset.package).unavailable;
23
+ if (unavailable && unavailable.some(entry => entry.provider === provider && entry.version === asset.version)) {
24
+ return {
25
+ template: PROVIDERS.LIBRARY.jsdelivr,
26
+ asset
27
+ };
28
+ }
29
+ let family = provider === 'unpkg' ? 'unpkg' : null;
30
+ if (template.startsWith('[cdnjs]')) {
31
+ family = 'cdnjs';
32
+ template = template.substr(7);
33
+ }
34
+ if (!family) return {
35
+ template,
36
+ asset
37
+ };
38
+ try {
39
+ const mapping = packageRule(asset.package)[family];
40
+ if (mapping === null) return {
41
+ template: PROVIDERS.LIBRARY.jsdelivr,
42
+ asset
43
+ };
44
+ if (mapping) {
45
+ // Commit a mapping only after its path conversion succeeds. Later failures retain renames.
46
+ asset = {
47
+ package: mapping.package || asset.package,
48
+ version: asset.version,
49
+ filename: mapping.filename ? mapping.filename(asset.filename) : asset.filename
50
+ };
51
+ }
52
+ if (family === 'cdnjs') asset.filename = stripPrefix(asset.filename, 'dist/');
53
+ } catch (e) {
54
+ template = PROVIDERS.LIBRARY.jsdelivr;
55
+ }
56
+ return {
57
+ template,
58
+ asset
59
+ };
60
+ }
61
+ function formatLibraryUrl({
62
+ template,
63
+ asset
64
+ }) {
65
+ return template.replace(/\${\s*package\s*}/gi, asset.package).replace(/\${\s*version\s*}/gi, asset.version).replace(/\${\s*filename\s*}/gi, asset.filename);
66
+ }
67
+
68
+ /**
69
+ * Resolve a library URL with optional per-package provider overrides.
70
+ * @param {Object} providers Provider configuration.
71
+ * @param {string} _package npm package name.
72
+ * @param {string} version Library version.
73
+ * @param {string} filename Path in the npm package.
74
+ * @returns {string} The resolved URL.
75
+ */
76
+ function resolveCdn(providers, _package, version, filename) {
77
+ const provider = selectLibraryProvider(providers, _package);
78
+ return formatLibraryUrl(resolveLibraryAsset(provider, {
79
+ package: _package,
80
+ version,
81
+ filename
82
+ }));
83
+ }
84
+
85
+ /**
86
+ * Resolve a web font URL; only undefined selects provider/type defaults.
87
+ * @param {Object} providers Provider configuration.
88
+ * @param {string} fontName Font family expression, unchanged by URL encoding.
89
+ * @param {string} [type=css] Google Fonts endpoint type.
90
+ * @returns {string} The resolved font URL.
91
+ */
92
+ function resolveFontCdn(providers, fontName, type = 'css') {
93
+ let {
94
+ fontcdn = 'google'
95
+ } = providers;
96
+ if (fontcdn in PROVIDERS.FONT) fontcdn = PROVIDERS.FONT[fontcdn];
97
+ return fontcdn.replace(/\${\s*fontname\s*}/gi, fontName).replace(/\${\s*type\s*}/gi, type);
98
+ }
99
+
100
+ /**
101
+ * Select an icon provider URL without interpolating/coercing a custom value.
102
+ * @param {Object} providers Provider configuration.
103
+ * @returns {*} The known URL or original custom value.
104
+ */
105
+ function resolveIconCdn(providers) {
106
+ let {
107
+ iconcdn = 'fontawesome'
108
+ } = providers;
109
+ if (iconcdn in PROVIDERS.ICON) iconcdn = PROVIDERS.ICON[iconcdn];
110
+ return iconcdn;
111
+ }
112
+ module.exports = {
113
+ resolveCdn,
114
+ resolveFontCdn,
115
+ resolveIconCdn
116
+ };
@@ -0,0 +1,122 @@
1
+ /* eslint-env browser */
2
+ /**
3
+ * Consent boundary for the theme's explicitly classified analytics integrations.
4
+ * @module util/consent
5
+ */
6
+ const {
7
+ toJs
8
+ } = require('./script');
9
+
10
+ // Kept self-contained because this function is emitted into the generated page.
11
+ function installConsent(type) {
12
+ if (window.hexoComponentConsent) {
13
+ return;
14
+ }
15
+ const integrations = Object.create(null);
16
+ let started = false;
17
+ const status = (document.cookie.match(/(?:^|;\s*)cookieconsent_status=([^;]*)/) || [])[1];
18
+ let allowed = type === 'opt-out' ? status !== 'deny' : status === 'allow';
19
+ let popup = null;
20
+ function run() {
21
+ Object.keys(integrations).forEach(id => {
22
+ const integration = integrations[id];
23
+ if (allowed && !integration.started) {
24
+ integration.started = true;
25
+ started = true;
26
+ integration.start();
27
+ }
28
+ });
29
+ }
30
+ window.hexoComponentConsent = {
31
+ allowed: function () {
32
+ return allowed;
33
+ },
34
+ register: function (id, start, stop) {
35
+ if (!integrations[id]) {
36
+ integrations[id] = {
37
+ start: start,
38
+ stop: stop,
39
+ started: false
40
+ };
41
+ }
42
+ run();
43
+ },
44
+ setPopup: function (value) {
45
+ popup = value;
46
+ },
47
+ setStatus: function (value) {
48
+ const previous = allowed;
49
+ allowed = type === 'opt-out' ? value !== 'deny' : value === 'allow';
50
+ if (previous && !allowed) {
51
+ try {
52
+ Object.keys(integrations).forEach(id => {
53
+ const integration = integrations[id];
54
+ if (integration.started && integration.stop) {
55
+ integration.stop();
56
+ }
57
+ });
58
+ } finally {
59
+ // A loaded vendor SDK may own timers/listeners that cannot safely be unloaded.
60
+ // A new document, with the saved denial, is the generic stop boundary.
61
+ if (started) {
62
+ window.location.reload();
63
+ }
64
+ }
65
+ } else {
66
+ run();
67
+ }
68
+ },
69
+ revoke: function () {
70
+ if (popup) {
71
+ popup.setStatus('deny');
72
+ } else {
73
+ document.cookie = 'cookieconsent_status=deny; Path=/; Max-Age=31536000; SameSite=Lax';
74
+ }
75
+ // setStatus on the popup invokes this too; a repeated denial is harmless.
76
+ this.setStatus('deny');
77
+ },
78
+ open: function () {
79
+ if (popup) {
80
+ popup.open();
81
+ }
82
+ }
83
+ };
84
+ }
85
+
86
+ /**
87
+ * Build the idempotent browser-side consent controller. Info is deliberately not gated.
88
+ * @param {string} type CookieConsent mode.
89
+ * @returns {string} Inline JavaScript.
90
+ */
91
+ function consentRuntime(type) {
92
+ return `(${installConsent.toString()})(${toJs(type)});`;
93
+ }
94
+
95
+ /**
96
+ * Defer all scripts (including inline SDK bootstrap code) until this integration is authorized.
97
+ * Noscript pixels are intentionally not restored: without JavaScript consent cannot be obtained.
98
+ * @param {string} id Stable integration ID, shared across PJAX navigations.
99
+ * @param {string} markup Rendered integration markup containing scripts.
100
+ * @param {string} type CookieConsent mode.
101
+ * @returns {string} Inline JavaScript without active external resources before permission.
102
+ */
103
+ function gateMarkup(id, markup, type) {
104
+ return `${consentRuntime(type)}
105
+ window.hexoComponentConsent.register(${toJs(id)}, function () {
106
+ var template = document.createElement('template');
107
+ template.innerHTML = ${toJs(markup)};
108
+ template.content.querySelectorAll('script').forEach(function (source) {
109
+ if (!window.hexoComponentConsent.allowed()) return;
110
+ var script = document.createElement('script');
111
+ Array.prototype.forEach.call(source.attributes, function (attr) {
112
+ script.setAttribute(attr.name, attr.value);
113
+ });
114
+ script.textContent = source.textContent;
115
+ document.head.appendChild(script);
116
+ });
117
+ });`;
118
+ }
119
+ module.exports = {
120
+ consentRuntime,
121
+ gateMarkup
122
+ };
@@ -13,7 +13,7 @@ const warned = new Set();
13
13
  * @param {string} name Name of the component, such as <code>share.addthis</code>.
14
14
  * @param {string} message Why the component is deprecated, and what to use instead.
15
15
  * @example
16
- * deprecate('share.addthis', 'AddThis shut down on May 31, 2023. Use AddToAny or ShareThis instead.');
16
+ * deprecate('share.addthis', 'Service shut down. Use AddToAny or ShareThis.');
17
17
  */
18
18
  function deprecate(name, message) {
19
19
  if (warned.has(name)) {