diff --git "a/2022/07/18/ vue3-vite\347\216\257\345\242\203\345\217\230\351\207\217\350\270\251\351\233\267/index.html" "b/2022/07/18/ vue3-vite\347\216\257\345\242\203\345\217\230\351\207\217\350\270\251\351\233\267/index.html" index fb9f32e..c5083d3 100644 --- "a/2022/07/18/ vue3-vite\347\216\257\345\242\203\345\217\230\351\207\217\350\270\251\351\233\267/index.html" +++ "b/2022/07/18/ vue3-vite\347\216\257\345\242\203\345\217\230\351\207\217\350\270\251\351\233\267/index.html" @@ -1,248 +1,165 @@ - - - - - - - - - - - - - - - - - - - - - - +vue3+vite环境变量踩雷 | ayozooZ + - - - - - - - - - -vue3+vite环境变量踩雷 | ayozooZ - - - - - - - - - - - - - - - - -
- -
-
-
- - -
- - - -

ayozooZ

- -
-

ayozooZ的blog

-
- - -
- - - - - - - - -
- - -
- - - - - - - - - - - - - - - - - + window.pjax ? abcjsInit() : window.addEventListener('load', abcjsInit) +})()
\ No newline at end of file diff --git "a/2022/10/19/ webpack\343\200\201vite\347\232\204\350\207\252\345\212\250\345\257\274\345\214\205\347\232\204-\347\210\261\346\201\250\346\203\205\344\273\207/index.html" "b/2022/10/19/ webpack\343\200\201vite\347\232\204\350\207\252\345\212\250\345\257\274\345\214\205\347\232\204-\347\210\261\346\201\250\346\203\205\344\273\207/index.html" index 91824c8..23dc6f2 100644 --- "a/2022/10/19/ webpack\343\200\201vite\347\232\204\350\207\252\345\212\250\345\257\274\345\214\205\347\232\204-\347\210\261\346\201\250\346\203\205\344\273\207/index.html" +++ "b/2022/10/19/ webpack\343\200\201vite\347\232\204\350\207\252\345\212\250\345\257\274\345\214\205\347\232\204-\347\210\261\346\201\250\346\203\205\344\273\207/index.html" @@ -1,248 +1,165 @@ - - - - - - - - - - - - - - - - - - - - - - +webpack、vite的自动导包的"爱恨情仇" | ayozooZ + - - - - - - - - - -webpack、vite的自动导包的"爱恨情仇" | ayozooZ - - - - - - - - - - - - - - - - -
- -
-
-
- - -
- - - -

ayozooZ

- -
-

ayozooZ的blog

-
- - -
- - - - - - - - -
- - -
- - - - - - - - - - - - - - - - - + window.pjax ? abcjsInit() : window.addEventListener('load', abcjsInit) +})()
\ No newline at end of file diff --git a/2023/08/27/ websocket/index.html b/2023/08/27/ websocket/index.html index 69f8f19..b539094 100644 --- a/2023/08/27/ websocket/index.html +++ b/2023/08/27/ websocket/index.html @@ -1,247 +1,164 @@ - - - - - - - - - - - - - - - - - - - - - - +http vs websocket | ayozooZ + - - - - - - - - - -http vs websocket | ayozooZ - - - - - - - - - - - - - - - - -
- -
-
-
- - -
- - - -

ayozooZ

- -
-

ayozooZ的blog

-
- - -
- - - - - - - - -
- - -
- - - - - - - - - - - - - - - - - + window.pjax ? abcjsInit() : window.addEventListener('load', abcjsInit) +})()
\ No newline at end of file diff --git "a/2023/10/31/ React-\346\217\217\350\277\260UI/index.html" "b/2023/10/31/ React-\346\217\217\350\277\260UI/index.html" index 84041c0..dced934 100644 --- "a/2023/10/31/ React-\346\217\217\350\277\260UI/index.html" +++ "b/2023/10/31/ React-\346\217\217\350\277\260UI/index.html" @@ -1,257 +1,165 @@ - - - - - - - - - - - - - - - - - - - - - - +React入门之描述UI | ayozooZ + - - - - - - - - - -React入门之描述UI | ayozooZ - - - - - - - - - - - - - - - - -
- -
-
-
- - -
- - - -

ayozooZ

- -
-

ayozooZ的blog

-
- - -
- - - - - - - - -
- - -
- - +
Author: ayozooZ
Link: https://ayozoo.github.io/2023/10/31/ React-描述UI/
Copyright Notice: All articles in this blog are licensed under CC BY-NC-SA 4.0 unless stating additionally.
Announcement
懒惰不是不努力的借口
Contents
  1. 1. React
    1. 1.1. React组件的基本要素
    2. 1.2. React 组件导入导出
    3. 1.3. JSX书写标签语言
      1. 1.3.1. 什么是JSX
      2. 1.3.2. 为什么出现JSX
      3. 1.3.3. 使用JSX的优势
      4. 1.3.4. JSX与HTML的区别
      5. 1.3.5. HTML转换为JSX
      6. 1.3.6. JSX语法规则
        1. 1.3.6.1. JSX中通过大括号使用javaScript
        2. 1.3.6.2. JSX使用引号字符串
        3. 1.3.6.3. 标签插值
        4. 1.3.6.4. 大括号的使用场景
        5. 1.3.6.5. 双大括号的使用场景
        6. 1.3.6.6. Props组件传递
          1. 1.3.6.6.1. Props使用妙计
          2. 1.3.6.6.2. 注意
        7. 1.3.6.7. 条件渲染
          1. 1.3.6.7.1. 如何根据条件返回JSX
            1. 1.3.6.7.1.1. 1、三目运算符(? :)
            2. 1.3.6.7.1.2. 2、与运算符(&&)
            3. 1.3.6.7.1.3. 1、可以选择将需要渲染的JSX表达式文本赋值给变量
            4. 1.3.6.7.1.4. 2、对于任意的JSX也是适用的
        8. 1.3.6.8. 渲染列表
          1. 1.3.6.8.1. 遍历渲染
          2. 1.3.6.8.2. 过滤渲染
          3. 1.3.6.8.3. 用key能保证渲染顺序
          4. 1.3.6.8.4. 如何设置key
            1. 1.3.6.8.4.1. 不同来源数据对应不同的key值获取方式:
            2. 1.3.6.8.4.2. key值需要满足的条件
            3. 1.3.6.8.4.3. 为什么需要key??
        9. 1.3.6.9. 保持组件纯粹
          1. 1.3.6.9.1. 纯函数
          2. 1.3.6.9.2. 副作用:(不符合)预期的结果
          3. 1.3.6.9.3. 使用严格模式检测不纯的计算
          4. 1.3.6.9.4. 局部mutation:组件的小秘密
          5. 1.3.6.9.5. 哪些地方可能引发副作用
            1. 1.3.6.9.5.1. 为什么React如此侧重于纯函数??
        10. 1.3.6.10. 将UI视为树
          1. 1.3.6.10.1. 渲染树
          2. 1.3.6.10.2. 模块依赖树
      7. 1.3.7. JSX语法规则
        1. 1.3.7.1. JSX中通过大括号使用javaScript
        2. 1.3.7.2. JSX使用引号字符串
        3. 1.3.7.3. 标签插值
        4. 1.3.7.4. 大括号的使用场景
        5. 1.3.7.5. 双大括号的使用场景
        6. 1.3.7.6. Props组件传递
          1. 1.3.7.6.1. Props使用妙计
          2. 1.3.7.6.2. 注意
        7. 1.3.7.7. 条件渲染
          1. 1.3.7.7.1. 如何根据条件返回JSX
            1. 1.3.7.7.1.1. 1、三目运算符(? :)
            2. 1.3.7.7.1.2. 2、与运算符(&&)
            3. 1.3.7.7.1.3. 1、可以选择将需要渲染的JSX表达式文本赋值给变量
            4. 1.3.7.7.1.4. 2、对于任意的JSX也是适用的
        8. 1.3.7.8. 渲染列表
          1. 1.3.7.8.1. 遍历渲染
          2. 1.3.7.8.2. 过滤渲染
          3. 1.3.7.8.3. 用key能保证渲染顺序
          4. 1.3.7.8.4. 如何设置key
            1. 1.3.7.8.4.1. 不同来源数据对应不同的key值获取方式:
            2. 1.3.7.8.4.2. key值需要满足的条件
            3. 1.3.7.8.4.3. 为什么需要key??
        9. 1.3.7.9. 保持组件纯粹
          1. 1.3.7.9.1. 纯函数
          2. 1.3.7.9.2. 副作用:(不符合)预期的结果
          3. 1.3.7.9.3. 使用严格模式检测不纯的计算
          4. 1.3.7.9.4. 局部mutation:组件的小秘密
          5. 1.3.7.9.5. 哪些地方可能引发副作用
            1. 1.3.7.9.5.1. 为什么React如此侧重于纯函数??
        10. 1.3.7.10. 将UI视为树
          1. 1.3.7.10.1. 渲染树
          2. 1.3.7.10.2. 模块依赖树
      8. 1.3.8. 什么是JSX
      9. 1.3.9. 为什么出现JSX
      10. 1.3.10. 使用JSX的优势
      11. 1.3.11. JSX与HTML的区别
      12. 1.3.12. HTML转换为JSX
      13. 1.3.13. JSX语法规则
        1. 1.3.13.1. JSX中通过大括号使用javaScript
        2. 1.3.13.2. JSX使用引号字符串
        3. 1.3.13.3. 标签插值
        4. 1.3.13.4. 大括号的使用场景
        5. 1.3.13.5. 双大括号的使用场景
        6. 1.3.13.6. Props组件传递
          1. 1.3.13.6.1. Props使用妙计
          2. 1.3.13.6.2. 注意
        7. 1.3.13.7. 条件渲染
          1. 1.3.13.7.1. 如何根据条件返回JSX
            1. 1.3.13.7.1.1. 1、三目运算符(? :)
            2. 1.3.13.7.1.2. 2、与运算符(&&)
            3. 1.3.13.7.1.3. 1、可以选择将需要渲染的JSX表达式文本赋值给变量
            4. 1.3.13.7.1.4. 2、对于任意的JSX也是适用的
        8. 1.3.13.8. 渲染列表
          1. 1.3.13.8.1. 遍历渲染
          2. 1.3.13.8.2. 过滤渲染
          3. 1.3.13.8.3. 用key能保证渲染顺序
          4. 1.3.13.8.4. 如何设置key
            1. 1.3.13.8.4.1. 不同来源数据对应不同的key值获取方式:
            2. 1.3.13.8.4.2. key值需要满足的条件
            3. 1.3.13.8.4.3. 为什么需要key??
      14. 1.3.14. 什么是JSX
      15. 1.3.15. 为什么出现JSX
      16. 1.3.16. 使用JSX的优势
      17. 1.3.17. JSX与HTML的区别
      18. 1.3.18. HTML转换为JSX
      19. 1.3.19. JSX语法规则
        1. 1.3.19.1. JSX中通过大括号使用javaScript
        2. 1.3.19.2. JSX使用引号字符串
        3. 1.3.19.3. 标签插值
        4. 1.3.19.4. 大括号的使用场景
        5. 1.3.19.5. 双大括号的使用场景
        6. 1.3.19.6. Props组件传递
          1. 1.3.19.6.1. Props使用妙计
          2. 1.3.19.6.2. 注意
Recent Post
- - - - - - - - - - - - - - + typeof ABCJS === 'object' ? abcjsFn() + : getScript('https://cdn.jsdelivr.net/npm/abcjs/dist/abcjs-basic-min.min.js').then(abcjsFn) + } - - + window.pjax ? abcjsInit() : window.addEventListener('load', abcjsInit) +})()
\ No newline at end of file diff --git "a/2023/11/02/ React-\346\267\273\345\212\240\344\272\244\344\272\222/index.html" "b/2023/11/02/ React-\346\267\273\345\212\240\344\272\244\344\272\222/index.html" index 0d8b712..d023fe1 100644 --- "a/2023/11/02/ React-\346\267\273\345\212\240\344\272\244\344\272\222/index.html" +++ "b/2023/11/02/ React-\346\267\273\345\212\240\344\272\244\344\272\222/index.html" @@ -1,257 +1,165 @@ - - - - - - - - - - - - - - - - - - - - - - +React入门之添加交互 | ayozooZ + - - - - - - - - - -React入门之添加交互 | ayozooZ - - - - - - - - - - - - - - - - -
- -
-
-
- - -
- - - -

ayozooZ

- -
-

ayozooZ的blog

-
- - -
- - - - - - - - -
- - -
- - - - - - - - - - - - - - + typeof ABCJS === 'object' ? abcjsFn() + : getScript('https://cdn.jsdelivr.net/npm/abcjs/dist/abcjs-basic-min.min.js').then(abcjsFn) + } - - + window.pjax ? abcjsInit() : window.addEventListener('load', abcjsInit) +})()
\ No newline at end of file diff --git "a/2023/11/08/ React\344\271\213hook\347\257\207/index.html" "b/2023/11/08/ React\344\271\213hook\347\257\207/index.html" index a1f767f..60ca3be 100644 --- "a/2023/11/08/ React\344\271\213hook\347\257\207/index.html" +++ "b/2023/11/08/ React\344\271\213hook\347\257\207/index.html" @@ -1,251 +1,165 @@ - - - - - - - - - - - - - - - - - - - - - - +React之Hook篇 | ayozooZ + - - - - - - - - - -React之Hook篇 | ayozooZ - - - - - - - - - - - - - - - - -
- -
-
-
- - -
- - - -

ayozooZ

- -
-

ayozooZ的blog

-
- - -
- - - - - - - - -
- - - - - -
- -
- - - - - -
- - - -
- - - - - - - -
-

- React之Hook篇 -

- -
-
- - - - - - -
-
+

useEffect

Author: ayozooZ
Link: https://ayozoo.github.io/2023/11/08/ React之hook篇/
Copyright Notice: All articles in this blog are licensed under CC BY-NC-SA 4.0 unless stating additionally.
avatar
ayozooZ
Follow Me
Announcement
懒惰不是不努力的借口
Recent Post
- - - - - - - - - - + const renderV9 = svg => { + mermaidSrc.insertAdjacentHTML('afterend', svg) + } + typeof renderFn === 'string' ? renderV9(renderFn) : renderV10() + }) + } + const loadMermaid = () => { + window.loadMermaid ? runMermaid() : getScript('https://cdn.jsdelivr.net/npm/mermaid/dist/mermaid.min.js').then(runMermaid) + } + btf.addGlobalFn('themeChange', runMermaid, 'mermaid') + window.pjax ? loadMermaid() : document.addEventListener('DOMContentLoaded', loadMermaid) +})()
\ No newline at end of file diff --git a/archives/2022/07/index.html b/archives/2022/07/index.html index c5f5965..d93950e 100644 --- a/archives/2022/07/index.html +++ b/archives/2022/07/index.html @@ -1,278 +1,200 @@ - - - - - - - - - - - - - - - - - - - - - - - +July 2022 | ayozooZ + + - - - - - - - - - -Archive | ayozooZ - - - - - - - - - - - - - - - - -
- -
-
-
- - -
- - - -

ayozooZ

- -
-

ayozooZ的blog

-
- - -
- - - - - - - - -
- - - - - -
- -
- - - - - -
-
-
- Um..! 6 posts in total. Keep on posting. -
- + - - - - - - - - - - - - - - - - - + if (!itemStr) { + return undefined + } + const item = JSON.parse(itemStr) + const now = Date.now() + + if (now > item.expiry) { + localStorage.removeItem(key) + return undefined + } + return item.value + } + } + + win.getScript = (url, attr = {}) => new Promise((resolve, reject) => { + const script = document.createElement('script') + script.src = url + script.async = true + script.onerror = reject + script.onload = script.onreadystatechange = function() { + const loadState = this.readyState + if (loadState && loadState !== 'loaded' && loadState !== 'complete') return + script.onload = script.onreadystatechange = null + resolve() + } + + Object.keys(attr).forEach(key => { + script.setAttribute(key, attr[key]) + }) + + document.head.appendChild(script) + }) + + win.getCSS = (url, id = false) => new Promise((resolve, reject) => { + const link = document.createElement('link') + link.rel = 'stylesheet' + link.href = url + if (id) link.id = id + link.onerror = reject + link.onload = link.onreadystatechange = function() { + const loadState = this.readyState + if (loadState && loadState !== 'loaded' && loadState !== 'complete') return + link.onload = link.onreadystatechange = null + resolve() + } + document.head.appendChild(link) + }) + + win.activateDarkMode = () => { + document.documentElement.setAttribute('data-theme', 'dark') + if (document.querySelector('meta[name="theme-color"]') !== null) { + document.querySelector('meta[name="theme-color"]').setAttribute('content', '#0d0d0d') + } + } + win.activateLightMode = () => { + document.documentElement.setAttribute('data-theme', 'light') + if (document.querySelector('meta[name="theme-color"]') !== null) { + document.querySelector('meta[name="theme-color"]').setAttribute('content', '#ffffff') + } + } + const t = saveToLocal.get('theme') + + if (t === 'dark') activateDarkMode() + else if (t === 'light') activateLightMode() + + const asideStatus = saveToLocal.get('aside-status') + if (asideStatus !== undefined) { + if (asideStatus === 'hide') { + document.documentElement.classList.add('hide-aside') + } else { + document.documentElement.classList.remove('hide-aside') + } + } + + const detectApple = () => { + if(/iPad|iPhone|iPod|Macintosh/.test(navigator.userAgent)){ + document.documentElement.classList.add('apple') + } + } + detectApple() + })(window) + + +
Articles - 1
2022
vue3+vite环境变量踩雷
Announcement
懒惰不是不努力的借口
Recent Post
+ + Categories + +
+
Info
Article :
6
Runtime :
Total Count :
39.5k
UV :
PV :
Last Update :
\ No newline at end of file diff --git a/archives/2022/10/index.html b/archives/2022/10/index.html index cf4856d..a1830eb 100644 --- a/archives/2022/10/index.html +++ b/archives/2022/10/index.html @@ -1,278 +1,200 @@ - - - - - - - - - - - - - - - - - - - - - - - +October 2022 | ayozooZ + + - - - - - - - - - -Archive | ayozooZ - - - - - - - - - - - - - - - - -
- -
-
-
- - -
- - - -

ayozooZ

- -
-

ayozooZ的blog

-
- - -
- - - - - - - - -
- - - - - -
- -
- - - - - -
-
-
- Um..! 6 posts in total. Keep on posting. -
- + - - - - - - - - - - - - - - - - - + if (!itemStr) { + return undefined + } + const item = JSON.parse(itemStr) + const now = Date.now() + + if (now > item.expiry) { + localStorage.removeItem(key) + return undefined + } + return item.value + } + } + + win.getScript = (url, attr = {}) => new Promise((resolve, reject) => { + const script = document.createElement('script') + script.src = url + script.async = true + script.onerror = reject + script.onload = script.onreadystatechange = function() { + const loadState = this.readyState + if (loadState && loadState !== 'loaded' && loadState !== 'complete') return + script.onload = script.onreadystatechange = null + resolve() + } + + Object.keys(attr).forEach(key => { + script.setAttribute(key, attr[key]) + }) + + document.head.appendChild(script) + }) + + win.getCSS = (url, id = false) => new Promise((resolve, reject) => { + const link = document.createElement('link') + link.rel = 'stylesheet' + link.href = url + if (id) link.id = id + link.onerror = reject + link.onload = link.onreadystatechange = function() { + const loadState = this.readyState + if (loadState && loadState !== 'loaded' && loadState !== 'complete') return + link.onload = link.onreadystatechange = null + resolve() + } + document.head.appendChild(link) + }) + + win.activateDarkMode = () => { + document.documentElement.setAttribute('data-theme', 'dark') + if (document.querySelector('meta[name="theme-color"]') !== null) { + document.querySelector('meta[name="theme-color"]').setAttribute('content', '#0d0d0d') + } + } + win.activateLightMode = () => { + document.documentElement.setAttribute('data-theme', 'light') + if (document.querySelector('meta[name="theme-color"]') !== null) { + document.querySelector('meta[name="theme-color"]').setAttribute('content', '#ffffff') + } + } + const t = saveToLocal.get('theme') + + if (t === 'dark') activateDarkMode() + else if (t === 'light') activateLightMode() + + const asideStatus = saveToLocal.get('aside-status') + if (asideStatus !== undefined) { + if (asideStatus === 'hide') { + document.documentElement.classList.add('hide-aside') + } else { + document.documentElement.classList.remove('hide-aside') + } + } + + const detectApple = () => { + if(/iPad|iPhone|iPod|Macintosh/.test(navigator.userAgent)){ + document.documentElement.classList.add('apple') + } + } + detectApple() + })(window) + + +
Announcement
懒惰不是不努力的借口
Recent Post
+ + Categories + +
+
Info
Article :
6
Runtime :
Total Count :
39.5k
UV :
PV :
Last Update :
\ No newline at end of file diff --git a/archives/2022/index.html b/archives/2022/index.html index d2a8384..70b15d5 100644 --- a/archives/2022/index.html +++ b/archives/2022/index.html @@ -1,298 +1,200 @@ - - - - - - - - - - - - - - - - - - - - - - - +2022 | ayozooZ + + - - - - - - - - - -Archive | ayozooZ - - - - - - - - - - - - - - - - -
- -
-
-
- - -
- - - -

ayozooZ

- -
-

ayozooZ的blog

-
- - -
- - - - - - - - -
- - - - - -
- -
- - - - - -
-
-
- Um..! 6 posts in total. Keep on posting. -
- + - - - - - - - - - - - - - - - - - + if (now > item.expiry) { + localStorage.removeItem(key) + return undefined + } + return item.value + } + } + + win.getScript = (url, attr = {}) => new Promise((resolve, reject) => { + const script = document.createElement('script') + script.src = url + script.async = true + script.onerror = reject + script.onload = script.onreadystatechange = function() { + const loadState = this.readyState + if (loadState && loadState !== 'loaded' && loadState !== 'complete') return + script.onload = script.onreadystatechange = null + resolve() + } + + Object.keys(attr).forEach(key => { + script.setAttribute(key, attr[key]) + }) + + document.head.appendChild(script) + }) + + win.getCSS = (url, id = false) => new Promise((resolve, reject) => { + const link = document.createElement('link') + link.rel = 'stylesheet' + link.href = url + if (id) link.id = id + link.onerror = reject + link.onload = link.onreadystatechange = function() { + const loadState = this.readyState + if (loadState && loadState !== 'loaded' && loadState !== 'complete') return + link.onload = link.onreadystatechange = null + resolve() + } + document.head.appendChild(link) + }) + + win.activateDarkMode = () => { + document.documentElement.setAttribute('data-theme', 'dark') + if (document.querySelector('meta[name="theme-color"]') !== null) { + document.querySelector('meta[name="theme-color"]').setAttribute('content', '#0d0d0d') + } + } + win.activateLightMode = () => { + document.documentElement.setAttribute('data-theme', 'light') + if (document.querySelector('meta[name="theme-color"]') !== null) { + document.querySelector('meta[name="theme-color"]').setAttribute('content', '#ffffff') + } + } + const t = saveToLocal.get('theme') + + if (t === 'dark') activateDarkMode() + else if (t === 'light') activateLightMode() + + const asideStatus = saveToLocal.get('aside-status') + if (asideStatus !== undefined) { + if (asideStatus === 'hide') { + document.documentElement.classList.add('hide-aside') + } else { + document.documentElement.classList.remove('hide-aside') + } + } + + const detectApple = () => { + if(/iPad|iPhone|iPod|Macintosh/.test(navigator.userAgent)){ + document.documentElement.classList.add('apple') + } + } + detectApple() + })(window) + + +
Announcement
懒惰不是不努力的借口
Recent Post
+ + Categories + +
+
Info
Article :
6
Runtime :
Total Count :
39.5k
UV :
PV :
Last Update :
\ No newline at end of file diff --git a/archives/2023/08/index.html b/archives/2023/08/index.html index 65d6986..a851670 100644 --- a/archives/2023/08/index.html +++ b/archives/2023/08/index.html @@ -1,278 +1,200 @@ - - - - - - - - - - - - - - - - - - - - - - - +August 2023 | ayozooZ + + - - - - - - - - - -Archive | ayozooZ - - - - - - - - - - - - - - - - -
- -
-
-
- - -
- - - -

