@mlikiowa/nanaeo 1.0.1702966759079 → 1.0.1702967739786

Sign up to get free protection for your applications and to get access to all the features.
Files changed (59) hide show
  1. package/2022/08/04/NewBlog/index.html +3473 -0
  2. package/2022/08/13/GaussWave/index.html +3407 -0
  3. package/about/index.html +3236 -0
  4. package/archives/2022/08/index.html +3411 -0
  5. package/archives/2022/index.html +3411 -0
  6. package/archives/index.html +3308 -0
  7. package/asset/Sotheby.ttf +0 -0
  8. package/asset/backimg.png +0 -0
  9. package/atom.xml +171 -0
  10. package/categories/DevLog/index.html +3351 -0
  11. package/categories/SiteLog/index.html +3351 -0
  12. package/categories/index.html +3174 -0
  13. package/category/devlog/atom.xml +58 -0
  14. package/category/devlog/feed.json +19 -0
  15. package/category/devlog/rss.xml +62 -0
  16. package/category/sitelog/atom.xml +125 -0
  17. package/category/sitelog/feed.json +20 -0
  18. package/category/sitelog/rss.xml +129 -0
  19. package/content.json +1 -0
  20. package/css/Readme.html +9 -0
  21. package/css/first.css +1504 -0
  22. package/css/style.css +7106 -0
  23. package/favicon.ico +0 -0
  24. package/feed.json +31 -0
  25. package/friends/index.html +3661 -0
  26. package/index.html +3421 -0
  27. package/js/app.js +1223 -0
  28. package/js/plugins/aplayer.js +186 -0
  29. package/js/plugins/parallax.js +191 -0
  30. package/js/plugins/rightMenu.js +577 -0
  31. package/js/plugins/rightMenus.js +618 -0
  32. package/js/plugins/tags/contributors.js +92 -0
  33. package/js/plugins/tags/friends.js +93 -0
  34. package/js/plugins/tags/sites.js +96 -0
  35. package/js/search/hexo.js +192 -0
  36. package/package.json +1 -1
  37. package/rss.xml +175 -0
  38. package/tag/devlog/atom.xml +58 -0
  39. package/tag/devlog/feed.json +19 -0
  40. package/tag/devlog/rss.xml +62 -0
  41. package/tag/gauss/atom.xml +58 -0
  42. package/tag/gauss/feed.json +19 -0
  43. package/tag/gauss/rss.xml +62 -0
  44. package/tag/hexo/atom.xml +125 -0
  45. package/tag/hexo/feed.json +20 -0
  46. package/tag/hexo/rss.xml +129 -0
  47. package/tag/hexothemes/atom.xml +125 -0
  48. package/tag/hexothemes/feed.json +20 -0
  49. package/tag/hexothemes/rss.xml +129 -0
  50. package/tag/sitelog/atom.xml +125 -0
  51. package/tag/sitelog/feed.json +20 -0
  52. package/tag/sitelog/rss.xml +129 -0
  53. package/tags/DevLog/index.html +3351 -0
  54. package/tags/Gauss/index.html +3351 -0
  55. package/tags/Hexo/index.html +3351 -0
  56. package/tags/HexoThemes/index.html +3351 -0
  57. package/tags/SiteLog/index.html +3351 -0
  58. package/tags/index.html +3159 -0
  59. package/volantis-sw.js +797 -0
