hexo-component-inferno 4.2.2 → 5.1.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 (73) 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/cusdis.json +1 -1
  15. package/lib/schema/comment/facebook.json +1 -1
  16. package/lib/schema/comment/gitalk.json +23 -6
  17. package/lib/schema/comment/gitment.json +1 -1
  18. package/lib/schema/comment/livere.json +9 -3
  19. package/lib/schema/comment/twikoo.json +3 -3
  20. package/lib/schema/comment/waline.json +28 -3
  21. package/lib/schema/misc/open_graph.json +2 -1
  22. package/lib/schema/plugin/cookie_consent.json +2 -2
  23. package/lib/schema/plugin/mathjax.json +1 -1
  24. package/lib/schema/plugin/outdated_browser.json +1 -1
  25. package/lib/schema/share/bdshare.json +1 -1
  26. package/lib/schema/share/sharejs.json +1 -1
  27. package/lib/schema/share/web_share.json +13 -0
  28. package/lib/schema/widget/github_calendar.json +30 -0
  29. package/lib/schema/widget/profile.json +67 -0
  30. package/lib/util/archives.js +86 -0
  31. package/lib/util/categories.js +102 -0
  32. package/lib/util/cdn-rules.js +109 -0
  33. package/lib/util/cdn.js +116 -0
  34. package/lib/util/consent.js +122 -0
  35. package/lib/util/deprecate.js +1 -1
  36. package/lib/util/gallery.js +227 -0
  37. package/lib/util/insight-search.js +98 -0
  38. package/lib/util/searchbox.js +88 -0
  39. package/lib/util/tab-options.js +109 -0
  40. package/lib/view/comment/artalk.js +24 -6
  41. package/lib/view/comment/cusdis.js +4 -0
  42. package/lib/view/comment/disqus.js +1 -1
  43. package/lib/view/comment/disqusjs.js +7 -6
  44. package/lib/view/comment/facebook.js +3 -16
  45. package/lib/view/comment/gitalk.js +18 -15
  46. package/lib/view/comment/gitment.js +2 -2
  47. package/lib/view/comment/livere.js +25 -4
  48. package/lib/view/comment/twikoo.js +5 -3
  49. package/lib/view/comment/valine.js +4 -3
  50. package/lib/view/comment/waline.js +8 -6
  51. package/lib/view/misc/open_graph.js +3 -5
  52. package/lib/view/plugin/cookie_consent.js +8 -7
  53. package/lib/view/plugin/gallery.js +42 -19
  54. package/lib/view/plugin/katex.js +4 -4
  55. package/lib/view/plugin/mathjax.js +10 -7
  56. package/lib/view/plugin/outdated_browser.js +59 -45
  57. package/lib/view/plugin/progressbar.js +6 -1
  58. package/lib/view/plugin/waline_pageview.js +2 -2
  59. package/lib/view/search/algolia.js +8 -3
  60. package/lib/view/search/google_cse.js +13 -10
  61. package/lib/view/search/insight.js +9 -2
  62. package/lib/view/share/addthis.js +4 -9
  63. package/lib/view/share/addtoany.js +23 -5
  64. package/lib/view/share/bdshare.js +9 -30
  65. package/lib/view/share/sharejs.js +7 -34
  66. package/lib/view/share/sharethis.js +21 -6
  67. package/lib/view/share/web_share.js +153 -0
  68. package/lib/view/widget/archives.js +19 -71
  69. package/lib/view/widget/categories.js +20 -67
  70. package/lib/view/widget/github_calendar.js +94 -0
  71. package/lib/view/widget/profile.js +194 -0
  72. package/lib/view/widget/subscribe_email.js +4 -26
  73. package/package.json +6 -4
