hexo-theme-stellar 1.35.0 → 1.37.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 (47) hide show
  1. package/CHANGELOG.md +1180 -0
  2. package/_config.yml +25 -10
  3. package/layout/_partial/comments/artalk/layout.ejs +2 -2
  4. package/layout/_partial/comments/artalk/script.ejs +3 -9
  5. package/layout/_partial/comments/beaudar/script.ejs +2 -8
  6. package/layout/_partial/comments/giscus/script.ejs +2 -8
  7. package/layout/_partial/comments/twikoo/layout.ejs +2 -2
  8. package/layout/_partial/comments/twikoo/script.ejs +3 -9
  9. package/layout/_partial/comments/utterances/script.ejs +2 -8
  10. package/layout/_partial/comments/waline/layout.ejs +1 -1
  11. package/layout/_partial/comments/waline/script.ejs +1 -7
  12. package/layout/_partial/scripts/defines.ejs +118 -1
  13. package/layout/_partial/scripts/lazyload.ejs +7 -6
  14. package/layout/_partial/scripts/services.ejs +11 -13
  15. package/layout/_partial/scripts/utils.ejs +359 -24
  16. package/layout/_partial/widgets/tagtree.ejs +1 -1
  17. package/layout/_partial/widgets/toc.ejs +1 -4
  18. package/layout/_partial/widgets/tree.ejs +1 -1
  19. package/layout/_plugins/fancybox.ejs +23 -8
  20. package/layout/_plugins/scrollreveal.ejs +77 -13
  21. package/package.json +1 -1
  22. package/scripts/filters/lib/img_lazyload.js +9 -9
  23. package/scripts/tags/lib/emoji.js +17 -2
  24. package/scripts/tags/lib/image.js +13 -18
  25. package/source/css/_common/html.styl +0 -2
  26. package/source/css/_components/widgets/toc.styl +2 -2
  27. package/source/css/_plugins/lazyload.styl +40 -26
  28. package/source/css/_plugins/scrollreveal.styl +5 -1
  29. package/source/js/main.js +157 -69
  30. package/source/js/search/algolia-search.js +62 -62
  31. package/source/js/search/local-search.js +37 -36
  32. package/source/js/services/artalk_latest_comment.js +5 -7
  33. package/source/js/services/contributors.js +4 -6
  34. package/source/js/services/fcircle.js +4 -6
  35. package/source/js/services/friends.js +4 -6
  36. package/source/js/services/friends_and_posts.js +4 -6
  37. package/source/js/services/ghinfo.js +6 -8
  38. package/source/js/services/giscus_latest_comment.js +4 -6
  39. package/source/js/services/mdrender.js +2 -2
  40. package/source/js/services/memos.js +3 -4
  41. package/source/js/services/rss.js +10 -12
  42. package/source/js/services/siteinfo.js +24 -26
  43. package/source/js/services/sites.js +4 -6
  44. package/source/js/services/timeline.js +4 -6
  45. package/source/js/services/twikoo_latest_comment.js +5 -7
  46. package/source/js/services/waline_latest_comment.js +4 -6
  47. package/source/js/services/weibo.js +4 -6
@@ -5,22 +5,22 @@
5
5
 
6
6
  'use strict';
7
7
 