ayozooZ

- -
-

ayozooZ的blog

-
- - -
- - - - - - - - -
- - - - - -
- -
- - - - - -
-
-
- Um..! 6 posts in total. Keep on posting. -
- + - - - - - - - - - - - - - - - - - + if (!itemStr) { + return undefined + } + const item = JSON.parse(itemStr) + const now = Date.now() + + if (now > item.expiry) { + localStorage.removeItem(key) + return undefined + } + return item.value + } + } + + win.getScript = (url, attr = {}) => new Promise((resolve, reject) => { + const script = document.createElement('script') + script.src = url + script.async = true + script.onerror = reject + script.onload = script.onreadystatechange = function() { + const loadState = this.readyState + if (loadState && loadState !== 'loaded' && loadState !== 'complete') return + script.onload = script.onreadystatechange = null + resolve() + } + + Object.keys(attr).forEach(key => { + script.setAttribute(key, attr[key]) + }) + + document.head.appendChild(script) + }) + + win.getCSS = (url, id = false) => new Promise((resolve, reject) => { + const link = document.createElement('link') + link.rel = 'stylesheet' + link.href = url + if (id) link.id = id + link.onerror = reject + link.onload = link.onreadystatechange = function() { + const loadState = this.readyState + if (loadState && loadState !== 'loaded' && loadState !== 'complete') return + link.onload = link.onreadystatechange = null + resolve() + } + document.head.appendChild(link) + }) + + win.activateDarkMode = () => { + document.documentElement.setAttribute('data-theme', 'dark') + if (document.querySelector('meta[name="theme-color"]') !== null) { + document.querySelector('meta[name="theme-color"]').setAttribute('content', '#0d0d0d') + } + } + win.activateLightMode = () => { + document.documentElement.setAttribute('data-theme', 'light') + if (document.querySelector('meta[name="theme-color"]') !== null) { + document.querySelector('meta[name="theme-color"]').setAttribute('content', '#ffffff') + } + } + const t = saveToLocal.get('theme') + + if (t === 'dark') activateDarkMode() + else if (t === 'light') activateLightMode() + + const asideStatus = saveToLocal.get('aside-status') + if (asideStatus !== undefined) { + if (asideStatus === 'hide') { + document.documentElement.classList.add('hide-aside') + } else { + document.documentElement.classList.remove('hide-aside') + } + } + + const detectApple = () => { + if(/iPad|iPhone|iPod|Macintosh/.test(navigator.userAgent)){ + document.documentElement.classList.add('apple') + } + } + detectApple() + })(window) + + +
Articles - 1
2023
http vs websocket
Announcement
懒惰不是不努力的借口
Recent Post
+ + Categories + +
+
Info
Article :
6
Runtime :
Total Count :
39.5k
UV :
PV :
Last Update :
\ No newline at end of file diff --git a/archives/2023/10/index.html b/archives/2023/10/index.html index 245f551..ae994e5 100644 --- a/archives/2023/10/index.html +++ b/archives/2023/10/index.html @@ -1,278 +1,200 @@ - - - - - - - - - - - - - - - - - - - - - - - +October 2023 | ayozooZ + + - - - - - - - - - -Archive | ayozooZ - - - - - - - - - - - - - - - - -
- -
-
-
- - -
- - - -

ayozooZ

- -
-

ayozooZ的blog

-
- - -
- - - - - - - - -
- - - - - -
- -
- - - - - -
-
-
- Um..! 6 posts in total. Keep on posting. -
- + - - - - - - - - - - - - - - - - - + if (!itemStr) { + return undefined + } + const item = JSON.parse(itemStr) + const now = Date.now() + + if (now > item.expiry) { + localStorage.removeItem(key) + return undefined + } + return item.value + } + } + + win.getScript = (url, attr = {}) => new Promise((resolve, reject) => { + const script = document.createElement('script') + script.src = url + script.async = true + script.onerror = reject + script.onload = script.onreadystatechange = function() { + const loadState = this.readyState + if (loadState && loadState !== 'loaded' && loadState !== 'complete') return + script.onload = script.onreadystatechange = null + resolve() + } + + Object.keys(attr).forEach(key => { + script.setAttribute(key, attr[key]) + }) + + document.head.appendChild(script) + }) + + win.getCSS = (url, id = false) => new Promise((resolve, reject) => { + const link = document.createElement('link') + link.rel = 'stylesheet' + link.href = url + if (id) link.id = id + link.onerror = reject + link.onload = link.onreadystatechange = function() { + const loadState = this.readyState + if (loadState && loadState !== 'loaded' && loadState !== 'complete') return + link.onload = link.onreadystatechange = null + resolve() + } + document.head.appendChild(link) + }) + + win.activateDarkMode = () => { + document.documentElement.setAttribute('data-theme', 'dark') + if (document.querySelector('meta[name="theme-color"]') !== null) { + document.querySelector('meta[name="theme-color"]').setAttribute('content', '#0d0d0d') + } + } + win.activateLightMode = () => { + document.documentElement.setAttribute('data-theme', 'light') + if (document.querySelector('meta[name="theme-color"]') !== null) { + document.querySelector('meta[name="theme-color"]').setAttribute('content', '#ffffff') + } + } + const t = saveToLocal.get('theme') + + if (t === 'dark') activateDarkMode() + else if (t === 'light') activateLightMode() + + const asideStatus = saveToLocal.get('aside-status') + if (asideStatus !== undefined) { + if (asideStatus === 'hide') { + document.documentElement.classList.add('hide-aside') + } else { + document.documentElement.classList.remove('hide-aside') + } + } + + const detectApple = () => { + if(/iPad|iPhone|iPod|Macintosh/.test(navigator.userAgent)){ + document.documentElement.classList.add('apple') + } + } + detectApple() + })(window) + + +
Articles - 1
2023
React入门之描述UI
Announcement
懒惰不是不努力的借口
Recent Post
+ + Categories + +
+
Info
Article :
6
Runtime :
Total Count :
39.5k
UV :
PV :
Last Update :
\ No newline at end of file diff --git a/archives/2023/11/index.html b/archives/2023/11/index.html index 7fffc58..0ddabd5 100644 --- a/archives/2023/11/index.html +++ b/archives/2023/11/index.html @@ -1,298 +1,200 @@ - - - - - - - - - - - - - - - - - - - - - - - +November 2023 | ayozooZ + + - - - - - - - - - -Archive | ayozooZ - - - - - - - - - - - - - - - - -
- -
-
-
- - -
- - - -

ayozooZ

- -
-

ayozooZ的blog

-
- - -
- - - - - - - - -
- - - - - -
- -
- - - - - -
-
-
- Um..! 6 posts in total. Keep on posting. -
- + - - - - - - - - - - - - - - - - - + if (now > item.expiry) { + localStorage.removeItem(key) + return undefined + } + return item.value + } + } + + win.getScript = (url, attr = {}) => new Promise((resolve, reject) => { + const script = document.createElement('script') + script.src = url + script.async = true + script.onerror = reject + script.onload = script.onreadystatechange = function() { + const loadState = this.readyState + if (loadState && loadState !== 'loaded' && loadState !== 'complete') return + script.onload = script.onreadystatechange = null + resolve() + } + + Object.keys(attr).forEach(key => { + script.setAttribute(key, attr[key]) + }) + + document.head.appendChild(script) + }) + + win.getCSS = (url, id = false) => new Promise((resolve, reject) => { + const link = document.createElement('link') + link.rel = 'stylesheet' + link.href = url + if (id) link.id = id + link.onerror = reject + link.onload = link.onreadystatechange = function() { + const loadState = this.readyState + if (loadState && loadState !== 'loaded' && loadState !== 'complete') return + link.onload = link.onreadystatechange = null + resolve() + } + document.head.appendChild(link) + }) + + win.activateDarkMode = () => { + document.documentElement.setAttribute('data-theme', 'dark') + if (document.querySelector('meta[name="theme-color"]') !== null) { + document.querySelector('meta[name="theme-color"]').setAttribute('content', '#0d0d0d') + } + } + win.activateLightMode = () => { + document.documentElement.setAttribute('data-theme', 'light') + if (document.querySelector('meta[name="theme-color"]') !== null) { + document.querySelector('meta[name="theme-color"]').setAttribute('content', '#ffffff') + } + } + const t = saveToLocal.get('theme') + + if (t === 'dark') activateDarkMode() + else if (t === 'light') activateLightMode() + + const asideStatus = saveToLocal.get('aside-status') + if (asideStatus !== undefined) { + if (asideStatus === 'hide') { + document.documentElement.classList.add('hide-aside') + } else { + document.documentElement.classList.remove('hide-aside') + } + } + + const detectApple = () => { + if(/iPad|iPhone|iPod|Macintosh/.test(navigator.userAgent)){ + document.documentElement.classList.add('apple') + } + } + detectApple() + })(window) + + +
Articles - 2
2023
React之Hook篇
React入门之添加交互
Announcement
懒惰不是不努力的借口
Recent Post
+ + Categories + +
+
Info
Article :
6
Runtime :
Total Count :
39.5k
UV :
PV :
Last Update :
\ No newline at end of file diff --git a/archives/2023/index.html b/archives/2023/index.html index 01a26e5..eb9522b 100644 --- a/archives/2023/index.html +++ b/archives/2023/index.html @@ -1,338 +1,200 @@ - - - - - - - - - - - - - - - - - - - - - - - +2023 | ayozooZ + + - - - - - - - - - -Archive | ayozooZ - - - - - - - - - - - - - - - - -
- -
-
-
- - -
- - - -

ayozooZ

- -
-

ayozooZ的blog

-
- - -
- - - - - - - - -
- - - - - -
- -
- - - - - -
-
-
- Um..! 6 posts in total. Keep on posting. -
- + - - - - - - - - - - - - - - - - - + const asideStatus = saveToLocal.get('aside-status') + if (asideStatus !== undefined) { + if (asideStatus === 'hide') { + document.documentElement.classList.add('hide-aside') + } else { + document.documentElement.classList.remove('hide-aside') + } + } + + const detectApple = () => { + if(/iPad|iPhone|iPod|Macintosh/.test(navigator.userAgent)){ + document.documentElement.classList.add('apple') + } + } + detectApple() + })(window) + + +
Articles - 4
2023
React之Hook篇
React入门之添加交互
React入门之描述UI
http vs websocket
Announcement
懒惰不是不努力的借口
Recent Post
+ + Categories + +
+
Info
Article :
6
Runtime :
Total Count :
39.5k
UV :
PV :
Last Update :
\ No newline at end of file diff --git a/archives/index.html b/archives/index.html index 5b44530..1a9a480 100644 --- a/archives/index.html +++ b/archives/index.html @@ -1,381 +1,200 @@ - - - - - - - - - - - - - - - - - - - - - - - +归档 | ayozooZ + + - - - - - - - - - -Archive | ayozooZ - - - - - - - - - - - - - - - - -
- -
-
-
- - -
- - - -

ayozooZ

- -
-

ayozooZ的blog

-
- - -
- - - - - - - - -
- - - - - -
- -
- - - - - -
-
-
- Um..! 6 posts in total. Keep on posting. -
- - -
- 2023 -
- -
-
- - -
- -
- + - - - - - - - - - - - - - - - - - + const asideStatus = saveToLocal.get('aside-status') + if (asideStatus !== undefined) { + if (asideStatus === 'hide') { + document.documentElement.classList.add('hide-aside') + } else { + document.documentElement.classList.remove('hide-aside') + } + } + + const detectApple = () => { + if(/iPad|iPhone|iPod|Macintosh/.test(navigator.userAgent)){ + document.documentElement.classList.add('apple') + } + } + detectApple() + })(window) + + +
Announcement
懒惰不是不努力的借口
Recent Post
+ + Categories + +
+
Info
Article :
6
Runtime :
Total Count :
39.5k
UV :
PV :
Last Update :
\ No newline at end of file diff --git a/categories/index.html b/categories/index.html index 59a4774..366565f 100644 --- a/categories/index.html +++ b/categories/index.html @@ -1,274 +1,215 @@ - - - - - - - - - - - - - - - - - - - - - - +categories | ayozooZ + - - - - - - - - - -categories | ayozooZ - - - - - - - - - - - - - - - - - -
- -
-
-
- - -
- - - -

ayozooZ

- -
-

ayozooZ的blog