package/js/app.js ADDED
@@ -0,0 +1,1223 @@
1
+ document.addEventListener("DOMContentLoaded", function () {
2
+ volantis.requestAnimationFrame(() => {
3
+ VolantisApp.init();
4
+ VolantisApp.subscribe();
5
+ VolantisFancyBox.init();
6
+ highlightKeyWords.startFromURL();
7
+ locationHash();
8
+
9
+ volantis.pjax.push(() => {
10
+ VolantisApp.pjaxReload();
11
+ VolantisFancyBox.init();
12
+ sessionStorage.setItem("domTitle", document.title);
13
+ highlightKeyWords.startFromURL();
14
+ }, 'app.js');
15
+ volantis.pjax.send(() => {
16
+ volantis.dom.switcher.removeClass('active'); // 关闭移动端激活的搜索框
17
+ volantis.dom.header.removeClass('z_search-open'); // 关闭移动端激活的搜索框
18
+ volantis.dom.wrapper.removeClass('sub'); // 跳转页面时关闭二级导航
19
+ volantis.EventListener.remove() // 移除事件监听器 see: layout/_partial/scripts/global.ejs
20
+ }, 'app.js');
21
+ });
22
+ });
23
+
24
+ /* 锚点定位 */
25
+ const locationHash = () => {
26
+ if (window.location.hash) {
27
+ let locationID = decodeURI(window.location.hash.split('#')[1]).replace(/\ /g, '-');
28
+ let target = document.getElementById(locationID);
29
+ if (target) {
30
+ setTimeout(() => {
31
+ if (window.location.hash.startsWith('#fn')) { // hexo-reference https://github.com/volantis-x/hexo-theme-volantis/issues/647
32
+ volantis.scroll.to(target, { addTop: - volantis.dom.header.offsetHeight - 5, behavior: 'instant', observer: true })
33
+ } else {
34
+ // 锚点中上半部有大片空白 高度大概是 volantis.dom.header.offsetHeight
35
+ volantis.scroll.to(target, { addTop: 5, behavior: 'instant', observer: true })
36
+ }
37
+ }, 1000)
38
+ }
39
+ }
40
+ }
41
+ Object.freeze(locationHash);
42
+
43
+ /* Main */
44
+ const VolantisApp = (() => {
45
+ const fn = {},
46
+ COPYHTML = '<button class="btn-copy" data-clipboard-snippet=""><i class="fa-solid fa-copy"></i><span>COPY</span></button>';
47
+ let scrollCorrection = 80;
48
+
49
+ fn.init = () => {
50
+ if (volantis.dom.header) {
51
+ scrollCorrection = volantis.dom.header.clientHeight + 16;
52
+ }
53
+
54
+ window.onresize = () => {
55
+ if (document.documentElement.clientWidth < 500) {
56
+ volantis.isMobile = 1;
57
+ } else {
58
+ volantis.isMobile = 0;
59
+ }
60
+ if (volantis.isMobile != volantis.isMobileOld) {
61
+ fn.setGlobalHeaderMenuEvent();
62
+ fn.setHeader();
63
+ fn.setHeaderSearch();
64
+ }
65
+ }
66
+ volantis.scroll.push(fn.scrollEventCallBack, "scrollEventCallBack")
67
+ }
68
+
69
+ fn.event = () => {
70
+ volantis.dom.$(document.getElementById("scroll-down"))?.on('click', function () {
71
+ fn.scrolltoElement(volantis.dom.bodyAnchor);
72
+ });
73
+
74
+ // 如果 sidebar 为空,隐藏 sidebar。
75
+ const sidebar = document.querySelector("#l_side")
76
+ if (sidebar) {
77
+ const sectionList = sidebar.querySelectorAll("section")
78
+ if (!sectionList.length) {
79
+ document.querySelector("#l_main").classList.add("no_sidebar")
80
+ }
81
+ }
82
+
83
+ // 站点信息 最后活动日期
84
+ if (volantis.GLOBAL_CONFIG.sidebar.for_page.includes('webinfo') || volantis.GLOBAL_CONFIG.sidebar.for_post.includes('webinfo')) {
85
+ const lastupd = volantis.GLOBAL_CONFIG.sidebar.webinfo.lastupd;
86
+ if (!!document.getElementById('last-update-show') && lastupd.enable && lastupd.friendlyShow) {
87
+ document.getElementById('last-update-show').innerHTML = fn.utilTimeAgo(volantis.GLOBAL_CONFIG.lastupdate);
88
+ }
89
+ }
90
+
91
+ // 站点信息 运行时间
92
+ if (!!document.getElementById('webinfo-runtime-count')) {
93
+ let BirthDay = new Date(volantis.GLOBAL_CONFIG.sidebar.webinfo.runtime.data);
94
+ let timeold = (new Date().getTime() - BirthDay.getTime());
95
+ let daysold = Math.floor(timeold / (24 * 60 * 60 * 1000));
96
+ document.getElementById('webinfo-runtime-count').innerHTML = `${daysold} ${volantis.GLOBAL_CONFIG.sidebar.webinfo.runtime.unit}`;
97
+ }
98
+
99
+ // 消息提示 复制时弹出
100
+ document.body.oncopy = function () {
101
+ fn.messageCopyright()
102
+ };
103
+ }
104
+
105
+ fn.restData = () => {
106
+ scrollCorrection = volantis.dom.header ? volantis.dom.header.clientHeight + 16 : 80;
107
+ }
108
+
109
+ fn.setIsMobile = () => {
110
+ if (document.documentElement.clientWidth < 500) {
111
+ volantis.isMobile = 1;
112
+ volantis.isMobileOld = 1;
113
+ } else {
114
+ volantis.isMobile = 0;
115
+ volantis.isMobileOld = 0;
116
+ }
117
+ }
118
+
119
+ // 校正页面定位(被导航栏挡住的区域)
120
+ fn.scrolltoElement = (elem, correction = scrollCorrection) => {
121
+ volantis.scroll.to(elem, {
122
+ top: elem.offsetTop - correction
123
+ })
124
+ }
125
+
126
+ // 滚动事件回调们
127
+ fn.scrollEventCallBack = () => {
128
+ // 【移动端 PC】//////////////////////////////////////////////////////////////////////
129
+
130
+ // 显示/隐藏 Header导航 topBtn 【移动端 PC】
131
+ const showHeaderPoint = volantis.dom.bodyAnchor.offsetTop - scrollCorrection;
132
+ const scrollTop = volantis.scroll.getScrollTop(); // 滚动条距离顶部的距离
133
+
134
+ // topBtn
135
+ if (volantis.dom.topBtn) {
136
+ if (scrollTop > volantis.dom.bodyAnchor.offsetTop) {
137
+ volantis.dom.topBtn.addClass('show');
138
+ // 向上滚动高亮 topBtn
139
+ if (volantis.scroll.del > 0) {
140
+ volantis.dom.topBtn.removeClass('hl');
141
+ } else {
142
+ volantis.dom.topBtn.addClass('hl');
143
+ }
144
+ } else {
145
+ volantis.dom.topBtn.removeClass('show').removeClass('hl');
146
+ }
147
+ }
148
+
149
+ // Header导航
150
+ if (volantis.dom.header) {
151
+ if (scrollTop - showHeaderPoint > -1) {
152
+ volantis.dom.header.addClass('show');
153
+ } else {
154
+ volantis.dom.header.removeClass('show');
155
+ }
156
+ }
157
+
158
+ // 决定一二级导航栏的切换 【向上滚动切换为一级导航栏;向下滚动切换为二级导航栏】 【移动端 PC】
159
+ if (pdata.ispage && volantis.dom.wrapper) {
160
+ if (volantis.scroll.del > 0 && scrollTop > 100) { // 向下滚动
161
+ volantis.dom.wrapper.addClass('sub'); // <---- 二级导航显示
162
+ } else if (volantis.scroll.del < 0) { // 向上滚动
163
+ volantis.dom.wrapper.removeClass('sub'); // <---- 取消二级导航显示 一级导航显示
164
+ }
165
+ }
166
+
167
+ // 【移动端】//////////////////////////////////////////////////////////////////////
168
+ if (volantis.isMobile) {
169
+ // 【移动端】 页面滚动 隐藏 移动端toc目录按钮
170
+ if (pdata.ispage && volantis.dom.tocTarget && volantis.dom.toc) {
171
+ volantis.dom.tocTarget.removeClass('active');
172
+ volantis.dom.toc.removeClass('active');
173
+ }
174
+ // 【移动端】 滚动时隐藏子菜单
175
+ if (volantis.dom.mPhoneList) {
176
+ volantis.dom.mPhoneList.forEach(function (e) {
177
+ volantis.dom.$(e).hide();
178
+ })
179
+ }
180
+ }
181
+ }
182
+
183
+ // 设置滚动锚点
184
+ fn.setScrollAnchor = () => {
185
+ // click topBtn 滚动至bodyAnchor 【移动端 PC】
186
+ if (volantis.dom.topBtn && volantis.dom.bodyAnchor) {
187
+ volantis.dom.topBtn.click(e => {
188
+ e.preventDefault();
189
+ e.stopPropagation();
190
+ fn.scrolltoElement(volantis.dom.bodyAnchor);
191
+ e.stopImmediatePropagation();
192
+ });
193
+ }
194
+
195
+ }
196
+
197
+ // 设置导航栏
198
+ fn.setHeader = () => {
199
+ // !!! 此处的Dom对象需要重载 !!!
200
+ if (!pdata.ispage) return;
201
+
202
+ // 填充二级导航文章标题 【移动端 PC】
203
+ volantis.dom.wrapper.find('.nav-sub .title').html(document.title.split(" - ")[0]);
204
+
205
+ // ====== bind events to every btn =========
206
+ // 评论按钮 【移动端 PC】
207
+ volantis.dom.comment = volantis.dom.$(document.getElementById("s-comment")); // 评论按钮 桌面端 移动端
208
+ volantis.dom.commentTarget = volantis.dom.$(document.querySelector('#l_main article#comments')); // 评论区域
209
+ if (volantis.dom.commentTarget) {
210
+ volantis.dom.comment.click(e => { // 评论按钮点击后 跳转到评论区域
211
+ e.preventDefault();
212
+ e.stopPropagation();
213
+ fn.scrolltoElement(volantis.dom.commentTarget);
214
+ e.stopImmediatePropagation();
215
+ });
216
+ } else volantis.dom.comment.style.display = 'none'; // 关闭了评论,则隐藏评论按钮
217
+
218
+ // 移动端toc目录按钮 【移动端】
219
+ if (volantis.isMobile) {
220
+ volantis.dom.toc = volantis.dom.$(document.getElementById("s-toc")); // 目录按钮 仅移动端
221
+ volantis.dom.tocTarget = volantis.dom.$(document.querySelector('#l_side .toc-wrapper')); // 侧边栏的目录列表
222
+ if (volantis.dom.tocTarget) {
223
+ // 点击移动端目录按钮 激活目录按钮 显示侧边栏的目录列表
224
+ volantis.dom.toc.click((e) => {
225
+ e.stopPropagation();
226
+ volantis.dom.tocTarget.toggleClass('active');
227
+ volantis.dom.toc.toggleClass('active');
228
+ });
229
+ // 点击空白 隐藏
230
+ volantis.dom.$(document).click(function (e) {
231
+ e.stopPropagation();
232
+ if (volantis.dom.tocTarget) {
233
+ volantis.dom.tocTarget.removeClass('active');
234
+ }
235
+ volantis.dom.toc.removeClass('active');
236
+ });
237
+ } else volantis.dom.toc.style.display = 'none'; // 隐藏toc目录按钮
238
+ }
239
+ }
240
+
241
+ // 设置导航栏菜单选中状态 【移动端 PC】
242
+ fn.setHeaderMenuSelection = () => {
243
+ // !!! 此处的Dom对象需要重载 !!!
244
+ volantis.dom.headerMenu = volantis.dom.$(document.querySelectorAll('#l_header .navigation,#l_cover .navigation,#l_side .navigation')); // 导航列表
245
+
246
+ // 先把已经激活的取消激活
247
+ volantis.dom.headerMenu.forEach(element => {
248
+ let li = volantis.dom.$(element).find('li a.active')
249
+ if (li)
250
+ li.removeClass('active')
251
+ let div = volantis.dom.$(element).find('div a.active')
252
+ if (div)
253
+ div.removeClass('active')
254
+ });
255
+
256
+ // replace '%' '/' '.'
257
+ var idname = location.pathname.replace(/\/|%|\./g, '');
258
+ if (idname.length == 0) {
259
+ idname = 'home';
260
+ }
261
+ var page = idname.match(/page\d{0,}$/g);
262
+ if (page) {
263
+ page = page[0];
264
+ idname = idname.split(page)[0];
265
+ }
266
+ var index = idname.match(/index.html/);
267
+ if (index) {
268
+ index = index[0];
269
+ idname = idname.split(index)[0];
270
+ }
271
+ // 转义字符如 [, ], ~, #, @
272
+ idname = idname.replace(/(\[|\]|~|#|@)/g, '\\$1');
273
+ if (idname && volantis.dom.headerMenu) {
274
+ volantis.dom.headerMenu.forEach(element => {
275
+ // idname 不能为数字开头, 加一个 action- 前缀
276
+ let id = element.querySelector("[active-action=action-" + idname + "]")
277
+ if (id) {
278
+ volantis.dom.$(id).addClass('active')
279
+ }
280
+ });
281
+ }
282
+ }
283
+
284
+ // 设置全局事件
285
+ fn.setGlobalHeaderMenuEvent = () => {
286
+ if (volantis.isMobile) {
287
+ // 【移动端】 关闭已经展开的子菜单 点击展开子菜单
288
+ document.querySelectorAll('#l_header .m-phone li').forEach(function (_e) {
289
+ if (_e.querySelector(".list-v")) {
290
+ // 点击菜单
291
+ volantis.dom.$(_e).click(function (e) {
292
+ e.stopPropagation();
293
+ let menuType = ''
294
+ // 关闭.menu-phone
295
+ Array.from(e.currentTarget.children).some(val => {
296
+ if(val.classList.contains('s-menu')) {
297
+ menuType = 'menu' // 代表点击的是一级菜单外层的icon
298
+ return
299
+ }
300
+ if(val.classList.contains('menuitem')) {
301
+ menuType = 'item' // 点击的是下拉一级菜单
302
+ return
303
+ }
304
+ })
305
+ if(menuType === 'item') {
306
+ // 关闭已经展开的子菜单, 这一步是针对点击多个拥有二级子菜单的一级菜单,关闭其他所有一级菜单的二级菜单
307
+ // ①
308
+ e.currentTarget.parentElement.childNodes.forEach(function (e2) {
309
+ if (Object.prototype.toString.call(e2) == '[object HTMLLIElement]') {
310
+ e2.childNodes.forEach(function (e1) {
311
+ if (Object.prototype.toString.call(e1) == '[object HTMLUListElement]') {
312
+ volantis.dom.$(e1).hide()
313
+ }
314
+ })
315
+ }
316
+ })
317
+ // 点击展开二级子菜单
318
+
319
+ /*
320
+ 由于采用事件委托,因此此处点击, 两种情况,currentTarget指向菜单按钮a.s-menu和ul的共同父元素li, 第二,指向ul中的li元素,也就是子菜单
321
+ 区分:情况一的第一个子元素a的类名是s-menu;情况二的子元素a的类名为menuitem
322
+ 我们要点击外部的menu icon时要关闭的是.menu-phone而不是.menuitem
323
+ */
324
+ let array = e.currentTarget.children
325
+ for (let index = 0; index < array.length; index++) {
326
+ const element = array[index];
327
+ if (volantis.dom.$(element).title === 'menu') { // 移动端菜单栏异常
328
+ volantis.dom.$(element).style.display = "flex" // https://github.com/volantis-x/hexo-theme-volantis/issues/706
329
+ } else {
330
+ volantis.dom.$(element).show()
331
+ }
332
+ }
333
+ } else {
334
+ let menuPhone = document.querySelector('.switcher .menu-phone')
335
+ let isHiding = window.getComputedStyle(menuPhone).display === 'none'
336
+ if(isHiding) {
337
+ volantis.dom.$(menuPhone).show()
338
+ } else {
339
+ volantis.dom.$(menuPhone).hide()
340
+ // 别忘了再执行①
341
+ // 准备关闭所有二级菜单, 注意此时的e和点击一级菜单时候的e层级不同
342
+ // 此处好像不能使用变量存储的menuPhone?要重新查询
343
+ document.querySelector('.switcher .menu-phone').childNodes.forEach(function (e2) {
344
+ if (Object.prototype.toString.call(e2) == '[object HTMLLIElement]') {
345
+ e2.childNodes.forEach(function (e1) {
346
+ if (Object.prototype.toString.call(e1) == '[object HTMLUListElement]') {
347
+ volantis.dom.$(e1).hide()
348
+ }
349
+ })
350
+ }
351
+ })
352
+ }
353
+ }
354
+
355
+ }, 0);
356
+ }
357
+ })
358
+ } else {
359
+ // 【PC端】 hover时展开子菜单,点击时[target.baseURI==origin时]隐藏子菜单? 现有逻辑大部分情况不隐藏子菜单
360
+ document.querySelectorAll('#wrapper .m-pc li > a[href]').forEach(function (e) {
361
+ volantis.dom.$(e.parentElement).click(function (e) {
362
+ e.stopPropagation();
363
+ if (e.target.origin == e.target.baseURI) {
364
+ document.querySelectorAll('#wrapper .m-pc .list-v').forEach(function (e) {
365
+ volantis.dom.$(e).hide(); // 大概率不会执行
366
+ })
367
+ }
368
+ }, 0);
369
+ })
370
+ }
371
+ fn.setPageHeaderMenuEvent();
372
+ }
373
+
374
+ // 【移动端】隐藏子菜单
375
+ fn.setPageHeaderMenuEvent = () => {
376
+ if (!volantis.isMobile) return
377
+ // 【移动端】 点击空白处隐藏子菜单
378
+ volantis.dom.$(document).click(function (e) {
379
+ volantis.dom.mPhoneList.forEach(function (e) {
380
+ volantis.dom.$(e).hide();
381
+ })
382
+ });
383
+ }
384
+
385
+ // 设置导航栏搜索框 【移动端】
386
+ fn.setHeaderSearch = () => {
387
+ if (!volantis.isMobile) return;
388
+ if (!volantis.dom.switcher) return;
389
+ // 点击移动端搜索按钮
390
+ volantis.dom.switcher.click(function (e) {
391
+ e.stopPropagation();
392
+ volantis.dom.header.toggleClass('z_search-open'); // 激活移动端搜索框
393
+ volantis.dom.switcher.toggleClass('active'); // 移动端搜索按钮
394
+ }, false); // false : pjax 不移除监听
395
+ // 点击空白取消激活
396
+ volantis.dom.$(document).click(function (e) {
397
+ volantis.dom.header.removeClass('z_search-open');
398
+ volantis.dom.switcher.removeClass('active');
399
+ }, false); // false : pjax 不移除监听
400
+ // 移动端点击搜索框 停止事件传播
401
+ volantis.dom.search.click(function (e) {
402
+ e.stopPropagation();
403
+ }, false); // false : pjax 不移除监听
404
+ }
405
+
406
+ // 设置 tabs 标签 【移动端 PC】
407
+ fn.setTabs = () => {
408
+ let tabs = document.querySelectorAll('#l_main .tabs .nav-tabs')
409
+ if (!tabs) return
410
+ tabs.forEach(function (e) {
411
+ e.querySelectorAll('a').forEach(function (e) {
412
+ volantis.dom.$(e).on('click', (e) => {
413
+ e.preventDefault();
414
+ e.stopPropagation();
415
+ const $tab = volantis.dom.$(e.target.parentElement.parentElement.parentElement);
416
+ $tab.find('.nav-tabs .active').removeClass('active');
417
+ volantis.dom.$(e.target.parentElement).addClass('active');
418
+ $tab.find('.tab-content .active').removeClass('active');
419
+ $tab.find(e.target.className).addClass('active');
420
+ return false;
421
+ });
422
+ })
423
+ })
424
+ }
425
+
426
+ // hexo-reference 页脚跳转 https://github.com/volantis-x/hexo-theme-volantis/issues/647
427
+ fn.footnotes = () => {
428
+ let ref = document.querySelectorAll('#l_main .footnote-backref, #l_main .footnote-ref > a');
429
+ ref.forEach(function (e, i) {
430
+ ref[i].click = () => { }; // 强制清空原 click 事件
431
+ volantis.dom.$(e).on('click', (e) => {
432
+ e.stopPropagation();
433
+ e.preventDefault();
434
+ let targetID = decodeURI(e.target.hash.split('#')[1]).replace(/\ /g, '-');
435
+ let target = document.getElementById(targetID);
436
+ if (target) {
437
+ volantis.scroll.to(target, { addTop: - volantis.dom.header.offsetHeight - 5, behavior: 'instant' })
438
+ }
439
+ });
440
+ })
441
+ }
442
+
443
+ // 工具类:代码块复制
444
+ fn.utilCopyCode = (Selector) => {
445
+ document.querySelectorAll(Selector).forEach(node => {
446
+ const test = node.insertAdjacentHTML("beforebegin", COPYHTML);
447
+ const _BtnCopy = node.previousSibling;
448
+ _BtnCopy.onclick = e => {
449
+ e.stopPropagation();
450
+ const _icon = _BtnCopy.querySelector('i');
451
+ const _span = _BtnCopy.querySelector('span');
452
+
453
+ node.focus();
454
+ const range = new Range();
455
+ range.selectNodeContents(node);
456
+ document.getSelection().removeAllRanges();
457
+ document.getSelection().addRange(range);
458
+
459
+ const str = document.getSelection().toString();
460
+ fn.utilWriteClipText(str).then(() => {
461
+ fn.messageCopyright();
462
+ _BtnCopy.classList.add('copied');
463
+ _icon.classList.remove('fa-copy');
464
+ _icon.classList.add('fa-check-circle');
465
+ _span.innerText = "COPIED";
466
+ setTimeout(() => {
467
+ _icon.classList.remove('fa-check-circle');
468
+ _icon.classList.add('fa-copy');
469
+ _span.innerText = "COPY";
470
+ }, 2000)
471
+ }).catch(e => {
472
+ VolantisApp.message('系统提示', e, {
473
+ icon: 'fa fa-exclamation-circle red'
474
+ });
475
+ _BtnCopy.classList.add('copied-failed');
476
+ _icon.classList.remove('fa-copy');
477
+ _icon.classList.add('fa-exclamation-circle');
478
+ _span.innerText = "COPY FAILED";
479
+ setTimeout(() => {
480
+ _icon.classList.remove('fa-exclamation-circle');
481
+ _icon.classList.add('fa-copy');
482
+ _span.innerText = "COPY";
483
+ })
484
+ })
485
+ }
486
+ });
487
+ }
488
+
489
+ // 工具类:复制字符串到剪切板
490
+ fn.utilWriteClipText = (str) => {
491
+ return navigator.clipboard
492
+ .writeText(str)
493
+ .then(() => {
494
+ return Promise.resolve()
495
+ })
496
+ .catch(e => {
497
+ const input = document.createElement('textarea');
498
+ input.setAttribute('readonly', 'readonly');
499
+ document.body.appendChild(input);
500
+ input.innerHTML = str;
501
+ input.select();
502
+ try {
503
+ let result = document.execCommand('copy')
504
+ document.body.removeChild(input);
505
+ if (!result || result === 'unsuccessful') {
506
+ return Promise.reject('复制文本失败!')
507
+ } else {
508
+ return Promise.resolve()
509
+ }
510
+ } catch (e) {
511
+ document.body.removeChild(input);
512
+ return Promise.reject(
513
+ '当前浏览器不支持复制功能,请检查更新或更换其他浏览器操作!'
514
+ )
515
+ }
516
+ })
517
+ }
518
+
519
+ // 工具类:返回时间间隔
520
+ fn.utilTimeAgo = (dateTimeStamp) => {
521
+ const minute = 1e3 * 60, hour = minute * 60, day = hour * 24, week = day * 7, month = day * 30;
522
+ const now = new Date().getTime();
523
+ const diffValue = now - dateTimeStamp;
524
+ const minC = diffValue / minute,
525
+ hourC = diffValue / hour,
526
+ dayC = diffValue / day,
527
+ weekC = diffValue / week,
528
+ monthC = diffValue / month;
529
+ if (diffValue < 0) {
530
+ result = ""
531
+ } else if (monthC >= 1 && monthC < 7) {
532
+ result = " " + parseInt(monthC) + " 月前"
533
+ } else if (weekC >= 1 && weekC < 4) {
534
+ result = " " + parseInt(weekC) + " 周前"
535
+ } else if (dayC >= 1 && dayC < 7) {
536
+ result = " " + parseInt(dayC) + " 天前"
537
+ } else if (hourC >= 1 && hourC < 24) {
538
+ result = " " + parseInt(hourC) + " 小时前"
539
+ } else if (minC >= 1 && minC < 60) {
540
+ result = " " + parseInt(minC) + " 分钟前"
541
+ } else if (diffValue >= 0 && diffValue <= minute) {
542
+ result = "刚刚"
543
+ } else {
544
+ const datetime = new Date();
545
+ datetime.setTime(dateTimeStamp);
546
+ const Nyear = datetime.getFullYear();
547
+ const Nmonth = datetime.getMonth() + 1 < 10 ? "0" + (datetime.getMonth() + 1) : datetime.getMonth() + 1;
548
+ const Ndate = datetime.getDate() < 10 ? "0" + datetime.getDate() : datetime.getDate();
549
+ const Nhour = datetime.getHours() < 10 ? "0" + datetime.getHours() : datetime.getHours();
550
+ const Nminute = datetime.getMinutes() < 10 ? "0" + datetime.getMinutes() : datetime.getMinutes();
551
+ const Nsecond = datetime.getSeconds() < 10 ? "0" + datetime.getSeconds() : datetime.getSeconds();
552
+ result = Nyear + "-" + Nmonth + "-" + Ndate
553
+ }
554
+ return result;
555
+ }
556
+
557
+ // 消息提示:标准
558
+ fn.message = (title, message, option = {}, done = null) => {
559
+ if (typeof iziToast === "undefined") {
560
+ volantis.css(volantis.GLOBAL_CONFIG.plugins.message.css)
561
+ volantis.js(volantis.GLOBAL_CONFIG.plugins.message.js, () => {
562
+ tozashMessage(title, message, option, done);
563
+ });
564
+ } else {
565
+ tozashMessage(title, message, option, done);
566
+ }
567
+ function tozashMessage(title, message, option, done) {
568
+ const {
569
+ icon,
570
+ time,
571
+ position,
572
+ transitionIn,
573
+ transitionOut,
574
+ messageColor,
575
+ titleColor,
576
+ backgroundColor,
577
+ zindex,
578
+ displayMode
579
+ } = option;
580
+ iziToast.show({
581
+ layout: '2',
582
+ icon: 'Fontawesome',
583
+ closeOnEscape: 'true',
584
+ displayMode: displayMode || 'replace',
585
+ transitionIn: transitionIn || volantis.GLOBAL_CONFIG.plugins.message.transitionIn,
586
+ transitionOut: transitionOut || volantis.GLOBAL_CONFIG.plugins.message.transitionOut,
587
+ messageColor: messageColor || volantis.GLOBAL_CONFIG.plugins.message.messageColor,
588
+ titleColor: titleColor || volantis.GLOBAL_CONFIG.plugins.message.titleColor,
589
+ backgroundColor: backgroundColor || volantis.GLOBAL_CONFIG.plugins.message.backgroundColor,
590
+ zindex: zindex || volantis.GLOBAL_CONFIG.plugins.message.zindex,
591
+ icon: icon || volantis.GLOBAL_CONFIG.plugins.message.icon.default,
592
+ timeout: time || volantis.GLOBAL_CONFIG.plugins.message.time.default,
593
+ position: position || volantis.GLOBAL_CONFIG.plugins.message.position,
594
+ title: title,
595
+ message: message,
596
+ onClosed: () => {
597
+ if (done) done();
598
+ },
599
+ });
600
+ }
601
+ }
602
+
603
+ // 消息提示:询问
604
+ fn.question = (title, message, option = {}, success = null, cancel = null, done = null) => {
605
+ if (typeof iziToast === "undefined") {
606
+ volantis.css(volantis.GLOBAL_CONFIG.plugins.message.css)
607
+ volantis.js(volantis.GLOBAL_CONFIG.plugins.message.js, () => {
608
+ tozashQuestion(title, message, option, success, cancel, done);
609
+ });
610
+ } else {
611
+ tozashQuestion(title, message, option, success, cancel, done);
612
+ }
613
+
614
+ function tozashQuestion(title, message, option, success, cancel, done) {
615
+ const {
616
+ icon,
617
+ time,
618
+ position,
619
+ transitionIn,
620
+ transitionOut,
621
+ messageColor,
622
+ titleColor,
623
+ backgroundColor,
624
+ zindex
625
+ } = option;
626
+ iziToast.question({
627
+ id: 'question',
628
+ icon: 'Fontawesome',
629
+ close: false,
630
+ overlay: true,
631
+ displayMode: 'once',
632
+ position: 'center',
633
+ messageColor: messageColor || volantis.GLOBAL_CONFIG.plugins.message.messageColor,
634
+ titleColor: titleColor || volantis.GLOBAL_CONFIG.plugins.message.titleColor,
635
+ backgroundColor: backgroundColor || volantis.GLOBAL_CONFIG.plugins.message.backgroundColor,
636
+ zindex: zindex || volantis.GLOBAL_CONFIG.plugins.message.zindex,
637
+ icon: icon || volantis.GLOBAL_CONFIG.plugins.message.icon.quection,
638
+ timeout: time || volantis.GLOBAL_CONFIG.plugins.message.time.quection,
639
+ title: title,
640
+ message: message,
641
+ buttons: [
642
+ ['<button><b>是</b></button>', (instance, toast) => {
643
+ instance.hide({ transitionOut: transitionOut || 'fadeOut' }, toast, 'button');
644
+ if (success) success(instance, toast)
645
+ }],
646
+ ['<button><b>否</b></button>', (instance, toast) => {
647
+ instance.hide({ transitionOut: transitionOut || 'fadeOut' }, toast, 'button');
648
+ if (cancel) cancel(instance, toast)
649
+ }]
650
+ ],
651
+ onClosed: (instance, toast, closedBy) => {
652
+ if (done) done(instance, toast, closedBy);
653
+ }
654
+ });
655
+ }
656
+ }
657
+
658
+ // 消息提示:隐藏
659
+ fn.hideMessage = (done = null) => {
660
+ const toast = document.querySelector('.iziToast');
661
+ if (!toast) {
662
+ if (done) done()
663
+ return;
664
+ }
665
+
666
+ if (typeof iziToast === "undefined") {
667
+ volantis.css(volantis.GLOBAL_CONFIG.plugins.message.css)
668
+ volantis.js(volantis.GLOBAL_CONFIG.plugins.message.js, () => {
669
+ hideMessage(done);
670
+ });
671
+ } else {
672
+ hideMessage(done);
673
+ }
674
+
675
+ function hideMessage(done) {
676
+ iziToast.hide({}, toast);
677
+ if (done) done();
678
+ }
679
+ }
680
+
681
+ // 消息提示:复制
682
+ let messageCopyrightShow = 0;
683
+ fn.messageCopyright = () => {
684
+ // 消息提示 复制时弹出
685
+ if (volantis.GLOBAL_CONFIG.plugins.message.enable
686
+ && volantis.GLOBAL_CONFIG.plugins.message.copyright.enable
687
+ && messageCopyrightShow < 1) {
688
+ messageCopyrightShow++;
689
+ VolantisApp.message(volantis.GLOBAL_CONFIG.plugins.message.copyright.title,
690
+ volantis.GLOBAL_CONFIG.plugins.message.copyright.message, {
691
+ icon: volantis.GLOBAL_CONFIG.plugins.message.copyright.icon,
692
+ transitionIn: 'flipInX',
693
+ transitionOut: 'flipOutX',
694
+ displayMode: 1
695
+ });
696
+ }
697
+ }
698
+
699
+ return {
700
+ init: () => {
701
+ fn.init();
702
+ fn.event();
703
+ },
704
+ subscribe: () => {
705
+ fn.setIsMobile();
706
+ fn.setHeader();
707
+ fn.setHeaderMenuSelection();
708
+ fn.setGlobalHeaderMenuEvent();
709
+ fn.setHeaderSearch();
710
+ fn.setScrollAnchor();
711
+ fn.setTabs();
712
+ fn.footnotes();
713
+ },
714
+ pjaxReload: () => {
715
+ fn.event();
716
+ fn.restData();
717
+ fn.setHeader();
718
+ fn.setHeaderMenuSelection();
719
+ fn.setPageHeaderMenuEvent();
720
+ fn.setScrollAnchor();
721
+ fn.setTabs();
722
+ fn.footnotes();
723
+
724
+ // 移除小尾巴的移除
725
+ document.querySelector("#l_header .nav-main").querySelectorAll('.list-v:not(.menu-phone)').forEach(function (e) {
726
+ e.removeAttribute("style")
727
+ })
728
+ document.querySelector("#l_header .menu-phone.list-v").removeAttribute("style");
729
+ messageCopyrightShow = 0;
730
+ },
731
+ utilCopyCode: fn.utilCopyCode,
732
+ utilWriteClipText: fn.utilWriteClipText,
733
+ utilTimeAgo: fn.utilTimeAgo,
734
+ message: fn.message,
735
+ question: fn.question,
736
+ hideMessage: fn.hideMessage,
737
+ messageCopyright: fn.messageCopyright,
738
+ scrolltoElement: fn.scrolltoElement
739
+ }
740
+ })()
741
+ Object.freeze(VolantisApp);
742
+
743
+ /* FancyBox */
744
+ const VolantisFancyBox = (() => {
745
+ const fn = {};
746
+
747
+ fn.loadFancyBox = (done) => {
748
+ volantis.css(volantis.GLOBAL_CONFIG.plugins.fancybox.css);
749
+ volantis.js(volantis.GLOBAL_CONFIG.plugins.fancybox.js).then(() => {
750
+ if (done) done();
751
+ })
752
+ }
753
+
754
+ /**
755
+ * 加载及处理
756
+ *
757
+ * @param {*} checkMain 是否只处理文章区域的文章
758
+ * @param {*} done FancyBox 加载完成后的动作,默认执行分组绑定
759
+ * @returns
760
+ */
761
+ fn.init = (checkMain = true, done = fn.groupBind) => {
762
+ if (!document.querySelector(".md .gallery img, .fancybox") && checkMain) return;
763
+ if (typeof Fancybox === "undefined") {
764
+ fn.loadFancyBox(done);
765
+ } else {
766
+ done();
767
+ }
768
+ }
769
+
770
+ /**
771
+ * 图片元素预处理
772
+ *
773
+ * @param {*} selectors 选择器
774
+ * @param {*} name 分组
775
+ */
776
+ fn.elementHandling = (selectors, name) => {
777
+ const nodeList = document.querySelectorAll(selectors);
778
+ nodeList.forEach($item => {
779
+ if ($item.hasAttribute('fancybox')) return;
780
+ $item.setAttribute('fancybox', '');
781
+ const $link = document.createElement('a');
782
+ $link.setAttribute('href', $item.src);
783
+ $link.setAttribute('data-caption', $item.alt);
784
+ $link.setAttribute('data-fancybox', name);
785
+ $link.classList.add('fancybox');
786
+ $link.append($item.cloneNode());
787
+ $item.replaceWith($link);
788
+ })
789
+ }
790
+
791
+ /**
792
+ * 原生绑定
793
+ *
794
+ * @param {*} selectors 选择器
795
+ */
796
+ fn.bind = (selectors) => {
797
+ fn.init(false, () => {
798
+ Fancybox.bind(selectors, {
799
+ groupAll: true,
800
+ Hash: false,
801
+ hideScrollbar: false,
802
+ Thumbs: {
803
+ autoStart: false,
804
+ },
805
+ caption: function (fancybox, carousel, slide) {
806
+ return slide.$trigger.alt || null
807
+ }
808
+ });
809
+ });
810
+ }
811
+
812
+ /**
813
+ * 分组绑定
814
+ *
815
+ * @param {*} groupName 分组名称
816
+ */
817
+ fn.groupBind = (groupName = null) => {
818
+ const group = new Set();
819
+
820
+ document.querySelectorAll(".gallery").forEach(ele => {
821
+ if (ele.querySelector("img")) {
822
+ group.add(ele.getAttribute('data-group') || 'default');
823
+ }
824
+ })
825
+
826
+ if (!!groupName) group.add(groupName);
827
+
828
+ for (const iterator of group) {
829
+ Fancybox.unbind('[data-fancybox="' + iterator + '"]');
830
+ Fancybox.bind('[data-fancybox="' + iterator + '"]', {
831
+ Hash: false,
832
+ hideScrollbar: false,
833
+ Thumbs: {
834
+ autoStart: false,
835
+ }
836
+ });
837
+ }
838
+ }
839
+
840
+ return {
841
+ init: fn.init,
842
+ bind: fn.bind,
843
+ groupBind: (selectors, groupName = 'default') => {
844
+ try {
845
+ fn.elementHandling(selectors, groupName);
846
+ fn.init(false, () => {
847
+ fn.groupBind(groupName)
848
+ });
849
+ } catch (error) {
850
+ console.error(error)
851
+ }
852
+ }
853
+ }
854
+ })()
855
+ Object.freeze(VolantisFancyBox);
856
+
857
+ // highlightKeyWords 与 搜索功能搭配 https://github.com/next-theme/hexo-theme-next/blob/eb194a7258058302baf59f02d4b80b6655338b01/source/js/third-party/search/local-search.js
858
+ // Question: 锚点稳定性未知
859
+ // ToDo: 查找模式
860
+ // 0. (/////////要知道浏览器自带全页面查找功能 CTRL + F)
861
+ // 1. 右键开启查找模式 / 导航栏菜单开启?? / CTRL + F ???
862
+ // 2. 查找模式面板 (可拖动? or 固定?)
863
+ // 3. keyword mark id 从 0 开始编号 查找下一处 highlightKeyWords.scrollToNextHighlightKeywordMark() 查找上一处 scrollToPrevHighlightKeywordMark() 循环查找(取模%)
864
+ // 4. 可输入修改 查找关键词 keywords(type:list)
865
+ // 5. 区分大小写 caseSensitive (/ 全字匹配?? / 正则匹配??)
866
+ // 6. 在选定区域中查找 querySelector ??
867
+ // 7. 关闭查找模式
868
+ // 8. 搜索跳转 (URL 入口) 自动开启查找模式 调用 scrollToNextHighlightKeywordMark()
869
+ const highlightKeyWords = (() => {
870
+ let fn = {}
871
+ fn.markNum = 0
872
+ fn.markNextId = -1
873
+ fn.startFromURL = () => {
874
+ const params = decodeURI(new URL(location.href).searchParams.get('keyword'));
875
+ const keywords = params ? params.split(' ') : [];
876
+ const post = document.querySelector('#l_main');
877
+ if (keywords.length == 1 && keywords[0] == "null") {
878
+ return;
879
+ }
880
+ fn.start(keywords, post); // 渲染耗时较长
881
+ fn.scrollToFirstHighlightKeywordMark()
882
+ }
883
+ fn.scrollToFirstHighlightKeywordMark = () => {
884
+ volantis.cleanContentVisibility();
885
+ let target = fn.scrollToNextHighlightKeywordMark("0");
886
+ if (!target) {
887
+ volantis.requestAnimationFrame(fn.scrollToFirstHighlightKeywordMark)
888
+ }
889
+ }
890
+ fn.scrollToNextHighlightKeywordMark = (id) => {
891
+ // Next Id
892
+ let input = id || (fn.markNextId + 1) % fn.markNum;
893
+ fn.markNextId = parseInt(input)
894
+ let target = document.getElementById("keyword-mark-" + fn.markNextId);
895
+ if (!target) {
896
+ fn.markNextId = (fn.markNextId + 1) % fn.markNum;
897
+ target = document.getElementById("keyword-mark-" + fn.markNextId);
898
+ }
899
+ if (target) {
900
+ volantis.scroll.to(target, { addTop: - volantis.dom.header.offsetHeight - 5, behavior: 'instant' })
901
+ }
902
+ // Current target
903
+ return target
904
+ }
905
+ fn.scrollToPrevHighlightKeywordMark = (id) => {
906
+ // Prev Id
907
+ let input = id || (fn.markNextId - 1 + fn.markNum) % fn.markNum;
908
+ fn.markNextId = parseInt(input)
909
+ let target = document.getElementById("keyword-mark-" + fn.markNextId);
910
+ if (!target) {
911
+ fn.markNextId = (fn.markNextId - 1 + fn.markNum) % fn.markNum;
912
+ target = document.getElementById("keyword-mark-" + fn.markNextId);
913
+ }
914
+ if (target) {
915
+ volantis.scroll.to(target, { addTop: - volantis.dom.header.offsetHeight - 5, behavior: 'instant' })
916
+ }
917
+ // Current target
918
+ return target
919
+ }
920
+ fn.start = (keywords, querySelector) => {
921
+ fn.markNum = 0
922
+ if (!keywords.length || !querySelector || (keywords.length == 1 && keywords[0] == "null")) return;
923
+ console.log(keywords);
924
+ const walk = document.createTreeWalker(querySelector, NodeFilter.SHOW_TEXT, null);
925
+ const allNodes = [];
926
+ while (walk.nextNode()) {
927
+ if (!walk.currentNode.parentNode.matches('button, select, textarea')) allNodes.push(walk.currentNode);
928
+ }
929
+ allNodes.forEach(node => {
930
+ const [indexOfNode] = fn.getIndexByWord(keywords, node.nodeValue);
931
+ if (!indexOfNode.length) return;
932
+ const slice = fn.mergeIntoSlice(0, node.nodeValue.length, indexOfNode);
933
+ fn.highlightText(node, slice, 'keyword');
934
+ fn.highlightStyle()
935
+ });
936
+ }
937
+ fn.getIndexByWord = (words, text, caseSensitive = false) => {
938
+ const index = [];
939
+ const included = new Set();
940
+ words.forEach(word => {
941
+ const div = document.createElement('div');
942
+ div.innerText = word;
943
+ word = div.innerHTML;
944
+
945
+ const wordLen = word.length;
946
+ if (wordLen === 0) return;
947
+ let startPosition = 0;
948
+ let position = -1;
949
+ if (!caseSensitive) {
950
+ text = text.toLowerCase();
951
+ word = word.toLowerCase();
952
+ }
953
+ while ((position = text.indexOf(word, startPosition)) > -1) {
954
+ index.push({ position, word });
955
+ included.add(word);
956
+ startPosition = position + wordLen;
957
+ }
958
+ });
959
+ index.sort((left, right) => {
960
+ if (left.position !== right.position) {
961
+ return left.position - right.position;
962
+ }
963
+ return right.word.length - left.word.length;
964
+ });
965
+ return [index, included];
966
+ };
967
+ fn.mergeIntoSlice = (start, end, index) => {
968
+ let item = index[0];
969
+ let { position, word } = item;
970
+ const hits = [];
971
+ const count = new Set();
972
+ while (position + word.length <= end && index.length !== 0) {
973
+ count.add(word);
974
+ hits.push({
975
+ position,
976
+ length: word.length
977
+ });
978
+ const wordEnd = position + word.length;
979
+
980
+ index.shift();
981
+ while (index.length !== 0) {
982
+ item = index[0];
983
+ position = item.position;
984
+ word = item.word;
985
+ if (wordEnd > position) {
986
+ index.shift();
987
+ } else {
988
+ break;
989
+ }
990
+ }
991
+ }
992
+ return {
993
+ hits,
994
+ start,
995
+ end,
996
+ count: count.size
997
+ };
998
+ };
999
+ fn.highlightText = (node, slice, className) => {
1000
+ const val = node.nodeValue;
1001
+ let index = slice.start;
1002
+ const children = [];
1003
+ for (const { position, length } of slice.hits) {
1004
+ const text = document.createTextNode(val.substring(index, position));
1005
+ index = position + length;
1006
+ let mark = document.createElement('mark');
1007
+ mark.className = className;
1008
+ mark = fn.highlightStyle(mark)
1009
+ mark.appendChild(document.createTextNode(val.substr(position, length)));
1010
+ children.push(text, mark);
1011
+ }
1012
+ node.nodeValue = val.substring(index, slice.end);
1013
+ children.forEach(element => {
1014
+ node.parentNode.insertBefore(element, node);
1015
+ });
1016
+ }
1017
+ fn.highlightStyle = (mark) => {
1018
+ if (!mark) return;
1019
+ mark.id = "keyword-mark-" + fn.markNum;
1020
+ fn.markNum++;
1021
+ mark.style.background = "transparent";
1022
+ mark.style["border-bottom"] = "1px dashed #ff2a2a";
1023
+ mark.style["color"] = "#ff2a2a";
1024
+ mark.style["font-weight"] = "bold";
1025
+ return mark
1026
+ }
1027
+ fn.cleanHighlightStyle = () => {
1028
+ document.querySelectorAll(".keyword").forEach(mark => {
1029
+ mark.style.background = "transparent";
1030
+ mark.style["border-bottom"] = null;
1031
+ mark.style["color"] = null;
1032
+ mark.style["font-weight"] = null;
1033
+ })
1034
+ }
1035
+ return {
1036
+ start: (keywords, querySelector) => {
1037
+ fn.start(keywords, querySelector)
1038
+ },
1039
+ startFromURL: () => {
1040
+ fn.startFromURL()
1041
+ },
1042
+ scrollToNextHighlightKeywordMark: (id) => {
1043
+ fn.scrollToNextHighlightKeywordMark(id)
1044
+ },
1045
+ scrollToPrevHighlightKeywordMark: (id) => {
1046
+ fn.scrollToPrevHighlightKeywordMark(id)
1047
+ },
1048
+ cleanHighlightStyle: () => {
1049
+ fn.cleanHighlightStyle()
1050
+ },
1051
+ }
1052
+ })()
1053
+ Object.freeze(highlightKeyWords);
1054
+
1055
+ /* DOM 控制 */
1056
+ const DOMController = {
1057
+ /**
1058
+ * 控制元素显隐
1059
+ */
1060
+ visible: (ele, type = true) => {
1061
+ if (ele) ele.style.display = type === true ? 'block' : 'none';
1062
+ },
1063
+
1064
+ /**
1065
+ * 移除元素
1066
+ */
1067
+ remove: (param) => {
1068
+ const node = document.querySelectorAll(param);
1069
+ node.forEach(ele => {
1070
+ ele.remove();
1071
+ })
1072
+ },
1073
+
1074
+ removeList: (list) => {
1075
+ list.forEach(param => {
1076
+ DOMController.remove(param)
1077
+ })
1078
+ },
1079
+
1080
+ /**
1081
+ * 设置属性
1082
+ */
1083
+ setAttribute: (param, attrName, attrValue) => {
1084
+ const node = document.querySelectorAll(param);
1085
+ node.forEach(ele => {
1086
+ ele.setAttribute(attrName, attrValue)
1087
+ })
1088
+ },
1089
+
1090
+ setAttributeList: (list) => {
1091
+ list.forEach(item => {
1092
+ DOMController.setAttribute(item[0], item[1], item[2])
1093
+ })
1094
+ },
1095
+
1096
+ /**
1097
+ * 设置样式
1098
+ */
1099
+ setStyle: (param, styleName, styleValue) => {
1100
+ const node = document.querySelectorAll(param);
1101
+ node.forEach(ele => {
1102
+ ele.style[styleName] = styleValue;
1103
+ })
1104
+ },
1105
+
1106
+ setStyleList: (list) => {
1107
+ list.forEach(item => {
1108
+ DOMController.setStyle(item[0], item[1], item[2])
1109
+ })
1110
+ },
1111
+
1112
+ fadeIn: (e) => {
1113
+ if (!e) return;
1114
+ e.style.visibility = "visible";
1115
+ e.style.opacity = 1;
1116
+ e.style.display = "block";
1117
+ e.style.transition = "all 0.5s linear";
1118
+ return e
1119
+ },
1120
+
1121
+ fadeOut: (e) => {
1122
+ if (!e) return;
1123
+ e.style.visibility = "hidden";
1124
+ e.style.opacity = 0;
1125
+ e.style.display = "none";
1126
+ e.style.transition = "all 0.5s linear";
1127
+ return e
1128
+ },
1129
+
1130
+ fadeToggle: (e) => {
1131
+ if (!e) return;
1132
+ if (e.style.visibility == "hidden") {
1133
+ e = DOMController.fadeIn(e)
1134
+ } else {
1135
+ e = DOMController.fadeOut(e)
1136
+ }
1137
+ return e
1138
+ },
1139
+
1140
+ fadeToggleList: (list) => {
1141
+ list.forEach(param => {
1142
+ DOMController.fadeToggle(param)
1143
+ })
1144
+ },
1145
+
1146
+ hasClass: (e, c) => {
1147
+ if (!e) return;
1148
+ return e.className.match(new RegExp('(\\s|^)' + c + '(\\s|$)'));
1149
+ },
1150
+
1151
+ addClass: (e, c) => {
1152
+ if (!e) return;
1153
+ e.classList.add(c);
1154
+ return e
1155
+ },
1156
+
1157
+ removeClass: (e, c) => {
1158
+ if (!e) return;
1159
+ e.classList.remove(c);
1160
+ return e
1161
+ },
1162
+
1163
+ toggleClass: (e, c) => {
1164
+ if (!e) return;
1165
+ if (DOMController.hasClass(e, c)) {
1166
+ DOMController.removeClass(e, c)
1167
+ } else {
1168
+ DOMController.addClass(e, c)
1169
+ }
1170
+ return e
1171
+ },
1172
+
1173
+ toggleClassList: (list) => {
1174
+ list.forEach(item => {
1175
+ DOMController.toggleClass(item[0], item[1])
1176
+ })
1177
+ }
1178
+ }
1179
+ Object.freeze(DOMController);
1180
+
1181
+ const VolantisRequest = {
1182
+ timeoutFetch: (url, ms, requestInit) => {
1183
+ const controller = new AbortController()
1184
+ requestInit.signal?.addEventListener('abort', () => controller.abort())
1185
+ let promise = fetch(url, { ...requestInit, signal: controller.signal })
1186
+ if (ms > 0) {
1187
+ const timer = setTimeout(() => controller.abort(), ms)
1188
+ promise.finally(() => { clearTimeout(timer) })
1189
+ }
1190
+ promise = promise.catch((err) => {
1191
+ throw ((err || {}).name === 'AbortError') ? new Error(`Fetch timeout: ${url}`) : err
1192
+ })
1193
+ return promise
1194
+ },
1195
+
1196
+ Fetch: async (url, requestInit, timeout = 15000) => {
1197
+ const resp = await VolantisRequest.timeoutFetch(url, timeout, requestInit);
1198
+ if (!resp.ok) throw new Error(`Fetch error: ${url} | ${resp.status}`);
1199
+ let json = await resp.json()
1200
+ if (!json.success) throw json
1201
+ return json
1202
+ },
1203
+
1204
+ POST: async (url, data) => {
1205
+ const requestInit = {
1206
+ method: 'POST',
1207
+ }
1208
+ if (data) {
1209
+ const formData = new FormData();
1210
+ Object.keys(data).forEach(key => formData.append(key, String(data[key])))
1211
+ requestInit.body = formData;
1212
+ }
1213
+ const json = await VolantisRequest.Fetch(url, requestInit)
1214
+ return json.data;
1215
+ },
1216
+
1217
+ Get: async (url, data) => {
1218
+ const json = await VolantisRequest.Fetch(url + (data ? (`?${new URLSearchParams(data)}`) : ''), {
1219
+ method: 'GET'
1220
+ })
1221
+ }
1222
+ }
1223
+ Object.freeze(VolantisRequest);