8
- const fs = require('hexo-fs');
9
-
8
+ // 懒加载强制开启:不再读取 enable 配置,no-lazy 是唯一例外
10
9
  function lazyProcess(htmlContent) {
11
- const cfg = this.theme.config.plugins.lazyload;
12
- if (cfg == undefined || cfg.enable != true) {
13
- return htmlContent;
14
- }
15
10
  return htmlContent.replace(/<img(.*?)src="(.*?)"(.*?)>/gi, function(imgTag, src_before, src_value, src_after) {
16
- // might be duplicate
17
- if (/data-srcset/gi.test(imgTag)) {
11
+ // 已由 tag 插件输出懒加载标记(data-src / data-srcset)的图片不重复处理
12
+ if (/data-src/gi.test(imgTag)) {
13
+ return imgTag;
14
+ }
15
+ // 使用 srcset 的图片交给浏览器原生处理,避免占位图与 srcset 冲突
16
+ if (/srcset=/gi.test(imgTag)) {
18
17
  return imgTag;
19
18
  }
20
19
  if (/src="data:image(.*?)/gi.test(imgTag)) {
21
20
  return imgTag;
22
21
  }
23
- if (imgTag.includes(' no-lazy ')) {
22
+ // no-lazy 兼容 `no-lazy` / `no-lazy=""` 两种写法
23
+ if (/\bno-lazy\b/gi.test(imgTag)) {
24
24
  return imgTag;
25
25
  }
26
26
  var newImgTag = imgTag;
@@ -3,6 +3,7 @@
3
3
  * 格式与官方标签插件一致使用空格分隔,中括号内的是可选参数(中括号不需要写出来)
4
4
  *
5
5
  * {% emoji [source] name [height:1.75em] %}
6
+ * {% emoji url:https://example.com/emoji.png [name:alt] [height:1.75em] %}
6
7
  *
7
8
  */
8
9
 
@@ -10,12 +11,26 @@
10
11
 
11
12
  module.exports = ctx => function(args) {
12
13
  const config = ctx.theme.config.tag_plugins.emoji
13
- args = ctx.args.map(args, ['height'], ['source', 'name'])
14
+ args = ctx.args.map(args, ['url', 'height', 'name'], ['source', 'name'])
14
15
  var el = ''
16
+ el += '<span class="tag-plugin emoji">'
17
+ if (args.url) {
18
+ // 直接引用外部图片,不再走 source 配置查找
19
+ el += '<img no-lazy="" class="inline"'
20
+ el += ' src="' + args.url + '"'
21
+ if (args.name) {
22
+ el += ' alt="' + args.name + '"'
23
+ }
24
+ if (args.height) {
25
+ el += ' style="height:' + args.height + '"'
26
+ }
27
+ el += '/>'
28
+ el += '</span>'
29
+ return el
30
+ }
15
31
  if (args.source == undefined) {
16
32
  return el
17
33
  }
18
- el += '<span class="tag-plugin emoji">'
19
34
  if (args.name == undefined) {
20
35
  // 省略了 source
21
36
  for (let id in config) {
@@ -16,32 +16,27 @@ module.exports = ctx => function(args) {
16
16
  if (args.height) {
17
17
  style += 'height:' + args.height + ';'
18
18
  }
19
- // fancybox
20
- var fancybox = false
19
+ // fancybox 默认开启(不再支持全局关闭),单图可用 fancybox:false 关闭
20
+ var fancybox = true
21
21
  var fancyboxHref = null
22
- if (ctx.theme.config.plugins.fancybox && ctx.theme.config.plugins.fancybox.enable) {
23
- // 主题配置
24
- if (ctx.theme.config.tag_plugins.image && ctx.theme.config.tag_plugins.image.fancybox) {
25
- fancybox = ctx.theme.config.tag_plugins.image.fancybox
26
- }
27
- // 覆盖配置
28
- if (args.fancybox && args.fancybox.length > 0) {
29
- if (args.fancybox == 'false') {
30
- fancybox = false
31
- } else if (args.fancybox === 'true') {
32
- fancybox = args.fancybox
33
- } else {
34
- fancybox = true
35
- fancyboxHref = args.fancybox
36
- }
22
+ if (args.fancybox && args.fancybox.length > 0) {
23
+ if (args.fancybox == 'false') {
24
+ fancybox = false
25
+ } else if (args.fancybox === 'true') {
26
+ fancybox = true
27
+ } else {
28
+ fancybox = true
29
+ fancyboxHref = args.fancybox
37
30
  }
38
31
  }
39
32
 
40
33
  var safeAlt = require('hexo-util').escapeHTML(args.alt || '')
34
+ // 懒加载占位图(1x1 透明 PNG),真实地址放在 data-src
35
+ const loadingImg = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAABGdBTUEAALGPC/xhBQAAADhlWElmTU0AKgAAAAgAAYdpAAQAAAABAAAAGgAAAAAAAqACAAQAAAABAAAAAaADAAQAAAABAAAAAQAAAADa6r/EAAAAC0lEQVQIHWNgAAIAAAUAAY27m/MAAAAASUVORK5CYII='
41
36
  function img(src, alt, style) {
42
37
  let a = '<a data-fancybox'
43
38
  let img = ''
44
- img += `<img class="lazy" src="${src}" data-src="${src}"`
39
+ img += `<img class="lazy" src="${loadingImg}" data-src="${src}"`
45
40
  if (safeAlt) {
46
41
  img += ` alt="${safeAlt}"`
47
42
  a += ` data-caption="${safeAlt}"`
@@ -10,8 +10,6 @@ html
10
10
  -webkit-text-size-adjust: 100%
11
11
  -ms-text-size-adjust: 100%
12
12
  scroll-padding-top: 8px
13
- if hexo-config('style.smooth_scroll')
14
- scroll-behavior: smooth
15
13
  body
16
14
  background: var(--background)
17
15
  margin: 0
@@ -141,7 +141,7 @@
141
141
  .widget-body
142
142
  grid-template-rows: 0fr
143
143
 
144
- // 编辑本文按钮
144
+ // 操作按钮(回到顶部 / 参与讨论)
145
145
  .widget-wrapper.toc .widget-body+.widget-footer:before
146
146
  content: ''
147
147
  position absolute
@@ -188,4 +188,4 @@
188
188
  a
189
189
  background: var(--block)
190
190
  a+a
191
- margin-top: 4px
191
+ margin-top: 4px
@@ -1,9 +1,13 @@
1
- $lazyTransitionType = hexo-config('plugins.lazyload.transition')
1
+ $lazyTransitionType = hexo-config('dependencies.lazyload.transition')
2
2
  $loadingImageSize = 2rem
3
3
 
4
4
  img:not([src])
5
5
  visibility: hidden
6
6
 
7
+ .lazy
8
+ opacity: 0
9
+ z-index: 1
10
+
7
11
  .lazy-box
8
12
  position: relative
9
13
  overflow: hidden
@@ -12,26 +16,8 @@ img:not([src])
12
16
  background: var(--block)
13
17
  min-height: 4rem
14
18
 
15
- // 懒加载
16
- img[data-ll-status]
17
- z-index: 1
18
- &:not(.loaded)
19
- opacity: 0
20
- &.loaded,&.error
21
- opacity: 1
22
- if $lazyTransitionType == 'blur'
23
- trans1 all 0.5s
24
- &:not(.loaded)
25
- filter blur(20px)
26
- -webkit-filter blur(20px)
27
- &.loaded,&.error
28
- filter none
29
- -webkit-filter none
30
- else
31
- trans1 all 0.38s
32
-
33
19
  // 加载占位动画
34
- img[data-ll-status]+.lazy-icon
20
+ img.lazy.loading + .lazy-icon
35
21
  position: absolute
36
22
  width: $loadingImageSize
37
23
  height: $loadingImageSize
@@ -45,13 +31,41 @@ img[data-ll-status]+.lazy-icon
45
31
  background-position: center
46
32
 
47
33
  // 加载完成
48
- img[data-ll-status].loaded
49
- &+.lazy-icon
50
- display: none
34
+ img.lazy.loaded + .lazy-icon,
35
+ img.lazy.error + .lazy-icon
36
+ display: none
51
37
 
52
38
  // 加载失败
53
- img[data-ll-status].error
39
+ img.lazy.error
54
40
  width: $loadingImageSize
55
41
  height: $loadingImageSize
56
- &+.lazy-icon
57
- display: none
42
+
43
+ // 懒加载完成动画:使用 keyframes 而非 transition,
44
+ // 因为缓存图片可能在 loading 状态被绘制前就完成加载,transition 没有起点会直接闪现
45
+ if $lazyTransitionType == 'blur'
46
+ .lazy
47
+ filter: blur(20px)
48
+ -webkit-filter: blur(20px)
49
+ .lazy.loaded, .lazy.error
50
+ opacity: 1
51
+ filter: none
52
+ -webkit-filter: none
53
+ animation: ll-blur-in 0.5s ease-out
54
+ @keyframes ll-blur-in
55
+ from
56
+ opacity: 0
57
+ filter: blur(20px)
58
+ -webkit-filter: blur(20px)
59
+ to
60
+ opacity: 1
61
+ filter: none
62
+ -webkit-filter: none
63
+ else
64
+ .lazy.loaded, .lazy.error
65
+ opacity: 1
66
+ animation: ll-fade-in 0.5s ease-out
67
+ @keyframes ll-fade-in
68
+ from
69
+ opacity: 0
70
+ to
71
+ opacity: 1
@@ -1,2 +1,6 @@
1
1
  .slide-up
2
- visibility: hidden
2
+ visibility: hidden
3
+
4
+ // ScrollReveal 加载/初始化失败时强制显示内容,避免空白页
5
+ html.sr-fallback .slide-up
6
+ visibility: visible !important
package/source/js/main.js CHANGED
@@ -53,11 +53,14 @@ const util = {
53
53
  },
54
54
 
55
55
  scrollTop: () => {
56
- window.scrollTo({ top: 0, behavior: "smooth" });
56
+ smoothScrollTo(0);
57
57
  },
58
58
 
59
59
  scrollComment: () => {
60
- document.getElementById('comments').scrollIntoView({ behavior: "smooth" });
60
+ const el = document.getElementById('comments');
61
+ if (el) {
62
+ smoothScrollTo(el.getBoundingClientRect().top + window.scrollY - 32);
63
+ }
61
64
  },
62
65
 
63
66
  viewportLazyload: (target, func, enabled = true) => {
@@ -94,73 +97,168 @@ const hud = {
94
97
 
95
98
  const l_body = document.querySelector('.l_body');
96
99
 
100
+ // 通用平滑滚动(自定义动画,TOC / 回到顶部 / 参与讨论共用)
101
+ let scrollAnim = null;
102
+ function cancelSmoothScroll() {
103
+ if (scrollAnim !== null) {
104
+ cancelAnimationFrame(scrollAnim);
105
+ scrollAnim = null;
106
+ }
107
+ }
108
+ function smoothScrollTo(targetY) {
109
+ cancelSmoothScroll();
110
+ targetY = Math.max(0, targetY);
111
+ const startY = window.scrollY;
112
+ const diff = targetY - startY;
113
+ if (Math.abs(diff) < 2) {
114
+ return;
115
+ }
116
+ // 短距离 300ms,长距离最多 600ms
117
+ const duration = Math.min(600, Math.max(300, Math.abs(diff) * 0.15));
118
+ const startTime = performance.now();
119
+ function step(now) {
120
+ const t = Math.min(1, (now - startTime) / duration);
121
+ const eased = 1 - Math.pow(1 - t, 3); // easeOutCubic
122
+ // 显式指定 instant,避免全局 scroll-behavior: smooth 与自定义动画叠加导致滚动变慢
123
+ window.scrollTo({ top: startY + diff * eased, behavior: 'instant' });
124
+ if (t < 1) {
125
+ scrollAnim = requestAnimationFrame(step);
126
+ } else {
127
+ scrollAnim = null;
128
+ }
129
+ }
130
+ scrollAnim = requestAnimationFrame(step);
131
+ }
132
+ window.addEventListener('wheel', cancelSmoothScroll, { passive: true });
133
+ window.addEventListener('touchstart', cancelSmoothScroll, { passive: true });
134
+
97
135
 
98
136
  const init = {
99
137
  toc: () => {
100
- utils.jq(() => {
101
- const scrollOffset = 32;
102
- var segs = [];
103
- $("article.md-text :header").each(function (idx, node) {
104
- segs.push(node);
105
- });
106
- function activeTOC() {
107
- var scrollTop = $(this).scrollTop();
108
- var topSeg = null;
109
- for (var idx in segs) {
110
- var seg = $(segs[idx]);
111
- if (seg.offset().top > scrollTop + scrollOffset) {
112
- continue;
113
- }
114
- if (!topSeg) {
115
- topSeg = seg;
116
- } else if (seg.offset().top >= topSeg.offset().top) {
117
- topSeg = seg;
118
- }
138
+ const scrollOffset = 32;
139
+ // 滚动位置取整后标题顶可能落在偏移线下方 1~2px,加容差避免高亮回跳到上一条
140
+ const scrollTolerance = 4;
141
+ var segs = utils.qsa("article.md-text h1, article.md-text h2, article.md-text h3, article.md-text h4, article.md-text h5, article.md-text h6");
142
+ function activeTOC() {
143
+ var scrollTop = window.scrollY;
144
+ var topSeg = null;
145
+ for (var i = 0; i < segs.length; i++) {
146
+ var segTop = segs[i].getBoundingClientRect().top + window.scrollY;
147
+ if (segTop > scrollTop + scrollOffset + scrollTolerance) {
148
+ continue;
119
149
  }
120
- if (topSeg) {
121
- $("#data-toc a.toc-link").removeClass("active");
122
- var link = "#" + topSeg.attr("id");
123
- if (link != '#undefined') {
124
- const highlightItem = $('#data-toc a.toc-link[href="' + encodeURI(link) + '"]');
125
- if (highlightItem.length > 0) {
126
- highlightItem.addClass("active");
127
- }
128
- } else {
129
- $('#data-toc a.toc-link:first').addClass("active");
130
- }
150
+ if (!topSeg || segTop >= topSeg.getBoundingClientRect().top + window.scrollY) {
151
+ topSeg = segs[i];
131
152
  }
132
153
  }
133
- function scrollTOC() {
134
- const e0 = document.querySelector('#data-toc .toc');
135
- const e1 = document.querySelector('#data-toc .toc a.toc-link.active');
136
- if (e0 == null || e1 == null) {
137
- return;
138
- }
139
- const offsetBottom = e1.getBoundingClientRect().bottom - e0.getBoundingClientRect().bottom + 100;
140
- const offsetTop = e1.getBoundingClientRect().top - e0.getBoundingClientRect().top - 64;
141
- if (offsetTop < 0) {
142
- e0.scrollBy({ top: offsetTop, behavior: "smooth" });
143
- } else if (offsetBottom > 0) {
144
- e0.scrollBy({ top: offsetBottom, behavior: "smooth" });
154
+ if (topSeg) {
155
+ utils.dom("#data-toc a.toc-link").removeClass("active");
156
+ var id = topSeg.getAttribute("id");
157
+ var link = id ? "#" + id : "#undefined";
158
+ if (link != '#undefined') {
159
+ const highlightItem = utils.dom('#data-toc a.toc-link[href="' + encodeURI(link) + '"]');
160
+ if (highlightItem.length > 0) {
161
+ highlightItem.addClass("active");
162
+ }
163
+ } else {
164
+ const first = utils.qs('#data-toc a.toc-link');
165
+ if (first) first.classList.add("active");
145
166
  }
146
167
  }
168
+ }
169
+ function scrollTOC() {
170
+ const e0 = document.querySelector('#data-toc .toc');
171
+ const e1 = document.querySelector('#data-toc .toc a.toc-link.active');
172
+ if (e0 == null || e1 == null) {
173
+ return;
174
+ }
175
+ const offsetBottom = e1.getBoundingClientRect().bottom - e0.getBoundingClientRect().bottom + 100;
176
+ const offsetTop = e1.getBoundingClientRect().top - e0.getBoundingClientRect().top - 64;
177
+ if (offsetTop < 0) {
178
+ e0.scrollBy({ top: offsetTop, behavior: "smooth" });
179
+ } else if (offsetBottom > 0) {
180
+ e0.scrollBy({ top: offsetBottom, behavior: "smooth" });
181
+ }
182
+ }
147
183
 
148
- var timeout = null;
149
- window.addEventListener('scroll', function () {
150
- activeTOC();
151
- if (timeout !== null) clearTimeout(timeout);
152
- timeout = setTimeout(function () {
153
- scrollTOC();
154
- }.bind(this), 50);
155
- });
156
- })
184
+ var timeout = null;
185
+ window.addEventListener('scroll', function () {
186
+ activeTOC();
187
+ if (timeout !== null) clearTimeout(timeout);
188
+ timeout = setTimeout(function () {
189
+ scrollTOC();
190
+ }, 50);
191
+ });
157
192
  },
158
193
  sidebar: () => {
159
- utils.jq(() => {
160
- $("#data-toc a.toc-link").click(function (e) {
161
- sidebar.dismiss();
162
- });
163
- })
194
+ utils.dom("#data-toc a.toc-link").click(function (e) {
195
+ const href = this.getAttribute("href");
196
+ const id = href && href.indexOf("#") === 0 ? decodeURIComponent(href.slice(1)) : null;
197
+ const target = id && document.getElementById(id);
198
+ if (target) {
199
+ e.preventDefault();
200
+ const offset = 32; // 与 activeTOC 的 scrollOffset 保持一致
201
+ const targetY = target.getBoundingClientRect().top + window.scrollY - offset;
202
+ smoothScrollTo(targetY);
203
+ if (window.history && window.history.pushState) {
204
+ window.history.pushState(null, "", href);
205
+ }
206
+ }
207
+ sidebar.dismiss();
208
+ });
209
+ },
210
+ leftbarScroll: () => {
211
+ const container = document.querySelector('.l_left .widgets');
212
+ if (container == null) {
213
+ return;
214
+ }
215
+ const PREFIX = 'Stellar.leftbarScroll.';
216
+ const encode = (s) => encodeURIComponent(String(s || ''));
217
+ function scope() {
218
+ const wikiEl = document.querySelector('.doc-tree[data-wiki]');
219
+ if (wikiEl != null) {
220
+ return 'wiki:' + encode(wikiEl.getAttribute('data-wiki'));
221
+ }
222
+ const notebookEl = document.querySelector('widget[data-notebook]');
223
+ if (notebookEl != null) {
224
+ return 'notebook:' + encode(notebookEl.getAttribute('data-notebook'));
225
+ }
226
+ const body = document.querySelector('.l_body');
227
+ return 'layout:' + encode((body && body.getAttribute('layout')) || 'default');
228
+ }
229
+ window.addEventListener('pagehide', function () {
230
+ try {
231
+ const s = scope();
232
+ sessionStorage.setItem(PREFIX + s, String(container.scrollTop));
233
+ sessionStorage.setItem(PREFIX + 'last', s);
234
+ } catch (e) {}
235
+ });
236
+ try {
237
+ const s = scope();
238
+ // 仅当上一页与当前页属于同一分区时才恢复,离开分区后再回来不跳回旧位置
239
+ if (sessionStorage.getItem(PREFIX + 'last') !== s) {
240
+ return;
241
+ }
242
+ const value = sessionStorage.getItem(PREFIX + s);
243
+ if (value == null) {
244
+ return;
245
+ }
246
+ container.scrollTop = parseInt(value, 10) || 0;
247
+ const link = container.querySelector('a.link.active');
248
+ if (link == null) {
249
+ return;
250
+ }
251
+ const padding = 16;
252
+ const containerRect = container.getBoundingClientRect();
253
+ const linkRect = link.getBoundingClientRect();
254
+ const top = linkRect.top - containerRect.top;
255
+ const bottom = linkRect.bottom - containerRect.top;
256
+ if (top < 0) {
257
+ container.scrollTop += top - padding;
258
+ } else if (bottom > container.clientHeight) {
259
+ container.scrollTop += bottom - container.clientHeight + padding;
260
+ }
261
+ } catch (e) {}
164
262
  },
165
263
  relativeDate: (selector) => {
166
264
  selector.forEach(item => {
@@ -293,25 +391,15 @@ window.stellar = window.stellar || {};
293
391
 
294
392
  /**
295
393
  * Initialize page components
296
- * Called on initial load and after PJAX navigation
297
394
  */
298
395
  stellar.initPage = function () {
299
396
  init.toc();
300
397
  init.sidebar();
398
+ init.leftbarScroll();
301
399
  init.relativeDate(document.querySelectorAll('#post-meta time'));
302
400
  init.registerTabsTag();
303
-
304
- // Reinitialize comments after PJAX navigation
305
- if (stellar.initComments) {
306
- for (const commentSystem in stellar.initComments) {
307
- if (typeof stellar.initComments[commentSystem] === 'function') {
308
- stellar.initComments[commentSystem]();
309
- }
310
- }
311
- }
312
401
  };
313
402
 
314
403
  // Initial page load
315
404
  stellar.initPage();
316
405
  init.canonicalCheck();
317
-
@@ -1,77 +1,77 @@
1
1
  utils.js(window.searchConfig.js).then(() => {
2
- utils.jq(() => {
3
- var $inputArea = $("input#search-input");
4
- if ($inputArea.length === 0) {
5
- return;
6
- }
2
+ var inputArea = document.querySelector("input#search-input");
3
+ if (!inputArea) {
4
+ return;
5
+ }
7
6
 
8
- var $resultArea = $("#search-result");
9
- var $searchWrapper = $("#search-wrapper");
10
- var client = algoliasearch(window.searchConfig.appId, window.searchConfig.apiKey);
11
- var index = client.initIndex(window.searchConfig.indexName);
7
+ var resultArea = document.querySelector("#search-result");
8
+ var searchWrapper = document.querySelector("#search-wrapper");
9
+ var client = algoliasearch(window.searchConfig.appId, window.searchConfig.apiKey);
10
+ var index = client.initIndex(window.searchConfig.indexName);
12
11
 
13
- function filterResults(hits, filterPath) {
14
- if (!filterPath || filterPath === '/') return hits;
15
- var regex = new RegExp(filterPath);
16
- return hits.filter(hit => regex.test(hit.url));
17
- }
12
+ function filterResults(hits, filterPath) {
13
+ if (!filterPath || filterPath === '/') return hits;
14
+ var regex = new RegExp(filterPath);
15
+ return hits.filter(hit => regex.test(hit.url));
16
+ }
18
17
 
19
- function displayResults(hits) {
20
- var $resultList = $("<ul>").addClass("search-result-list");
21
- if (hits.length === 0) {
22
- $searchWrapper.addClass('noresult');
23
- } else {
24
- $searchWrapper.removeClass('noresult');
25
- hits.forEach(function(hit) {
26
- var contentSnippet = hit._snippetResult.content.value;
27
- var title = hit.hierarchy.lvl1 || 'Untitled';
28
- var $item = $("<li>").html(`<a href="${hit.url}"><span class='search-result-title'>${title}</span><p class="search-result-content">${contentSnippet}</p></a>`);
29
- $resultList.append($item);
30
- });
31
- }
32
- $resultArea.html($resultList);
18
+ function displayResults(hits) {
19
+ var resultList = document.createElement("ul");
20
+ resultList.classList.add("search-result-list");
21
+ if (hits.length === 0) {
22
+ searchWrapper.classList.add('noresult');
23
+ } else {
24
+ searchWrapper.classList.remove('noresult');
25
+ hits.forEach(function(hit) {
26
+ var contentSnippet = hit._snippetResult.content.value;
27
+ var title = hit.hierarchy.lvl1 || 'Untitled';
28
+ var item = document.createElement("li");
29
+ item.innerHTML = `<a href="${hit.url}"><span class='search-result-title'>${title}</span><p class="search-result-content">${contentSnippet}</p></a>`;
30
+ resultList.appendChild(item);
31
+ });
33
32
  }
33
+ resultArea.replaceChildren(resultList);
34
+ }
34
35
 
35
- $inputArea.on("input", function() {
36
- var query = $(this).val().trim();
37
- var filterPath = $inputArea.data('filter');
36
+ inputArea.addEventListener("input", function() {
37
+ var query = inputArea.value.trim();
38
+ var filterPath = inputArea.getAttribute('data-filter');
38
39
 
39
- if (query.length <= 0) {
40
- $searchWrapper.attr('searching', 'false');
41
- $resultArea.empty();
42
- return;
43
- }
40
+ if (query.length <= 0) {
41
+ searchWrapper.setAttribute('searching', 'false');
42
+ resultArea.replaceChildren();
43
+ return;
44
+ }
44
45
 
45
- $searchWrapper.attr('searching', 'true');
46
+ searchWrapper.setAttribute('searching', 'true');
46
47
 
47
- index.search(query, {
48
- hitsPerPage: window.searchConfig.hitsPerPage,
49
- attributesToHighlight: ['content'],
50
- attributesToSnippet: ['content:30'],
51
- highlightPreTag: '<span class="search-keyword">',
52
- highlightPostTag: '</span>',
53
- restrictSearchableAttributes: ['content']
54
- }).then(function(responses) {
55
- displayResults(filterResults(responses.hits, filterPath));
56
- });
48
+ index.search(query, {
49
+ hitsPerPage: window.searchConfig.hitsPerPage,
50
+ attributesToHighlight: ['content'],
51
+ attributesToSnippet: ['content:30'],
52
+ highlightPreTag: '<span class="search-keyword">',
53
+ highlightPostTag: '</span>',
54
+ restrictSearchableAttributes: ['content']
55
+ }).then(function(responses) {
56
+ displayResults(filterResults(responses.hits, filterPath));
57
57
  });
58
+ });
58
59
 
59
- $inputArea.on("keydown", function(e) {
60
- if (e.which == 13) {
61
- e.preventDefault();
62
- }
63
- });
60
+ inputArea.addEventListener("keydown", function(e) {
61
+ if (e.key == 'Enter') {
62
+ e.preventDefault();
63
+ }
64
+ });
64
65
 
65
- var observer = new MutationObserver(function(mutationsList) {
66
- if (mutationsList.length === 1) {
67
- if (mutationsList[0].addedNodes.length) {
68
- $searchWrapper.removeClass('noresult');
69
- } else if (mutationsList[0].removedNodes.length) {
70
- $searchWrapper.addClass('noresult');
71
- }
66
+ var observer = new MutationObserver(function(mutationsList) {
67
+ if (mutationsList.length === 1) {
68
+ if (mutationsList[0].addedNodes.length) {
69
+ searchWrapper.classList.remove('noresult');
70
+ } else if (mutationsList[0].removedNodes.length) {
71
+ searchWrapper.classList.add('noresult');
72
72
  }
73
- });
74
-
75
- observer.observe($resultArea[0], { childList: true });
73
+ }
76
74
  });
75
+
76
+ observer.observe(resultArea, { childList: true });
77
77
  });