-
- - -
- - - - - - - - -
- - -
-
+在项目中,为了避免频繁导入。大家都会在对应,例如项目使用Webpack 打包工具的。在其项目store文件下其中的index.js 文件中使用webpack提供的apirequire.context的方法实现自动导入。 +ES 模块规范在实现自动导入模块前,我们先了解下ES 模块规范。 +所谓的ES模块规范,即 JavaScript 的标准模块系统,它允许您使用 import 和 export 关键字来导入和导出模块。这是现代 JavaScript 中推荐使用的模块化方式。 +在ES模块规范中,提供了import.meta.glob功能。它允许在项目运行中动态匹配特定模式的模块。例如以下代码就可以动态匹配到module模块下的所有js文件。 +1import.meta.glob('./module/*.js') +话不多说,上干货例如,你的项目sotre文件目录为 +12345678- store - module - module ...
- -
- -
- - - - -
- - 0% -
- - - - - - - - - - - - - - - - - - - - - - - +
Info
Article :
6
Runtime :
Total Count :
39.5k
UV :
PV :
Last Update :
\ No newline at end of file diff --git a/js/bookmark.js b/js/bookmark.js deleted file mode 100644 index 8e3ae6a..0000000 --- a/js/bookmark.js +++ /dev/null @@ -1,56 +0,0 @@ -/* global CONFIG */ - -document.addEventListener('DOMContentLoaded', () => { - 'use strict'; - - const doSaveScroll = () => { - localStorage.setItem('bookmark' + location.pathname, window.scrollY); - }; - - const scrollToMark = () => { - let top = localStorage.getItem('bookmark' + location.pathname); - top = parseInt(top, 10); - // If the page opens with a specific hash, just jump out - if (!isNaN(top) && location.hash === '') { - // Auto scroll to the position - window.anime({ - targets : document.scrollingElement, - duration : 200, - easing : 'linear', - scrollTop: top - }); - } - }; - // Register everything - const init = function(trigger) { - // Create a link element - const link = document.querySelector('.book-mark-link'); - // Scroll event - window.addEventListener('scroll', () => link.classList.toggle('book-mark-link-fixed', window.scrollY === 0), { passive: true }); - // Register beforeunload event when the trigger is auto - if (trigger === 'auto') { - // Register beforeunload event - window.addEventListener('beforeunload', doSaveScroll); - document.addEventListener('pjax:send', doSaveScroll); - } - // Save the position by clicking the icon - link.addEventListener('click', () => { - doSaveScroll(); - window.anime({ - targets : link, - duration: 200, - easing : 'linear', - top : -30, - complete: () => { - setTimeout(() => { - link.style.top = ''; - }, 400); - } - }); - }); - scrollToMark(); - document.addEventListener('pjax:success', scrollToMark); - }; - - init(CONFIG.bookmark.save); -}); diff --git a/js/comments-buttons.js b/js/comments-buttons.js deleted file mode 100644 index 505c21b..0000000 --- a/js/comments-buttons.js +++ /dev/null @@ -1,25 +0,0 @@ -/* global CONFIG */ - -(function() { - const commentButton = document.querySelectorAll('.comment-button'); - commentButton.forEach(element => { - const commentClass = element.classList[2]; - element.addEventListener('click', () => { - commentButton.forEach(active => active.classList.toggle('active', active === element)); - document.querySelectorAll('.comment-position').forEach(active => active.classList.toggle('active', active.classList.contains(commentClass))); - if (CONFIG.comments.storage) { - localStorage.setItem('comments_active', commentClass); - } - }); - }); - let { activeClass } = CONFIG.comments; - if (CONFIG.comments.storage) { - activeClass = localStorage.getItem('comments_active') || activeClass; - } - if (activeClass) { - const activeButton = document.querySelector(`.comment-button.${activeClass}`); - if (activeButton) { - activeButton.click(); - } - } -})(); diff --git a/js/comments.js b/js/comments.js deleted file mode 100644 index 4045e8c..0000000 --- a/js/comments.js +++ /dev/null @@ -1,21 +0,0 @@ -/* global CONFIG */ - -window.addEventListener('tabs:register', () => { - let { activeClass } = CONFIG.comments; - if (CONFIG.comments.storage) { - activeClass = localStorage.getItem('comments_active') || activeClass; - } - if (activeClass) { - const activeTab = document.querySelector(`a[href="#comment-${activeClass}"]`); - if (activeTab) { - activeTab.click(); - } - } -}); -if (CONFIG.comments.storage) { - window.addEventListener('tabs:click', event => { - if (!event.target.matches('.tabs-comment .tab-content .tab-pane')) return; - const commentClass = event.target.classList[1]; - localStorage.setItem('comments_active', commentClass); - }); -} diff --git a/js/config.js b/js/config.js deleted file mode 100644 index caa0075..0000000 --- a/js/config.js +++ /dev/null @@ -1,66 +0,0 @@ -if (!window.NexT) window.NexT = {}; - -(function() { - const className = 'next-config'; - - const staticConfig = {}; - let variableConfig = {}; - - const parse = text => JSON.parse(text || '{}'); - - const update = name => { - const targetEle = document.querySelector(`.${className}[data-name="${name}"]`); - if (!targetEle) return; - const parsedConfig = parse(targetEle.text); - if (name === 'main') { - Object.assign(staticConfig, parsedConfig); - } else { - variableConfig[name] = parsedConfig; - } - }; - - update('main'); - - window.CONFIG = new Proxy({}, { - get(overrideConfig, name) { - let existing; - if (name in staticConfig) { - existing = staticConfig[name]; - } else { - if (!(name in variableConfig)) update(name); - existing = variableConfig[name]; - } - - // For unset override and mixable existing - if (!(name in overrideConfig) && typeof existing === 'object') { - // Get ready to mix. - overrideConfig[name] = {}; - } - - if (name in overrideConfig) { - const override = overrideConfig[name]; - - // When mixable - if (typeof override === 'object' && typeof existing === 'object') { - // Mix, proxy changes to the override. - return new Proxy({ ...existing, ...override }, { - set(target, prop, value) { - target[prop] = value; - override[prop] = value; - return true; - } - }); - } - - return override; - } - - // Only when not mixable and override hasn't been set. - return existing; - } - }); - - document.addEventListener('pjax:success', () => { - variableConfig = {}; - }); -})(); diff --git a/js/main.js b/js/main.js new file mode 100644 index 0000000..a22b57f --- /dev/null +++ b/js/main.js @@ -0,0 +1,879 @@ +document.addEventListener('DOMContentLoaded', function () { + let headerContentWidth, $nav + let mobileSidebarOpen = false + + const adjustMenu = init => { + const getAllWidth = ele => { + return Array.from(ele).reduce((width, i) => width + i.offsetWidth, 0) + } + + if (init) { + const blogInfoWidth = getAllWidth(document.querySelector('#blog-info > a').children) + const menusWidth = getAllWidth(document.getElementById('menus').children) + headerContentWidth = blogInfoWidth + menusWidth + $nav = document.getElementById('nav') + } + + const hideMenuIndex = window.innerWidth <= 768 || headerContentWidth > $nav.offsetWidth - 120 + $nav.classList.toggle('hide-menu', hideMenuIndex) + } + + // 初始化header + const initAdjust = () => { + adjustMenu(true) + $nav.classList.add('show') + } + + // sidebar menus + const sidebarFn = { + open: () => { + btf.sidebarPaddingR() + document.body.style.overflow = 'hidden' + btf.animateIn(document.getElementById('menu-mask'), 'to_show 0.5s') + document.getElementById('sidebar-menus').classList.add('open') + mobileSidebarOpen = true + }, + close: () => { + const $body = document.body + $body.style.overflow = '' + $body.style.paddingRight = '' + btf.animateOut(document.getElementById('menu-mask'), 'to_hide 0.5s') + document.getElementById('sidebar-menus').classList.remove('open') + mobileSidebarOpen = false + } + } + + /** + * 首頁top_img底下的箭頭 + */ + const scrollDownInIndex = () => { + const handleScrollToDest = () => { + btf.scrollToDest(document.getElementById('content-inner').offsetTop, 300) + } + + const $scrollDownEle = document.getElementById('scroll-down') + $scrollDownEle && btf.addEventListenerPjax($scrollDownEle, 'click', handleScrollToDest) + } + + /** + * 代碼 + * 只適用於Hexo默認的代碼渲染 + */ + const addHighlightTool = () => { + const highLight = GLOBAL_CONFIG.highlight + if (!highLight) return + + const { highlightCopy, highlightLang, highlightHeightLimit, plugin } = highLight + const isHighlightShrink = GLOBAL_CONFIG_SITE.isHighlightShrink + const isShowTool = highlightCopy || highlightLang || isHighlightShrink !== undefined + const $figureHighlight = plugin === 'highlighjs' ? document.querySelectorAll('figure.highlight') : document.querySelectorAll('pre[class*="language-"]') + + if (!((isShowTool || highlightHeightLimit) && $figureHighlight.length)) return + + const isPrismjs = plugin === 'prismjs' + const highlightShrinkClass = isHighlightShrink === true ? 'closed' : '' + const highlightShrinkEle = isHighlightShrink !== undefined ? '' : '' + const highlightCopyEle = highlightCopy ? '
' : '' + + const copy = (text, ctx) => { + if (document.queryCommandSupported && document.queryCommandSupported('copy')) { + document.execCommand('copy') + if (GLOBAL_CONFIG.Snackbar !== undefined) { + btf.snackbarShow(GLOBAL_CONFIG.copy.success) + } else { + const prevEle = ctx.previousElementSibling + prevEle.textContent = GLOBAL_CONFIG.copy.success + prevEle.style.opacity = 1 + setTimeout(() => { prevEle.style.opacity = 0 }, 700) + } + } else { + if (GLOBAL_CONFIG.Snackbar !== undefined) { + btf.snackbarShow(GLOBAL_CONFIG.copy.noSupport) + } else { + ctx.previousElementSibling.textContent = GLOBAL_CONFIG.copy.noSupport + } + } + } + + // click events + const highlightCopyFn = ele => { + const $buttonParent = ele.parentNode + $buttonParent.classList.add('copy-true') + const selection = window.getSelection() + const range = document.createRange() + const preCodeSelector = isPrismjs ? 'pre code' : 'table .code pre' + range.selectNodeContents($buttonParent.querySelectorAll(`${preCodeSelector}`)[0]) + selection.removeAllRanges() + selection.addRange(range) + const text = selection.toString() + copy(text, ele.lastChild) + selection.removeAllRanges() + $buttonParent.classList.remove('copy-true') + } + + const highlightShrinkFn = ele => { + ele.classList.toggle('closed') + } + + const highlightToolsFn = function (e) { + const $target = e.target.classList + if ($target.contains('expand')) highlightShrinkFn(this) + else if ($target.contains('copy-button')) highlightCopyFn(this) + } + + const expandCode = function () { + this.classList.toggle('expand-done') + } + + const createEle = (lang, item, service) => { + const fragment = document.createDocumentFragment() + + if (isShowTool) { + const hlTools = document.createElement('div') + hlTools.className = `highlight-tools ${highlightShrinkClass}` + hlTools.innerHTML = highlightShrinkEle + lang + highlightCopyEle + btf.addEventListenerPjax(hlTools, 'click', highlightToolsFn) + fragment.appendChild(hlTools) + } + + if (highlightHeightLimit && item.offsetHeight > highlightHeightLimit + 30) { + const ele = document.createElement('div') + ele.className = 'code-expand-btn' + ele.innerHTML = '' + btf.addEventListenerPjax(ele, 'click', expandCode) + fragment.appendChild(ele) + } + + if (service === 'hl') { + item.insertBefore(fragment, item.firstChild) + } else { + item.parentNode.insertBefore(fragment, item) + } + } + + if (isPrismjs) { + $figureHighlight.forEach(item => { + if (highlightLang) { + const langName = item.getAttribute('data-language') || 'Code' + const highlightLangEle = `
${langName}
` + btf.wrap(item, 'figure', { class: 'highlight' }) + createEle(highlightLangEle, item) + } else { + btf.wrap(item, 'figure', { class: 'highlight' }) + createEle('', item) + } + }) + } else { + $figureHighlight.forEach(item => { + if (highlightLang) { + let langName = item.getAttribute('class').split(' ')[1] + if (langName === 'plain' || langName === undefined) langName = 'Code' + const highlightLangEle = `
${langName}
` + createEle(highlightLangEle, item, 'hl') + } else { + createEle('', item, 'hl') + } + }) + } + } + + /** + * PhotoFigcaption + */ + const addPhotoFigcaption = () => { + document.querySelectorAll('#article-container img').forEach(item => { + const altValue = item.title || item.alt + if (!altValue) return + const ele = document.createElement('div') + ele.className = 'img-alt is-center' + ele.textContent = altValue + item.insertAdjacentElement('afterend', ele) + }) + } + + /** + * Lightbox + */ + const runLightbox = () => { + btf.loadLightbox(document.querySelectorAll('#article-container img:not(.no-lightbox)')) + } + + /** + * justified-gallery 圖庫排版 + */ + + const fetchUrl = async (url) => { + const response = await fetch(url) + return await response.json() + } + + const runJustifiedGallery = (item, data, isButton = false, tabs) => { + const dataLength = data.length + + const ig = new InfiniteGrid.JustifiedInfiniteGrid(item, { + gap: 5, + isConstantSize: true, + sizeRange: [150, 600], + useResizeObserver: true, + observeChildren: true, + useTransform: true + // useRecycle: false + }) + + if (tabs) { + btf.addGlobalFn('igOfTabs', () => { ig.destroy() }, false, tabs) + } + + const replaceDq = str => str.replace(/"/g, '"') // replace double quotes to " + + const getItems = (nextGroupKey, count) => { + const nextItems = [] + const startCount = (nextGroupKey - 1) * count + + for (let i = 0; i < count; ++i) { + const num = startCount + i + if (num >= dataLength) { + break + } + + const item = data[num] + const alt = item.alt ? `alt="${replaceDq(item.alt)}"` : '' + const title = item.title ? `title="${replaceDq(item.title)}"` : '' + + nextItems.push(`
+ +
`) + } + return nextItems + } + + const buttonText = GLOBAL_CONFIG.infinitegrid.buttonText + const addButton = item => { + const button = document.createElement('button') + button.textContent = buttonText + + const buttonFn = e => { + e.target.removeEventListener('click', buttonFn) + e.target.remove() + btf.setLoading.add(item) + appendItem(ig.getGroups().length + 1, 10) + } + + button.addEventListener('click', buttonFn) + item.insertAdjacentElement('afterend', button) + } + + const appendItem = (nextGroupKey, count) => { + ig.append(getItems(nextGroupKey, count), nextGroupKey) + } + + const maxGroupKey = Math.ceil(dataLength / 10) + + const completeFn = e => { + const { updated, isResize, mounted } = e + if (!updated.length || !mounted.length || isResize) { + return + } + + btf.loadLightbox(item.querySelectorAll('img:not(.medium-zoom-image)')) + + if (ig.getGroups().length === maxGroupKey) { + btf.setLoading.remove(item) + ig.off('renderComplete', completeFn) + return + } + + if (isButton) { + btf.setLoading.remove(item) + addButton(item) + } + } + + const requestAppendFn = btf.debounce(e => { + const nextGroupKey = (+e.groupKey || 0) + 1 + appendItem(nextGroupKey, 10) + + if (nextGroupKey === maxGroupKey) { + ig.off('requestAppend', requestAppendFn) + } + }, 300) + + btf.setLoading.add(item) + ig.on('renderComplete', completeFn) + + if (isButton) { + appendItem(1, 10) + } else { + ig.on('requestAppend', requestAppendFn) + ig.renderItems() + } + + btf.addGlobalFn('justifiedGallery', () => { ig.destroy() }) + } + + const addJustifiedGallery = async (ele, tabs = false) => { + const init = async () => { + for (const item of ele) { + if (btf.isHidden(item)) continue + if (tabs && item.classList.contains('loaded')) { + item.querySelector('.gallery-items').innerHTML = '' + const button = item.querySelector(':scope > button') + const loadingContainer = item.querySelector(':scope > .loading-container') + button && button.remove() + loadingContainer && loadingContainer.remove() + } + + const isButton = item.getAttribute('data-button') === 'true' + const text = item.firstElementChild.textContent + item.classList.add('loaded') + const content = item.getAttribute('data-type') === 'url' ? await fetchUrl(text) : JSON.parse(text) + runJustifiedGallery(item.lastElementChild, content, isButton, tabs) + } + } + + if (typeof InfiniteGrid === 'function') { + init() + } else { + await getScript(`${GLOBAL_CONFIG.infinitegrid.js}`) + init() + } + } + + /** + * rightside scroll percent + */ + const rightsideScrollPercent = currentTop => { + const scrollPercent = btf.getScrollPercent(currentTop, document.body) + const goUpElement = document.getElementById('go-up') + + if (scrollPercent < 95) { + goUpElement.classList.add('show-percent') + goUpElement.querySelector('.scroll-percent').textContent = scrollPercent + } else { + goUpElement.classList.remove('show-percent') + } + } + + /** + * 滾動處理 + */ + const scrollFn = () => { + const $rightside = document.getElementById('rightside') + const innerHeight = window.innerHeight + 56 + let initTop = 0 + const $header = document.getElementById('page-header') + const isChatBtn = typeof chatBtn !== 'undefined' + const isShowPercent = GLOBAL_CONFIG.percent.rightside + + // 當滾動條小于 56 的時候 + if (document.body.scrollHeight <= innerHeight) { + $rightside.classList.add('rightside-show') + return + } + + // find the scroll direction + const scrollDirection = currentTop => { + const result = currentTop > initTop // true is down & false is up + initTop = currentTop + return result + } + + let flag = '' + const scrollTask = btf.throttle(() => { + const currentTop = window.scrollY || document.documentElement.scrollTop + const isDown = scrollDirection(currentTop) + if (currentTop > 56) { + if (flag === '') { + $header.classList.add('nav-fixed') + $rightside.classList.add('rightside-show') + } + + if (isDown) { + if (flag !== 'down') { + $header.classList.remove('nav-visible') + isChatBtn && window.chatBtn.hide() + flag = 'down' + } + } else { + if (flag !== 'up') { + $header.classList.add('nav-visible') + isChatBtn && window.chatBtn.show() + flag = 'up' + } + } + } else { + flag = '' + if (currentTop === 0) { + $header.classList.remove('nav-fixed', 'nav-visible') + } + $rightside.classList.remove('rightside-show') + } + + isShowPercent && rightsideScrollPercent(currentTop) + + if (document.body.scrollHeight <= innerHeight) { + $rightside.classList.add('rightside-show') + } + }, 300) + + btf.addEventListenerPjax(window, 'scroll', scrollTask, { passive: true }) + } + + /** + * toc,anchor + */ + const scrollFnToDo = () => { + const isToc = GLOBAL_CONFIG_SITE.isToc + const isAnchor = GLOBAL_CONFIG.isAnchor + const $article = document.getElementById('article-container') + + if (!($article && (isToc || isAnchor))) return + + let $tocLink, $cardToc, autoScrollToc, $tocPercentage, isExpand + + if (isToc) { + const $cardTocLayout = document.getElementById('card-toc') + $cardToc = $cardTocLayout.querySelector('.toc-content') + $tocLink = $cardToc.querySelectorAll('.toc-link') + $tocPercentage = $cardTocLayout.querySelector('.toc-percentage') + isExpand = $cardToc.classList.contains('is-expand') + + // toc元素點擊 + const tocItemClickFn = e => { + const target = e.target.closest('.toc-link') + if (!target) return + + e.preventDefault() + btf.scrollToDest(btf.getEleTop(document.getElementById(decodeURI(target.getAttribute('href')).replace('#', ''))), 300) + if (window.innerWidth < 900) { + $cardTocLayout.classList.remove('open') + } + } + + btf.addEventListenerPjax($cardToc, 'click', tocItemClickFn) + + autoScrollToc = item => { + const activePosition = item.getBoundingClientRect().top + const sidebarScrollTop = $cardToc.scrollTop + if (activePosition > (document.documentElement.clientHeight - 100)) { + $cardToc.scrollTop = sidebarScrollTop + 150 + } + if (activePosition < 100) { + $cardToc.scrollTop = sidebarScrollTop - 150 + } + } + } + + // find head position & add active class + const $articleList = $article.querySelectorAll('h1,h2,h3,h4,h5,h6') + let detectItem = '' + const findHeadPosition = top => { + if (top === 0) { + return false + } + + let currentId = '' + let currentIndex = '' + + $articleList.forEach((ele, index) => { + if (top > btf.getEleTop(ele) - 80) { + const id = ele.id + currentId = id ? '#' + encodeURI(id) : '' + currentIndex = index + } + }) + + if (detectItem === currentIndex) return + + if (isAnchor) btf.updateAnchor(currentId) + + detectItem = currentIndex + + if (isToc) { + $cardToc.querySelectorAll('.active').forEach(i => { i.classList.remove('active') }) + + if (currentId === '') { + return + } + + const currentActive = $tocLink[currentIndex] + currentActive.classList.add('active') + + setTimeout(() => { + autoScrollToc(currentActive) + }, 0) + + if (isExpand) return + let parent = currentActive.parentNode + + for (; !parent.matches('.toc'); parent = parent.parentNode) { + if (parent.matches('li')) parent.classList.add('active') + } + } + } + + // main of scroll + const tocScrollFn = btf.throttle(() => { + const currentTop = window.scrollY || document.documentElement.scrollTop + if (isToc && GLOBAL_CONFIG.percent.toc) { + $tocPercentage.textContent = btf.getScrollPercent(currentTop, $article) + } + findHeadPosition(currentTop) + }, 100) + + btf.addEventListenerPjax(window, 'scroll', tocScrollFn, { passive: true }) + } + + const handleThemeChange = mode => { + const globalFn = window.globalFn || {} + const themeChange = globalFn.themeChange || {} + if (!themeChange) { + return + } + + Object.keys(themeChange).forEach(key => { + const themeChangeFn = themeChange[key] + if (['disqus', 'disqusjs'].includes(key)) { + setTimeout(() => themeChangeFn(mode), 300) + } else { + themeChangeFn(mode) + } + }) + } + + /** + * Rightside + */ + const rightSideFn = { + readmode: () => { // read mode + const $body = document.body + $body.classList.add('read-mode') + const newEle = document.createElement('button') + newEle.type = 'button' + newEle.className = 'fas fa-sign-out-alt exit-readmode' + $body.appendChild(newEle) + + const clickFn = () => { + $body.classList.remove('read-mode') + newEle.remove() + newEle.removeEventListener('click', clickFn) + } + + newEle.addEventListener('click', clickFn) + }, + darkmode: () => { // switch between light and dark mode + const willChangeMode = document.documentElement.getAttribute('data-theme') === 'dark' ? 'light' : 'dark' + if (willChangeMode === 'dark') { + activateDarkMode() + GLOBAL_CONFIG.Snackbar !== undefined && btf.snackbarShow(GLOBAL_CONFIG.Snackbar.day_to_night) + } else { + activateLightMode() + GLOBAL_CONFIG.Snackbar !== undefined && btf.snackbarShow(GLOBAL_CONFIG.Snackbar.night_to_day) + } + saveToLocal.set('theme', willChangeMode, 2) + handleThemeChange(willChangeMode) + }, + 'rightside-config': item => { // Show or hide rightside-hide-btn + const hideLayout = item.firstElementChild + if (hideLayout.classList.contains('show')) { + hideLayout.classList.add('status') + setTimeout(() => { + hideLayout.classList.remove('status') + }, 300) + } + + hideLayout.classList.toggle('show') + }, + 'go-up': () => { // Back to top + btf.scrollToDest(0, 500) + }, + 'hide-aside-btn': () => { // Hide aside + const $htmlDom = document.documentElement.classList + const saveStatus = $htmlDom.contains('hide-aside') ? 'show' : 'hide' + saveToLocal.set('aside-status', saveStatus, 2) + $htmlDom.toggle('hide-aside') + }, + 'mobile-toc-button': item => { // Show mobile toc + const tocEle = document.getElementById('card-toc') + tocEle.style.transition = 'transform 0.3s ease-in-out' + tocEle.classList.toggle('open') + tocEle.addEventListener('transitionend', () => { + tocEle.style.transition = '' + }, { once: true }) + }, + 'chat-btn': () => { // Show chat + window.chatBtnFn() + }, + translateLink: () => { // switch between traditional and simplified chinese + window.translateFn.translatePage() + } + } + + document.getElementById('rightside').addEventListener('click', function (e) { + const $target = e.target.closest('[id]') + if ($target && rightSideFn[$target.id]) { + rightSideFn[$target.id](this) + } + }) + + /** + * menu + * 側邊欄sub-menu 展開/收縮 + */ + const clickFnOfSubMenu = () => { + const handleClickOfSubMenu = e => { + const target = e.target.closest('.site-page.group') + if (!target) return + target.classList.toggle('hide') + } + + document.querySelector('#sidebar-menus .menus_items').addEventListener('click', handleClickOfSubMenu) + } + + /** + * 手机端目录点击 + */ + const openMobileMenu = () => { + const handleClick = () => { sidebarFn.open() } + btf.addEventListenerPjax(document.getElementById('toggle-menu'), 'click', handleClick) + } + + /** + * 複製時加上版權信息 + */ + const addCopyright = () => { + const { limitCount, languages } = GLOBAL_CONFIG.copyright + + const handleCopy = (e) => { + e.preventDefault() + const copyFont = window.getSelection(0).toString() + let textFont = copyFont + if (copyFont.length > limitCount) { + textFont = `${copyFont}\n\n\n${languages.author}\n${languages.link}${window.location.href}\n${languages.source}\n${languages.info}` + } + if (e.clipboardData) { + return e.clipboardData.setData('text', textFont) + } else { + return window.clipboardData.setData('text', textFont) + } + } + + document.body.addEventListener('copy', handleCopy) + } + + /** + * 網頁運行時間 + */ + const addRuntime = () => { + const $runtimeCount = document.getElementById('runtimeshow') + if ($runtimeCount) { + const publishDate = $runtimeCount.getAttribute('data-publishDate') + $runtimeCount.textContent = `${btf.diffDate(publishDate)} ${GLOBAL_CONFIG.runtime}` + } + } + + /** + * 最後一次更新時間 + */ + const addLastPushDate = () => { + const $lastPushDateItem = document.getElementById('last-push-date') + if ($lastPushDateItem) { + const lastPushDate = $lastPushDateItem.getAttribute('data-lastPushDate') + $lastPushDateItem.textContent = btf.diffDate(lastPushDate, true) + } + } + + /** + * table overflow + */ + const addTableWrap = () => { + const $table = document.querySelectorAll('#article-container table') + if (!$table.length) return + + $table.forEach(item => { + if (!item.closest('.highlight')) { + btf.wrap(item, 'div', { class: 'table-wrap' }) + } + }) + } + + /** + * tag-hide + */ + const clickFnOfTagHide = () => { + const hideButtons = document.querySelectorAll('#article-container .hide-button') + if (!hideButtons.length) return + const handleClick = function (e) { + const $this = this + $this.classList.add('open') + const $fjGallery = $this.nextElementSibling.querySelectorAll('.gallery-container') + $fjGallery.length && addJustifiedGallery($fjGallery) + } + + hideButtons.forEach(item => { + item.addEventListener('click', handleClick, { once: true }) + }) + } + + const tabsFn = () => { + const navTabsElement = document.querySelectorAll('#article-container .tabs') + if (!navTabsElement.length) return + + const removeAndAddActiveClass = (elements, detect) => { + Array.from(elements).forEach(element => { + element.classList.remove('active') + if (element === detect || element.id === detect) { + element.classList.add('active') + } + }) + } + + const addTabNavEventListener = (item, isJustifiedGallery) => { + const navClickHandler = function (e) { + const target = e.target.closest('button') + if (target.classList.contains('active')) return + removeAndAddActiveClass(this.children, target) + this.classList.remove('no-default') + const tabId = target.getAttribute('data-href') + const tabContent = this.nextElementSibling + removeAndAddActiveClass(tabContent.children, tabId) + if (isJustifiedGallery) { + btf.removeGlobalFnEvent('igOfTabs', this) + const justifiedGalleryItems = tabContent.querySelectorAll(`:scope > #${tabId} .gallery-container`) + justifiedGalleryItems.length && addJustifiedGallery(justifiedGalleryItems, this) + } + } + btf.addEventListenerPjax(item.firstElementChild, 'click', navClickHandler) + } + + const addTabToTopEventListener = item => { + const btnClickHandler = (e) => { + const target = e.target.closest('button') + if (!target) return + btf.scrollToDest(btf.getEleTop(item), 300) + } + btf.addEventListenerPjax(item.lastElementChild, 'click', btnClickHandler) + } + + navTabsElement.forEach(item => { + const isJustifiedGallery = !!item.querySelectorAll('.gallery-container') + addTabNavEventListener(item, isJustifiedGallery) + addTabToTopEventListener(item) + }) + } + + const toggleCardCategory = () => { + const cardCategory = document.querySelector('#aside-cat-list.expandBtn') + if (!cardCategory) return + + const handleToggleBtn = (e) => { + const target = e.target + if (target.nodeName === 'I') { + e.preventDefault() + target.parentNode.classList.toggle('expand') + } + } + btf.addEventListenerPjax(cardCategory, 'click', handleToggleBtn, true) + } + + const switchComments = () => { + const switchBtn = document.getElementById('switch-btn') + if (!switchBtn) return + let switchDone = false + const commentContainer = document.getElementById('post-comment') + const handleSwitchBtn = () => { + commentContainer.classList.toggle('move') + if (!switchDone) { + switchDone = true + loadOtherComment() + } + } + btf.addEventListenerPjax(switchBtn, 'click', handleSwitchBtn) + } + + const addPostOutdateNotice = () => { + const { limitDay, messagePrev, messageNext, position } = GLOBAL_CONFIG.noticeOutdate + const diffDay = btf.diffDate(GLOBAL_CONFIG_SITE.postUpdate) + if (diffDay >= limitDay) { + const ele = document.createElement('div') + ele.className = 'post-outdate-notice' + ele.textContent = `${messagePrev} ${diffDay} ${messageNext}` + const $targetEle = document.getElementById('article-container') + if (position === 'top') { + $targetEle.insertBefore(ele, $targetEle.firstChild) + } else { + $targetEle.appendChild(ele) + } + } + } + + const lazyloadImg = () => { + window.lazyLoadInstance = new LazyLoad({ + elements_selector: 'img', + threshold: 0, + data_src: 'lazy-src' + }) + } + + const relativeDate = function (selector) { + selector.forEach(item => { + const timeVal = item.getAttribute('datetime') + item.textContent = btf.diffDate(timeVal, true) + item.style.display = 'inline' + }) + } + + const unRefreshFn = function () { + window.addEventListener('resize', () => { + adjustMenu(false) + mobileSidebarOpen && btf.isHidden(document.getElementById('toggle-menu')) && sidebarFn.close() + }) + + document.getElementById('menu-mask').addEventListener('click', e => { sidebarFn.close() }) + + clickFnOfSubMenu() + GLOBAL_CONFIG.islazyload && lazyloadImg() + GLOBAL_CONFIG.copyright !== undefined && addCopyright() + + if (GLOBAL_CONFIG.autoDarkmode) { + window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', e => { + if (saveToLocal.get('theme') !== undefined) return + e.matches ? handleThemeChange('dark') : handleThemeChange('light') + }) + } + } + + window.refreshFn = function () { + initAdjust() + + if (GLOBAL_CONFIG_SITE.isPost) { + GLOBAL_CONFIG.noticeOutdate !== undefined && addPostOutdateNotice() + GLOBAL_CONFIG.relativeDate.post && relativeDate(document.querySelectorAll('#post-meta time')) + } else { + GLOBAL_CONFIG.relativeDate.homepage && relativeDate(document.querySelectorAll('#recent-posts time')) + GLOBAL_CONFIG.runtime && addRuntime() + addLastPushDate() + toggleCardCategory() + } + + scrollFnToDo() + GLOBAL_CONFIG_SITE.isHome && scrollDownInIndex() + addHighlightTool() + GLOBAL_CONFIG.isPhotoFigcaption && addPhotoFigcaption() + scrollFn() + + btf.removeGlobalFnEvent('justifiedGallery') + const galleryContainer = document.querySelectorAll('#article-container .gallery-container') + galleryContainer.length && addJustifiedGallery(galleryContainer) + + runLightbox() + addTableWrap() + clickFnOfTagHide() + tabsFn() + switchComments() + openMobileMenu() + } + + refreshFn() + unRefreshFn() +}) diff --git a/js/motion.js b/js/motion.js deleted file mode 100644 index aad22db..0000000 --- a/js/motion.js +++ /dev/null @@ -1,140 +0,0 @@ -/* global NexT, CONFIG */ - -NexT.motion = {}; - -NexT.motion.integrator = { - queue: [], - init : function() { - this.queue = []; - return this; - }, - add: function(fn) { - const sequence = fn(); - if (CONFIG.motion.async) this.queue.push(sequence); - else this.queue = this.queue.concat(sequence); - return this; - }, - bootstrap: function() { - if (!CONFIG.motion.async) this.queue = [this.queue]; - this.queue.forEach(sequence => { - const timeline = window.anime.timeline({ - duration: 200, - easing : 'linear' - }); - sequence.forEach(item => { - if (item.deltaT) timeline.add(item, item.deltaT); - else timeline.add(item); - }); - }); - } -}; - -NexT.motion.middleWares = { - header: function() { - const sequence = []; - - function getMistLineSettings(targets) { - sequence.push({ - targets, - scaleX : [0, 1], - duration: 500, - deltaT : '-=200' - }); - } - - function pushToSequence(targets, sequenceQueue = false) { - sequence.push({ - targets, - opacity: 1, - top : 0, - deltaT : sequenceQueue ? '-=200' : '-=0' - }); - } - - pushToSequence('.column'); - CONFIG.scheme === 'Mist' && getMistLineSettings('.logo-line'); - CONFIG.scheme === 'Muse' && pushToSequence('.custom-logo-image'); - pushToSequence('.site-title'); - pushToSequence('.site-brand-container .toggle', true); - pushToSequence('.site-subtitle'); - (CONFIG.scheme === 'Pisces' || CONFIG.scheme === 'Gemini') && pushToSequence('.custom-logo-image'); - - const menuItemTransition = CONFIG.motion.transition.menu_item; - if (menuItemTransition) { - document.querySelectorAll('.menu-item').forEach(targets => { - sequence.push({ - targets, - complete: () => targets.classList.add('animated', menuItemTransition), - deltaT : '-=200' - }); - }); - } - - return sequence; - }, - - subMenu: function() { - const subMenuItem = document.querySelectorAll('.sub-menu .menu-item'); - if (subMenuItem.length > 0) { - subMenuItem.forEach(element => { - element.classList.add('animated'); - }); - } - return []; - }, - - postList: function() { - const sequence = []; - const { post_block, post_header, post_body, coll_header } = CONFIG.motion.transition; - - function animate(animation, elements) { - if (!animation) return; - elements.forEach(targets => { - sequence.push({ - targets, - complete: () => targets.classList.add('animated', animation), - deltaT : '-=100' - }); - }); - } - - document.querySelectorAll('.post-block').forEach(targets => { - sequence.push({ - targets, - complete: () => targets.classList.add('animated', post_block), - deltaT : '-=100' - }); - animate(coll_header, targets.querySelectorAll('.collection-header')); - animate(post_header, targets.querySelectorAll('.post-header')); - animate(post_body, targets.querySelectorAll('.post-body')); - }); - - animate(post_block, document.querySelectorAll('.pagination, .comments')); - - return sequence; - }, - - sidebar: function() { - const sequence = []; - const sidebar = document.querySelectorAll('.sidebar-inner'); - const sidebarTransition = CONFIG.motion.transition.sidebar; - // Only for Pisces | Gemini. - if (sidebarTransition && (CONFIG.scheme === 'Pisces' || CONFIG.scheme === 'Gemini')) { - sidebar.forEach(targets => { - sequence.push({ - targets, - complete: () => targets.classList.add('animated', sidebarTransition), - deltaT : '-=100' - }); - }); - } - return sequence; - }, - - footer: function() { - return [{ - targets: document.querySelector('.footer'), - opacity: 1 - }]; - } -}; diff --git a/js/next-boot.js b/js/next-boot.js deleted file mode 100644 index 1225fd2..0000000 --- a/js/next-boot.js +++ /dev/null @@ -1,75 +0,0 @@ -/* global NexT, CONFIG */ - -NexT.boot = {}; - -NexT.boot.registerEvents = function() { - - NexT.utils.registerScrollPercent(); - NexT.utils.registerCanIUseTag(); - - // Mobile top menu bar. - document.querySelector('.site-nav-toggle .toggle').addEventListener('click', event => { - event.currentTarget.classList.toggle('toggle-close'); - const siteNav = document.querySelector('.site-nav'); - if (!siteNav) return; - siteNav.style.setProperty('--scroll-height', siteNav.scrollHeight + 'px'); - document.body.classList.toggle('site-nav-on'); - }); - - document.querySelectorAll('.sidebar-nav li').forEach((element, index) => { - element.addEventListener('click', () => { - NexT.utils.activateSidebarPanel(index); - }); - }); - - window.addEventListener('hashchange', () => { - const tHash = location.hash; - if (tHash !== '' && !tHash.match(/%\S{2}/)) { - const target = document.querySelector(`.tabs ul.nav-tabs li a[href="${tHash}"]`); - target && target.click(); - } - }); -}; - -NexT.boot.refresh = function() { - - /** - * Register JS handlers by condition option. - * Need to add config option in Front-End at 'scripts/helpers/next-config.js' file. - */ - CONFIG.prism && window.Prism.highlightAll(); - CONFIG.mediumzoom && window.mediumZoom('.post-body :not(a) > img, .post-body > img', { - background: 'var(--content-bg-color)' - }); - CONFIG.lazyload && window.lozad('.post-body img').observe(); - CONFIG.pangu && window.pangu.spacingPage(); - - CONFIG.exturl && NexT.utils.registerExtURL(); - NexT.utils.wrapTableWithBox(); - NexT.utils.registerCopyCode(); - NexT.utils.registerTabsTag(); - NexT.utils.registerActiveMenuItem(); - NexT.utils.registerLangSelect(); - NexT.utils.registerSidebarTOC(); - NexT.utils.registerPostReward(); - NexT.utils.registerVideoIframe(); -}; - -NexT.boot.motion = function() { - // Define Motion Sequence & Bootstrap Motion. - if (CONFIG.motion.enable) { - NexT.motion.integrator - .add(NexT.motion.middleWares.header) - .add(NexT.motion.middleWares.postList) - .add(NexT.motion.middleWares.sidebar) - .add(NexT.motion.middleWares.footer) - .bootstrap(); - } - NexT.utils.updateSidebarPosition(); -}; - -document.addEventListener('DOMContentLoaded', () => { - NexT.boot.registerEvents(); - NexT.boot.refresh(); - NexT.boot.motion(); -}); diff --git a/js/pjax.js b/js/pjax.js deleted file mode 100644 index f81a6a0..0000000 --- a/js/pjax.js +++ /dev/null @@ -1,50 +0,0 @@ -/* global NexT, CONFIG, Pjax */ - -const pjax = new Pjax({ - selectors: [ - 'head title', - 'script[type="application/json"]', - // Precede .main-inner to prevent placeholder TOC changes asap - '.post-toc-wrap', - '.main-inner', - '.languages', - '.pjax' - ], - switches: { - '.post-toc-wrap': function(oldWrap, newWrap) { - if (newWrap.querySelector('.post-toc')) { - Pjax.switches.outerHTML.call(this, oldWrap, newWrap); - } else { - const curTOC = oldWrap.querySelector('.post-toc'); - if (curTOC) { - curTOC.classList.add('placeholder-toc'); - } - this.onSwitch(); - } - } - }, - analytics: false, - cacheBust: false, - scrollTo : !CONFIG.bookmark.enable -}); - -document.addEventListener('pjax:success', () => { - pjax.executeScripts(document.querySelectorAll('script[data-pjax]')); - NexT.boot.refresh(); - // Define Motion Sequence & Bootstrap Motion. - if (CONFIG.motion.enable) { - NexT.motion.integrator - .init() - .add(NexT.motion.middleWares.subMenu) - .add(NexT.motion.middleWares.postList) - // Add sidebar-post-related transition. - .add(NexT.motion.middleWares.sidebar) - .bootstrap(); - } - if (CONFIG.sidebar.display !== 'remove') { - const hasTOC = document.querySelector('.post-toc:not(.placeholder-toc)'); - document.querySelector('.sidebar-inner').classList.toggle('sidebar-nav-active', hasTOC); - NexT.utils.activateSidebarPanel(hasTOC ? 0 : 1); - NexT.utils.updateSidebarPosition(); - } -}); diff --git a/js/schedule.js b/js/schedule.js deleted file mode 100644 index 8f0c26c..0000000 --- a/js/schedule.js +++ /dev/null @@ -1,138 +0,0 @@ -/* global CONFIG */ - -// https://developers.google.com/calendar/api/v3/reference/events/list -(function() { - // Initialization - const calendar = { - orderBy : 'startTime', - showLocation: false, - offsetMax : 72, - offsetMin : 4, - showDeleted : false, - singleEvents: true, - maxResults : 250 - }; - - // Read config form theme config file - Object.assign(calendar, CONFIG.calendar); - - const now = new Date(); - const timeMax = new Date(); - const timeMin = new Date(); - - timeMax.setHours(now.getHours() + calendar.offsetMax); - timeMin.setHours(now.getHours() - calendar.offsetMin); - - // Build URL - const params = { - key : calendar.api_key, - orderBy : calendar.orderBy, - timeMax : timeMax.toISOString(), - timeMin : timeMin.toISOString(), - showDeleted : calendar.showDeleted, - singleEvents: calendar.singleEvents, - maxResults : calendar.maxResults - }; - - const request_url = new URL(`https://www.googleapis.com/calendar/v3/calendars/${calendar.calendar_id}/events`); - Object.entries(params).forEach(param => request_url.searchParams.append(...param)); - - function getRelativeTime(current, previous) { - const msPerMinute = 60 * 1000; - const msPerHour = msPerMinute * 60; - const msPerDay = msPerHour * 24; - const msPerMonth = msPerDay * 30; - const msPerYear = msPerDay * 365; - - let elapsed = current - previous; - const tense = elapsed > 0 ? ' ago' : ' later'; - - elapsed = Math.abs(elapsed); - - if (elapsed < msPerHour) { - return Math.round(elapsed / msPerMinute) + ' minutes' + tense; - } else if (elapsed < msPerDay) { - return Math.round(elapsed / msPerHour) + ' hours' + tense; - } else if (elapsed < msPerMonth) { - return 'about ' + Math.round(elapsed / msPerDay) + ' days' + tense; - } else if (elapsed < msPerYear) { - return 'about ' + Math.round(elapsed / msPerMonth) + ' months' + tense; - } - - return 'about ' + Math.round(elapsed / msPerYear) + ' years' + tense; - } - - function buildEventDOM(tense, event, start, end) { - const durationFormat = { - weekday: 'short', - hour : '2-digit', - minute : '2-digit' - }; - const relativeTime = tense === 'now' ? 'NOW' : getRelativeTime(now, start); - const duration = start.toLocaleTimeString([], durationFormat) + ' - ' + end.toLocaleTimeString([], durationFormat); - - let location = ''; - if (calendar.showLocation && event.location) { - location = `${event.location}`; - } - let description = ''; - if (event.description) { - description = `${event.description}`; - } - - const eventContent = `
-

- ${event.summary} - ${relativeTime} -

- ${location} - ${duration} - ${description} -
`; - return eventContent; - } - - function fetchData() { - const eventList = document.querySelector('.event-list'); - if (!eventList) return; - - fetch(request_url.href).then(response => { - return response.json(); - }).then(data => { - if (data.items.length === 0) { - eventList.innerHTML = '
'; - return; - } - // Clean the event list - eventList.innerHTML = ''; - let prevEnd = 0; // used to decide where to insert an
- const utc = new Date().getTimezoneOffset() * 60000; - - data.items.forEach(event => { - // Parse data - const start = new Date(event.start.dateTime || (new Date(event.start.date).getTime() + utc)); - const end = new Date(event.end.dateTime || (new Date(event.end.date).getTime() + utc)); - - let tense = 'now'; - if (end < now) { - tense = 'past'; - } else if (start > now) { - tense = 'future'; - } - - if (tense === 'future' && prevEnd < now) { - eventList.insertAdjacentHTML('beforeend', '
'); - } - - eventList.insertAdjacentHTML('beforeend', buildEventDOM(tense, event, start, end)); - prevEnd = end; - }); - }); - } - - fetchData(); - const fetchDataTimer = setInterval(fetchData, 60000); - document.addEventListener('pjax:send', () => { - clearInterval(fetchDataTimer); - }); -})(); diff --git a/js/schemes/muse.js b/js/schemes/muse.js deleted file mode 100644 index ba60b51..0000000 --- a/js/schemes/muse.js +++ /dev/null @@ -1,60 +0,0 @@ -/* global CONFIG */ - -document.addEventListener('DOMContentLoaded', () => { - - const isRight = CONFIG.sidebar.position === 'right'; - - const sidebarToggleMotion = { - mouse: {}, - init : function() { - window.addEventListener('mousedown', this.mousedownHandler.bind(this)); - window.addEventListener('mouseup', this.mouseupHandler.bind(this)); - document.querySelector('.sidebar-dimmer').addEventListener('click', this.clickHandler.bind(this)); - document.querySelector('.sidebar-toggle').addEventListener('click', this.clickHandler.bind(this)); - window.addEventListener('sidebar:show', this.showSidebar); - window.addEventListener('sidebar:hide', this.hideSidebar); - }, - mousedownHandler: function(event) { - this.mouse.X = event.pageX; - this.mouse.Y = event.pageY; - }, - mouseupHandler: function(event) { - const deltaX = event.pageX - this.mouse.X; - const deltaY = event.pageY - this.mouse.Y; - const clickingBlankPart = Math.hypot(deltaX, deltaY) < 20 && event.target.matches('.main'); - // Fancybox has z-index property, but medium-zoom does not, so the sidebar will overlay the zoomed image. - if (clickingBlankPart || event.target.matches('img.medium-zoom-image')) { - this.hideSidebar(); - } - }, - clickHandler: function() { - document.body.classList.contains('sidebar-active') ? this.hideSidebar() : this.showSidebar(); - }, - showSidebar: function() { - document.body.classList.add('sidebar-active'); - const animateAction = isRight ? 'fadeInRight' : 'fadeInLeft'; - document.querySelectorAll('.sidebar .animated').forEach((element, index) => { - element.style.animationDelay = (100 * index) + 'ms'; - element.classList.remove(animateAction); - setTimeout(() => { - // Trigger a DOM reflow - element.classList.add(animateAction); - }); - }); - }, - hideSidebar: function() { - document.body.classList.remove('sidebar-active'); - } - }; - if (CONFIG.sidebar.display !== 'remove') sidebarToggleMotion.init(); - - function updateFooterPosition() { - const footer = document.querySelector('.footer'); - const containerHeight = document.querySelector('.main').offsetHeight + footer.offsetHeight; - footer.classList.toggle('footer-fixed', containerHeight <= window.innerHeight); - } - - updateFooterPosition(); - window.addEventListener('resize', updateFooterPosition); - window.addEventListener('scroll', updateFooterPosition, { passive: true }); -}); diff --git a/js/search/algolia.js b/js/search/algolia.js new file mode 100644 index 0000000..9ce7b0e --- /dev/null +++ b/js/search/algolia.js @@ -0,0 +1,177 @@ +window.addEventListener('load', () => { + const $searchMask = document.getElementById('search-mask') + const $searchDialog = document.querySelector('#algolia-search .search-dialog') + + const openSearch = () => { + const bodyStyle = document.body.style + bodyStyle.width = '100%' + bodyStyle.overflow = 'hidden' + btf.animateIn($searchMask, 'to_show 0.5s') + btf.animateIn($searchDialog, 'titleScale 0.5s') + setTimeout(() => { document.querySelector('#algolia-search .ais-SearchBox-input').focus() }, 100) + + // shortcut: ESC + document.addEventListener('keydown', function f (event) { + if (event.code === 'Escape') { + closeSearch() + document.removeEventListener('keydown', f) + } + }) + + fixSafariHeight() + window.addEventListener('resize', fixSafariHeight) + } + + const closeSearch = () => { + const bodyStyle = document.body.style + bodyStyle.width = '' + bodyStyle.overflow = '' + btf.animateOut($searchDialog, 'search_close .5s') + btf.animateOut($searchMask, 'to_hide 0.5s') + window.removeEventListener('resize', fixSafariHeight) + } + + // fix safari + const fixSafariHeight = () => { + if (window.innerWidth < 768) { + $searchDialog.style.setProperty('--search-height', window.innerHeight + 'px') + } + } + + const searchClickFn = () => { + btf.addEventListenerPjax(document.querySelector('#search-button > .search'), 'click', openSearch) + } + + const searchFnOnce = () => { + $searchMask.addEventListener('click', closeSearch) + document.querySelector('#algolia-search .search-close-button').addEventListener('click', closeSearch) + } + + const cutContent = content => { + if (content === '') return '' + + const firstOccur = content.indexOf('') + + let start = firstOccur - 30 + let end = firstOccur + 120 + let pre = '' + let post = '' + + if (start <= 0) { + start = 0 + end = 140 + } else { + pre = '...' + } + + if (end > content.length) { + end = content.length + } else { + post = '...' + } + + const matchContent = pre + content.substring(start, end) + post + return matchContent + } + + const algolia = GLOBAL_CONFIG.algolia + const isAlgoliaValid = algolia.appId && algolia.apiKey && algolia.indexName + if (!isAlgoliaValid) { + return console.error('Algolia setting is invalid!') + } + + const search = instantsearch({ + indexName: algolia.indexName, + /* global algoliasearch */ + searchClient: algoliasearch(algolia.appId, algolia.apiKey), + searchFunction (helper) { + helper.state.query && helper.search() + } + }) + + const configure = instantsearch.widgets.configure({ + hitsPerPage: 5 + }) + + const searchBox = instantsearch.widgets.searchBox({ + container: '#algolia-search-input', + showReset: false, + showSubmit: false, + placeholder: GLOBAL_CONFIG.algolia.languages.input_placeholder, + showLoadingIndicator: true + }) + + const hits = instantsearch.widgets.hits({ + container: '#algolia-hits', + templates: { + item (data) { + const link = data.permalink ? data.permalink : (GLOBAL_CONFIG.root + data.path) + const result = data._highlightResult + const content = result.contentStripTruncate + ? cutContent(result.contentStripTruncate.value) + : result.contentStrip + ? cutContent(result.contentStrip.value) + : result.content + ? cutContent(result.content.value) + : '' + return ` + + ${result.title.value || 'no-title'} +

${content}

+
` + }, + empty: function (data) { + return ( + '
' + + GLOBAL_CONFIG.algolia.languages.hits_empty.replace(/\$\{query}/, data.query) + + '
' + ) + } + } + }) + + const stats = instantsearch.widgets.stats({ + container: '#algolia-info > .algolia-stats', + templates: { + text: function (data) { + const stats = GLOBAL_CONFIG.algolia.languages.hits_stats + .replace(/\$\{hits}/, data.nbHits) + .replace(/\$\{time}/, data.processingTimeMS) + return ( + `
${stats}` + ) + } + } + }) + + const powerBy = instantsearch.widgets.poweredBy({ + container: '#algolia-info > .algolia-poweredBy' + }) + + const pagination = instantsearch.widgets.pagination({ + container: '#algolia-pagination', + totalPages: 5, + templates: { + first: '', + last: '', + previous: '', + next: '' + } + }) + + search.addWidgets([configure, searchBox, hits, stats, powerBy, pagination]) // add the widgets to the instantsearch instance + + search.start() + + searchClickFn() + searchFnOnce() + + window.addEventListener('pjax:complete', () => { + !btf.isHidden($searchMask) && closeSearch() + searchClickFn() + }) + + window.pjax && search.on('render', () => { + window.pjax.refresh(document.getElementById('algolia-hits')) + }) +}) diff --git a/js/search/local-search.js b/js/search/local-search.js new file mode 100644 index 0000000..0eecff6 --- /dev/null +++ b/js/search/local-search.js @@ -0,0 +1,364 @@ +/** + * Refer to hexo-generator-searchdb + * https://github.com/next-theme/hexo-generator-searchdb/blob/main/dist/search.js + * Modified by hexo-theme-butterfly + */ + +class LocalSearch { + constructor ({ + path = '', + unescape = false, + top_n_per_article = 1 + }) { + this.path = path + this.unescape = unescape + this.top_n_per_article = top_n_per_article + this.isfetched = false + this.datas = null + } + + getIndexByWord (words, text, caseSensitive = false) { + const index = [] + const included = new Set() + + if (!caseSensitive) { + text = text.toLowerCase() + } + words.forEach(word => { + if (this.unescape) { + const div = document.createElement('div') + div.innerText = word + word = div.innerHTML + } + const wordLen = word.length + if (wordLen === 0) return + let startPosition = 0 + let position = -1 + if (!caseSensitive) { + word = word.toLowerCase() + } + while ((position = text.indexOf(word, startPosition)) > -1) { + index.push({ position, word }) + included.add(word) + startPosition = position + wordLen + } + }) + // Sort index by position of keyword + index.sort((left, right) => { + if (left.position !== right.position) { + return left.position - right.position + } + return right.word.length - left.word.length + }) + return [index, included] + } + + // Merge hits into slices + mergeIntoSlice (start, end, index) { + let item = index[0] + let { position, word } = item + const hits = [] + const count = new Set() + while (position + word.length <= end && index.length !== 0) { + count.add(word) + hits.push({ + position, + length: word.length + }) + const wordEnd = position + word.length + + // Move to next position of hit + index.shift() + while (index.length !== 0) { + item = index[0] + position = item.position + word = item.word + if (wordEnd > position) { + index.shift() + } else { + break + } + } + } + return { + hits, + start, + end, + count: count.size + } + } + + // Highlight title and content + highlightKeyword (val, slice) { + let result = '' + let index = slice.start + for (const { position, length } of slice.hits) { + result += val.substring(index, position) + index = position + length + result += `${val.substr(position, length)}` + } + result += val.substring(index, slice.end) + return result + } + + getResultItems (keywords) { + const resultItems = [] + this.datas.forEach(({ title, content, url }) => { + // The number of different keywords included in the article. + const [indexOfTitle, keysOfTitle] = this.getIndexByWord(keywords, title) + const [indexOfContent, keysOfContent] = this.getIndexByWord(keywords, content) + const includedCount = new Set([...keysOfTitle, ...keysOfContent]).size + + // Show search results + const hitCount = indexOfTitle.length + indexOfContent.length + if (hitCount === 0) return + + const slicesOfTitle = [] + if (indexOfTitle.length !== 0) { + slicesOfTitle.push(this.mergeIntoSlice(0, title.length, indexOfTitle)) + } + + let slicesOfContent = [] + while (indexOfContent.length !== 0) { + const item = indexOfContent[0] + const { position } = item + // Cut out 120 characters. The maxlength of .search-input is 80. + const start = Math.max(0, position - 20) + const end = Math.min(content.length, position + 100) + slicesOfContent.push(this.mergeIntoSlice(start, end, indexOfContent)) + } + + // Sort slices in content by included keywords' count and hits' count + slicesOfContent.sort((left, right) => { + if (left.count !== right.count) { + return right.count - left.count + } else if (left.hits.length !== right.hits.length) { + return right.hits.length - left.hits.length + } + return left.start - right.start + }) + + // Select top N slices in content + const upperBound = parseInt(this.top_n_per_article, 10) + if (upperBound >= 0) { + slicesOfContent = slicesOfContent.slice(0, upperBound) + } + + let resultItem = '' + + url = new URL(url, location.origin) + url.searchParams.append('highlight', keywords.join(' ')) + + if (slicesOfTitle.length !== 0) { + resultItem += `
${this.highlightKeyword(title, slicesOfTitle[0])}` + } else { + resultItem += `' + resultItems.push({ + item: resultItem, + id: resultItems.length, + hitCount, + includedCount + }) + }) + return resultItems + } + + fetchData () { + const isXml = !this.path.endsWith('json') + fetch(this.path) + .then(response => response.text()) + .then(res => { + // Get the contents from search data + this.isfetched = true + this.datas = isXml + ? [...new DOMParser().parseFromString(res, 'text/xml').querySelectorAll('entry')].map(element => ({ + title: element.querySelector('title').textContent, + content: element.querySelector('content').textContent, + url: element.querySelector('url').textContent + })) + : JSON.parse(res) + // Only match articles with non-empty titles + this.datas = this.datas.filter(data => data.title).map(data => { + data.title = data.title.trim() + data.content = data.content ? data.content.trim().replace(/<[^>]+>/g, '') : '' + data.url = decodeURIComponent(data.url).replace(/\/{2,}/g, '/') + return data + }) + // Remove loading animation + window.dispatchEvent(new Event('search:loaded')) + }) + } + + // Highlight by wrapping node in mark elements with the given class name + highlightText (node, slice, className) { + const val = node.nodeValue + let index = slice.start + const children = [] + for (const { position, length } of slice.hits) { + const text = document.createTextNode(val.substring(index, position)) + index = position + length + const mark = document.createElement('mark') + mark.className = className + mark.appendChild(document.createTextNode(val.substr(position, length))) + children.push(text, mark) + } + node.nodeValue = val.substring(index, slice.end) + children.forEach(element => { + node.parentNode.insertBefore(element, node) + }) + } + + // Highlight the search words provided in the url in the text + highlightSearchWords (body) { + const params = new URL(location.href).searchParams.get('highlight') + const keywords = params ? params.split(' ') : [] + if (!keywords.length || !body) return + const walk = document.createTreeWalker(body, NodeFilter.SHOW_TEXT, null) + const allNodes = [] + while (walk.nextNode()) { + if (!walk.currentNode.parentNode.matches('button, select, textarea, .mermaid')) allNodes.push(walk.currentNode) + } + allNodes.forEach(node => { + const [indexOfNode] = this.getIndexByWord(keywords, node.nodeValue) + if (!indexOfNode.length) return + const slice = this.mergeIntoSlice(0, node.nodeValue.length, indexOfNode) + this.highlightText(node, slice, 'search-keyword') + }) + } +} + +window.addEventListener('load', () => { +// Search + const { path, top_n_per_article, unescape, languages } = GLOBAL_CONFIG.localSearch + const localSearch = new LocalSearch({ + path, + top_n_per_article, + unescape + }) + + const input = document.querySelector('#local-search-input input') + const statsItem = document.getElementById('local-search-stats-wrap') + const $loadingStatus = document.getElementById('loading-status') + const isXml = !path.endsWith('json') + + const inputEventFunction = () => { + if (!localSearch.isfetched) return + let searchText = input.value.trim().toLowerCase() + isXml && (searchText = searchText.replace(//g, '>')) + if (searchText !== '') $loadingStatus.innerHTML = '' + const keywords = searchText.split(/[-\s]+/) + const container = document.getElementById('local-search-results') + let resultItems = [] + if (searchText.length > 0) { + // Perform local searching + resultItems = localSearch.getResultItems(keywords) + } + if (keywords.length === 1 && keywords[0] === '') { + container.textContent = '' + statsItem.textContent = '' + } else if (resultItems.length === 0) { + container.textContent = '' + const statsDiv = document.createElement('div') + statsDiv.className = 'search-result-stats' + statsDiv.textContent = languages.hits_empty.replace(/\$\{query}/, searchText) + statsItem.innerHTML = statsDiv.outerHTML + } else { + resultItems.sort((left, right) => { + if (left.includedCount !== right.includedCount) { + return right.includedCount - left.includedCount + } else if (left.hitCount !== right.hitCount) { + return right.hitCount - left.hitCount + } + return right.id - left.id + }) + + const stats = languages.hits_stats.replace(/\$\{hits}/, resultItems.length) + + container.innerHTML = `
${resultItems.map(result => result.item).join('')}
` + statsItem.innerHTML = `
${stats}
` + window.pjax && window.pjax.refresh(container) + } + + $loadingStatus.textContent = '' + } + + let loadFlag = false + const $searchMask = document.getElementById('search-mask') + const $searchDialog = document.querySelector('#local-search .search-dialog') + + // fix safari + const fixSafariHeight = () => { + if (window.innerWidth < 768) { + $searchDialog.style.setProperty('--search-height', window.innerHeight + 'px') + } + } + + const openSearch = () => { + const bodyStyle = document.body.style + bodyStyle.width = '100%' + bodyStyle.overflow = 'hidden' + btf.animateIn($searchMask, 'to_show 0.5s') + btf.animateIn($searchDialog, 'titleScale 0.5s') + setTimeout(() => { input.focus() }, 300) + if (!loadFlag) { + !localSearch.isfetched && localSearch.fetchData() + input.addEventListener('input', inputEventFunction) + loadFlag = true + } + // shortcut: ESC + document.addEventListener('keydown', function f (event) { + if (event.code === 'Escape') { + closeSearch() + document.removeEventListener('keydown', f) + } + }) + + fixSafariHeight() + window.addEventListener('resize', fixSafariHeight) + } + + const closeSearch = () => { + const bodyStyle = document.body.style + bodyStyle.width = '' + bodyStyle.overflow = '' + btf.animateOut($searchDialog, 'search_close .5s') + btf.animateOut($searchMask, 'to_hide 0.5s') + window.removeEventListener('resize', fixSafariHeight) + } + + const searchClickFn = () => { + btf.addEventListenerPjax(document.querySelector('#search-button > .search'), 'click', openSearch) + } + + const searchFnOnce = () => { + document.querySelector('#local-search .search-close-button').addEventListener('click', closeSearch) + $searchMask.addEventListener('click', closeSearch) + if (GLOBAL_CONFIG.localSearch.preload) { + localSearch.fetchData() + } + localSearch.highlightSearchWords(document.getElementById('article-container')) + } + + window.addEventListener('search:loaded', () => { + const $loadDataItem = document.getElementById('loading-database') + $loadDataItem.nextElementSibling.style.display = 'block' + $loadDataItem.remove() + }) + + searchClickFn() + searchFnOnce() + + // pjax + window.addEventListener('pjax:complete', () => { + !btf.isHidden($searchMask) && closeSearch() + localSearch.highlightSearchWords(document.getElementById('article-container')) + searchClickFn() + }) +}) diff --git a/js/third-party/addtoany.js b/js/third-party/addtoany.js deleted file mode 100644 index f9009f8..0000000 --- a/js/third-party/addtoany.js +++ /dev/null @@ -1,8 +0,0 @@ -/* global NexT */ - -document.addEventListener('page:loaded', () => { - NexT.utils.getScript('https://static.addtoany.com/menu/page.js', { condition: window.a2a }) - .then(() => { - window.a2a.init(); - }); -}); diff --git a/js/third-party/analytics/baidu-analytics.js b/js/third-party/analytics/baidu-analytics.js deleted file mode 100644 index c10e7d0..0000000 --- a/js/third-party/analytics/baidu-analytics.js +++ /dev/null @@ -1,7 +0,0 @@ -/* global _hmt */ - -if (!window._hmt) window._hmt = []; - -document.addEventListener('pjax:success', () => { - _hmt.push(['_trackPageview', location.pathname]); -}); diff --git a/js/third-party/analytics/google-analytics.js b/js/third-party/analytics/google-analytics.js deleted file mode 100644 index 2cd128f..0000000 --- a/js/third-party/analytics/google-analytics.js +++ /dev/null @@ -1,35 +0,0 @@ -/* global CONFIG, dataLayer, gtag */ - -if (!CONFIG.google_analytics.only_pageview) { - if (CONFIG.hostname === location.hostname) { - window.dataLayer = window.dataLayer || []; - window.gtag = function() { - dataLayer.push(arguments); - }; - gtag('js', new Date()); - gtag('config', CONFIG.google_analytics.tracking_id); - - document.addEventListener('pjax:success', () => { - gtag('event', 'page_view', { - page_location: location.href, - page_path : location.pathname, - page_title : document.title - }); - }); - } -} else { - const sendPageView = () => { - if (CONFIG.hostname !== location.hostname) return; - const uid = localStorage.getItem('uid') || (Math.random() + '.' + Math.random()); - localStorage.setItem('uid', uid); - navigator.sendBeacon('https://www.google-analytics.com/collect', new URLSearchParams({ - v : 1, - tid: CONFIG.google_analytics.tracking_id, - cid: uid, - t : 'pageview', - dp : encodeURIComponent(location.pathname) - })); - }; - document.addEventListener('pjax:complete', sendPageView); - sendPageView(); -} diff --git a/js/third-party/analytics/growingio.js b/js/third-party/analytics/growingio.js deleted file mode 100644 index 0460833..0000000 --- a/js/third-party/analytics/growingio.js +++ /dev/null @@ -1,10 +0,0 @@ -/* global CONFIG, gio */ - -if (!window.gio) { - window.gio = function() { - (window.gio.q = window.gio.q || []).push(arguments); - }; -} - -gio('init', `${CONFIG.growingio_analytics}`, {}); -gio('send'); diff --git a/js/third-party/analytics/matomo.js b/js/third-party/analytics/matomo.js deleted file mode 100644 index 290a3e0..0000000 --- a/js/third-party/analytics/matomo.js +++ /dev/null @@ -1,19 +0,0 @@ -/* global CONFIG */ - -if (CONFIG.matomo.enable) { - window._paq = window._paq || []; - const _paq = window._paq; - - /* tracker methods like "setCustomDimension" should be called before "trackPageView" */ - _paq.push(['trackPageView']); - _paq.push(['enableLinkTracking']); - const u = CONFIG.matomo.server_url; - _paq.push(['setTrackerUrl', u + 'matomo.php']); - _paq.push(['setSiteId', CONFIG.matomo.site_id]); - const d = document; - const g = d.createElement('script'); - const s = d.getElementsByTagName('script')[0]; - g.async = true; - g.src = u + 'matomo.js'; - s.parentNode.insertBefore(g, s); -} diff --git a/js/third-party/chat/chatra.js b/js/third-party/chat/chatra.js deleted file mode 100644 index e495b8e..0000000 --- a/js/third-party/chat/chatra.js +++ /dev/null @@ -1,19 +0,0 @@ -/* global CONFIG, Chatra */ - -(function() { - if (CONFIG.chatra.embed) { - window.ChatraSetup = { - mode : 'frame', - injectTo: CONFIG.chatra.embed - }; - } - - window.ChatraID = CONFIG.chatra.id; - - const chatButton = document.querySelector('.sidebar-button button'); - if (chatButton) { - chatButton.addEventListener('click', () => { - Chatra('openChat', true); - }); - } -})(); diff --git a/js/third-party/chat/gitter.js b/js/third-party/chat/gitter.js deleted file mode 100644 index 2b26d05..0000000 --- a/js/third-party/chat/gitter.js +++ /dev/null @@ -1,5 +0,0 @@ -/* global CONFIG */ - -((window.gitter = {}).chat = {}).options = { - room: CONFIG.gitter.room -}; diff --git a/js/third-party/chat/tidio.js b/js/third-party/chat/tidio.js deleted file mode 100644 index bffb918..0000000 --- a/js/third-party/chat/tidio.js +++ /dev/null @@ -1,10 +0,0 @@ -/* global tidioChatApi */ - -(function() { - const chatButton = document.querySelector('.sidebar-button button'); - if (chatButton) { - chatButton.addEventListener('click', () => { - tidioChatApi.open(); - }); - } -})(); diff --git a/js/third-party/comments/changyan.js b/js/third-party/comments/changyan.js deleted file mode 100644 index 18a1be4..0000000 --- a/js/third-party/comments/changyan.js +++ /dev/null @@ -1,39 +0,0 @@ -/* global NexT, CONFIG */ - -document.addEventListener('page:loaded', () => { - const { appid, appkey } = CONFIG.changyan; - const mainJs = 'https://cy-cdn.kuaizhan.com/upload/changyan.js'; - const countJs = `https://cy-cdn.kuaizhan.com/upload/plugins/plugins.list.count.js?clientId=${appid}`; - - // Get the number of comments - setTimeout(() => { - return NexT.utils.getScript(countJs, { - attributes: { - async: true, - id : 'cy_cmt_num' - } - }); - }, 0); - - // When scroll to comment section - if (CONFIG.page.comments && !CONFIG.page.isHome) { - NexT.utils.loadComments('#SOHUCS') - .then(() => { - return NexT.utils.getScript(mainJs, { - attributes: { - async: true - } - }); - }) - .then(() => { - window.changyan.api.config({ - appid, - conf: appkey - }); - }) - .catch(error => { - // eslint-disable-next-line no-console - console.error('Failed to load Changyan', error); - }); - } -}); diff --git a/js/third-party/comments/disqus.js b/js/third-party/comments/disqus.js deleted file mode 100644 index 4d1ca9e..0000000 --- a/js/third-party/comments/disqus.js +++ /dev/null @@ -1,41 +0,0 @@ -/* global NexT, CONFIG, DISQUS */ - -document.addEventListener('page:loaded', () => { - - if (CONFIG.disqus.count) { - if (window.DISQUSWIDGETS) { - window.DISQUSWIDGETS.getCount({ reset: true }); - } else { - // Defer loading until the whole page loading is completed - NexT.utils.getScript(`https://${CONFIG.disqus.shortname}.disqus.com/count.js`, { - attributes: { id: 'dsq-count-scr', defer: true } - }); - } - } - - if (CONFIG.page.comments) { - // `disqus_config` should be a global variable - // See https://help.disqus.com/en/articles/1717084-javascript-configuration-variables - window.disqus_config = function() { - this.page.url = CONFIG.page.permalink; - this.page.identifier = CONFIG.page.path; - this.page.title = CONFIG.page.title; - if (CONFIG.disqus.i18n.disqus !== 'disqus') { - this.language = CONFIG.disqus.i18n.disqus; - } - }; - NexT.utils.loadComments('#disqus_thread').then(() => { - if (window.DISQUS) { - DISQUS.reset({ - reload: true, - config: window.disqus_config - }); - } else { - NexT.utils.getScript(`https://${CONFIG.disqus.shortname}.disqus.com/embed.js`, { - attributes: { dataset: { timestamp: '' + +new Date() } } - }); - } - }); - } - -}); diff --git a/js/third-party/comments/disqusjs.js b/js/third-party/comments/disqusjs.js deleted file mode 100644 index d8401ee..0000000 --- a/js/third-party/comments/disqusjs.js +++ /dev/null @@ -1,23 +0,0 @@ -/* global NexT, CONFIG, DisqusJS */ - -document.addEventListener('page:loaded', () => { - if (!CONFIG.page.comments) return; - - NexT.utils.loadComments('#disqus_thread') - .then(() => NexT.utils.getScript(CONFIG.disqusjs.js, { condition: window.DisqusJS })) - .then(() => { - window.dsqjs = new DisqusJS({ - api : CONFIG.disqusjs.api || 'https://disqus.com/api/', - apikey : CONFIG.disqusjs.apikey, - shortname : CONFIG.disqusjs.shortname, - url : CONFIG.page.permalink, - identifier: CONFIG.page.path, - title : CONFIG.page.title - }); - window.dsqjs.render(document.querySelector('.disqusjs-container')); - }); -}); - -document.addEventListener('pjax:send', () => { - if (window.dsqjs) window.dsqjs.destroy(); -}); diff --git a/js/third-party/comments/gitalk.js b/js/third-party/comments/gitalk.js deleted file mode 100644 index 08d07f4..0000000 --- a/js/third-party/comments/gitalk.js +++ /dev/null @@ -1,24 +0,0 @@ -/* global NexT, CONFIG, Gitalk */ - -document.addEventListener('page:loaded', () => { - if (!CONFIG.page.comments) return; - - NexT.utils.loadComments('.gitalk-container') - .then(() => NexT.utils.getScript(CONFIG.gitalk.js, { - condition: window.Gitalk - })) - .then(() => { - const gitalk = new Gitalk({ - clientID : CONFIG.gitalk.client_id, - clientSecret : CONFIG.gitalk.client_secret, - repo : CONFIG.gitalk.repo, - owner : CONFIG.gitalk.github_id, - admin : [CONFIG.gitalk.admin_user], - id : CONFIG.gitalk.path_md5, - proxy : CONFIG.gitalk.proxy, - language : CONFIG.gitalk.language || window.navigator.language, - distractionFreeMode: CONFIG.gitalk.distraction_free_mode - }); - gitalk.render(document.querySelector('.gitalk-container')); - }); -}); diff --git a/js/third-party/comments/isso.js b/js/third-party/comments/isso.js deleted file mode 100644 index 2c70601..0000000 --- a/js/third-party/comments/isso.js +++ /dev/null @@ -1,15 +0,0 @@ -/* global NexT, CONFIG */ - -document.addEventListener('page:loaded', () => { - if (!CONFIG.page.comments) return; - - NexT.utils.loadComments('#isso-thread') - .then(() => NexT.utils.getScript(`${CONFIG.isso}js/embed.min.js`, { - attributes: { - dataset: { - isso: `${CONFIG.isso}` - } - }, - parentNode: document.querySelector('#isso-thread') - })); -}); diff --git a/js/third-party/comments/livere.js b/js/third-party/comments/livere.js deleted file mode 100644 index c4bcd2e..0000000 --- a/js/third-party/comments/livere.js +++ /dev/null @@ -1,19 +0,0 @@ -/* global NexT, CONFIG, LivereTower */ - -document.addEventListener('page:loaded', () => { - if (!CONFIG.page.comments) return; - - NexT.utils.loadComments('#lv-container').then(() => { - window.livereOptions = { - refer: CONFIG.page.path.replace(/index\.html$/, '') - }; - - if (typeof LivereTower === 'function') return; - - NexT.utils.getScript('https://cdn-city.livere.com/js/embed.dist.js', { - attributes: { - async: true - } - }); - }); -}); diff --git a/js/third-party/comments/utterances.js b/js/third-party/comments/utterances.js deleted file mode 100644 index 332ee05..0000000 --- a/js/third-party/comments/utterances.js +++ /dev/null @@ -1,17 +0,0 @@ -/* global NexT, CONFIG */ - -document.addEventListener('page:loaded', () => { - if (!CONFIG.page.comments) return; - - NexT.utils.loadComments('.utterances-container') - .then(() => NexT.utils.getScript('https://utteranc.es/client.js', { - attributes: { - async : true, - crossOrigin : 'anonymous', - 'repo' : CONFIG.utterances.repo, - 'issue-term': CONFIG.utterances.issue_term, - 'theme' : CONFIG.utterances.theme - }, - parentNode: document.querySelector('.utterances-container') - })); -}); diff --git a/js/third-party/fancybox.js b/js/third-party/fancybox.js deleted file mode 100644 index bb436ab..0000000 --- a/js/third-party/fancybox.js +++ /dev/null @@ -1,38 +0,0 @@ -document.addEventListener('page:loaded', () => { - - /** - * Wrap images with fancybox. - */ - document.querySelectorAll('.post-body :not(a) > img, .post-body > img').forEach(element => { - const $image = $(element); - const imageLink = $image.attr('data-src') || $image.attr('src'); - const $imageWrapLink = $image.wrap(``).parent('a'); - if ($image.is('.post-gallery img')) { - $imageWrapLink.attr('data-fancybox', 'gallery').attr('rel', 'gallery'); - } else if ($image.is('.group-picture img')) { - $imageWrapLink.attr('data-fancybox', 'group').attr('rel', 'group'); - } else { - $imageWrapLink.attr('data-fancybox', 'default').attr('rel', 'default'); - } - - const imageTitle = $image.attr('title') || $image.attr('alt'); - if (imageTitle) { - // Do not append image-caption if pandoc has already created a figcaption - if (!$imageWrapLink.next('figcaption').length) { - $imageWrapLink.append(`

${imageTitle}

`); - } - // Make sure img title tag will show correctly in fancybox - $imageWrapLink.attr('title', imageTitle).attr('data-caption', imageTitle); - } - }); - - $.fancybox.defaults.hash = false; - $('.fancybox').fancybox({ - loop : true, - helpers: { - overlay: { - locked: false - } - } - }); -}); diff --git a/js/third-party/math/katex.js b/js/third-party/math/katex.js deleted file mode 100644 index ad745b1..0000000 --- a/js/third-party/math/katex.js +++ /dev/null @@ -1,7 +0,0 @@ -/* global NexT, CONFIG */ - -document.addEventListener('page:loaded', () => { - if (!CONFIG.enableMath) return; - - NexT.utils.getScript(CONFIG.katex.copy_tex_js).catch(() => {}); -}); diff --git a/js/third-party/math/mathjax.js b/js/third-party/math/mathjax.js deleted file mode 100644 index fe4d448..0000000 --- a/js/third-party/math/mathjax.js +++ /dev/null @@ -1,36 +0,0 @@ -/* global NexT, CONFIG, MathJax */ - -document.addEventListener('page:loaded', () => { - if (!CONFIG.enableMath) return; - - if (typeof MathJax === 'undefined') { - window.MathJax = { - tex: { - inlineMath: { '[+]': [['$', '$']] }, - tags : CONFIG.mathjax.tags - }, - options: { - renderActions: { - insertedScript: [200, () => { - document.querySelectorAll('mjx-container').forEach(node => { - const target = node.parentNode; - if (target.nodeName.toLowerCase() === 'li') { - target.parentNode.classList.add('has-jax'); - } - }); - }, '', false] - } - } - }; - NexT.utils.getScript(CONFIG.mathjax.js, { - attributes: { - defer: true - } - }); - } else { - MathJax.startup.document.state(0); - MathJax.typesetClear(); - MathJax.texReset(); - MathJax.typesetPromise(); - } -}); diff --git a/js/third-party/pace.js b/js/third-party/pace.js deleted file mode 100644 index c22d59f..0000000 --- a/js/third-party/pace.js +++ /dev/null @@ -1,7 +0,0 @@ -/* global Pace */ - -Pace.options.restartOnPushState = false; - -document.addEventListener('pjax:send', () => { - Pace.restart(); -}); diff --git a/js/third-party/quicklink.js b/js/third-party/quicklink.js deleted file mode 100644 index 2543ad1..0000000 --- a/js/third-party/quicklink.js +++ /dev/null @@ -1,37 +0,0 @@ -/* global CONFIG, quicklink */ - -(function() { - if (typeof CONFIG.quicklink.ignores === 'string') { - const ignoresStr = `[${CONFIG.quicklink.ignores}]`; - CONFIG.quicklink.ignores = JSON.parse(ignoresStr); - } - - let resetFn = null; - - const onRefresh = () => { - if (resetFn) resetFn(); - if (!CONFIG.quicklink.enable) return; - - let ignoresArr = CONFIG.quicklink.ignores || []; - if (!Array.isArray(ignoresArr)) { - ignoresArr = [ignoresArr]; - } - - resetFn = quicklink.listen({ - timeout : CONFIG.quicklink.timeout, - priority: CONFIG.quicklink.priority, - ignores : [ - uri => uri.includes('#'), - uri => uri === CONFIG.quicklink.url, - ...ignoresArr - ] - }); - }; - - if (CONFIG.quicklink.delay) { - window.addEventListener('load', onRefresh); - document.addEventListener('pjax:success', onRefresh); - } else { - document.addEventListener('page:loaded', onRefresh); - } -})(); diff --git a/js/third-party/search/algolia-search.js b/js/third-party/search/algolia-search.js deleted file mode 100644 index 12a554c..0000000 --- a/js/third-party/search/algolia-search.js +++ /dev/null @@ -1,130 +0,0 @@ -/* global instantsearch, algoliasearch, CONFIG, pjax */ - -document.addEventListener('DOMContentLoaded', () => { - const { indexName, appID, apiKey, hits } = CONFIG.algolia; - - const search = instantsearch({ - indexName, - searchClient : algoliasearch(appID, apiKey), - searchFunction: helper => { - if (document.querySelector('.search-input').value) { - helper.search(); - } - } - }); - - if (typeof pjax === 'object') { - search.on('render', () => { - pjax.refresh(document.querySelector('.algolia-hits')); - }); - } - - // Registering Widgets - search.addWidgets([ - instantsearch.widgets.configure({ - hitsPerPage: hits.per_page || 10 - }), - - instantsearch.widgets.searchBox({ - container : '.search-input-container', - placeholder : CONFIG.i18n.placeholder, - // Hide default icons of algolia search - showReset : false, - showSubmit : false, - showLoadingIndicator: false, - cssClasses : { - input: 'search-input' - } - }), - - instantsearch.widgets.stats({ - container: '.algolia-stats', - templates: { - text: data => { - const stats = CONFIG.i18n.hits_time - .replace('${hits}', data.nbHits) - .replace('${time}', data.processingTimeMS); - return `${stats} - Algolia`; - } - }, - cssClasses: { - text: 'search-stats' - } - }), - - instantsearch.widgets.hits({ - container : '.algolia-hits', - escapeHTML: false, - templates : { - item: data => { - const { title, excerpt, excerptStrip, contentStripTruncate } = data._highlightResult; - let result = `${title.value}`; - const content = excerpt || excerptStrip || contentStripTruncate; - if (content && content.value) { - const div = document.createElement('div'); - div.innerHTML = content.value; - result += `

${div.textContent.substring(0, 100)}...

`; - } - return result; - }, - empty: data => { - return `
- ${CONFIG.i18n.empty.replace('${query}', data.query)} -
`; - } - }, - cssClasses: { - list: 'search-result-list' - } - }), - - instantsearch.widgets.pagination({ - container: '.algolia-pagination', - scrollTo : false, - showFirst: false, - showLast : false, - templates: { - first : '', - last : '', - previous: '', - next : '' - }, - cssClasses: { - list : ['pagination', 'algolia-pagination'], - item : 'pagination-item', - link : 'page-number', - selectedItem: 'current', - disabledItem: 'disabled-item' - } - }) - ]); - - search.start(); - - // Handle and trigger popup window - document.querySelectorAll('.popup-trigger').forEach(element => { - element.addEventListener('click', () => { - document.body.classList.add('search-active'); - setTimeout(() => document.querySelector('.search-input').focus(), 500); - }); - }); - - // Monitor main search box - const onPopupClose = () => { - document.body.classList.remove('search-active'); - }; - - document.querySelector('.search-pop-overlay').addEventListener('click', event => { - if (event.target === document.querySelector('.search-pop-overlay')) { - onPopupClose(); - } - }); - document.querySelector('.popup-btn-close').addEventListener('click', onPopupClose); - document.addEventListener('pjax:success', onPopupClose); - window.addEventListener('keyup', event => { - if (event.key === 'Escape') { - onPopupClose(); - } - }); -}); diff --git a/js/third-party/search/local-search.js b/js/third-party/search/local-search.js deleted file mode 100644 index 92a264d..0000000 --- a/js/third-party/search/local-search.js +++ /dev/null @@ -1,99 +0,0 @@ -/* global CONFIG, pjax, LocalSearch */ - -document.addEventListener('DOMContentLoaded', () => { - if (!CONFIG.path) { - // Search DB path - console.warn('`hexo-generator-searchdb` plugin is not installed!'); - return; - } - const localSearch = new LocalSearch({ - path : CONFIG.path, - top_n_per_article: CONFIG.localsearch.top_n_per_article, - unescape : CONFIG.localsearch.unescape - }); - - const input = document.querySelector('.search-input'); - - const inputEventFunction = () => { - if (!localSearch.isfetched) return; - const searchText = input.value.trim().toLowerCase(); - const keywords = searchText.split(/[-\s]+/); - const container = document.querySelector('.search-result-container'); - let resultItems = []; - if (searchText.length > 0) { - // Perform local searching - resultItems = localSearch.getResultItems(keywords); - } - if (keywords.length === 1 && keywords[0] === '') { - container.classList.add('no-result'); - container.innerHTML = '
'; - } else if (resultItems.length === 0) { - container.classList.add('no-result'); - container.innerHTML = '
'; - } else { - resultItems.sort((left, right) => { - if (left.includedCount !== right.includedCount) { - return right.includedCount - left.includedCount; - } else if (left.hitCount !== right.hitCount) { - return right.hitCount - left.hitCount; - } - return right.id - left.id; - }); - const stats = CONFIG.i18n.hits.replace('${hits}', resultItems.length); - - container.classList.remove('no-result'); - container.innerHTML = `
${stats}
-
-
    ${resultItems.map(result => result.item).join('')}
`; - if (typeof pjax === 'object') pjax.refresh(container); - } - }; - - localSearch.highlightSearchWords(document.querySelector('.post-body')); - if (CONFIG.localsearch.preload) { - localSearch.fetchData(); - } - - if (CONFIG.localsearch.trigger === 'auto') { - input.addEventListener('input', inputEventFunction); - } else { - document.querySelector('.search-icon').addEventListener('click', inputEventFunction); - input.addEventListener('keypress', event => { - if (event.key === 'Enter') { - inputEventFunction(); - } - }); - } - window.addEventListener('search:loaded', inputEventFunction); - - // Handle and trigger popup window - document.querySelectorAll('.popup-trigger').forEach(element => { - element.addEventListener('click', () => { - document.body.classList.add('search-active'); - // Wait for search-popup animation to complete - setTimeout(() => input.focus(), 500); - if (!localSearch.isfetched) localSearch.fetchData(); - }); - }); - - // Monitor main search box - const onPopupClose = () => { - document.body.classList.remove('search-active'); - }; - - document.querySelector('.search-pop-overlay').addEventListener('click', event => { - if (event.target === document.querySelector('.search-pop-overlay')) { - onPopupClose(); - } - }); - document.querySelector('.popup-btn-close').addEventListener('click', onPopupClose); - document.addEventListener('pjax:success', () => { - localSearch.highlightSearchWords(document.querySelector('.post-body')); - onPopupClose(); - }); - window.addEventListener('keyup', event => { - if (event.key === 'Escape') { - onPopupClose(); - } - }); -}); diff --git a/js/third-party/statistics/firestore.js b/js/third-party/statistics/firestore.js deleted file mode 100644 index 3ea7ba6..0000000 --- a/js/third-party/statistics/firestore.js +++ /dev/null @@ -1,60 +0,0 @@ -/* global CONFIG, firebase */ - -firebase.initializeApp({ - apiKey : CONFIG.firestore.apiKey, - projectId: CONFIG.firestore.projectId -}); - -(function() { - const getCount = (doc, increaseCount) => { - // IncreaseCount will be false when not in article page - return doc.get().then(d => { - // Has no data, initialize count - let count = d.exists ? d.data().count : 0; - // If first view this article - if (increaseCount) { - // Increase count - count++; - doc.set({ - count - }); - } - return count; - }); - }; - - const db = firebase.firestore(); - const articles = db.collection(CONFIG.firestore.collection); - - document.addEventListener('page:loaded', () => { - - if (CONFIG.page.isPost) { - // Fix issue #118 - // https://developer.mozilla.org/en-US/docs/Web/API/Node/textContent - const title = document.querySelector('.post-title').textContent.trim(); - const doc = articles.doc(title); - let increaseCount = CONFIG.hostname === location.hostname; - if (localStorage.getItem(title)) { - increaseCount = false; - } else { - // Mark as visited - localStorage.setItem(title, true); - } - getCount(doc, increaseCount).then(count => { - document.querySelector('.firestore-visitors-count').innerText = count; - }); - } else if (CONFIG.page.isHome) { - const promises = [...document.querySelectorAll('.post-title')].map(element => { - const title = element.textContent.trim(); - const doc = articles.doc(title); - return getCount(doc); - }); - Promise.all(promises).then(counts => { - const metas = document.querySelectorAll('.firestore-visitors-count'); - counts.forEach((val, idx) => { - metas[idx].innerText = val; - }); - }); - } - }); -})(); diff --git a/js/third-party/statistics/lean-analytics.js b/js/third-party/statistics/lean-analytics.js deleted file mode 100644 index 8397112..0000000 --- a/js/third-party/statistics/lean-analytics.js +++ /dev/null @@ -1,107 +0,0 @@ -/* global CONFIG */ -/* eslint-disable no-console */ - -(function() { - const leancloudSelector = url => { - url = encodeURI(url); - return document.getElementById(url).querySelector('.leancloud-visitors-count'); - }; - - const addCount = Counter => { - const visitors = document.querySelector('.leancloud_visitors'); - const url = decodeURI(visitors.id); - const title = visitors.dataset.flagTitle; - - Counter('get', `/classes/Counter?where=${encodeURIComponent(JSON.stringify({ url }))}`) - .then(response => response.json()) - .then(({ results }) => { - if (results.length > 0) { - const counter = results[0]; - leancloudSelector(url).innerText = counter.time + 1; - Counter('put', '/classes/Counter/' + counter.objectId, { - time: { - '__op' : 'Increment', - 'amount': 1 - } - }) - .catch(error => { - console.error('Failed to save visitor count', error); - }); - } else if (CONFIG.leancloud_visitors.security) { - leancloudSelector(url).innerText = 'Counter not initialized! More info at console err msg.'; - console.error('ATTENTION! LeanCloud counter has security bug, see how to solve it here: https://github.com/theme-next/hexo-leancloud-counter-security. \n However, you can still use LeanCloud without security, by setting `security` option to `false`.'); - } else { - Counter('post', '/classes/Counter', { title, url, time: 1 }) - .then(response => response.json()) - .then(() => { - leancloudSelector(url).innerText = 1; - }) - .catch(error => { - console.error('Failed to create', error); - }); - } - }) - .catch(error => { - console.error('LeanCloud Counter Error', error); - }); - }; - - const showTime = Counter => { - const visitors = document.querySelectorAll('.leancloud_visitors'); - const entries = [...visitors].map(element => { - return decodeURI(element.id); - }); - - Counter('get', `/classes/Counter?where=${encodeURIComponent(JSON.stringify({ url: { '$in': entries } }))}`) - .then(response => response.json()) - .then(({ results }) => { - for (const url of entries) { - const target = results.find(item => item.url === url); - leancloudSelector(url).innerText = target ? target.time : 0; - } - }) - .catch(error => { - console.error('LeanCloud Counter Error', error); - }); - }; - - const { app_id, app_key, server_url } = CONFIG.leancloud_visitors; - const fetchData = api_server => { - const Counter = (method, url, data) => { - return fetch(`${api_server}/1.1${url}`, { - method, - headers: { - 'X-LC-Id' : app_id, - 'X-LC-Key' : app_key, - 'Content-Type': 'application/json' - }, - body: JSON.stringify(data) - }); - }; - if (CONFIG.page.isPost) { - if (CONFIG.hostname !== location.hostname) return; - addCount(Counter); - } else if (document.querySelectorAll('.post-title-link').length >= 1) { - showTime(Counter); - } - }; - - let api_server; - if (server_url) { - api_server = server_url; - } else if (app_id.slice(-9) === '-MdYXbMMI') { - api_server = `https://${app_id.slice(0, 8).toLowerCase()}.api.lncldglobal.com`; - } - - document.addEventListener('page:loaded', () => { - if (api_server) { - fetchData(api_server); - } else { - fetch(`https://app-router.leancloud.cn/2/route?appId=${app_id}`) - .then(response => response.json()) - .then(({ api_server }) => { - fetchData(`https://${api_server}`); - }); - } - }); -})(); diff --git a/js/third-party/tags/mermaid.js b/js/third-party/tags/mermaid.js deleted file mode 100644 index 9623dc5..0000000 --- a/js/third-party/tags/mermaid.js +++ /dev/null @@ -1,32 +0,0 @@ -/* global NexT, CONFIG, mermaid */ - -document.addEventListener('page:loaded', () => { - const mermaidElements = document.querySelectorAll('.mermaid'); - if (mermaidElements.length) { - NexT.utils.getScript(CONFIG.mermaid.js, { - condition: window.mermaid - }).then(() => { - mermaidElements.forEach(element => { - const newElement = document.createElement('div'); - newElement.innerHTML = element.innerHTML; - newElement.className = element.className; - const parent = element.parentNode; - // Fix issue #347 - // Support mermaid inside backtick code block - if (parent.matches('pre')) { - parent.parentNode.replaceChild(newElement, parent); - } else { - parent.replaceChild(newElement, element); - } - }); - mermaid.initialize({ - theme : CONFIG.darkmode && window.matchMedia('(prefers-color-scheme: dark)').matches ? CONFIG.mermaid.theme.dark : CONFIG.mermaid.theme.light, - logLevel : 4, - flowchart: { curve: 'linear' }, - gantt : { axisFormat: '%m/%d/%Y' }, - sequence : { actorMargin: 50 } - }); - mermaid.init(); - }); - } -}); diff --git a/js/third-party/tags/pdf.js b/js/third-party/tags/pdf.js deleted file mode 100644 index 7e82891..0000000 --- a/js/third-party/tags/pdf.js +++ /dev/null @@ -1,23 +0,0 @@ -/* global NexT, CONFIG, PDFObject */ - -document.addEventListener('page:loaded', () => { - if (document.querySelectorAll('.pdf-container').length) { - NexT.utils.getScript(CONFIG.pdf.object_url, { - condition: window.PDFObject - }).then(() => { - document.querySelectorAll('.pdf-container').forEach(element => { - PDFObject.embed(element.dataset.target, element, { - pdfOpenParams: { - navpanes : 0, - toolbar : 0, - statusbar: 0, - pagemode : 'thumbs', - view : 'FitH' - }, - PDFJS_URL: CONFIG.pdf.url, - height : element.dataset.height - }); - }); - }); - } -}); diff --git a/js/third-party/tags/wavedrom.js b/js/third-party/tags/wavedrom.js deleted file mode 100644 index ddd9a1d..0000000 --- a/js/third-party/tags/wavedrom.js +++ /dev/null @@ -1,13 +0,0 @@ -/* global NexT, CONFIG, WaveDrom */ - -document.addEventListener('page:loaded', () => { - NexT.utils.getScript(CONFIG.wavedrom.js, { - condition: window.WaveDrom - }).then(() => { - NexT.utils.getScript(CONFIG.wavedrom_skin.js, { - condition: window.WaveSkin - }).then(() => { - WaveDrom.ProcessAll(); - }); - }); -}); diff --git a/js/tw_cn.js b/js/tw_cn.js new file mode 100644 index 0000000..15d8d15 --- /dev/null +++ b/js/tw_cn.js @@ -0,0 +1,122 @@ +document.addEventListener('DOMContentLoaded', function () { + const { defaultEncoding, translateDelay, msgToTraditionalChinese, msgToSimplifiedChinese } = GLOBAL_CONFIG.translate + const snackbarData = GLOBAL_CONFIG.Snackbar + let currentEncoding = defaultEncoding + const targetEncodingCookie = 'translate-chn-cht' + let targetEncoding = + saveToLocal.get(targetEncodingCookie) === undefined + ? defaultEncoding + : Number(saveToLocal.get('translate-chn-cht')) + let translateButtonObject + const isSnackbar = snackbarData !== undefined + + function setLang () { + document.documentElement.lang = targetEncoding === 1 ? 'zh-TW' : 'zh-CN' + } + + function translateText (txt) { + if (txt === '' || txt == null) return '' + if (currentEncoding === 1 && targetEncoding === 2) return Simplized(txt) + else if (currentEncoding === 2 && targetEncoding === 1) { + return Traditionalized(txt) + } else return txt + } + + function translateBody (fobj) { + let objs + if (typeof fobj === 'object') objs = fobj.childNodes + else objs = document.body.childNodes + for (let i = 0; i < objs.length; i++) { + const obj = objs.item(i) + if ( + '||BR|HR|'.indexOf('|' + obj.tagName + '|') > 0 || + obj === translateButtonObject + ) { + continue + } + if (obj.title !== '' && obj.title != null) { + obj.title = translateText(obj.title) + } + if (obj.alt !== '' && obj.alt != null) obj.alt = translateText(obj.alt) + if (obj.placeholder !== '' && obj.placeholder != null) { obj.placeholder = translateText(obj.placeholder) } + if ( + obj.tagName === 'INPUT' && + obj.value !== '' && + obj.type !== 'text' && + obj.type !== 'hidden' + ) { + obj.value = translateText(obj.value) + } + if (obj.nodeType === 3) obj.data = translateText(obj.data) + else translateBody(obj) + } + } + function translatePage () { + if (targetEncoding === 1) { + currentEncoding = 1 + targetEncoding = 2 + translateButtonObject.textContent = msgToTraditionalChinese + isSnackbar && btf.snackbarShow(snackbarData.cht_to_chs) + } else if (targetEncoding === 2) { + currentEncoding = 2 + targetEncoding = 1 + translateButtonObject.textContent = msgToSimplifiedChinese + isSnackbar && btf.snackbarShow(snackbarData.chs_to_cht) + } + saveToLocal.set(targetEncodingCookie, targetEncoding, 2) + setLang() + translateBody() + } + + function JTPYStr () { + return '万与丑专业丛东丝丢两严丧个丬丰临为丽举么义乌乐乔习乡书买乱争于亏云亘亚产亩亲亵亸亿仅从仑仓仪们价众优伙会伛伞伟传伤伥伦伧伪伫体余佣佥侠侣侥侦侧侨侩侪侬俣俦俨俩俪俭债倾偬偻偾偿傥傧储傩儿兑兖党兰关兴兹养兽冁内冈册写军农冢冯冲决况冻净凄凉凌减凑凛几凤凫凭凯击凼凿刍划刘则刚创删别刬刭刽刿剀剂剐剑剥剧劝办务劢动励劲劳势勋勐勚匀匦匮区医华协单卖卢卤卧卫却卺厂厅历厉压厌厍厕厢厣厦厨厩厮县参叆叇双发变叙叠叶号叹叽吁后吓吕吗吣吨听启吴呒呓呕呖呗员呙呛呜咏咔咙咛咝咤咴咸哌响哑哒哓哔哕哗哙哜哝哟唛唝唠唡唢唣唤唿啧啬啭啮啰啴啸喷喽喾嗫呵嗳嘘嘤嘱噜噼嚣嚯团园囱围囵国图圆圣圹场坂坏块坚坛坜坝坞坟坠垄垅垆垒垦垧垩垫垭垯垱垲垴埘埙埚埝埯堑堕塆墙壮声壳壶壸处备复够头夸夹夺奁奂奋奖奥妆妇妈妩妪妫姗姜娄娅娆娇娈娱娲娴婳婴婵婶媪嫒嫔嫱嬷孙学孪宁宝实宠审宪宫宽宾寝对寻导寿将尔尘尧尴尸尽层屃屉届属屡屦屿岁岂岖岗岘岙岚岛岭岳岽岿峃峄峡峣峤峥峦崂崃崄崭嵘嵚嵛嵝嵴巅巩巯币帅师帏帐帘帜带帧帮帱帻帼幂幞干并广庄庆庐庑库应庙庞废庼廪开异弃张弥弪弯弹强归当录彟彦彻径徕御忆忏忧忾怀态怂怃怄怅怆怜总怼怿恋恳恶恸恹恺恻恼恽悦悫悬悭悯惊惧惨惩惫惬惭惮惯愍愠愤愦愿慑慭憷懑懒懔戆戋戏戗战戬户扎扑扦执扩扪扫扬扰抚抛抟抠抡抢护报担拟拢拣拥拦拧拨择挂挚挛挜挝挞挟挠挡挢挣挤挥挦捞损捡换捣据捻掳掴掷掸掺掼揸揽揿搀搁搂搅携摄摅摆摇摈摊撄撑撵撷撸撺擞攒敌敛数斋斓斗斩断无旧时旷旸昙昼昽显晋晒晓晔晕晖暂暧札术朴机杀杂权条来杨杩杰极构枞枢枣枥枧枨枪枫枭柜柠柽栀栅标栈栉栊栋栌栎栏树栖样栾桊桠桡桢档桤桥桦桧桨桩梦梼梾检棂椁椟椠椤椭楼榄榇榈榉槚槛槟槠横樯樱橥橱橹橼檐檩欢欤欧歼殁殇残殒殓殚殡殴毁毂毕毙毡毵氇气氢氩氲汇汉污汤汹沓沟没沣沤沥沦沧沨沩沪沵泞泪泶泷泸泺泻泼泽泾洁洒洼浃浅浆浇浈浉浊测浍济浏浐浑浒浓浔浕涂涌涛涝涞涟涠涡涢涣涤润涧涨涩淀渊渌渍渎渐渑渔渖渗温游湾湿溃溅溆溇滗滚滞滟滠满滢滤滥滦滨滩滪漤潆潇潋潍潜潴澜濑濒灏灭灯灵灾灿炀炉炖炜炝点炼炽烁烂烃烛烟烦烧烨烩烫烬热焕焖焘煅煳熘爱爷牍牦牵牺犊犟状犷犸犹狈狍狝狞独狭狮狯狰狱狲猃猎猕猡猪猫猬献獭玑玙玚玛玮环现玱玺珉珏珐珑珰珲琎琏琐琼瑶瑷璇璎瓒瓮瓯电画畅畲畴疖疗疟疠疡疬疮疯疱疴痈痉痒痖痨痪痫痴瘅瘆瘗瘘瘪瘫瘾瘿癞癣癫癯皑皱皲盏盐监盖盗盘眍眦眬着睁睐睑瞒瞩矫矶矾矿砀码砖砗砚砜砺砻砾础硁硅硕硖硗硙硚确硷碍碛碜碱碹磙礼祎祢祯祷祸禀禄禅离秃秆种积称秽秾稆税稣稳穑穷窃窍窑窜窝窥窦窭竖竞笃笋笔笕笺笼笾筑筚筛筜筝筹签简箓箦箧箨箩箪箫篑篓篮篱簖籁籴类籼粜粝粤粪粮糁糇紧絷纟纠纡红纣纤纥约级纨纩纪纫纬纭纮纯纰纱纲纳纴纵纶纷纸纹纺纻纼纽纾线绀绁绂练组绅细织终绉绊绋绌绍绎经绐绑绒结绔绕绖绗绘给绚绛络绝绞统绠绡绢绣绤绥绦继绨绩绪绫绬续绮绯绰绱绲绳维绵绶绷绸绹绺绻综绽绾绿缀缁缂缃缄缅缆缇缈缉缊缋缌缍缎缏缐缑缒缓缔缕编缗缘缙缚缛缜缝缞缟缠缡缢缣缤缥缦缧缨缩缪缫缬缭缮缯缰缱缲缳缴缵罂网罗罚罢罴羁羟羡翘翙翚耢耧耸耻聂聋职聍联聩聪肃肠肤肷肾肿胀胁胆胜胧胨胪胫胶脉脍脏脐脑脓脔脚脱脶脸腊腌腘腭腻腼腽腾膑臜舆舣舰舱舻艰艳艹艺节芈芗芜芦苁苇苈苋苌苍苎苏苘苹茎茏茑茔茕茧荆荐荙荚荛荜荞荟荠荡荣荤荥荦荧荨荩荪荫荬荭荮药莅莜莱莲莳莴莶获莸莹莺莼萚萝萤营萦萧萨葱蒇蒉蒋蒌蓝蓟蓠蓣蓥蓦蔷蔹蔺蔼蕲蕴薮藁藓虏虑虚虫虬虮虽虾虿蚀蚁蚂蚕蚝蚬蛊蛎蛏蛮蛰蛱蛲蛳蛴蜕蜗蜡蝇蝈蝉蝎蝼蝾螀螨蟏衅衔补衬衮袄袅袆袜袭袯装裆裈裢裣裤裥褛褴襁襕见观觃规觅视觇览觉觊觋觌觍觎觏觐觑觞触觯詟誉誊讠计订讣认讥讦讧讨让讪讫训议讯记讱讲讳讴讵讶讷许讹论讻讼讽设访诀证诂诃评诅识诇诈诉诊诋诌词诎诏诐译诒诓诔试诖诗诘诙诚诛诜话诞诟诠诡询诣诤该详诧诨诩诪诫诬语诮误诰诱诲诳说诵诶请诸诹诺读诼诽课诿谀谁谂调谄谅谆谇谈谊谋谌谍谎谏谐谑谒谓谔谕谖谗谘谙谚谛谜谝谞谟谠谡谢谣谤谥谦谧谨谩谪谫谬谭谮谯谰谱谲谳谴谵谶谷豮贝贞负贠贡财责贤败账货质贩贪贫贬购贮贯贰贱贲贳贴贵贶贷贸费贺贻贼贽贾贿赀赁赂赃资赅赆赇赈赉赊赋赌赍赎赏赐赑赒赓赔赕赖赗赘赙赚赛赜赝赞赟赠赡赢赣赪赵赶趋趱趸跃跄跖跞践跶跷跸跹跻踊踌踪踬踯蹑蹒蹰蹿躏躜躯车轧轨轩轪轫转轭轮软轰轱轲轳轴轵轶轷轸轹轺轻轼载轾轿辀辁辂较辄辅辆辇辈辉辊辋辌辍辎辏辐辑辒输辔辕辖辗辘辙辚辞辩辫边辽达迁过迈运还这进远违连迟迩迳迹适选逊递逦逻遗遥邓邝邬邮邹邺邻郁郄郏郐郑郓郦郧郸酝酦酱酽酾酿释里鉅鉴銮錾钆钇针钉钊钋钌钍钎钏钐钑钒钓钔钕钖钗钘钙钚钛钝钞钟钠钡钢钣钤钥钦钧钨钩钪钫钬钭钮钯钰钱钲钳钴钵钶钷钸钹钺钻钼钽钾钿铀铁铂铃铄铅铆铈铉铊铋铍铎铏铐铑铒铕铗铘铙铚铛铜铝铞铟铠铡铢铣铤铥铦铧铨铪铫铬铭铮铯铰铱铲铳铴铵银铷铸铹铺铻铼铽链铿销锁锂锃锄锅锆锇锈锉锊锋锌锍锎锏锐锑锒锓锔锕锖锗错锚锜锞锟锠锡锢锣锤锥锦锨锩锫锬锭键锯锰锱锲锳锴锵锶锷锸锹锺锻锼锽锾锿镀镁镂镃镆镇镈镉镊镌镍镎镏镐镑镒镕镖镗镙镚镛镜镝镞镟镠镡镢镣镤镥镦镧镨镩镪镫镬镭镮镯镰镱镲镳镴镶长门闩闪闫闬闭问闯闰闱闲闳间闵闶闷闸闹闺闻闼闽闾闿阀阁阂阃阄阅阆阇阈阉阊阋阌阍阎阏阐阑阒阓阔阕阖阗阘阙阚阛队阳阴阵阶际陆陇陈陉陕陧陨险随隐隶隽难雏雠雳雾霁霉霭靓静靥鞑鞒鞯鞴韦韧韨韩韪韫韬韵页顶顷顸项顺须顼顽顾顿颀颁颂颃预颅领颇颈颉颊颋颌颍颎颏颐频颒颓颔颕颖颗题颙颚颛颜额颞颟颠颡颢颣颤颥颦颧风飏飐飑飒飓飔飕飖飗飘飙飚飞飨餍饤饥饦饧饨饩饪饫饬饭饮饯饰饱饲饳饴饵饶饷饸饹饺饻饼饽饾饿馀馁馂馃馄馅馆馇馈馉馊馋馌馍馎馏馐馑馒馓馔馕马驭驮驯驰驱驲驳驴驵驶驷驸驹驺驻驼驽驾驿骀骁骂骃骄骅骆骇骈骉骊骋验骍骎骏骐骑骒骓骔骕骖骗骘骙骚骛骜骝骞骟骠骡骢骣骤骥骦骧髅髋髌鬓魇魉鱼鱽鱾鱿鲀鲁鲂鲄鲅鲆鲇鲈鲉鲊鲋鲌鲍鲎鲏鲐鲑鲒鲓鲔鲕鲖鲗鲘鲙鲚鲛鲜鲝鲞鲟鲠鲡鲢鲣鲤鲥鲦鲧鲨鲩鲪鲫鲬鲭鲮鲯鲰鲱鲲鲳鲴鲵鲶鲷鲸鲹鲺鲻鲼鲽鲾鲿鳀鳁鳂鳃鳄鳅鳆鳇鳈鳉鳊鳋鳌鳍鳎鳏鳐鳑鳒鳓鳔鳕鳖鳗鳘鳙鳛鳜鳝鳞鳟鳠鳡鳢鳣鸟鸠鸡鸢鸣鸤鸥鸦鸧鸨鸩鸪鸫鸬鸭鸮鸯鸰鸱鸲鸳鸴鸵鸶鸷鸸鸹鸺鸻鸼鸽鸾鸿鹀鹁鹂鹃鹄鹅鹆鹇鹈鹉鹊鹋鹌鹍鹎鹏鹐鹑鹒鹓鹔鹕鹖鹗鹘鹚鹛鹜鹝鹞鹟鹠鹡鹢鹣鹤鹥鹦鹧鹨鹩鹪鹫鹬鹭鹯鹰鹱鹲鹳鹴鹾麦麸黄黉黡黩黪黾龙历志制一台皋准复猛钟注范签' + } + function FTPYStr () { + return '萬與醜專業叢東絲丟兩嚴喪個爿豐臨為麗舉麼義烏樂喬習鄉書買亂爭於虧雲亙亞產畝親褻嚲億僅從侖倉儀們價眾優夥會傴傘偉傳傷倀倫傖偽佇體餘傭僉俠侶僥偵側僑儈儕儂俁儔儼倆儷儉債傾傯僂僨償儻儐儲儺兒兌兗黨蘭關興茲養獸囅內岡冊寫軍農塚馮衝決況凍淨淒涼淩減湊凜幾鳳鳧憑凱擊氹鑿芻劃劉則剛創刪別剗剄劊劌剴劑剮劍剝劇勸辦務勱動勵勁勞勢勳猛勩勻匭匱區醫華協單賣盧鹵臥衛卻巹廠廳曆厲壓厭厙廁廂厴廈廚廄廝縣參靉靆雙發變敘疊葉號歎嘰籲後嚇呂嗎唚噸聽啟吳嘸囈嘔嚦唄員咼嗆嗚詠哢嚨嚀噝吒噅鹹呱響啞噠嘵嗶噦嘩噲嚌噥喲嘜嗊嘮啢嗩唕喚呼嘖嗇囀齧囉嘽嘯噴嘍嚳囁嗬噯噓嚶囑嚕劈囂謔團園囪圍圇國圖圓聖壙場阪壞塊堅壇壢壩塢墳墜壟壟壚壘墾坰堊墊埡墶壋塏堖塒塤堝墊垵塹墮壪牆壯聲殼壺壼處備複夠頭誇夾奪奩奐奮獎奧妝婦媽嫵嫗媯姍薑婁婭嬈嬌孌娛媧嫻嫿嬰嬋嬸媼嬡嬪嬙嬤孫學孿寧寶實寵審憲宮寬賓寢對尋導壽將爾塵堯尷屍盡層屭屜屆屬屢屨嶼歲豈嶇崗峴嶴嵐島嶺嶽崠巋嶨嶧峽嶢嶠崢巒嶗崍嶮嶄嶸嶔崳嶁脊巔鞏巰幣帥師幃帳簾幟帶幀幫幬幘幗冪襆幹並廣莊慶廬廡庫應廟龐廢廎廩開異棄張彌弳彎彈強歸當錄彠彥徹徑徠禦憶懺憂愾懷態慫憮慪悵愴憐總懟懌戀懇惡慟懨愷惻惱惲悅愨懸慳憫驚懼慘懲憊愜慚憚慣湣慍憤憒願懾憖怵懣懶懍戇戔戲戧戰戩戶紮撲扡執擴捫掃揚擾撫拋摶摳掄搶護報擔擬攏揀擁攔擰撥擇掛摯攣掗撾撻挾撓擋撟掙擠揮撏撈損撿換搗據撚擄摑擲撣摻摜摣攬撳攙擱摟攪攜攝攄擺搖擯攤攖撐攆擷擼攛擻攢敵斂數齋斕鬥斬斷無舊時曠暘曇晝曨顯晉曬曉曄暈暉暫曖劄術樸機殺雜權條來楊榪傑極構樅樞棗櫪梘棖槍楓梟櫃檸檉梔柵標棧櫛櫳棟櫨櫟欄樹棲樣欒棬椏橈楨檔榿橋樺檜槳樁夢檮棶檢欞槨櫝槧欏橢樓欖櫬櫚櫸檟檻檳櫧橫檣櫻櫫櫥櫓櫞簷檁歡歟歐殲歿殤殘殞殮殫殯毆毀轂畢斃氈毿氌氣氫氬氳彙漢汙湯洶遝溝沒灃漚瀝淪滄渢溈滬濔濘淚澩瀧瀘濼瀉潑澤涇潔灑窪浹淺漿澆湞溮濁測澮濟瀏滻渾滸濃潯濜塗湧濤澇淶漣潿渦溳渙滌潤澗漲澀澱淵淥漬瀆漸澠漁瀋滲溫遊灣濕潰濺漵漊潷滾滯灩灄滿瀅濾濫灤濱灘澦濫瀠瀟瀲濰潛瀦瀾瀨瀕灝滅燈靈災燦煬爐燉煒熗點煉熾爍爛烴燭煙煩燒燁燴燙燼熱煥燜燾煆糊溜愛爺牘犛牽犧犢強狀獷獁猶狽麅獮獰獨狹獅獪猙獄猻獫獵獼玀豬貓蝟獻獺璣璵瑒瑪瑋環現瑲璽瑉玨琺瓏璫琿璡璉瑣瓊瑤璦璿瓔瓚甕甌電畫暢佘疇癤療瘧癘瘍鬁瘡瘋皰屙癰痙癢瘂癆瘓癇癡癉瘮瘞瘺癟癱癮癭癩癬癲臒皚皺皸盞鹽監蓋盜盤瞘眥矓著睜睞瞼瞞矚矯磯礬礦碭碼磚硨硯碸礪礱礫礎硜矽碩硤磽磑礄確鹼礙磧磣堿镟滾禮禕禰禎禱禍稟祿禪離禿稈種積稱穢穠穭稅穌穩穡窮竊竅窯竄窩窺竇窶豎競篤筍筆筧箋籠籩築篳篩簹箏籌簽簡籙簀篋籜籮簞簫簣簍籃籬籪籟糴類秈糶糲粵糞糧糝餱緊縶糸糾紆紅紂纖紇約級紈纊紀紉緯紜紘純紕紗綱納紝縱綸紛紙紋紡紵紖紐紓線紺絏紱練組紳細織終縐絆紼絀紹繹經紿綁絨結絝繞絰絎繪給絢絳絡絕絞統綆綃絹繡綌綏絛繼綈績緒綾緓續綺緋綽緔緄繩維綿綬繃綢綯綹綣綜綻綰綠綴緇緙緗緘緬纜緹緲緝縕繢緦綞緞緶線緱縋緩締縷編緡緣縉縛縟縝縫縗縞纏縭縊縑繽縹縵縲纓縮繆繅纈繚繕繒韁繾繰繯繳纘罌網羅罰罷羆羈羥羨翹翽翬耮耬聳恥聶聾職聹聯聵聰肅腸膚膁腎腫脹脅膽勝朧腖臚脛膠脈膾髒臍腦膿臠腳脫腡臉臘醃膕齶膩靦膃騰臏臢輿艤艦艙艫艱豔艸藝節羋薌蕪蘆蓯葦藶莧萇蒼苧蘇檾蘋莖蘢蔦塋煢繭荊薦薘莢蕘蓽蕎薈薺蕩榮葷滎犖熒蕁藎蓀蔭蕒葒葤藥蒞蓧萊蓮蒔萵薟獲蕕瑩鶯蓴蘀蘿螢營縈蕭薩蔥蕆蕢蔣蔞藍薊蘺蕷鎣驀薔蘞藺藹蘄蘊藪槁蘚虜慮虛蟲虯蟣雖蝦蠆蝕蟻螞蠶蠔蜆蠱蠣蟶蠻蟄蛺蟯螄蠐蛻蝸蠟蠅蟈蟬蠍螻蠑螿蟎蠨釁銜補襯袞襖嫋褘襪襲襏裝襠褌褳襝褲襇褸襤繈襴見觀覎規覓視覘覽覺覬覡覿覥覦覯覲覷觴觸觶讋譽謄訁計訂訃認譏訐訌討讓訕訖訓議訊記訒講諱謳詎訝訥許訛論訩訟諷設訪訣證詁訶評詛識詗詐訴診詆謅詞詘詔詖譯詒誆誄試詿詩詰詼誠誅詵話誕詬詮詭詢詣諍該詳詫諢詡譸誡誣語誚誤誥誘誨誑說誦誒請諸諏諾讀諑誹課諉諛誰諗調諂諒諄誶談誼謀諶諜謊諫諧謔謁謂諤諭諼讒諮諳諺諦謎諞諝謨讜謖謝謠謗諡謙謐謹謾謫譾謬譚譖譙讕譜譎讞譴譫讖穀豶貝貞負貟貢財責賢敗賬貨質販貪貧貶購貯貫貳賤賁貰貼貴貺貸貿費賀貽賊贄賈賄貲賃賂贓資賅贐賕賑賚賒賦賭齎贖賞賜贔賙賡賠賧賴賵贅賻賺賽賾贗讚贇贈贍贏贛赬趙趕趨趲躉躍蹌蹠躒踐躂蹺蹕躚躋踴躊蹤躓躑躡蹣躕躥躪躦軀車軋軌軒軑軔轉軛輪軟轟軲軻轤軸軹軼軤軫轢軺輕軾載輊轎輈輇輅較輒輔輛輦輩輝輥輞輬輟輜輳輻輯轀輸轡轅轄輾轆轍轔辭辯辮邊遼達遷過邁運還這進遠違連遲邇逕跡適選遜遞邐邏遺遙鄧鄺鄔郵鄒鄴鄰鬱郤郟鄶鄭鄆酈鄖鄲醞醱醬釅釃釀釋裏钜鑒鑾鏨釓釔針釘釗釙釕釷釺釧釤鈒釩釣鍆釹鍚釵鈃鈣鈈鈦鈍鈔鍾鈉鋇鋼鈑鈐鑰欽鈞鎢鉤鈧鈁鈥鈄鈕鈀鈺錢鉦鉗鈷缽鈳鉕鈽鈸鉞鑽鉬鉭鉀鈿鈾鐵鉑鈴鑠鉛鉚鈰鉉鉈鉍鈹鐸鉶銬銠鉺銪鋏鋣鐃銍鐺銅鋁銱銦鎧鍘銖銑鋌銩銛鏵銓鉿銚鉻銘錚銫鉸銥鏟銃鐋銨銀銣鑄鐒鋪鋙錸鋱鏈鏗銷鎖鋰鋥鋤鍋鋯鋨鏽銼鋝鋒鋅鋶鐦鐧銳銻鋃鋟鋦錒錆鍺錯錨錡錁錕錩錫錮鑼錘錐錦鍁錈錇錟錠鍵鋸錳錙鍥鍈鍇鏘鍶鍔鍤鍬鍾鍛鎪鍠鍰鎄鍍鎂鏤鎡鏌鎮鎛鎘鑷鐫鎳鎿鎦鎬鎊鎰鎔鏢鏜鏍鏰鏞鏡鏑鏃鏇鏐鐔钁鐐鏷鑥鐓鑭鐠鑹鏹鐙鑊鐳鐶鐲鐮鐿鑔鑣鑞鑲長門閂閃閆閈閉問闖閏闈閑閎間閔閌悶閘鬧閨聞闥閩閭闓閥閣閡閫鬮閱閬闍閾閹閶鬩閿閽閻閼闡闌闃闠闊闋闔闐闒闕闞闤隊陽陰陣階際陸隴陳陘陝隉隕險隨隱隸雋難雛讎靂霧霽黴靄靚靜靨韃鞽韉韝韋韌韍韓韙韞韜韻頁頂頃頇項順須頊頑顧頓頎頒頌頏預顱領頗頸頡頰頲頜潁熲頦頤頻頮頹頷頴穎顆題顒顎顓顏額顳顢顛顙顥纇顫顬顰顴風颺颭颮颯颶颸颼颻飀飄飆飆飛饗饜飣饑飥餳飩餼飪飫飭飯飲餞飾飽飼飿飴餌饒餉餄餎餃餏餅餑餖餓餘餒餕餜餛餡館餷饋餶餿饞饁饃餺餾饈饉饅饊饌饢馬馭馱馴馳驅馹駁驢駔駛駟駙駒騶駐駝駑駕驛駘驍罵駰驕驊駱駭駢驫驪騁驗騂駸駿騏騎騍騅騌驌驂騙騭騤騷騖驁騮騫騸驃騾驄驏驟驥驦驤髏髖髕鬢魘魎魚魛魢魷魨魯魴魺鮁鮃鯰鱸鮋鮓鮒鮊鮑鱟鮍鮐鮭鮚鮳鮪鮞鮦鰂鮜鱠鱭鮫鮮鮺鯗鱘鯁鱺鰱鰹鯉鰣鰷鯀鯊鯇鮶鯽鯒鯖鯪鯕鯫鯡鯤鯧鯝鯢鯰鯛鯨鯵鯴鯔鱝鰈鰏鱨鯷鰮鰃鰓鱷鰍鰒鰉鰁鱂鯿鰠鼇鰭鰨鰥鰩鰟鰜鰳鰾鱈鱉鰻鰵鱅鰼鱖鱔鱗鱒鱯鱤鱧鱣鳥鳩雞鳶鳴鳲鷗鴉鶬鴇鴆鴣鶇鸕鴨鴞鴦鴒鴟鴝鴛鴬鴕鷥鷙鴯鴰鵂鴴鵃鴿鸞鴻鵐鵓鸝鵑鵠鵝鵒鷳鵜鵡鵲鶓鵪鶤鵯鵬鵮鶉鶊鵷鷫鶘鶡鶚鶻鶿鶥鶩鷊鷂鶲鶹鶺鷁鶼鶴鷖鸚鷓鷚鷯鷦鷲鷸鷺鸇鷹鸌鸏鸛鸘鹺麥麩黃黌黶黷黲黽龍歷誌製壹臺臯準復勐鐘註範籤' + } + function Traditionalized (cc) { + let str = '' + const ss = JTPYStr() + const tt = FTPYStr() + for (let i = 0; i < cc.length; i++) { + if (cc.charCodeAt(i) > 10000 && ss.indexOf(cc.charAt(i)) !== -1) { + str += tt.charAt(ss.indexOf(cc.charAt(i))) + } else str += cc.charAt(i) + } + return str + } + function Simplized (cc) { + let str = '' + const ss = JTPYStr() + const tt = FTPYStr() + for (let i = 0; i < cc.length; i++) { + if (cc.charCodeAt(i) > 10000 && tt.indexOf(cc.charAt(i)) !== -1) { + str += ss.charAt(tt.indexOf(cc.charAt(i))) + } else str += cc.charAt(i) + } + return str + } + + function translateInitialization () { + translateButtonObject = document.getElementById('translateLink') + if (translateButtonObject) { + if (currentEncoding !== targetEncoding) { + translateButtonObject.textContent = + targetEncoding === 1 + ? msgToSimplifiedChinese + : msgToTraditionalChinese + setLang() + setTimeout(translateBody, translateDelay) + } + } + } + + window.translateFn = { + translatePage, + Traditionalized, + Simplized + } + + translateInitialization() + document.addEventListener('pjax:complete', translateInitialization) +}) diff --git a/js/utils.js b/js/utils.js index ee10f61..2c8242c 100644 --- a/js/utils.js +++ b/js/utils.js @@ -1,452 +1,296 @@ -/* global NexT, CONFIG */ - -HTMLElement.prototype.wrap = function(wrapper) { - this.parentNode.insertBefore(wrapper, this); - this.parentNode.removeChild(this); - wrapper.appendChild(this); -}; - -(function() { - const onPageLoaded = () => document.dispatchEvent( - new Event('page:loaded', { - bubbles: true - }) - ); - - if (document.readyState === 'loading') { - document.addEventListener('readystatechange', onPageLoaded, { once: true }); - } else { - onPageLoaded(); - } - document.addEventListener('pjax:success', onPageLoaded); -})(); - -NexT.utils = { - - registerExtURL: function() { - document.querySelectorAll('span.exturl').forEach(element => { - const link = document.createElement('a'); - // https://stackoverflow.com/questions/30106476/using-javascripts-atob-to-decode-base64-doesnt-properly-decode-utf-8-strings - link.href = decodeURIComponent(atob(element.dataset.url).split('').map(c => { - return '%' + ('00' + c.charCodeAt(0).toString(16)).slice(-2); - }).join('')); - link.rel = 'noopener external nofollow noreferrer'; - link.target = '_blank'; - link.className = element.className; - link.title = element.title; - link.innerHTML = element.innerHTML; - element.parentNode.replaceChild(link, element); - }); +const btf = { + debounce: (func, wait = 0, immediate = false) => { + let timeout + return (...args) => { + const later = () => { + timeout = null + if (!immediate) func(...args) + } + const callNow = immediate && !timeout + clearTimeout(timeout) + timeout = setTimeout(later, wait) + if (callNow) func(...args) + } }, - /** - * One-click copy code support. - */ - registerCopyCode: function() { - let figure = document.querySelectorAll('figure.highlight'); - if (figure.length === 0) figure = document.querySelectorAll('pre:not(.mermaid)'); - figure.forEach(element => { - element.querySelectorAll('.code .line span').forEach(span => { - span.classList.forEach(name => { - span.classList.replace(name, `hljs-${name}`); - }); - }); - if (!CONFIG.copycode.enable) return; - let target = element; - if (CONFIG.copycode.style !== 'mac') target = element.querySelector('.table-container') || element; - target.insertAdjacentHTML('beforeend', '
'); - const button = element.querySelector('.copy-btn'); - button.addEventListener('click', () => { - const lines = element.querySelector('.code') || element.querySelector('code'); - const code = lines.innerText; - if (navigator.clipboard) { - // https://caniuse.com/mdn-api_clipboard_writetext - navigator.clipboard.writeText(code).then(() => { - button.querySelector('i').className = 'fa fa-check-circle fa-fw'; - }, () => { - button.querySelector('i').className = 'fa fa-times-circle fa-fw'; - }); - } else { - const ta = document.createElement('textarea'); - ta.style.top = window.scrollY + 'px'; // Prevent page scrolling - ta.style.position = 'absolute'; - ta.style.opacity = '0'; - ta.readOnly = true; - ta.value = code; - document.body.append(ta); - ta.select(); - ta.setSelectionRange(0, code.length); - ta.readOnly = false; - const result = document.execCommand('copy'); - button.querySelector('i').className = result ? 'fa fa-check-circle fa-fw' : 'fa fa-times-circle fa-fw'; - ta.blur(); // For iOS - button.blur(); - document.body.removeChild(ta); - } - }); - element.addEventListener('mouseleave', () => { - setTimeout(() => { - button.querySelector('i').className = 'fa fa-copy fa-fw'; - }, 300); - }); - }); - }, + throttle: function (func, wait, options = {}) { + let timeout, context, args + let previous = 0 - wrapTableWithBox: function() { - document.querySelectorAll('table').forEach(element => { - const box = document.createElement('div'); - box.className = 'table-container'; - element.wrap(box); - }); - }, + const later = () => { + previous = options.leading === false ? 0 : new Date().getTime() + timeout = null + func.apply(context, args) + if (!timeout) context = args = null + } - registerVideoIframe: function() { - document.querySelectorAll('iframe').forEach(element => { - const supported = [ - 'www.youtube.com', - 'player.vimeo.com', - 'player.youku.com', - 'player.bilibili.com', - 'www.tudou.com' - ].some(host => element.src.includes(host)); - if (supported && !element.parentNode.matches('.video-container')) { - const box = document.createElement('div'); - box.className = 'video-container'; - element.wrap(box); - const width = Number(element.width); - const height = Number(element.height); - if (width && height) { - box.style.paddingTop = (height / width * 100) + '%'; + const throttled = (...params) => { + const now = new Date().getTime() + if (!previous && options.leading === false) previous = now + const remaining = wait - (now - previous) + context = this + args = params + if (remaining <= 0 || remaining > wait) { + if (timeout) { + clearTimeout(timeout) + timeout = null } + previous = now + func.apply(context, args) + if (!timeout) context = args = null + } else if (!timeout && options.trailing !== false) { + timeout = setTimeout(later, remaining) } - }); + } + + return throttled }, - updateActiveNav: function() { - if (!Array.isArray(NexT.utils.sections)) return; - let index = NexT.utils.sections.findIndex(element => { - return element && element.getBoundingClientRect().top > 10; - }); - if (index === -1) { - index = NexT.utils.sections.length - 1; - } else if (index > 0) { - index--; + sidebarPaddingR: () => { + const innerWidth = window.innerWidth + const clientWidth = document.body.clientWidth + const paddingRight = innerWidth - clientWidth + if (innerWidth !== clientWidth) { + document.body.style.paddingRight = paddingRight + 'px' } - this.activateNavByIndex(index); }, - registerScrollPercent: function() { - const backToTop = document.querySelector('.back-to-top'); - const readingProgressBar = document.querySelector('.reading-progress-bar'); - // For init back to top in sidebar if page was scrolled after page refresh. - window.addEventListener('scroll', () => { - if (backToTop || readingProgressBar) { - const contentHeight = document.body.scrollHeight - window.innerHeight; - const scrollPercent = contentHeight > 0 ? Math.min(100 * window.scrollY / contentHeight, 100) : 0; - if (backToTop) { - backToTop.classList.toggle('back-to-top-on', Math.round(scrollPercent) >= 5); - backToTop.querySelector('span').innerText = Math.round(scrollPercent) + '%'; - } - if (readingProgressBar) { - readingProgressBar.style.setProperty('--progress', scrollPercent.toFixed(2) + '%'); - } - } - this.updateActiveNav(); - }, { passive: true }); - - backToTop && backToTop.addEventListener('click', () => { - window.anime({ - targets : document.scrollingElement, - duration : 500, - easing : 'linear', - scrollTop: 0 - }); - }); + snackbarShow: (text, showAction = false, duration = 2000) => { + const { position, bgLight, bgDark } = GLOBAL_CONFIG.Snackbar + const bg = document.documentElement.getAttribute('data-theme') === 'light' ? bgLight : bgDark + Snackbar.show({ + text, + backgroundColor: bg, + showAction, + duration, + pos: position, + customClass: 'snackbar-css' + }) }, - /** - * Tabs tag listener (without twitter bootstrap). - */ - registerTabsTag: function() { - // Binding `nav-tabs` & `tab-content` by real time permalink changing. - document.querySelectorAll('.tabs ul.nav-tabs .tab').forEach(element => { - element.addEventListener('click', event => { - event.preventDefault(); - // Prevent selected tab to select again. - if (element.classList.contains('active')) return; - const nav = element.parentNode; - // Get the height of `tab-pane` which is activated before, and set it as the height of `tab-content` with extra margin / paddings. - const tabContent = nav.nextElementSibling; - tabContent.style.overflow = 'hidden'; - tabContent.style.transition = 'height 1s'; - // Comment system selection tab does not contain .active class. - const activeTab = tabContent.querySelector('.active') || tabContent.firstElementChild; - // Hight might be `auto`. - const prevHeight = parseInt(window.getComputedStyle(activeTab).height.replace('px', ''), 10) || 0; - const paddingTop = parseInt(window.getComputedStyle(activeTab).paddingTop.replace('px', ''), 10); - const marginBottom = parseInt(window.getComputedStyle(activeTab.firstElementChild).marginBottom.replace('px', ''), 10); - tabContent.style.height = prevHeight + paddingTop + marginBottom + 'px'; - // Add & Remove active class on `nav-tabs` & `tab-content`. - [...nav.children].forEach(target => { - target.classList.toggle('active', target === element); - }); - // https://stackoverflow.com/questions/20306204/using-queryselector-with-ids-that-are-numbers - const tActive = document.getElementById(element.querySelector('a').getAttribute('href').replace('#', '')); - [...tActive.parentNode.children].forEach(target => { - target.classList.toggle('active', target === tActive); - }); - // Trigger event - tActive.dispatchEvent(new Event('tabs:click', { - bubbles: true - })); - // Get the height of `tab-pane` which is activated now. - const hasScrollBar = document.body.scrollHeight > (window.innerHeight || document.documentElement.clientHeight); - const currHeight = parseInt(window.getComputedStyle(tabContent.querySelector('.active')).height.replace('px', ''), 10); - // Reset the height of `tab-content` and see the animation. - tabContent.style.height = currHeight + paddingTop + marginBottom + 'px'; - // Change the height of `tab-content` may cause scrollbar show / disappear, which may result in the change of the `tab-pane`'s height - setTimeout(() => { - if ((document.body.scrollHeight > (window.innerHeight || document.documentElement.clientHeight)) !== hasScrollBar) { - tabContent.style.transition = 'height 0.3s linear'; - // After the animation, we need reset the height of `tab-content` again. - const currHeightAfterScrollBarChange = parseInt(window.getComputedStyle(tabContent.querySelector('.active')).height.replace('px', ''), 10); - tabContent.style.height = currHeightAfterScrollBarChange + paddingTop + marginBottom + 'px'; - } - // Remove all the inline styles, and let the height be adaptive again. - setTimeout(() => { - tabContent.style.transition = ''; - tabContent.style.height = ''; - }, 250); - }, 1000); - if (!CONFIG.stickytabs) return; - const offset = nav.parentNode.getBoundingClientRect().top + window.scrollY + 10; - window.anime({ - targets : document.scrollingElement, - duration : 500, - easing : 'linear', - scrollTop: offset - }); - }); - }); - - window.dispatchEvent(new Event('tabs:register')); + diffDate: (d, more = false) => { + const dateNow = new Date() + const datePost = new Date(d) + const dateDiff = dateNow.getTime() - datePost.getTime() + const minute = 1000 * 60 + const hour = minute * 60 + const day = hour * 24 + const month = day * 30 + const { dateSuffix } = GLOBAL_CONFIG + + if (!more) return parseInt(dateDiff / day) + + const monthCount = dateDiff / month + const dayCount = dateDiff / day + const hourCount = dateDiff / hour + const minuteCount = dateDiff / minute + + if (monthCount > 12) return datePost.toISOString().slice(0, 10) + if (monthCount >= 1) return `${parseInt(monthCount)} ${dateSuffix.month}` + if (dayCount >= 1) return `${parseInt(dayCount)} ${dateSuffix.day}` + if (hourCount >= 1) return `${parseInt(hourCount)} ${dateSuffix.hour}` + if (minuteCount >= 1) return `${parseInt(minuteCount)} ${dateSuffix.min}` + return dateSuffix.just }, - registerCanIUseTag: function() { - // Get responsive height passed from iframe. - window.addEventListener('message', ({ data }) => { - if (typeof data === 'string' && data.includes('ciu_embed')) { - const featureID = data.split(':')[1]; - const height = data.split(':')[2]; - document.querySelector(`iframe[data-feature=${featureID}]`).style.height = parseInt(height, 10) + 5 + 'px'; - } - }, false); + loadComment: (dom, callback) => { + if ('IntersectionObserver' in window) { + const observerItem = new IntersectionObserver((entries) => { + if (entries[0].isIntersecting) { + callback() + observerItem.disconnect() + } + }, { threshold: [0] }) + observerItem.observe(dom) + } else { + callback() + } }, - registerActiveMenuItem: function() { - document.querySelectorAll('.menu-item a[href]').forEach(target => { - const isSamePath = target.pathname === location.pathname || target.pathname === location.pathname.replace('index.html', ''); - const isSubPath = !CONFIG.root.startsWith(target.pathname) && location.pathname.startsWith(target.pathname); - target.classList.toggle('menu-item-active', target.hostname === location.hostname && (isSamePath || isSubPath)); - }); + scrollToDest: (pos, time = 500) => { + const currentPos = window.pageYOffset + const isNavFixed = document.getElementById('page-header').classList.contains('fixed') + if (currentPos > pos || isNavFixed) pos = pos - 70 + + if ('scrollBehavior' in document.documentElement.style) { + window.scrollTo({ + top: pos, + behavior: 'smooth' + }) + return + } + + let start = null + pos = +pos + window.requestAnimationFrame(function step (currentTime) { + start = !start ? currentTime : start + const progress = currentTime - start + if (currentPos < pos) { + window.scrollTo(0, ((pos - currentPos) * progress / time) + currentPos) + } else { + window.scrollTo(0, currentPos - ((currentPos - pos) * progress / time)) + } + if (progress < time) { + window.requestAnimationFrame(step) + } else { + window.scrollTo(0, pos) + } + }) }, - registerLangSelect: function() { - const selects = document.querySelectorAll('.lang-select'); - selects.forEach(sel => { - sel.value = CONFIG.page.lang; - sel.addEventListener('change', () => { - const target = sel.options[sel.selectedIndex]; - document.querySelectorAll('.lang-select-label span').forEach(span => { - span.innerText = target.text; - }); - // Disable Pjax to force refresh translation of menu item - window.location.href = target.dataset.href; - }); - }); + animateIn: (ele, text) => { + ele.style.display = 'block' + ele.style.animation = text }, - registerSidebarTOC: function() { - this.sections = [...document.querySelectorAll('.post-toc:not(.placeholder-toc) li a.nav-link')].map(element => { - const target = document.getElementById(decodeURI(element.getAttribute('href')).replace('#', '')); - // TOC item animation navigate. - element.addEventListener('click', event => { - event.preventDefault(); - const offset = target.getBoundingClientRect().top + window.scrollY; - window.anime({ - targets : document.scrollingElement, - duration : 500, - easing : 'linear', - scrollTop: offset, - complete : () => { - history.pushState(null, document.title, element.href); - } - }); - }); - return target; - }); - this.updateActiveNav(); + animateOut: (ele, text) => { + ele.addEventListener('animationend', function f () { + ele.style.display = '' + ele.style.animation = '' + ele.removeEventListener('animationend', f) + }) + ele.style.animation = text }, - registerPostReward: function() { - const button = document.querySelector('.reward-container button'); - if (!button) return; - button.addEventListener('click', () => { - document.querySelector('.post-reward').classList.toggle('active'); - }); + wrap: (selector, eleType, options) => { + const createEle = document.createElement(eleType) + for (const [key, value] of Object.entries(options)) { + createEle.setAttribute(key, value) + } + selector.parentNode.insertBefore(createEle, selector) + createEle.appendChild(selector) }, - activateNavByIndex: function(index) { - const nav = document.querySelector('.post-toc:not(.placeholder-toc) .nav'); - if (!nav) return; + isHidden: ele => ele.offsetHeight === 0 && ele.offsetWidth === 0, - const navItemList = nav.querySelectorAll('.nav-item'); - const target = navItemList[index]; - if (!target || target.classList.contains('active-current')) return; + getEleTop: ele => { + let actualTop = ele.offsetTop + let current = ele.offsetParent - const singleHeight = navItemList[navItemList.length - 1].offsetHeight; + while (current !== null) { + actualTop += current.offsetTop + current = current.offsetParent + } - nav.querySelectorAll('.active').forEach(navItem => { - navItem.classList.remove('active', 'active-current'); - }); - target.classList.add('active', 'active-current'); + return actualTop + }, - let activateEle = target.querySelector('.nav-child') || target.parentElement; - let navChildHeight = 0; + loadLightbox: ele => { + const service = GLOBAL_CONFIG.lightbox - while (nav.contains(activateEle)) { - if (activateEle.classList.contains('nav-item')) { - activateEle.classList.add('active'); - } else { // .nav-child or .nav - // scrollHeight isn't reliable for transitioning child items. - // The last nav-item in a list has a margin-bottom of 5px. - navChildHeight += (singleHeight * activateEle.childElementCount) + 5; - activateEle.style.setProperty('--height', `${navChildHeight}px`); - } - activateEle = activateEle.parentElement; + if (service === 'mediumZoom') { + mediumZoom(ele, { background: 'var(--zoom-bg)' }) } - // Scrolling to center active TOC element if TOC content is taller then viewport. - const tocElement = document.querySelector(CONFIG.scheme === 'Pisces' || CONFIG.scheme === 'Gemini' ? '.sidebar-panel-container' : '.sidebar'); - if (!document.querySelector('.sidebar-toc-active')) return; - window.anime({ - targets : tocElement, - duration : 200, - easing : 'linear', - scrollTop: tocElement.scrollTop - (tocElement.offsetHeight / 2) + target.getBoundingClientRect().top - tocElement.getBoundingClientRect().top - }); + if (service === 'fancybox') { + Array.from(ele).forEach(i => { + if (i.parentNode.tagName !== 'A') { + const dataSrc = i.dataset.lazySrc || i.src + const dataCaption = i.title || i.alt || '' + btf.wrap(i, 'a', { href: dataSrc, 'data-fancybox': 'gallery', 'data-caption': dataCaption, 'data-thumb': dataSrc }) + } + }) + + if (!window.fancyboxRun) { + Fancybox.bind('[data-fancybox]', { + Hash: false, + Thumbs: { + showOnStart: false + }, + Images: { + Panzoom: { + maxScale: 4 + } + }, + Carousel: { + transition: 'slide' + }, + Toolbar: { + display: { + left: ['infobar'], + middle: [ + 'zoomIn', + 'zoomOut', + 'toggle1to1', + 'rotateCCW', + 'rotateCW', + 'flipX', + 'flipY' + ], + right: ['slideshow', 'thumbs', 'close'] + } + } + }) + window.fancyboxRun = true + } + } }, - updateSidebarPosition: function() { - if (window.innerWidth < 1200 || CONFIG.scheme === 'Pisces' || CONFIG.scheme === 'Gemini') return; - // Expand sidebar on post detail page by default, when post has a toc. - const hasTOC = document.querySelector('.post-toc:not(.placeholder-toc)'); - let display = CONFIG.page.sidebar; - if (typeof display !== 'boolean') { - // There's no definition sidebar in the page front-matter. - display = CONFIG.sidebar.display === 'always' || (CONFIG.sidebar.display === 'post' && hasTOC); - } - if (display) { - window.dispatchEvent(new Event('sidebar:show')); + setLoading: { + add: ele => { + const html = ` +
+
+
+
+
+ ` + ele.insertAdjacentHTML('afterend', html) + }, + remove: ele => { + ele.nextElementSibling.remove() } }, - activateSidebarPanel: function(index) { - const sidebar = document.querySelector('.sidebar-inner'); - const activeClassNames = ['sidebar-toc-active', 'sidebar-overview-active']; - if (sidebar.classList.contains(activeClassNames[index])) return; - - const panelContainer = sidebar.querySelector('.sidebar-panel-container'); - const tocPanel = panelContainer.firstElementChild; - const overviewPanel = panelContainer.lastElementChild; - - let postTOCHeight = tocPanel.scrollHeight; - // For TOC activation, try to use the animated TOC height - if (index === 0) { - const nav = tocPanel.querySelector('.nav'); - if (nav) { - postTOCHeight = parseInt(nav.style.getPropertyValue('--height'), 10); - } + updateAnchor: (anchor) => { + if (anchor !== window.location.hash) { + if (!anchor) anchor = location.pathname + const title = GLOBAL_CONFIG_SITE.title + window.history.replaceState({ + url: location.href, + title + }, title, anchor) } - const panelHeights = [ - postTOCHeight, - overviewPanel.scrollHeight - ]; - panelContainer.style.setProperty('--inactive-panel-height', `${panelHeights[1 - index]}px`); - panelContainer.style.setProperty('--active-panel-height', `${panelHeights[index]}px`); - - sidebar.classList.replace(activeClassNames[1 - index], activeClassNames[index]); }, - getScript: function(src, options = {}, legacyCondition) { - if (typeof options === 'function') { - return this.getScript(src, { - condition: legacyCondition - }).then(options); - } - const { - condition = false, - attributes: { - id = '', - async = false, - defer = false, - crossOrigin = '', - dataset = {}, - ...otherAttributes - } = {}, - parentNode = null - } = options; - return new Promise((resolve, reject) => { - if (condition) { - resolve(); - } else { - const script = document.createElement('script'); - - if (id) script.id = id; - if (crossOrigin) script.crossOrigin = crossOrigin; - script.async = async; - script.defer = defer; - Object.assign(script.dataset, dataset); - Object.entries(otherAttributes).forEach(([name, value]) => { - script.setAttribute(name, String(value)); - }); - - script.onload = resolve; - script.onerror = reject; - - if (typeof src === 'object') { - const { url, integrity } = src; - script.src = url; - if (integrity) { - script.integrity = integrity; - script.crossOrigin = 'anonymous'; - } - } else { - script.src = src; - } - (parentNode || document.head).appendChild(script); - } - }); + getScrollPercent: (currentTop, ele) => { + const docHeight = ele.clientHeight + const winHeight = document.documentElement.clientHeight + const headerHeight = ele.offsetTop + const contentMath = (docHeight > winHeight) ? (docHeight - winHeight) : (document.documentElement.scrollHeight - winHeight) + const scrollPercent = (currentTop - headerHeight) / (contentMath) + const scrollPercentRounded = Math.round(scrollPercent * 100) + const percentage = (scrollPercentRounded > 100) ? 100 : (scrollPercentRounded <= 0) ? 0 : scrollPercentRounded + return percentage }, - loadComments: function(selector, legacyCallback) { - if (legacyCallback) { - return this.loadComments(selector).then(legacyCallback); - } - return new Promise(resolve => { - const element = document.querySelector(selector); - if (!CONFIG.comments.lazyload || !element) { - resolve(); - return; - } - const intersectionObserver = new IntersectionObserver((entries, observer) => { - const entry = entries[0]; - if (!entry.isIntersecting) return; - - resolve(); - observer.disconnect(); - }); - intersectionObserver.observe(element); - }); + addGlobalFn: (key, fn, name = false, parent = window) => { + const globalFn = parent.globalFn || {} + const keyObj = globalFn[key] || {} + + if (name && keyObj[name]) return + + name = name || Object.keys(keyObj).length + keyObj[name] = fn + globalFn[key] = keyObj + parent.globalFn = globalFn + }, + + addEventListenerPjax: (ele, event, fn, option = false) => { + ele.addEventListener(event, fn, option) + btf.addGlobalFn('pjax', () => { + ele.removeEventListener(event, fn, option) + }) + }, + + removeGlobalFnEvent: (key, parent = window) => { + const { globalFn = {} } = parent + const keyObj = globalFn[key] || {} + const keyArr = Object.keys(keyObj) + if (!keyArr.length) return + keyArr.forEach(i => { + keyObj[i]() + }) + delete parent.globalFn[key] } -}; +} diff --git a/tags/index-1.html b/tags/index-1.html index 7314d62..7d7b88f 100644 --- a/tags/index-1.html +++ b/tags/index-1.html @@ -1,198 +1,170 @@ - - - - - - - - - - - - - - - - - - - - - - +标签 | ayozooZ + - - - - - - - - - -标签 | ayozooZ - - - - - - - - - - - - - - - - - -
- -
-
-
- - -
- - - -

ayozooZ

- -
-

ayozooZ的blog

-
- - -
- - - - - - - - -
- - -