@@ -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)) {
@@ -0,0 +1,227 @@
1
+ /**
2
+ * Inline Gallery installer shared by the standalone component and consuming themes.
3
+ * Installing it does not enable or download either SDK. Keep it self-contained after Babel so it
4
+ * can also be used without the Hexo asset generator.
5
+ * @module util/gallery
6
+ */
7
+ function installGallery(window) {
8
+ if (window.HexoGallery) return;
9
+ const document = window.document;
10
+ const requests = new Map();
11
+ const layouts = new Map();
12
+ const lightboxes = new Map();
13
+ function destroyLightbox(container) {
14
+ const state = lightboxes.get(container);
15
+ if (!state) return;
16
+ container.removeEventListener('click', state.click);
17
+ // The SDK removes its container through parentNode, even after a host replaced the body.
18
+ const dialog = state.instance.$container && state.instance.$container.get();
19
+ if (dialog && !dialog.parentNode) {
20
+ dialog.style.display = 'none';
21
+ document.body.appendChild(dialog);
22
+ }
23
+ if (state.instance.lgOpened) {
24
+ state.instance.settings.startAnimationDuration = 0;
25
+ state.instance.settings.backdropDuration = 0;
26
+ }
27
+ state.instance.destroy();
28
+ lightboxes.delete(container);
29
+ }
30
+ function lightbox(container, enabled) {
31
+ if (!enabled) {
32
+ destroyLightbox(container);
33
+ return;
34
+ }
35
+ if (typeof window.lightGallery !== 'function') return;
36
+ const plugins = (window.HexoGallery.lightGalleryPlugins || []).map(name => window[name]);
37
+ if (plugins.some(plugin => typeof plugin !== 'function')) return;
38
+ const items = Array.from(container.querySelectorAll('a.gallery-item[href]'));
39
+ if (!items.length) {
40
+ destroyLightbox(container);
41
+ return;
42
+ }
43
+ const elements = items.map(link => {
44
+ const image = link.querySelector('img');
45
+ const caption = document.createElement('span');
46
+ caption.textContent = image ? image.alt : '';
47
+ return {
48
+ src: link.href,
49
+ thumb: image ? image.currentSrc || image.src : link.href,
50
+ subHtml: caption.innerHTML,
51
+ alt: caption.innerHTML.replace(/"/g, '&quot;')
52
+ };
53
+ });
54
+ const signature = JSON.stringify(elements);
55
+ let state = lightboxes.get(container);
56
+ if (state) {
57
+ if (!sameItems(state.items, items) || state.signature !== signature) {
58
+ if (state.instance.lgOpened) {
59
+ state.instance.updateSlides(elements, Math.min(state.instance.index, elements.length - 1));
60
+ } else {
61
+ state.instance.refresh(elements);
62
+ }
63
+ state.items = items;
64
+ state.signature = signature;
65
+ }
66
+ return;
67
+ }
68
+ const instance = window.lightGallery(container, {
69
+ dynamic: true,
70
+ dynamicEl: elements,
71
+ plugins
72
+ });
73
+ state = {
74
+ instance,
75
+ items,
76
+ signature,
77
+ click: null
78
+ };
79
+ state.click = event => {
80
+ if (event.defaultPrevented || event.button !== 0 || event.ctrlKey || event.metaKey || event.shiftKey || event.altKey) return;
81
+ const link = event.target.closest('a.gallery-item');
82
+ const index = state.items.indexOf(link);
83
+ if (index < 0) return;
84
+ event.preventDefault();
85
+ instance.openGallery(index, link);
86
+ };
87
+ container.addEventListener('click', state.click);
88
+ lightboxes.set(container, state);
89
+ }
90
+ function sameItems(items, current) {
91
+ return items.length === current.length && Array.from(items).every((item, i) => item === current[i]);
92
+ }
93
+ function enhance(container) {
94
+ container.querySelectorAll('img:not(.not-gallery-item)').forEach(image => {
95
+ // Third-party SDK icons are controls, not article images. Script-load retries also
96
+ // scan icons inserted after the article's initial enhancement.
97
+ if (image.closest('a, .sharethis-inline-share-buttons, .a2a_kit, [data-share-provider]')) return;
98
+ const link = document.createElement('a');
99
+ link.className = 'gallery-item';
100
+ const src = image.getAttribute('src');
101
+ if (src !== null) link.setAttribute('href', src);
102
+ image.parentNode.insertBefore(link, image);
103
+ link.appendChild(image);
104
+ if (image.alt) {
105
+ const caption = document.createElement('p');
106
+ caption.className = 'has-text-centered is-size-6 caption';
107
+ caption.textContent = image.alt;
108
+ link.appendChild(caption);
109
+ }
110
+ });
111
+ }
112
+ function apply(root, options) {
113
+ const containers = root === document ? root.querySelectorAll('.article') : [root];
114
+ containers.forEach(enhance);
115
+ // Do not capture an incomplete article while HTML is still being parsed.
116
+ if (document.readyState === 'loading') return;
117
+ containers.forEach(container => lightbox(container, options.lightGallery));
118
+ if (options.justifiedGallery && typeof window.JustifiedGallery === 'function') {
119
+ const galleries = Array.from(root.querySelectorAll('.justified-gallery'));
120
+ if (root !== document && root.matches('.justified-gallery')) galleries.unshift(root);
121
+ galleries.forEach(gallery => {
122
+ const paragraphs = new Set(Array.from(gallery.querySelectorAll(':scope > p > .gallery-item'), link => link.parentNode));
123
+ paragraphs.forEach(paragraph => {
124
+ paragraph.replaceWith(...paragraph.childNodes);
125
+ });
126
+ gallery.querySelectorAll(':scope > .gallery-item > .caption').forEach(caption => {
127
+ caption.classList.add('jg-caption');
128
+ });
129
+ let state = layouts.get(gallery);
130
+ const items = () => Array.from(gallery.children).filter(item => !item.classList.contains('jg-spinner') && (item.matches(state.instance.settings.selector) || item.matches('div')));
131
+ if (state) {
132
+ if (sameItems(state.items, items())) return;
133
+ // init refreshes entries but starts another width watcher; stop and rewind first.
134
+ state.instance.widthWatcher.stop();
135
+ state.instance.stopImgAnalyzerStarter();
136
+ state.instance.rewind();
137
+ } else {
138
+ // The SDK reuses .jg-caption and safely creates captions for pre-existing links.
139
+ state = {
140
+ instance: new window.JustifiedGallery(gallery)
141
+ };
142
+ layouts.set(gallery, state);
143
+ }
144
+ state.instance.init();
145
+ state.items = items();
146
+ });
147
+ }
148
+ }
149
+ function prune() {
150
+ layouts.forEach((state, gallery) => {
151
+ if (!gallery.isConnected) destroyLayout(gallery);
152
+ });
153
+ lightboxes.forEach((state, container) => {
154
+ if (!container.isConnected) destroyLightbox(container);
155
+ });
156
+ requests.forEach((options, root) => {
157
+ if (root !== document && !root.isConnected) requests.delete(root);
158
+ });
159
+ }
160
+ function destroyLayout(gallery) {
161
+ const {
162
+ instance
163
+ } = layouts.get(gallery);
164
+ // The SDK's destroy does not stop an active loading spinner.
165
+ instance.stopLoadingSpinnerAnimation();
166
+ instance.destroy();
167
+ layouts.delete(gallery);
168
+ }
169
+ function retry() {
170
+ prune();
171
+ requests.forEach((options, root) => apply(root, options));
172
+ }
173
+ window.HexoGallery = {
174
+ /**
175
+ * Enhance images in a content container, optionally initializing the two Gallery SDKs.
176
+ * Repeated calls reuse existing wrappers, captions and plugin instances. Missing SDKs can be
177
+ * retried by calling init again; DOM ready, window load and subsequent script loads also retry
178
+ * connected containers with their latest options. SDK initialization waits for DOM readiness.
179
+ * Changed item collections refresh their existing SDK instance. Disabling the lightbox destroys
180
+ * it; the independently managed layout plugin is unchanged.
181
+ * @param {Document|Element} [root=document] Content container; document targets .article images
182
+ * and lightboxes, and all .justified-gallery elements.
183
+ * @param {Object} [options={}] SDK opt-ins; omitted flags disable plugin initialization.
184
+ * @param {boolean} [options.lightGallery=false] Initialize lightGallery when available.
185
+ * @param {boolean} [options.justifiedGallery=false] Initialize justifiedGallery when available.
186
+ */
187
+ init: function (root, options) {
188
+ root = root || document;
189
+ options = options || {};
190
+ const enabled = {
191
+ lightGallery: options.lightGallery === true,
192
+ justifiedGallery: options.justifiedGallery === true
193
+ };
194
+ prune();
195
+ requests.set(root, enabled);
196
+ apply(root, enabled);
197
+ },
198
+ /** Destroy lightboxes, layouts and pending retries before content is replaced (e.g. PJAX). */
199
+ destroy: function (root) {
200
+ root = root || document;
201
+ lightboxes.forEach((state, container) => {
202
+ if (root === document || root === container || root.contains(container)) destroyLightbox(container);
203
+ });
204
+ layouts.forEach((state, gallery) => {
205
+ if (root === document || root === gallery || root.contains(gallery)) destroyLayout(gallery);
206
+ });
207
+ requests.forEach((options, target) => {
208
+ if (root === document || root === target || root.contains(target)) requests.delete(target);
209
+ });
210
+ }
211
+ };
212
+ if (document.readyState === 'loading') {
213
+ document.addEventListener('DOMContentLoaded', retry, {
214
+ once: true
215
+ });
216
+ }
217
+ if (document.readyState !== 'complete') window.addEventListener('load', retry, {
218
+ once: true
219
+ });
220
+ document.addEventListener('hexo:gallery:ready', retry);
221
+ // Deferred scripts may arrive after init, including on the first enabled page reached by PJAX.
222
+ // Retry the latest requests, rather than capturing a previous page's enabled configuration.
223
+ document.addEventListener('load', event => {
224
+ if (event.target.tagName === 'SCRIPT') retry();
225
+ }, true);
226
+ }
227
+ module.exports = '(' + installGallery.toString() + ')(window);';
@@ -0,0 +1,98 @@
1
+ /**
2
+ * Create the DOM-independent Insight query engine.
3
+ * Keep the factory self-contained: the search view serializes it into an inline script, including
4
+ * when used without the Hexo asset generator or with a CDN-hosted insight.js.
5
+ * @module util/insight-search
6
+ * @returns {Function} Search (index, query), returning parsed keywords and ranked result sections.
7
+ */
8
+ function createInsightSearch() {
9
+ const content = {
10
+ fields: ['title', 'text'],
11
+ weights: [3, 1]
12
+ };
13
+ const taxonomy = {
14
+ fields: ['name', 'slug'],
15
+ weights: [1, 1]
16
+ };
17
+ const sections = {
18
+ posts: content,
19
+ pages: content,
20
+ categories: taxonomy,
21
+ tags: taxonomy
22
+ };
23
+ function parseKeywords(query) {
24
+ return query.split(' ').filter(keyword => {
25
+ return !!keyword;
26
+ }).map(keyword => {
27
+ return keyword.toLowerCase();
28
+ });
29
+ }
30
+
31
+ // Every keyword must occur in at least one own field; keywords may span different fields.
32
+ function filter(keywords, obj, fields) {
33
+ const containKeywords = keywords.filter(keyword => {
34
+ const containFields = fields.filter(field => {
35
+ if (!Object.prototype.hasOwnProperty.call(obj, field)) {
36
+ return false;
37
+ }
38
+ if (obj[field].toLowerCase().indexOf(keyword) > -1) {
39
+ return true;
40
+ }
41
+ return false;
42
+ });
43
+ if (containFields.length > 0) {
44
+ return true;
45
+ }
46
+ return false;
47
+ });
48
+ return containKeywords.length === keywords.length;
49
+ }
50
+
51
+ // Count non-overlapping occurrences of a literal lowercase keyword, not a regular expression.
52
+ function countMatches(text, keyword) {
53
+ let count = 0;
54
+ for (let i = text.indexOf(keyword); i !== -1; i = text.indexOf(keyword, i + keyword.length)) {
55
+ count++;
56
+ }
57
+ return count;
58
+ }
59
+ function weight(keywords, obj, fields, weights) {
60
+ let value = 0;
61
+ keywords.forEach(keyword => {
62
+ fields.forEach((field, index) => {
63
+ if (Object.prototype.hasOwnProperty.call(obj, field)) {
64
+ value += countMatches(obj[field].toLowerCase(), keyword) * weights[index];
65
+ }
66
+ });
67
+ });
68
+ return value;
69
+ }
70
+
71
+ // Weigh each matching item once. Equal weights retain the input order, including an empty query.
72
+ function topItems(items, keywords, config) {
73
+ return items.filter(item => {
74
+ return filter(keywords, item, config.fields);
75
+ }).map(item => {
76
+ return {
77
+ item,
78
+ weight: weight(keywords, item, config.fields, config.weights)
79
+ };
80
+ }).sort((a, b) => {
81
+ return b.weight - a.weight;
82
+ }).slice(0, 5).map(result => {
83
+ return result.item;
84
+ });
85
+ }
86
+ return function search(json, query) {
87
+ const keywords = parseKeywords(query);
88
+ const results = {};
89
+ Object.keys(sections).forEach(section => {
90
+ results[section] = topItems(json[section], keywords, sections[section]);
91
+ });
92
+ return {
93
+ keywords,
94
+ results
95
+ };
96
+ };
97
+ }
98
+ module.exports = createInsightSearch;
@@ -0,0 +1,88 @@
1
+ /**
2
+ * Inline installer shared by the search components. Keep it self-contained: components can be used
3
+ * without the Hexo asset generator, and PJAX may execute this script more than once.
4
+ * @module util/searchbox
5
+ */
6
+ function installSearchbox(window) {
7
+ if (window.loadSearchbox) return;
8
+ const document = window.document;
9
+ let current;
10
+ window.loadSearchbox = function ($main, options) {
11
+ const $ = window.jQuery;
12
+ options = options || {};
13
+ // Preserve focus state only when replacing the controller for the same open DOM element.
14
+ let opener = current ? current.destroy($main[0]) : null;
15
+ let touch = false;
16
+ const $document = $(document);
17
+ const namespace = '.hexoSearchbox';
18
+ const controller = {
19
+ isCurrent: function () {
20
+ return current === controller && document.contains($main[0]);
21
+ },
22
+ destroy: function (nextMain) {
23
+ if (current !== controller) return null;
24
+ const nextOpener = nextMain === $main[0] && $main.hasClass('show') ? opener : null;
25
+ $document.off(namespace);
26
+ opener = null;
27
+ current = null;
28
+ if (options.onDestroy) options.onDestroy();
29
+ return nextOpener;
30
+ }
31
+ };
32
+ current = controller;
33
+ function open() {
34
+ if (!controller.isCurrent()) return;
35
+ if (!$main.hasClass('show')) {
36
+ opener = document.activeElement;
37
+ $main.addClass('show');
38
+ }
39
+ $main.find('.searchbox-input').focus();
40
+ }
41
+ function close(restoreFocus) {
42
+ $main.removeClass('show');
43
+ if (restoreFocus !== false && opener && document.contains(opener)) opener.focus();
44
+ opener = null;
45
+ }
46
+ function isSearchShortcut(e) {
47
+ if (e.altKey) return false;
48
+ if (e.ctrlKey || e.metaKey) {
49
+ return !e.shiftKey && typeof e.key === 'string' && e.key.toLowerCase() === 'k';
50
+ }
51
+ // e.key already accounts for the keyboard layout (including Shift).
52
+ const target = e.target;
53
+ const typing = target.isContentEditable || /^(input|textarea|select)$/i.test(target.tagName);
54
+ return e.key === '/' && !typing;
55
+ }
56
+ $document.on('click' + namespace, '.navbar-main .search', open).on('click' + namespace + (options.touch ? ' touchend' + namespace : ''), '.searchbox-close, .searchbox .searchbox-mask', e => {
57
+ if (!controller.isCurrent()) return;
58
+ if (e.type !== 'click' && !touch) return;
59
+ if (options.touch) {
60
+ $('.navbar-main').css('pointer-events', 'none');
61
+ setTimeout(() => {
62
+ $('.navbar-main').css('pointer-events', 'auto');
63
+ }, 400);
64
+ }
65
+ close();
66
+ touch = false;
67
+ }).on('focusin' + namespace, e => {
68
+ if (controller.isCurrent() && $main.hasClass('show') && !$main[0].contains(e.target)) {
69
+ close(false);
70
+ }
71
+ }).on('keydown' + namespace, e => {
72
+ if (!controller.isCurrent()) return;
73
+ if (isSearchShortcut(e)) {
74
+ e.preventDefault();
75
+ open();
76
+ return;
77
+ }
78
+ if (!$main.hasClass('show')) return;
79
+ if (e.keyCode === 27) close();else if (options.onKeyDown) options.onKeyDown(e);
80
+ }).on('touchstart' + namespace, () => {
81
+ touch = true;
82
+ }).on('touchmove' + namespace, () => {
83
+ touch = false;
84
+ });
85
+ return controller;
86
+ };
87
+ }
88
+ module.exports = '(' + installSearchbox.toString() + ')(window);';
@@ -0,0 +1,109 @@
1
+ /**
2
+ * Pure option splitting for the Hexo tabs tag and its individual tab comments.
3
+ * @module util/tab-options
4
+ */
5
+
6
+ /**
7
+ * Split a tabs option at its first colon, without trimming or interpreting the value.
8
+ * @param {string} option An option such as <code>title:Step 1: Install</code>.
9
+ * @returns {string[]} The key and value, or only the key when there is no colon.
10
+ */
11
+ function splitOption(option) {
12
+ const index = option.indexOf(':');
13
+ return index === -1 ? [option] : [option.slice(0, index), option.slice(index + 1)];
14
+ }
15
+
16
+ /**
17
+ * Split individual tab comment options on ASCII spaces, respecting leading single or double quotes.
18
+ * Quotes within bare tokens are literal. Backslashes inside quotes protect the next character from
19
+ * closing the token, but remain in the result; they do not escape spaces in bare tokens.
20
+ * @param {string} source The option text inside a <code>&lt;!-- tab ... --&gt;</code> comment.
21
+ * @returns {string[]} Options with enclosing quotes removed and all other characters preserved.
22
+ * @throws {Error} For an unclosed quoted option or a non-space character after its closing quote.
23
+ */
24
+ function splitTabOptions(source) {
25
+ const options = [];
26
+ const EOL = -1;
27
+ let i = 0;
28
+ let state = 1;
29
+ let begin = 0;
30
+ while (i <= source.length) {
31
+ const c = i < source.length ? source.charAt(i) : '';
32
+ switch (state) {
33
+ case 1:
34
+ // Start of an option
35
+ if (c === ' ') {
36
+ state = 1;
37
+ begin = i + 1;
38
+ } else if (c === '"') {
39
+ state = 2;
40
+ begin = i + 1;
41
+ } else if (c === "'") {
42
+ state = 4;
43
+ begin = i + 1;
44
+ } else if (c === '') {
45
+ state = EOL;
46
+ } else {
47
+ state = 7;
48
+ }
49
+ break;
50
+ case 2:
51
+ // Double-quoted option
52
+ if (c === '\\') {
53
+ state = 3;
54
+ } else if (c === '"') {
55
+ state = 6;
56
+ }
57
+ break;
58
+ case 3:
59
+ // Escaped character in a double-quoted option
60
+ state = 2;
61
+ break;
62
+ case 4:
63
+ // Single-quoted option
64
+ if (c === '\\') {
65
+ state = 5;
66
+ } else if (c === "'") {
67
+ state = 6;
68
+ }
69
+ break;
70
+ case 5:
71
+ // Escaped character in a single-quoted option
72
+ state = 4;
73
+ break;
74
+ case 6:
75
+ // The preceding character closed the quote
76
+ options.push(source.substring(begin, i - 1));
77
+ begin = i + 1;
78
+ if (c === ' ') {
79
+ state = 1;
80
+ } else if (c === '') {
81
+ state = EOL;
82
+ } else {
83
+ throw new Error(`malformatted tab options: ${source}`);
84
+ }
85
+ break;
86
+ case 7:
87
+ // Bare option
88
+ if (c === ' ') {
89
+ options.push(source.substring(begin, i));
90
+ begin = i + 1;
91
+ state = 1;
92
+ } else if (c === '') {
93
+ options.push(source.substring(begin, i));
94
+ begin = i + 1;
95
+ state = EOL;
96
+ }
97
+ break;
98
+ }
99
+ i++;
100
+ }
101
+ if (state !== EOL) {
102
+ throw new Error(`malformatted tab options: ${source}`);
103
+ }
104
+ return options;
105
+ }
106
+ module.exports = {
107
+ splitOption,
108
+ splitTabOptions
109
+ };