diff --git a/CNAME b/CNAME new file mode 100644 index 0000000..6ea5a6e --- /dev/null +++ b/CNAME @@ -0,0 +1 @@ +www.7zmonkey.tech \ No newline at end of file diff --git a/archives/2022/05/index.html b/archives/2022/05/index.html new file mode 100644 index 0000000..e3dfb77 --- /dev/null +++ b/archives/2022/05/index.html @@ -0,0 +1 @@ +Archive | 7zMonkey' BLOG
Um..! 4 posts in total. Keep on posting.
2022
0%
\ No newline at end of file diff --git a/archives/2022/index.html b/archives/2022/index.html new file mode 100644 index 0000000..a46a60d --- /dev/null +++ b/archives/2022/index.html @@ -0,0 +1 @@ +Archive | 7zMonkey' BLOG
Um..! 4 posts in total. Keep on posting.
2022
0%
\ No newline at end of file diff --git a/archives/2023/07/index.html b/archives/2023/07/index.html new file mode 100644 index 0000000..9d8a60e --- /dev/null +++ b/archives/2023/07/index.html @@ -0,0 +1 @@ +Archive | 7zMonkey' BLOG
Um..! 4 posts in total. Keep on posting.
2023
0%
\ No newline at end of file diff --git a/archives/2023/08/index.html b/archives/2023/08/index.html new file mode 100644 index 0000000..18a787e --- /dev/null +++ b/archives/2023/08/index.html @@ -0,0 +1 @@ +Archive | 7zMonkey' BLOG
Um..! 4 posts in total. Keep on posting.
2023
0%
\ No newline at end of file diff --git a/archives/2023/09/index.html b/archives/2023/09/index.html new file mode 100644 index 0000000..6c5eff8 --- /dev/null +++ b/archives/2023/09/index.html @@ -0,0 +1 @@ +Archive | 7zMonkey' BLOG
Um..! 4 posts in total. Keep on posting.
2023
0%
\ No newline at end of file diff --git a/archives/2023/index.html b/archives/2023/index.html new file mode 100644 index 0000000..459f672 --- /dev/null +++ b/archives/2023/index.html @@ -0,0 +1 @@ +Archive | 7zMonkey' BLOG
Um..! 4 posts in total. Keep on posting.
2023
0%
\ No newline at end of file diff --git a/archives/index.html b/archives/index.html new file mode 100644 index 0000000..8579c40 --- /dev/null +++ b/archives/index.html @@ -0,0 +1 @@ +Archive | 7zMonkey' BLOG
Um..! 4 posts in total. Keep on posting.
2023
2022
0%
\ No newline at end of file diff --git a/atom.xml b/atom.xml new file mode 100644 index 0000000..3048e48 --- /dev/null +++ b/atom.xml @@ -0,0 +1,125 @@ + + + 7zMonkey' BLOG + + + + + + 2023-08-31T22:28:22.134Z + https://www.7zmonkey.tech/ + + + 7zMonkey + + + + Hexo + + + 单例模式 + + https://www.7zmonkey.tech/blog/79b9f490ba99.html + 2023-09-01T06:23:23.000Z + 2023-09-01T06:23:23.000Z + + 单例模式,也就是要确保在某一个类在全剧中只有一个实例,并且提供y
在前端开发中可以使用闭包实现一个简单的单例模式,将一些配置内容或者一些其他内容放在一个单例中使用其他内容访问。

我们将通过闭包实现一个配置信息的管理,首先我们写一个简单的闭包代码:

1
2
3
4
5
6
const createConfig = (() {
let config;
return () => {
return config;
}
})()

但是我们要实现一个单例模式,就要在再次返回instance前做判断,如果没有 instance 要对他进行赋值。

1
2
3
4
5
6
7
8
9
10
11
const createConfig = (() => {
let config;
return () => {
if (!config) {
config = { /* 创建单例对象的代码 */ };
}
return config;
}
})()

const config = createConfig();

这样就是一个简单的单例模式的代码。

我们不知想要一个简单的单例模式,也想要一个可以设置删除的单例模式,我就的config 就不应该是一个简单的配置对象,而是一个对象其中包含简单的配置对象。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
const createConfig = (() => {
let instance;
return () => {
// 如果不存在 instance 就初始化
if (!instance) {
// 获取 localStorage 信息
const config = JSON.parse(localStorage.getItem('config') ?? '{}')
instance = {
config,
set ( key, value ) {
this.config[key] = value;
localStorage.setItem('config',
JSON.stringify(this.config ?? {})
);
}
};
}
return instance;
}
})();

// 初始化两次
const config = createConfig();
const config2 = createConfig();
console.log(
'config:', config.config,
'config2:', config2.config
);
// 修改 config 观察 config2 信息
config.set('key', 0);
console.log('config2.config', config2.config);
]]>
+ + + + + <p>单例模式,也就是要确保在某一个类在全剧中只有一个实例,并且提供y<br>在前端开发中可以使用闭包实现一个简单的单例模式,将一些配置内容或者一些其他内容放在一个单例中使用其他内容访问。</p> +<p>我们将通过闭包实现一个配置信息的管理,首先我们写一个简单的闭包代码:</p> + + + + + + + + + +
+ + + 关于 JavaScript 中 Object 顺序的一些探索 + + https://www.7zmonkey.tech/blog/f4a8e220f18b.html + 2023-08-18T17:47:53.000Z + 2023-08-18T17:47:53.000Z + + 昨天的面试中出现了一个题目:

将 对象 {6: 46, 8: 23, 14: 5, 10: 3} 按照值的顺序排列。

由于做开始他说的一个数组,说这个题的时候我以为是类数组对象,结果是是如上的对象,排序嘛,(不考虑时间复杂度和空间复杂度的话)基本上没什么难度, 但是一直有一个疑惑在脑中,Object 不是无序的吗?

本文主要探讨不同的 Object 定义方式(或者说声明方式)是否影响”Object 顺序“,”Object 顺序“是什么样的,以及常见处理”Object顺序“的方式。

首先再次之前我的认为是”对象是无序的,数组是有序的,如果处理对象的顺序的话,还是使用数组对象[{key, value}]的方式“ 。

ECMA-262_3rd_edition_december_1999 中有提到:

4.3.3 Object
An object is a member of the type Object. It is an unordered collection of properties each of which contains a primitive value, object, or function. A function stored in a property of an object is called a method.

也就是在 ES3 中是 Objiect 是乱序的,但是在 ES6 中逐渐开始在 Object 的中开始添加 Object 部分放的顺序之说。

不同浏览器在处理 for...in 时的解析顺序时不同的,在 Chrome 和 Opera 中遵循的是 ECMA-262 第五版本规范,而在 Firefox 和 Safari遵循的是 ECMA-262 第三版本规范。

在通常情况下,如果要处理对象的排序,我建议使用数组处理,也就是将对象处理为 [{ key, value }] 这样的的数组形式,按照数组排序,因为我还是觉得对象是无序的,尽管他是按照一定的顺序排序的,但为了避免在不同的浏览器中的排序不同还是将他作为数组处理比较好。

参考文献

  1. js能够保证object属性的输出顺序吗? - Jartto’s blog
  2. Does JavaScript guarantee object property order? - Stack Overflow
  3. ECMA-262_3rd_edition_december_1999
]]>
+ + + + + <p>昨天的面试中出现了一个题目:</p> +<blockquote> +<p>将 对象 <code>&#123;6: 46, 8: 23, 14: 5, 10: 3&#125;</code> 按照值的顺序排列。</p> +</blockquote> +<p>由于做开始他说的一个数组,说 + + + + + +
+ + + Obsidian + Hexo + Github Pages + Github Actions 自动化部署博客 + + https://www.7zmonkey.tech/blog/a871698f1912.html + 2023-07-29T12:43:27.000Z + 2023-07-29T12:43:27.000Z + + 当前网页使用 Obsidian 作为编辑器以及知识库,Hexo 作为静态博客框架,也、就是将markdown文档转换为静态 html ,放在 Github 并且部署 Github Pages 上的。

个人需需要将 Obsidian 的 markdown 文档放置在一个私有仓库,将 Hexo Template 放置在共有仓库,github pages 也放在另一个仓库,当然 Hexo Template 可以和 Github Pages 放在相同的仓库中,如果Hexo Template也需要分离的话在将其分离。

静态博客框架仓库和部署仓库分离的好处是如果更换静态博客框架例如Gatsby,VuePress的话,可以不需要修改部署仓库只需要新建新的静态博客框架仓库,调整Obsidian 仓库的 Actions 就可以。

分离的坏处就是需要有一个额外的仓库管理静态博客框架,当然个人觉得好处大于坏处。

在 Obsidian 仓库,和 Hexo 仓库分别设置了两个 Actions,Obsidian 的仓库 Actions 是主 Actions,Hexo 的 Actions 只是为了触发主 Actions。

也可以将主 Actions 放在 Hexo 上,甚至应该放到静态博客框架仓库里面,如果要更换静态博客框架,肯定需要重新修改主 Actions ,而且是大幅修改,而 Obsidian Actions 只需要修改触发的主 Actions 就可以,减少了不必要的 Obsidian 仓库的变动,如果没有仓库洁癖这些问题都是小问题。

本网站的部署 Actions 整体思路大致如下( Local 代表本地仓库):

sequenceDiagramparticipant Local as Localparticipant Obsidian as Obsidianparticipant Hexo as Hexo Templateparticipant Github as Github Pagesopt 推送 Obsidian 触发    Local->>Obsidian:推送    Obsidian->>Obsidian: 触发endopt 推送 Hexo template 触发    Local->>Hexo: 推送    par Hexo Actions    Hexo->>Obsidian: 触发    endendpar Obsidian ActionsHexo->>Obsidian: 拉取Obsidian->>Obsidian:生成静态文件Obsidian->>Github:推送end
]]>
+ + + + + <p>当前网页使用 Obsidian 作为编辑器以及知识库,Hexo 作为静态博客框架,也、就是将markdown文档转换为静态 html ,放在 Github 并且部署 Github Pages 上的。</p> +<p>个人需需要将 Obsidian 的 markdown 文档放 + + + + + + + + + + + +
+ + + 浅谈 CSS 选择器优先级的计算规则 + + https://www.7zmonkey.tech/blog/61b7ac3ee9e9.html + 2022-05-31T19:39:00.000Z + 2022-05-31T19:39:00.000Z + + CSS优先级是一个值得思考的问题,再次之前我对CSS优先级的理解是:

!important>内联样式>ID选择器>类选择器>类型选择器

相信很多人对CSS优先级的理解也是这样的,但是一篇文章(CSS选择器的优先级(精讲版) (biancheng.net))上面书写了关于CSS 选择器优先级的计算规则的内容,使我开始对CSS优先级进行重新研究。

根据W3C给出关于选择器特异性(specificity,国内一般称优先级)的解释,选择器分为ABC三个等级,其中A为ID选择器,B包括类选择器、属性选择器和伪类,C包括类型选择器和伪元素,当然还存在一个通用选择器,但是通用选择器一般忽略。

等级包含选择器
A计算选择器中 ID 选择器的数量
B计算选择器中类选择器、属性选择器和伪类的数量
C计算选择器中类型选择器和伪元素的数量

优先级的计算,从A级开始到C级结束,如果到C级是两个选择器的优先级还是相等的那么有限选择靠后的选择器。

重复简单选择器

CSS选择器允许重复出现简单选择器,并且简单选择器的重复出现会增加优先级。

1
2
3
4
5
6
7
.class.class{
background-color: red;
}

.class{
background-color: green;
}

也就是说如上代码中第一个选择器重复出现了.class选择器,第二个选择器只出现了一个.class选择器,这两种写法都是正确的,并且第一个选择器.class.class的优先级大于第二个选择器.class,所以结果是背景颜色将呈现红色。

选择器优先级 (A, B, C)
.class.class(0, 2, 0)
.class(0, 1, 0)

在低版本CSS中可能简单重复选择器会被忽略,如在ie8中重复id或被忽略,在ie5中重复的class或被忽略。

拒绝IE,从我做起!

特殊选择器

一些伪类和其他选择器中存在一些特殊的选择器,因此单独定义了这些特殊选择器的特异性。

  1. 选择器:is():not():has()的优先级是选择器列表中最具有复杂性的选择器的优先级取代。
  2. 选择器:nth-child():nth-last-child()的优先级是伪类本身的优先级(计为一个伪类选择器,也就是计为B),再加上选择器列表中最具复杂性的选择器的优先级。
  3. 选择器:where()伪类的优先级被零代替,也就是没有优先级,再优先级计算中不做数。
  4. 通用选择符以及其他选择符在优先级中不计数。

优先级计算

选择器优先级 (A, B, C)
.class(0, 1, 0)
#Red(1, 0, 0)
.container :is(.container>#Red, .container>.class)(1, 2, 0)
.container #Red.class:nth-child(1)(1, 3, 0)
:is(.container>.class.class)(0, 3, 0)
#Red:is(.container>.class)(1, 2, 0)
.container div:nth-child(1)(0, 2, 1)
:is(#Red.class)(1, 1, 0)
#Red.class(1, 1, 0)
#Red.class:nth-child(1)(1, 2, 0)
#Red#Red(2, 0, 0)

代码片段

codepen

specificity求和

在一些其他文档中将讲A、B、C分别比作100,10,1 进行求和,是不准确的,如果按照这样做那么10个class是不是相当于一个id,显然不是。

CSS Level 1Selectors Level 3中也有这样的描述。

在主流浏览器中高等级高于低等级是即使ABC求和相同也不会优先使用后声明的CSS。

造成这样的原因是权重的进制是并不是十进制,CSS 权重进制在 IE6 为 256,后来扩大到了 65536,现代浏览器则采用更大的数量。也可以理解选择器的权值不能进位,或者理解为选择器权值ABC单独计算比较。

关于!important

MDN指出“使用 !important 是一个坏习惯,应该尽量避免”,并给出了使用!important 的情况:

  • 一定要优先考虑使用样式规则的优先级来解决问题而不是 !important
  • 只有在需要覆盖全站或外部 CSS 的特定页面中使用 !important
  • 永远不要在你的插件中使用 !important
  • 永远不要在全站范围的 CSS 代码中使用 !important

以及替代 !important的方法:

  1. 更好地利用 CSS 级联属性
  2. 使用更具体的规则。在您选择的元素之前,增加一个或多个其他元素,使选择器变得更加具体,并获得更高的优先级。
  3. 对于(2)的一种特殊情况,当您无其他要指定的内容时,请复制简单的选择器以增加特异性。

推荐阅读优先级 - CSS(层叠样式表) | MDN (mozilla.org),了解更多!important的使用意见。

其他 CSS 优先规则

CSS 优先规则1: 最近的祖先样式比其他祖先样式优先级高。

CSS 优先规则2: “直接样式”比”祖先样式”优先级高。

CSS 优先规则3: 优先级关系:内联样式 > ID 选择器 > 类选择器 = 属性选择器 = 伪类选择器 > 标签选择器 = 伪元素选择器。

CSS 优先规则4: 计算选择符中 ID 选择器的个数(a),计算选择符中类选择器、属性选择器以及伪类选择器的个数之和(b),计算选择符中标签选择器和伪元素选择器的个数之和(c)。按 a、b、c 的顺序依次比较大小,大的则优先级高,相等则比较下一个。若最后两个的选择符中 a、b、c 都相等,则按照”就近原则”来判断。

CSS 优先规则5: 属性后插有 !important 的属性拥有最高优先级。若同时插有 !important,则再利用规则 3、4 判断优先级。

注意: 文档树中元素的接近度(Proximity of elements)对优先级没有影响。

参考文献

  1. Selectors Level 4
  2. 优先级 - CSS(层叠样式表) | MDN (mozilla.org)
  3. CSS 样式优先级 | 菜鸟教程 (runoob.com)
]]>
+ + + + + <p>CSS优先级是一个值得思考的问题,再次之前我对CSS优先级的理解是:</p> +<blockquote> +<p>!important&gt;内联样式&gt;ID选择器&gt;类选择器&gt;类型选择器</p> +</blockquote> +<p>相信很多人对CSS优先级的理解也 + + + + + + + + + + + +
+ +
\ No newline at end of file diff --git a/blog/233601af6e0a.html b/blog/233601af6e0a.html new file mode 100644 index 0000000..19d4da6 --- /dev/null +++ b/blog/233601af6e0a.html @@ -0,0 +1 @@ +序 | 7zMonkey' BLOG

设计模式通常是一种编程思想,常见的设计模式是对常见开发模式的总结归纳。它如同算法并不局限于某一种单一的编程语言,只要你对它理解透彻,那你在任何语言中都可以随心所欲的使用。

1995 年,GoF(Gang of Four,四人组/四人帮)合作出版了《设计模式:可复用面向对象软件的基础》一书,共收录了 23 种设计模式,从此树立了软件设计模式领域的里程碑,人称「GoF设计模式」。

「GoF设计模式」中将设计模式分成了三种创建型模式、结构型模式、行为型模式。

创建型模式,主要描述如何创建一个模型的模式,例如工厂模式,单例模式等等。

结构型模式,主要描述类和对象按照不同的模式组建成更大的结构,例如代理,桥接等等。

行为型模型,主要描述类和对象之间的共同协作互助完成单一对象无法完成的任务,例如观察者,迭代器等等。

一个合格的程序员应该熟练掌握二十三种设计模式,在你看到某个功能的时候应该不由自主的想到应该使用什么样的设计模式,当然如果现在你还不能很好的理解所有的设计模式,不要慌张,从现在开始了解它,熟悉它就可以,每个人都是从不了解到熟悉的。

这个专栏将会带你入门设计模式,并且尽可能让你掌握二十三种设计模式。

0%
\ No newline at end of file diff --git a/blog/61b7ac3ee9e9.html b/blog/61b7ac3ee9e9.html new file mode 100644 index 0000000..6618afd --- /dev/null +++ b/blog/61b7ac3ee9e9.html @@ -0,0 +1 @@ +浅谈 CSS 选择器优先级的计算规则 | 7zMonkey' BLOG

浅谈 CSS 选择器优先级的计算规则

CSS优先级是一个值得思考的问题,再次之前我对CSS优先级的理解是:

!important>内联样式>ID选择器>类选择器>类型选择器

相信很多人对CSS优先级的理解也是这样的,但是一篇文章(CSS选择器的优先级(精讲版) (biancheng.net))上面书写了关于CSS 选择器优先级的计算规则的内容,使我开始对CSS优先级进行重新研究。

根据W3C给出关于选择器特异性(specificity,国内一般称优先级)的解释,选择器分为ABC三个等级,其中A为ID选择器,B包括类选择器、属性选择器和伪类,C包括类型选择器和伪元素,当然还存在一个通用选择器,但是通用选择器一般忽略。

等级包含选择器
A计算选择器中 ID 选择器的数量
B计算选择器中类选择器、属性选择器和伪类的数量
C计算选择器中类型选择器和伪元素的数量

优先级的计算,从A级开始到C级结束,如果到C级是两个选择器的优先级还是相等的那么有限选择靠后的选择器。

重复简单选择器

CSS选择器允许重复出现简单选择器,并且简单选择器的重复出现会增加优先级。

1
2
3
4
5
6
7
.class.class{
background-color: red;
}

.class{
background-color: green;
}

也就是说如上代码中第一个选择器重复出现了.class选择器,第二个选择器只出现了一个.class选择器,这两种写法都是正确的,并且第一个选择器.class.class的优先级大于第二个选择器.class,所以结果是背景颜色将呈现红色。

选择器优先级 (A, B, C)
.class.class(0, 2, 0)
.class(0, 1, 0)

在低版本CSS中可能简单重复选择器会被忽略,如在ie8中重复id或被忽略,在ie5中重复的class或被忽略。

拒绝IE,从我做起!

特殊选择器

一些伪类和其他选择器中存在一些特殊的选择器,因此单独定义了这些特殊选择器的特异性。

  1. 选择器:is():not():has()的优先级是选择器列表中最具有复杂性的选择器的优先级取代。
  2. 选择器:nth-child():nth-last-child()的优先级是伪类本身的优先级(计为一个伪类选择器,也就是计为B),再加上选择器列表中最具复杂性的选择器的优先级。
  3. 选择器:where()伪类的优先级被零代替,也就是没有优先级,再优先级计算中不做数。
  4. 通用选择符以及其他选择符在优先级中不计数。

优先级计算

选择器优先级 (A, B, C)
.class(0, 1, 0)
#Red(1, 0, 0)
.container :is(.container>#Red, .container>.class)(1, 2, 0)
.container #Red.class:nth-child(1)(1, 3, 0)
:is(.container>.class.class)(0, 3, 0)
#Red:is(.container>.class)(1, 2, 0)
.container div:nth-child(1)(0, 2, 1)
:is(#Red.class)(1, 1, 0)
#Red.class(1, 1, 0)
#Red.class:nth-child(1)(1, 2, 0)
#Red#Red(2, 0, 0)

代码片段

codepen

specificity求和

在一些其他文档中将讲A、B、C分别比作100,10,1 进行求和,是不准确的,如果按照这样做那么10个class是不是相当于一个id,显然不是。

CSS Level 1Selectors Level 3中也有这样的描述。

在主流浏览器中高等级高于低等级是即使ABC求和相同也不会优先使用后声明的CSS。

造成这样的原因是权重的进制是并不是十进制,CSS 权重进制在 IE6 为 256,后来扩大到了 65536,现代浏览器则采用更大的数量。也可以理解选择器的权值不能进位,或者理解为选择器权值ABC单独计算比较。

关于!important

MDN指出“使用 !important 是一个坏习惯,应该尽量避免”,并给出了使用!important 的情况:

  • 一定要优先考虑使用样式规则的优先级来解决问题而不是 !important
  • 只有在需要覆盖全站或外部 CSS 的特定页面中使用 !important
  • 永远不要在你的插件中使用 !important
  • 永远不要在全站范围的 CSS 代码中使用 !important

以及替代 !important的方法:

  1. 更好地利用 CSS 级联属性
  2. 使用更具体的规则。在您选择的元素之前,增加一个或多个其他元素,使选择器变得更加具体,并获得更高的优先级。
  3. 对于(2)的一种特殊情况,当您无其他要指定的内容时,请复制简单的选择器以增加特异性。

推荐阅读优先级 - CSS(层叠样式表) | MDN (mozilla.org),了解更多!important的使用意见。

其他 CSS 优先规则

CSS 优先规则1: 最近的祖先样式比其他祖先样式优先级高。

CSS 优先规则2: “直接样式”比”祖先样式”优先级高。

CSS 优先规则3: 优先级关系:内联样式 > ID 选择器 > 类选择器 = 属性选择器 = 伪类选择器 > 标签选择器 = 伪元素选择器。

CSS 优先规则4: 计算选择符中 ID 选择器的个数(a),计算选择符中类选择器、属性选择器以及伪类选择器的个数之和(b),计算选择符中标签选择器和伪元素选择器的个数之和(c)。按 a、b、c 的顺序依次比较大小,大的则优先级高,相等则比较下一个。若最后两个的选择符中 a、b、c 都相等,则按照”就近原则”来判断。

CSS 优先规则5: 属性后插有 !important 的属性拥有最高优先级。若同时插有 !important,则再利用规则 3、4 判断优先级。

注意: 文档树中元素的接近度(Proximity of elements)对优先级没有影响。

参考文献

  1. Selectors Level 4
  2. 优先级 - CSS(层叠样式表) | MDN (mozilla.org)
  3. CSS 样式优先级 | 菜鸟教程 (runoob.com)
0%
\ No newline at end of file diff --git a/blog/79b9f490ba99.html b/blog/79b9f490ba99.html new file mode 100644 index 0000000..5a245c5 --- /dev/null +++ b/blog/79b9f490ba99.html @@ -0,0 +1 @@ +单例模式 | 7zMonkey' BLOG

单例模式

单例模式,也就是要确保在某一个类在全剧中只有一个实例,并且提供y
在前端开发中可以使用闭包实现一个简单的单例模式,将一些配置内容或者一些其他内容放在一个单例中使用其他内容访问。

我们将通过闭包实现一个配置信息的管理,首先我们写一个简单的闭包代码:

1
2
3
4
5
6
const createConfig = (() {
let config;
return () => {
return config;
}
})()

但是我们要实现一个单例模式,就要在再次返回instance前做判断,如果没有 instance 要对他进行赋值。

1
2
3
4
5
6
7
8
9
10
11
const createConfig = (() => {
let config;
return () => {
if (!config) {
config = { /* 创建单例对象的代码 */ };
}
return config;
}
})()

const config = createConfig();

这样就是一个简单的单例模式的代码。

我们不知想要一个简单的单例模式,也想要一个可以设置删除的单例模式,我就的config 就不应该是一个简单的配置对象,而是一个对象其中包含简单的配置对象。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
const createConfig = (() => {
let instance;
return () => {
// 如果不存在 instance 就初始化
if (!instance) {
// 获取 localStorage 信息
const config = JSON.parse(localStorage.getItem('config') ?? '{}')
instance = {
config,
set ( key, value ) {
this.config[key] = value;
localStorage.setItem('config',
JSON.stringify(this.config ?? {})
);
}
};
}
return instance;
}
})();

// 初始化两次
const config = createConfig();
const config2 = createConfig();
console.log(
'config:', config.config,
'config2:', config2.config
);
// 修改 config 观察 config2 信息
config.set('key', 0);
console.log('config2.config', config2.config);
0%
\ No newline at end of file diff --git a/blog/821cb113e490.html b/blog/821cb113e490.html new file mode 100644 index 0000000..3c1929a --- /dev/null +++ b/blog/821cb113e490.html @@ -0,0 +1 @@ +使用 Babel 自定义 js 语法 | 7zMonkey' BLOG

使用 Babel 自定义 js 语法

使用 Bable 实现自定义js 语法

once function functionName () {}

[[语法树]]

0%
\ No newline at end of file diff --git a/blog/96b0a631fd19.html b/blog/96b0a631fd19.html new file mode 100644 index 0000000..dd1d985 --- /dev/null +++ b/blog/96b0a631fd19.html @@ -0,0 +1 @@ +使用 Babel 自定义 js 语法 | 7zMonkey' BLOG

使用 Babel 自定义 js 语法

0%
\ No newline at end of file diff --git a/blog/a871698f1912.html b/blog/a871698f1912.html new file mode 100644 index 0000000..ad7174e --- /dev/null +++ b/blog/a871698f1912.html @@ -0,0 +1,20 @@ +Obsidian + Hexo + Github Pages + Github Actions 自动化部署博客 | 7zMonkey' BLOG

Obsidian + Hexo + Github Pages + Github Actions 自动化部署博客

当前网页使用 Obsidian 作为编辑器以及知识库,Hexo 作为静态博客框架,也、就是将markdown文档转换为静态 html ,放在 Github 并且部署 Github Pages 上的。

个人需需要将 Obsidian 的 markdown 文档放置在一个私有仓库,将 Hexo Template 放置在共有仓库,github pages 也放在另一个仓库,当然 Hexo Template 可以和 Github Pages 放在相同的仓库中,如果Hexo Template也需要分离的话在将其分离。

静态博客框架仓库和部署仓库分离的好处是如果更换静态博客框架例如Gatsby,VuePress的话,可以不需要修改部署仓库只需要新建新的静态博客框架仓库,调整Obsidian 仓库的 Actions 就可以。

分离的坏处就是需要有一个额外的仓库管理静态博客框架,当然个人觉得好处大于坏处。

在 Obsidian 仓库,和 Hexo 仓库分别设置了两个 Actions,Obsidian 的仓库 Actions 是主 Actions,Hexo 的 Actions 只是为了触发主 Actions。

也可以将主 Actions 放在 Hexo 上,甚至应该放到静态博客框架仓库里面,如果要更换静态博客框架,肯定需要重新修改主 Actions ,而且是大幅修改,而 Obsidian Actions 只需要修改触发的主 Actions 就可以,减少了不必要的 Obsidian 仓库的变动,如果没有仓库洁癖这些问题都是小问题。

本网站的部署 Actions 整体思路大致如下( Local 代表本地仓库):

sequenceDiagram
+participant Local as Local
+participant Obsidian as Obsidian
+participant Hexo as Hexo Template
+participant Github as Github Pages
+opt 推送 Obsidian 触发
+    Local->>Obsidian:推送
+    Obsidian->>Obsidian: 触发
+end
+opt 推送 Hexo template 触发
+    Local->>Hexo: 推送
+    par Hexo Actions
+    Hexo->>Obsidian: 触发
+    end
+end
+par Obsidian Actions
+Hexo->>Obsidian: 拉取
+Obsidian->>Obsidian:生成静态文件
+Obsidian->>Github:推送
+end
0%
\ No newline at end of file diff --git a/blog/f4a8e220f18b.html b/blog/f4a8e220f18b.html new file mode 100644 index 0000000..b6d14b2 --- /dev/null +++ b/blog/f4a8e220f18b.html @@ -0,0 +1 @@ +关于 JavaScript 中 Object 顺序的一些探索 | 7zMonkey' BLOG

关于 JavaScript 中 Object 顺序的一些探索

昨天的面试中出现了一个题目:

将 对象 {6: 46, 8: 23, 14: 5, 10: 3} 按照值的顺序排列。

由于做开始他说的一个数组,说这个题的时候我以为是类数组对象,结果是是如上的对象,排序嘛,(不考虑时间复杂度和空间复杂度的话)基本上没什么难度, 但是一直有一个疑惑在脑中,Object 不是无序的吗?

本文主要探讨不同的 Object 定义方式(或者说声明方式)是否影响”Object 顺序“,”Object 顺序“是什么样的,以及常见处理”Object顺序“的方式。

首先再次之前我的认为是”对象是无序的,数组是有序的,如果处理对象的顺序的话,还是使用数组对象[{key, value}]的方式“ 。

ECMA-262_3rd_edition_december_1999 中有提到:

4.3.3 Object
An object is a member of the type Object. It is an unordered collection of properties each of which contains a primitive value, object, or function. A function stored in a property of an object is called a method.

也就是在 ES3 中是 Objiect 是乱序的,但是在 ES6 中逐渐开始在 Object 的中开始添加 Object 部分放的顺序之说。

不同浏览器在处理 for...in 时的解析顺序时不同的,在 Chrome 和 Opera 中遵循的是 ECMA-262 第五版本规范,而在 Firefox 和 Safari遵循的是 ECMA-262 第三版本规范。

在通常情况下,如果要处理对象的排序,我建议使用数组处理,也就是将对象处理为 [{ key, value }] 这样的的数组形式,按照数组排序,因为我还是觉得对象是无序的,尽管他是按照一定的顺序排序的,但为了避免在不同的浏览器中的排序不同还是将他作为数组处理比较好。

参考文献

  1. js能够保证object属性的输出顺序吗? - Jartto’s blog
  2. Does JavaScript guarantee object property order? - Stack Overflow
  3. ECMA-262_3rd_edition_december_1999
0%
\ No newline at end of file diff --git a/categories/CSS/index.html b/categories/CSS/index.html new file mode 100644 index 0000000..46b302d --- /dev/null +++ b/categories/CSS/index.html @@ -0,0 +1 @@ +Category: CSS | 7zMonkey' BLOG

CSS Category

2022
0%
\ No newline at end of file diff --git a/categories/DevOps/index.html b/categories/DevOps/index.html new file mode 100644 index 0000000..e5d2674 --- /dev/null +++ b/categories/DevOps/index.html @@ -0,0 +1 @@ +Category: DevOps | 7zMonkey' BLOG

DevOps Category

2023
0%
\ No newline at end of file diff --git "a/categories/\350\256\276\350\256\241\346\250\241\345\274\217/index.html" "b/categories/\350\256\276\350\256\241\346\250\241\345\274\217/index.html" new file mode 100644 index 0000000..22b83d3 --- /dev/null +++ "b/categories/\350\256\276\350\256\241\346\250\241\345\274\217/index.html" @@ -0,0 +1 @@ +Category: 设计模式 | 7zMonkey' BLOG

设计模式 Category

2023
0%
\ No newline at end of file diff --git a/css/main.css b/css/main.css new file mode 100644 index 0000000..6ba1d79 --- /dev/null +++ b/css/main.css @@ -0,0 +1,11 @@ +/* build time:Thu Aug 31 2023 22:28:47 GMT+0000 (Coordinated Universal Time)*/ +:root{--body-bg-color:#fff;--content-bg-color:#fff;--card-bg-color:#f5f5f5;--text-color:#555;--blockquote-color:#666;--link-color:#555;--link-hover-color:#222;--brand-color:#fff;--brand-hover-color:#fff;--table-row-odd-bg-color:#f9f9f9;--table-row-hover-bg-color:#f5f5f5;--menu-item-bg-color:#f5f5f5;--theme-color:#222;--btn-default-bg:#222;--btn-default-color:#fff;--btn-default-border-color:#222;--btn-default-hover-bg:#fff;--btn-default-hover-color:#222;--btn-default-hover-border-color:#222;--highlight-background:#f3f3f3;--highlight-foreground:#444;--highlight-gutter-background:#e1e1e1;--highlight-gutter-foreground:#555;color-scheme:light}html{line-height:1.15;-webkit-text-size-adjust:100%}body{margin:0}main{display:block}h1{font-size:2em;margin:.67em 0}hr{box-sizing:content-box;height:0;overflow:visible}pre{font-family:monospace,monospace;font-size:1em}a{background:0 0}abbr[title]{border-bottom:none;text-decoration:underline;text-decoration:underline dotted}b,strong{font-weight:bolder}code,kbd,samp{font-family:monospace,monospace;font-size:1em}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}img{border-style:none}button,input,optgroup,select,textarea{font-family:inherit;font-size:100%;line-height:1.15;margin:0}button,input{overflow:visible}button,select{text-transform:none}[type=button],[type=reset],[type=submit],button{-webkit-appearance:button}[type=button]::-moz-focus-inner,[type=reset]::-moz-focus-inner,[type=submit]::-moz-focus-inner,button::-moz-focus-inner{border-style:none;padding:0}[type=button]:-moz-focusring,[type=reset]:-moz-focusring,[type=submit]:-moz-focusring,button:-moz-focusring{outline:1px dotted ButtonText}fieldset{padding:.35em .75em .625em}legend{box-sizing:border-box;color:inherit;display:table;max-width:100%;padding:0;white-space:normal}progress{vertical-align:baseline}textarea{overflow:auto}[type=checkbox],[type=radio]{box-sizing:border-box;padding:0}[type=number]::-webkit-inner-spin-button,[type=number]::-webkit-outer-spin-button{height:auto}[type=search]{outline-offset:-2px;-webkit-appearance:textfield}[type=search]::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{font:inherit;-webkit-appearance:button}details{display:block}summary{display:list-item}template{display:none}[hidden]{display:none}::selection{background:#262a30;color:#eee}body,html{height:100%}body{background:var(--body-bg-color);box-sizing:border-box;color:var(--text-color);font-family:Lato,'PingFang SC','Microsoft YaHei',sans-serif;font-size:1em;line-height:2;min-height:100%;position:relative;transition:padding .2s ease-in-out}h1,h2,h3,h4,h5,h6{font-family:Lato,'PingFang SC','Microsoft YaHei',sans-serif;font-weight:700;line-height:1.5;margin:30px 0 15px}h1{font-size:1.5em}h2{font-size:1.375em}h3{font-size:1.25em}h4{font-size:1.125em}h5{font-size:1em}h6{font-size:.875em}p{margin:0 0 20px}a{border-bottom:1px solid #999;color:var(--link-color);cursor:pointer;outline:0;text-decoration:none;overflow-wrap:break-word}a:hover{border-bottom-color:var(--link-hover-color);color:var(--link-hover-color)}embed,iframe,img,video{display:block;margin-left:auto;margin-right:auto;max-width:100%}hr{background-image:repeating-linear-gradient(-45deg,#ddd,#ddd 4px,transparent 4px,transparent 8px);border:0;height:3px;margin:40px 0}blockquote{border-left:4px solid #ddd;color:var(--blockquote-color);margin:0;padding:0 15px}blockquote cite::before{content:'-';padding:0 5px}dt{font-weight:700}dd{margin:0;padding:0}.table-container{overflow:auto}table{border-collapse:collapse;border-spacing:0;font-size:.875em;margin:0 0 20px;width:100%}tbody tr:nth-of-type(odd){background:var(--table-row-odd-bg-color)}tbody tr:hover{background:var(--table-row-hover-bg-color)}caption,td,th{padding:8px}td,th{border:1px solid #ddd;border-bottom:3px solid #ddd}th{font-weight:700;padding-bottom:10px}td{border-bottom-width:1px}.btn{background:var(--btn-default-bg);border:2px solid var(--btn-default-border-color);border-radius:0;color:var(--btn-default-color);display:inline-block;font-size:.875em;line-height:2;padding:0 20px;transition:background-color .2s ease-in-out}.btn:hover{background:var(--btn-default-hover-bg);border-color:var(--btn-default-hover-border-color);color:var(--btn-default-hover-color)}.btn+.btn{margin:0 0 8px 8px}.btn .fa-fw{text-align:left;width:1.285714285714286em}.toggle{line-height:0}.toggle .toggle-line{background:#fff;display:block;height:2px;left:0;position:relative;top:0;transition:all .4s;width:100%}.toggle .toggle-line:first-child{margin-top:1px}.toggle .toggle-line:not(:first-child){margin-top:4px}.toggle.toggle-arrow :first-child{left:50%;top:2px;transform:rotate(45deg);width:50%}.toggle.toggle-arrow :last-child{left:50%;top:-2px;transform:rotate(-45deg);width:50%}.toggle.toggle-close :nth-child(2){opacity:0}.toggle.toggle-close :first-child{top:6px;transform:rotate(45deg)}.toggle.toggle-close :last-child{top:-6px;transform:rotate(-45deg)}/*! + Theme: Default + Description: Original highlight.js style + Author: (c) Ivan Sagalaev + Maintainer: @highlightjs/core-team + Website: https://highlightjs.org/ + License: see project LICENSE + Touched: 2021 +*/pre code.hljs{display:block;overflow-x:auto;padding:1em}code.hljs{padding:3px 5px}.hljs{background:#f3f3f3;color:#444}.hljs-comment{color:#697070}.hljs-punctuation,.hljs-tag{color:#444a}.hljs-tag .hljs-attr,.hljs-tag .hljs-name{color:#444}.hljs-attribute,.hljs-doctag,.hljs-keyword,.hljs-meta .hljs-keyword,.hljs-name,.hljs-selector-tag{font-weight:700}.hljs-deletion,.hljs-number,.hljs-quote,.hljs-selector-class,.hljs-selector-id,.hljs-string,.hljs-template-tag,.hljs-type{color:#800}.hljs-section,.hljs-title{color:#800;font-weight:700}.hljs-link,.hljs-operator,.hljs-regexp,.hljs-selector-attr,.hljs-selector-pseudo,.hljs-symbol,.hljs-template-variable,.hljs-variable{color:#ab5656}.hljs-literal{color:#695}.hljs-addition,.hljs-built_in,.hljs-bullet,.hljs-code{color:#397300}.hljs-meta{color:#1f7199}.hljs-meta .hljs-string{color:#38a}.hljs-emphasis{font-style:italic}.hljs-strong{font-weight:700}code,figure.highlight,kbd,pre{background:var(--highlight-background);color:var(--highlight-foreground)}figure.highlight,pre{line-height:1.6;margin:0 auto 20px}figure.highlight figcaption,pre .caption,pre figcaption{background:var(--highlight-gutter-background);color:var(--highlight-foreground);display:flow-root;font-size:.875em;line-height:1.2;padding:.5em}figure.highlight figcaption a,pre .caption a,pre figcaption a{color:var(--highlight-foreground);float:right}figure.highlight figcaption a:hover,pre .caption a:hover,pre figcaption a:hover{border-bottom-color:var(--highlight-foreground)}code,pre{font-family:consolas,Menlo,monospace,'PingFang SC','Microsoft YaHei'}code{border-radius:3px;font-size:.875em;padding:2px 4px;overflow-wrap:break-word}kbd{border:2px solid #ccc;border-radius:.2em;box-shadow:.1em .1em .2em rgba(0,0,0,.1);font-family:inherit;padding:.1em .3em;white-space:nowrap}figure.highlight{overflow:auto;position:relative}figure.highlight pre{border:0;margin:0;padding:10px 0}figure.highlight table{border:0;margin:0;width:auto}figure.highlight td{border:0;padding:0}figure.highlight .gutter{-moz-user-select:none;-ms-user-select:none;-webkit-user-select:none;user-select:none}figure.highlight .gutter pre{background:var(--highlight-gutter-background);color:var(--highlight-gutter-foreground);padding-left:10px;padding-right:10px;text-align:right}figure.highlight .code pre{padding-left:10px;width:100%}figure.highlight .marked{background:rgba(0,0,0,.3)}pre .caption,pre figcaption{margin-bottom:10px}.gist table{width:auto}.gist table td{border:0}pre{overflow:auto;padding:10px}pre code{background:0 0;padding:0;text-shadow:none}.blockquote-center{border-left:0;margin:40px 0;padding:0;position:relative;text-align:center}.blockquote-center::after,.blockquote-center::before{left:0;line-height:1;opacity:.6;position:absolute;width:100%}.blockquote-center::before{border-top:1px solid #ccc;text-align:left;top:-20px;content:'\f10d';font-family:'Font Awesome 6 Free';font-weight:900}.blockquote-center::after{border-bottom:1px solid #ccc;bottom:-20px;text-align:right;content:'\f10e';font-family:'Font Awesome 6 Free';font-weight:900}.blockquote-center div,.blockquote-center p{text-align:center}.group-picture{margin-bottom:20px}.group-picture .group-picture-row{display:flex;gap:3px;margin-bottom:3px}.group-picture .group-picture-column{flex:1}.group-picture .group-picture-column img{height:100%;margin:0;object-fit:cover;width:100%}.post-body .label{color:#555;padding:0 2px}.post-body .label.default{background:#f0f0f0}.post-body .label.primary{background:#efe6f7}.post-body .label.info{background:#e5f2f8}.post-body .label.success{background:#e7f4e9}.post-body .label.warning{background:#fcf6e1}.post-body .label.danger{background:#fae8eb}.post-body .link-grid{display:grid;grid-gap:1.5rem;gap:1.5rem;grid-template-columns:1fr 1fr;margin-bottom:20px;padding:1rem}@media (max-width:767px){.post-body .link-grid{grid-template-columns:1fr}}.post-body .link-grid .link-grid-container{border:solid #ddd;box-shadow:1rem 1rem .5rem rgba(0,0,0,.5);min-height:5rem;min-width:0;padding:.5rem;position:relative;transition:background .3s}.post-body .link-grid .link-grid-container:hover{animation:next-shake .5s;background:var(--card-bg-color)}.post-body .link-grid .link-grid-container:active{box-shadow:.5rem .5rem .25rem rgba(0,0,0,.5);transform:translate(.2rem,.2rem)}.post-body .link-grid .link-grid-container .link-grid-image{border:1px solid #ddd;border-radius:50%;box-sizing:border-box;height:5rem;padding:3px;position:absolute;width:5rem}.post-body .link-grid .link-grid-container p{margin:0 1rem 0 6rem}.post-body .link-grid .link-grid-container p:first-of-type{font-size:1.2em}.post-body .link-grid .link-grid-container p:last-of-type{font-size:.8em;line-height:1.3rem;opacity:.7}.post-body .link-grid .link-grid-container a{border:0;height:100%;left:0;position:absolute;top:0;width:100%}@keyframes next-shake{0%{transform:translate(1pt,1pt) rotate(0)}10%{transform:translate(-1pt,-2pt) rotate(-1deg)}20%{transform:translate(-3pt,0) rotate(1deg)}30%{transform:translate(3pt,2pt) rotate(0)}40%{transform:translate(1pt,-1pt) rotate(1deg)}50%{transform:translate(-1pt,2pt) rotate(-1deg)}60%{transform:translate(-3pt,1pt) rotate(0)}70%{transform:translate(3pt,1pt) rotate(-1deg)}80%{transform:translate(-1pt,-1pt) rotate(1deg)}90%{transform:translate(1pt,2pt) rotate(0)}100%{transform:translate(1pt,-2pt) rotate(-1deg)}}.mermaid{margin-bottom:20px;text-align:center}.post-body .note{border-radius:3px;margin-bottom:20px;padding:1em;position:relative;border:1px solid #eee;border-left-width:5px}.post-body .note summary{cursor:pointer;outline:0}.post-body .note summary p{display:inline}.post-body .note h2,.post-body .note h3,.post-body .note h4,.post-body .note h5,.post-body .note h6{border-bottom:initial;margin:0;padding-top:0}.post-body .note :first-child{margin-top:0}.post-body .note :last-child{margin-bottom:0}.post-body .note.default{border-left-color:#777}.post-body .note.default h2,.post-body .note.default h3,.post-body .note.default h4,.post-body .note.default h5,.post-body .note.default h6{color:#777}.post-body .note.primary{border-left-color:#6f42c1}.post-body .note.primary h2,.post-body .note.primary h3,.post-body .note.primary h4,.post-body .note.primary h5,.post-body .note.primary h6{color:#6f42c1}.post-body .note.info{border-left-color:#428bca}.post-body .note.info h2,.post-body .note.info h3,.post-body .note.info h4,.post-body .note.info h5,.post-body .note.info h6{color:#428bca}.post-body .note.success{border-left-color:#5cb85c}.post-body .note.success h2,.post-body .note.success h3,.post-body .note.success h4,.post-body .note.success h5,.post-body .note.success h6{color:#5cb85c}.post-body .note.warning{border-left-color:#f0ad4e}.post-body .note.warning h2,.post-body .note.warning h3,.post-body .note.warning h4,.post-body .note.warning h5,.post-body .note.warning h6{color:#f0ad4e}.post-body .note.danger{border-left-color:#d9534f}.post-body .note.danger h2,.post-body .note.danger h3,.post-body .note.danger h4,.post-body .note.danger h5,.post-body .note.danger h6{color:#d9534f}.post-body .tabs{margin-bottom:20px}.post-body .tabs,.tabs-comment{padding-top:10px}.post-body .tabs ul.nav-tabs,.tabs-comment ul.nav-tabs{background:var(--body-bg-color);display:flex;display:flex;flex-wrap:wrap;justify-content:center;margin:0;padding:0;position:-webkit-sticky;position:sticky;top:0;z-index:5}@media (max-width:413px){.post-body .tabs ul.nav-tabs,.tabs-comment ul.nav-tabs{display:block;margin-bottom:5px}}.post-body .tabs ul.nav-tabs li.tab,.tabs-comment ul.nav-tabs li.tab{border-bottom:1px solid #ddd;border-left:1px solid transparent;border-right:1px solid transparent;border-radius:0;border-top:3px solid transparent;flex-grow:1;list-style-type:none}@media (max-width:413px){.post-body .tabs ul.nav-tabs li.tab,.tabs-comment ul.nav-tabs li.tab{border-bottom:1px solid transparent;border-left:3px solid transparent;border-right:1px solid transparent;border-top:1px solid transparent}}@media (max-width:413px){.post-body .tabs ul.nav-tabs li.tab,.tabs-comment ul.nav-tabs li.tab{border-radius:0}}.post-body .tabs ul.nav-tabs li.tab a,.tabs-comment ul.nav-tabs li.tab a{border-bottom:initial;display:block;line-height:1.8;padding:.25em .75em;text-align:center;transition:all .2s ease-out}.post-body .tabs ul.nav-tabs li.tab a i[class^=fa],.tabs-comment ul.nav-tabs li.tab a i[class^=fa]{width:1.285714285714286em}.post-body .tabs ul.nav-tabs li.tab.active,.tabs-comment ul.nav-tabs li.tab.active{border-bottom-color:transparent;border-left-color:#ddd;border-right-color:#ddd;border-top-color:#fc6423}@media (max-width:413px){.post-body .tabs ul.nav-tabs li.tab.active,.tabs-comment ul.nav-tabs li.tab.active{border-bottom-color:#ddd;border-left-color:#fc6423;border-right-color:#ddd;border-top-color:#ddd}}.post-body .tabs ul.nav-tabs li.tab.active a,.tabs-comment ul.nav-tabs li.tab.active a{cursor:default}.post-body .tabs .tab-content,.tabs-comment .tab-content{border:1px solid #ddd;border-radius:0;border-top-color:transparent}@media (max-width:413px){.post-body .tabs .tab-content,.tabs-comment .tab-content{border-radius:0;border-top-color:#ddd}}.post-body .tabs .tab-content .tab-pane,.tabs-comment .tab-content .tab-pane{padding:20px 20px 0}.post-body .tabs .tab-content .tab-pane:not(.active),.tabs-comment .tab-content .tab-pane:not(.active){display:none}.pagination .next,.pagination .page-number,.pagination .prev,.pagination .space{display:inline-block;margin:-1px 10px 0;padding:0 10px}@media (max-width:767px){.pagination .next,.pagination .page-number,.pagination .prev,.pagination .space{margin:0 5px}}.pagination .page-number.current{background:#ccc;border-color:#ccc;color:var(--content-bg-color)}.pagination{border-top:1px solid #eee;margin:120px 0 0;text-align:center}.pagination .next,.pagination .page-number,.pagination .prev{border-bottom:0;border-top:1px solid #eee;transition:border-color .2s ease-in-out}.pagination .next:hover,.pagination .page-number:hover,.pagination .prev:hover{border-top-color:var(--link-hover-color)}@media (max-width:767px){.pagination{border-top:0}.pagination .next,.pagination .page-number,.pagination .prev{border-bottom:1px solid #eee;border-top:0}.pagination .next:hover,.pagination .page-number:hover,.pagination .prev:hover{border-bottom-color:var(--link-hover-color)}}.pagination .space{margin:0;padding:0}.comments{margin-top:60px;overflow:hidden}.comment-button-group{display:flex;display:flex;flex-wrap:wrap;justify-content:center;justify-content:center;margin:1em 0}.comment-button-group .comment-button{margin:.1em .2em}.comment-button-group .comment-button.active{background:var(--btn-default-hover-bg);border-color:var(--btn-default-hover-border-color);color:var(--btn-default-hover-color)}.comment-position{display:none}.comment-position.active{display:block}.tabs-comment{margin-top:4em;padding-top:0}.tabs-comment .comments{margin-top:0;padding-top:0}.headband{background:var(--theme-color);height:3px}@media (max-width:991px){.headband{display:none}}.site-brand-container{display:flex;flex-shrink:0;padding:0 10px}.use-motion .column,.use-motion .site-brand-container .toggle{opacity:0}.site-meta{flex-grow:1;text-align:center}@media (max-width:767px){.site-meta{text-align:center}}.custom-logo-image{margin-top:20px}@media (max-width:991px){.custom-logo-image{display:none}}.brand{border-bottom:0;color:var(--brand-color);display:inline-block;padding:0 40px}.brand:hover{color:var(--brand-hover-color)}.site-title{font-family:Lato,'PingFang SC','Microsoft YaHei',sans-serif;font-size:1.375em;font-weight:400;line-height:1.5;margin:0}.site-subtitle{color:#999;font-size:.8125em;margin:10px 0}.use-motion .custom-logo-image,.use-motion .site-subtitle,.use-motion .site-title{opacity:0;position:relative;top:-10px}.site-nav-right,.site-nav-toggle{display:none}@media (max-width:767px){.site-nav-right,.site-nav-toggle{display:flex;flex-direction:column;justify-content:center}}.site-nav-right .toggle,.site-nav-toggle .toggle{color:var(--text-color);padding:10px;width:22px}.site-nav-right .toggle .toggle-line,.site-nav-toggle .toggle .toggle-line{background:var(--text-color);border-radius:1px}@media (max-width:767px){.site-nav{--scroll-height:0;height:0;overflow:hidden;transition:.2s ease-in-out;transition-property:height,visibility;visibility:hidden}body:not(.site-nav-on) .site-nav .animated{animation:none}body.site-nav-on .site-nav{height:var(--scroll-height);visibility:unset}}.menu{margin:0;padding:1em 0;text-align:center}.menu-item{display:inline-block;list-style:none;margin:0 10px}@media (max-width:767px){.menu-item{display:block;margin-top:10px}.menu-item.menu-item-search{display:none}}.menu-item a{border-bottom:0;display:block;font-size:.8125em;transition:border-color .2s ease-in-out}.menu-item a.menu-item-active,.menu-item a:hover{background:var(--menu-item-bg-color)}.menu-item i[class^=fa]{margin-right:8px}.menu-item .badge{display:inline-block;font-weight:700;line-height:1;margin-left:.35em;margin-top:.35em;text-align:center;white-space:nowrap}@media (max-width:767px){.menu-item .badge{float:right;margin-left:0}}.use-motion .menu-item{visibility:hidden}.github-corner :hover .octo-arm{animation:octocat-wave 560ms ease-in-out}.github-corner svg{color:#fff;fill:var(--theme-color);position:absolute;right:0;top:0;z-index:5}@media (max-width:991px){.github-corner{display:none}.github-corner .github-corner:hover .octo-arm{animation:none}.github-corner .github-corner .octo-arm{animation:octocat-wave 560ms ease-in-out}}@keyframes octocat-wave{0%,100%{transform:rotate(0)}20%,60%{transform:rotate(-25deg)}40%,80%{transform:rotate(10deg)}}.sidebar-inner{color:#999;padding:18px 10px;text-align:center;display:flex;flex-direction:column;justify-content:center}.cc-license .cc-opacity{border-bottom:0;opacity:.7}.cc-license .cc-opacity:hover{opacity:.9}.cc-license img{display:inline-block}.site-author-image{border:2px solid #333;max-width:96px;padding:2px}.site-author-name{color:#f5f5f5;font-weight:400;margin:5px 0 0}.site-description{color:#999;font-size:1em;margin-top:5px}.links-of-author a{font-size:.8125em}.links-of-author i[class^=fa]{margin-right:2px}.sidebar .sidebar-button:not(:first-child){margin-top:15px}.sidebar .sidebar-button button{background:0 0;color:#fc6423;cursor:pointer;line-height:2;padding:0 15px;border:1px solid #fc6423;border-radius:4px}.sidebar .sidebar-button button:hover{background:#fc6423;color:#fff}.sidebar .sidebar-button button i[class^=fa]{margin-right:5px}.links-of-blogroll{font-size:.8125em}.links-of-blogroll-title{font-size:.875em;font-weight:600}.links-of-blogroll-list{list-style:none;margin:0;padding:0}.sidebar-nav{font-size:.875em;height:0;margin:0;overflow:hidden;padding-left:0;pointer-events:none;transition:.2s ease-in-out;transition-property:height,visibility;visibility:hidden}.sidebar-nav-active .sidebar-nav{height:calc(2em + 1px);pointer-events:unset;visibility:unset}.sidebar-nav li{border-bottom:1px solid transparent;color:#666;cursor:pointer;display:inline-block;transition:.2s ease-in-out;transition-property:border-bottom-color,color}.sidebar-nav li.sidebar-nav-overview{margin-left:10px}.sidebar-nav li:hover{color:#f5f5f5}.sidebar-overview-active .sidebar-nav-overview,.sidebar-toc-active .sidebar-nav-toc{border-bottom-color:#87daff;color:#87daff;transition-delay:.2s}.sidebar-overview-active .sidebar-nav-overview:hover,.sidebar-toc-active .sidebar-nav-toc:hover{color:#87daff}.sidebar-panel-container{align-items:start;display:grid;flex:1;overflow-x:hidden;overflow-y:auto;padding-top:0;transition:padding-top .2s ease-in-out}.sidebar-nav-active .sidebar-panel-container{padding-top:20px}.sidebar-panel{animation:deactivate-sidebar-panel .2s ease-in-out;grid-area:1/1;height:0;opacity:0;overflow:hidden;pointer-events:none;transform:translateY(0);transition:.2s ease-in-out;transition-delay:0s;transition-property:opacity,transform,visibility;visibility:hidden}.sidebar-nav-active .sidebar-panel,.sidebar-overview-active .sidebar-panel.post-toc-wrap{transform:translateY(-20px)}.sidebar-overview-active:not(.sidebar-nav-active) .sidebar-panel.post-toc-wrap{transition-delay:0s,.2s,0s}.sidebar-overview-active .sidebar-panel.site-overview-wrap,.sidebar-toc-active .sidebar-panel.post-toc-wrap{animation-name:activate-sidebar-panel;height:auto;opacity:1;pointer-events:unset;transform:translateY(0);transition-delay:.2s,.2s,0s;visibility:unset}.sidebar-panel.site-overview-wrap{display:flex;flex-direction:column;justify-content:center;gap:10px;justify-content:flex-start}@keyframes deactivate-sidebar-panel{from{height:var(--inactive-panel-height,0)}to{height:var(--active-panel-height,0)}}@keyframes activate-sidebar-panel{from{height:var(--inactive-panel-height,auto)}to{height:var(--active-panel-height,auto)}}.sidebar-toggle{bottom:61px;height:16px;padding:5px;width:16px;background:#222;cursor:pointer;opacity:.8;position:fixed;z-index:30;left:30px}@media (max-width:991px){.sidebar-toggle{left:20px}}.sidebar-toggle:hover{opacity:1}@media (max-width:991px){.sidebar-toggle{opacity:1}}.sidebar-toggle:hover .toggle-line{background:#87daff}@media (any-hover:hover){body:not(.sidebar-active) .sidebar-toggle:hover :first-child{left:50%;top:2px;transform:rotate(45deg);width:50%}body:not(.sidebar-active) .sidebar-toggle:hover :last-child{left:50%;top:-2px;transform:rotate(-45deg);width:50%}}.sidebar-active .sidebar-toggle :nth-child(2){opacity:0}.sidebar-active .sidebar-toggle :first-child{top:6px;transform:rotate(45deg)}.sidebar-active .sidebar-toggle :last-child{top:-6px;transform:rotate(-45deg)}.post-toc{font-size:.875em}.post-toc ol{list-style:none;margin:0;padding:0 2px 0 10px;text-align:left}.post-toc ol>:last-child{margin-bottom:5px}.post-toc ol>ol{padding-left:0}.post-toc ol a{transition:all .2s ease-in-out}.post-toc .nav-item{line-height:1.8;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.post-toc .nav .nav-child{--height:0;height:0;opacity:0;overflow:hidden;transition-property:height,opacity,visibility;transition:.2s ease-in-out;visibility:hidden}.post-toc .nav .active>.nav-child{height:var(--height,auto);opacity:1;visibility:unset}.post-toc .nav .active>a{border-bottom-color:#87daff;color:#87daff}.post-toc .nav .active-current>a{color:#87daff}.post-toc .nav .active-current>a:hover{color:#87daff}.site-state{display:flex;flex-wrap:wrap;justify-content:center;line-height:1.4}.site-state-item{padding:0 15px}.site-state-item a{border-bottom:0;display:block}.site-state-item-count{display:block;font-size:1.25em;font-weight:600}.site-state-item-name{color:inherit;font-size:.875em}.footer{color:#999;font-size:.875em;padding:20px 0;transition:.2s ease-in-out;transition-property:left,right}.footer.footer-fixed{bottom:0;left:0;position:absolute;right:0}.footer-inner{box-sizing:border-box;text-align:center;display:flex;flex-direction:column;justify-content:center;margin:0 auto;width:700px}@media (max-width:767px){.footer-inner{width:auto}}@media (min-width:1200px){.footer-inner{width:800px}}@media (min-width:1600px){.footer-inner{width:900px}}.use-motion .footer{opacity:0}.languages{display:inline-block;font-size:1.125em;position:relative}.languages .lang-select-label span{margin:0 .5em}.languages .lang-select{height:100%;left:0;opacity:0;position:absolute;top:0;width:100%}.with-love{color:red;display:inline-block;margin:0 5px}@keyframes icon-animate{0%,100%{transform:scale(1)}10%,30%{transform:scale(.9)}20%,40%,60%,80%{transform:scale(1.1)}50%,70%{transform:scale(1.1)}}.back-to-top{font-size:12px;align-items:center;bottom:-100px;color:#fff;display:flex;height:26px;transition:bottom .2s ease-in-out;background:#222;cursor:pointer;opacity:.8;position:fixed;z-index:30;left:30px}.back-to-top span{margin-right:8px;display:none}.back-to-top .fa{text-align:center;width:26px}@media (max-width:991px){.back-to-top{left:20px}}.back-to-top:hover{opacity:1}@media (max-width:991px){.back-to-top{opacity:1}}.back-to-top:hover{color:#87daff}.back-to-top.back-to-top-on{bottom:30px}.rtl.post-body a,.rtl.post-body h1,.rtl.post-body h2,.rtl.post-body h3,.rtl.post-body h4,.rtl.post-body h5,.rtl.post-body h6,.rtl.post-body li,.rtl.post-body ol,.rtl.post-body p,.rtl.post-body ul{direction:rtl;font-family:UKIJ Ekran}.rtl.post-title{font-family:UKIJ Ekran}.post-button{margin-top:40px;text-align:center}.use-motion .comments,.use-motion .pagination,.use-motion .post-block{visibility:hidden}.use-motion .post-header{visibility:hidden}.use-motion .post-body{visibility:hidden}.use-motion .collection-header{visibility:hidden}.posts-collapse .post-content{margin-bottom:35px;margin-left:35px;position:relative}@media (max-width:767px){.posts-collapse .post-content{margin-left:0;margin-right:0}}.posts-collapse .post-content .collection-title{font-size:1.125em;position:relative}.posts-collapse .post-content .collection-title::before{background:#999;border:1px solid #fff;margin-left:-6px;margin-top:-4px;position:absolute;top:50%;border-radius:50%;content:' ';height:10px;width:10px}.posts-collapse .post-content .collection-year{font-size:1.5em;font-weight:700;margin:60px 0;position:relative}.posts-collapse .post-content .collection-year::before{background:#bbb;margin-left:-4px;margin-top:-4px;position:absolute;top:50%;border-radius:50%;content:' ';height:8px;width:8px}.posts-collapse .post-content .collection-header{display:block;margin-left:20px}.posts-collapse .post-content .collection-header small{color:#bbb;margin-left:5px}.posts-collapse .post-content .post-header{border-bottom:1px dashed #ccc;margin:30px 2px 0;padding-left:15px;position:relative;transition:border .2s ease-in-out}.posts-collapse .post-content .post-header::before{background:#bbb;border:1px solid #fff;left:-6px;position:absolute;top:.75em;transition:background .2s ease-in-out;border-radius:50%;content:' ';height:6px;width:6px}.posts-collapse .post-content .post-header:hover{border-bottom-color:#666}.posts-collapse .post-content .post-header:hover::before{background:#222}.posts-collapse .post-content .post-meta-container{display:inline;font-size:.75em;margin-right:10px}.posts-collapse .post-content .post-title{display:inline}.posts-collapse .post-content .post-title a{border-bottom:0;color:var(--link-color)}.posts-collapse .post-content::before{background:#f5f5f5;content:' ';height:100%;margin-left:-2px;position:absolute;top:1.25em;width:4px}.post-body{font-family:Lato,'PingFang SC','Microsoft YaHei',sans-serif;overflow-wrap:break-word}@media (min-width:1200px){.post-body{font-size:1.125em}}@media (min-width:992px){.post-body{text-align:justify}}@media (max-width:991px){.post-body{text-align:justify}}.post-body h1 .header-anchor,.post-body h1 .headerlink,.post-body h2 .header-anchor,.post-body h2 .headerlink,.post-body h3 .header-anchor,.post-body h3 .headerlink,.post-body h4 .header-anchor,.post-body h4 .headerlink,.post-body h5 .header-anchor,.post-body h5 .headerlink,.post-body h6 .header-anchor,.post-body h6 .headerlink{border-bottom-style:none;color:inherit;float:right;font-size:.875em;margin-left:10px;opacity:0}.post-body h1 .header-anchor::before,.post-body h1 .headerlink::before,.post-body h2 .header-anchor::before,.post-body h2 .headerlink::before,.post-body h3 .header-anchor::before,.post-body h3 .headerlink::before,.post-body h4 .header-anchor::before,.post-body h4 .headerlink::before,.post-body h5 .header-anchor::before,.post-body h5 .headerlink::before,.post-body h6 .header-anchor::before,.post-body h6 .headerlink::before{content:'\f0c1';font-family:'Font Awesome 6 Free';font-weight:900}.post-body h1:hover .header-anchor,.post-body h1:hover .headerlink,.post-body h2:hover .header-anchor,.post-body h2:hover .headerlink,.post-body h3:hover .header-anchor,.post-body h3:hover .headerlink,.post-body h4:hover .header-anchor,.post-body h4:hover .headerlink,.post-body h5:hover .header-anchor,.post-body h5:hover .headerlink,.post-body h6:hover .header-anchor,.post-body h6:hover .headerlink{opacity:.5}.post-body h1:hover .header-anchor:hover,.post-body h1:hover .headerlink:hover,.post-body h2:hover .header-anchor:hover,.post-body h2:hover .headerlink:hover,.post-body h3:hover .header-anchor:hover,.post-body h3:hover .headerlink:hover,.post-body h4:hover .header-anchor:hover,.post-body h4:hover .headerlink:hover,.post-body h5:hover .header-anchor:hover,.post-body h5:hover .headerlink:hover,.post-body h6:hover .header-anchor:hover,.post-body h6:hover .headerlink:hover{opacity:1}.post-body .exturl .fa{font-size:.875em;margin-left:4px}.post-body .fancybox+figcaption,.post-body img+figcaption{color:#999;font-size:.875em;font-weight:700;line-height:1;margin:-15px auto 15px;text-align:center}.post-body embed,.post-body iframe,.post-body img,.post-body video{margin-bottom:20px}.post-body .video-container{height:0;margin-bottom:20px;overflow:hidden;padding-top:75%;position:relative;width:100%}.post-body .video-container embed,.post-body .video-container iframe,.post-body .video-container object{height:100%;left:0;margin:0;position:absolute;top:0;width:100%}.post-gallery{display:flex;min-height:200px}.post-gallery .post-gallery-image{flex:1}.post-gallery .post-gallery-image:not(:first-child){clip-path:polygon(40px 0,100% 0,100% 100%,0 100%);margin-left:-20px}.post-gallery .post-gallery-image:not(:last-child){margin-right:-20px}.post-gallery .post-gallery-image img{height:100%;object-fit:cover;opacity:1;width:100%}.posts-expand .post-gallery{margin-bottom:60px}.posts-collapse .post-gallery{margin:15px 0}.posts-expand .post-header{font-size:1.125em;margin-bottom:60px;text-align:center}.posts-expand .post-title{font-size:1.5em;font-weight:400;margin:initial;overflow-wrap:break-word}.posts-expand .post-title-link{border-bottom:0;color:var(--link-color);display:inline-block;position:relative}.posts-expand .post-title-link::before{background:var(--link-color);bottom:0;content:'';height:2px;left:0;position:absolute;transform:scaleX(0);transition:transform .2s ease-in-out;width:100%}.posts-expand .post-title-link:hover::before{transform:scaleX(1)}.posts-expand .post-title-link .fa{font-size:.875em;margin-left:5px}.post-sticky-flag{display:inline-block;margin-right:8px;transform:rotate(30deg)}.posts-expand .post-meta-container{color:#999;font-family:Lato,'PingFang SC','Microsoft YaHei',sans-serif;font-size:.75em;margin-top:3px}.posts-expand .post-meta-container .post-description{font-size:.875em;margin-top:2px}.posts-expand .post-meta-container time{border-bottom:1px dashed #999}.post-meta{display:flex;flex-wrap:wrap;justify-content:center}:not(.post-meta-break)+.post-meta-item::before{content:'|';margin:0 .5em}.post-meta-item-icon{margin-right:3px}@media (max-width:991px){.post-meta-item-text{display:none}}.post-meta-break{flex-basis:100%;height:0}.post-nav{border-top:1px solid #eee;display:flex;gap:30px;justify-content:space-between;margin-top:1em;padding:10px 5px 0}.post-nav-item{flex:1}.post-nav-item a{border-bottom:0;display:block;font-size:.875em;line-height:1.6}.post-nav-item a:active{top:2px}.post-nav-item .fa{font-size:.75em}.post-nav-item:first-child .fa{margin-right:5px}.post-nav-item:last-child{text-align:right}.post-nav-item:last-child .fa{margin-left:5px}.post-footer{display:flex;flex-direction:column;justify-content:center}.post-eof{background:#ccc;height:1px;margin:80px auto 60px;width:8%}.post-block:last-of-type .post-eof{display:none}.post-tags{margin-top:40px;text-align:center}.post-tags a{display:inline-block;font-size:.8125em}.post-tags a:not(:last-child){margin-right:10px}.social-like{border-top:1px solid #eee;font-size:.875em;margin-top:1em;padding-top:1em;display:flex;flex-wrap:wrap;justify-content:center}.social-like a{border-bottom:none}.reward-container{margin:1em 0 0;padding:1em 0;text-align:center}.reward-container button{background:0 0;color:#87daff;cursor:pointer;line-height:2;padding:0 15px;border:2px solid #87daff;border-radius:2px;outline:0;transition:all .2s ease-in-out;vertical-align:text-top}.reward-container button:hover{background:#87daff;color:#fff}.post-reward{display:none;padding-top:20px}.post-reward.active{display:block}.post-reward div{display:inline-block}.post-reward div span{display:block}.post-reward img{display:inline-block;margin:.8em 2em 0;max-width:100%;width:180px}@keyframes next-roll{from{transform:rotateZ(30deg)}to{transform:rotateZ(-30deg)}}.category-all-page .category-all-title{text-align:center}.category-all-page .category-all{margin-top:20px}.category-all-page .category-list{list-style:none;margin:0;padding:0}.category-all-page .category-list-item{margin:5px 10px}.category-all-page .category-list-count{color:#bbb}.category-all-page .category-list-count::before{content:' ('}.category-all-page .category-list-count::after{content:') '}.category-all-page .category-list-child{padding-left:10px}.event-list hr{background:#222;margin:20px 0 45px}.event-list hr::after{background:#222;color:#fff;content:'NOW';display:inline-block;font-weight:700;padding:0 5px}.event-list .event{--event-background:#222;--event-foreground:#bbb;--event-title:#fff;background:var(--event-background);padding:15px}.event-list .event .event-summary{border-bottom:0;color:var(--event-title);margin:0;padding:0 0 0 35px;position:relative}.event-list .event .event-summary::before{animation:dot-flash 1s alternate infinite ease-in-out;background:var(--event-title);left:0;margin-top:-6px;position:absolute;top:50%;border-radius:50%;content:' ';height:12px;width:12px}.event-list .event:nth-of-type(odd) .event-summary::before{animation-delay:.5s}.event-list .event:not(:last-child){margin-bottom:20px}.event-list .event .event-relative-time{color:var(--event-foreground);display:inline-block;font-size:12px;font-weight:400;padding-left:12px}.event-list .event .event-details{color:var(--event-foreground);display:block;line-height:18px;padding:6px 0 6px 35px}.event-list .event .event-details::before{color:var(--event-foreground);display:inline-block;margin-right:9px;width:14px;font-family:'Font Awesome 6 Free';font-weight:900}.event-list .event .event-details.event-location::before{content:'\f041'}.event-list .event .event-details.event-duration::before{content:'\f017'}.event-list .event .event-details.event-description::before{content:'\f024'}.event-list .event-past{--event-background:#f5f5f5;--event-foreground:#999;--event-title:#222}@keyframes dot-flash{from{opacity:1;transform:scale(1)}to{opacity:0;transform:scale(.8)}}ul.breadcrumb{font-size:.75em;list-style:none;margin:1em 0;padding:0 2em;text-align:center}ul.breadcrumb li{display:inline}ul.breadcrumb li:not(:first-child)::before{content:'/\00a0';font-weight:400;padding:.5em}ul.breadcrumb li:last-child{font-weight:700}.tag-cloud{text-align:center}.tag-cloud a{display:inline-block;margin:10px}.tag-cloud-0{border-bottom-color:#aaa;color:#aaa}.tag-cloud-1{border-bottom-color:#9a9a9a;color:#9a9a9a}.tag-cloud-2{border-bottom-color:#8b8b8b;color:#8b8b8b}.tag-cloud-3{border-bottom-color:#7c7c7c;color:#7c7c7c}.tag-cloud-4{border-bottom-color:#6c6c6c;color:#6c6c6c}.tag-cloud-5{border-bottom-color:#5d5d5d;color:#5d5d5d}.tag-cloud-6{border-bottom-color:#4e4e4e;color:#4e4e4e}.tag-cloud-7{border-bottom-color:#3e3e3e;color:#3e3e3e}.tag-cloud-8{border-bottom-color:#2f2f2f;color:#2f2f2f}.tag-cloud-9{border-bottom-color:#202020;color:#202020}.tag-cloud-10{border-bottom-color:#111;color:#111}.search-active{overflow:hidden}.search-pop-overlay{background:rgba(0,0,0,0);display:flex;height:100%;left:0;position:fixed;top:0;transition:visibility .4s,background .4s;visibility:hidden;width:100%;z-index:40}.search-active .search-pop-overlay{background:rgba(0,0,0,.3);visibility:visible}.search-popup{background:var(--card-bg-color);border-radius:5px;height:80%;margin:auto;transform:scale(0);transition:transform .4s;width:700px}.search-active .search-popup{transform:scale(1)}@media (max-width:767px){.search-popup{border-radius:0;height:100%;width:100%}}.search-popup .popup-btn-close,.search-popup .search-icon{color:#999;font-size:18px;padding:0 10px}.search-popup .popup-btn-close{cursor:pointer}.search-popup .popup-btn-close:hover .fa{color:#222}.search-popup .search-header{background:#eee;border-top-left-radius:5px;border-top-right-radius:5px;display:flex;padding:5px}.search-popup input.search-input{background:0 0;border:0;outline:0;width:100%}.search-popup input.search-input::-webkit-search-cancel-button{display:none}.search-popup .search-result-container{height:calc(100% - 55px);overflow:auto;padding:5px 25px}.search-popup .search-result-container hr{margin:5px 0 10px}.search-popup .search-result-container hr:first-child{display:none}.search-popup .search-result-list{margin:0 5px;padding:0}.search-popup a.search-result-title{font-weight:700}.search-popup p.search-result{border-bottom:1px dashed #ccc;padding:5px 0}.search-popup .search-input-container{flex-grow:1;padding:2px}.search-popup .no-result{display:flex}.search-popup .search-result-list{width:100%}.search-popup .search-result-icon{color:#ccc;margin:auto}mark.search-keyword{background:0 0;border-bottom:1px dashed #ff2a2a;color:#ff2a2a;font-weight:700}.use-motion .animated{animation-fill-mode:none;visibility:inherit}.use-motion .sidebar .animated{animation-fill-mode:both}header.header{margin:0 auto;width:700px}@media (max-width:767px){header.header{width:auto}}@media (min-width:1200px){header.header{width:800px}}@media (min-width:1600px){header.header{width:900px}}.main-inner{margin:0 auto;width:700px;padding-bottom:60px}@media (max-width:767px){.main-inner{width:auto}}@media (min-width:1200px){.main-inner{width:800px}}@media (min-width:1600px){.main-inner{width:900px}}@media (max-width:767px){.main-inner{padding-left:20px;padding-right:20px}}.post-block:first-of-type{padding-top:70px}@media (max-width:767px){.post-block:first-of-type{padding-top:35px}}.custom-logo-image{background:#fff;margin:0 auto 10px;max-width:150px;padding:5px}.brand{background:var(--btn-default-bg)}header.header{padding-top:100px}@media (max-width:767px){header.header{padding-top:50px}}@media (max-width:767px){.site-nav{padding-top:30px}}@media (max-width:767px){.main-menu{border-bottom:1px solid #ddd;border-top:1px solid #ddd}}@media (max-width:767px){.menu{text-align:left}}@media (max-width:767px){.menu .menu-item{margin:0 10px}}.menu .menu-item a{border-bottom:1px solid transparent}@media (max-width:767px){.menu .menu-item a{padding:5px 10px}}.menu .menu-item a.menu-item-active,.menu .menu-item a:hover{background:0 0;border-bottom:1px solid var(--link-hover-color)}@media (max-width:767px){.menu .menu-item a.menu-item-active,.menu .menu-item a:hover{border-bottom:1px dotted #ddd}}@media (min-width:768px){.menu .menu-item i[class^=fa]{display:block;line-height:2;margin-right:0;width:100%}}.menu .menu-item .badge{background:#eee;color:#555;padding:1px 4px}.sub-menu{margin:10px 0}.sub-menu .menu-item{display:inline-block}@media (min-width:1200px){.sidebar-active{padding-left:320px}.sidebar-active .footer-fixed{left:320px}}.sidebar{left:-320px}.sidebar-active .sidebar{left:0}.sidebar{background:#222;bottom:0;box-shadow:inset 0 2px 6px #000;max-height:100vh;overflow-y:auto;position:fixed;top:0;transition:all .2s ease-out;width:320px;z-index:20}.sidebar a{border-bottom-color:#555;color:#999}.sidebar a:hover{border-bottom-color:#eee;color:#eee}.links-of-author:not(:first-child){margin-top:15px}.links-of-author a{border-bottom-color:#555;display:inline-block;margin-bottom:10px;margin-right:10px;vertical-align:middle}.links-of-author a::before{background:#12c38c;display:inline-block;margin-right:3px;transform:translateY(-2px);border-radius:50%;content:' ';height:4px;width:4px}.links-of-blogroll-item{padding:2px 10px}.links-of-blogroll-item a{box-sizing:border-box;display:inline-block;max-width:280px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.popular-posts .popular-posts-item .popular-posts-link:hover{background:0 0}.sidebar-dimmer{background:#000;height:100%;left:0;opacity:0;position:fixed;top:0;transition:visibility .4s,opacity .4s;visibility:hidden;width:100%;z-index:10}.sidebar-active .sidebar-dimmer{opacity:.7;visibility:visible}@media (min-width:1200px){.sidebar-dimmer{display:none}} +/* rebuild by neat */ \ No newline at end of file diff --git a/css/noscript.css b/css/noscript.css new file mode 100644 index 0000000..3888e3e --- /dev/null +++ b/css/noscript.css @@ -0,0 +1,3 @@ +/* build time:Thu Aug 31 2023 22:28:47 GMT+0000 (Coordinated Universal Time)*/ +body{margin-top:2rem}.use-motion .collection-header,.use-motion .comments,.use-motion .menu-item,.use-motion .pagination,.use-motion .post-block,.use-motion .post-body,.use-motion .post-header,.use-motion .sidebar,.use-motion .sidebar-inner{visibility:visible}.use-motion .column,.use-motion .footer,.use-motion .site-brand-container .toggle{opacity:initial}.use-motion .custom-logo-image,.use-motion .site-subtitle,.use-motion .site-title{opacity:initial;top:initial}.use-motion .logo-line{transform:scaleX(1)}.search-pop-overlay,.sidebar-nav{display:none}.sidebar-panel{display:block}.noscript-warning{background-color:#f55;color:#fff;font-family:sans-serif;font-size:1rem;font-weight:700;left:0;position:fixed;text-align:center;top:0;width:100%;z-index:50} +/* rebuild by neat */ \ No newline at end of file diff --git a/images/apple-touch-icon-next.png b/images/apple-touch-icon-next.png new file mode 100644 index 0000000..86a0d1d Binary files /dev/null and b/images/apple-touch-icon-next.png differ diff --git a/images/avatar.gif b/images/avatar.gif new file mode 100644 index 0000000..3b5d744 Binary files /dev/null and b/images/avatar.gif differ diff --git a/images/favicon-16x16-next.png b/images/favicon-16x16-next.png new file mode 100644 index 0000000..de8c5d3 Binary files /dev/null and b/images/favicon-16x16-next.png differ diff --git a/images/favicon-32x32-next.png b/images/favicon-32x32-next.png new file mode 100644 index 0000000..e02f5f4 Binary files /dev/null and b/images/favicon-32x32-next.png differ diff --git a/images/logo-algolia-nebula-blue-full.svg b/images/logo-algolia-nebula-blue-full.svg new file mode 100644 index 0000000..886c422 --- /dev/null +++ b/images/logo-algolia-nebula-blue-full.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/images/logo.svg b/images/logo.svg new file mode 100644 index 0000000..992c1a5 --- /dev/null +++ b/images/logo.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/index.html b/index.html new file mode 100644 index 0000000..8eea144 --- /dev/null +++ b/index.html @@ -0,0 +1,20 @@ +7zMonkey' BLOG

单例模式,也就是要确保在某一个类在全剧中只有一个实例,并且提供y
在前端开发中可以使用闭包实现一个简单的单例模式,将一些配置内容或者一些其他内容放在一个单例中使用其他内容访问。

我们将通过闭包实现一个配置信息的管理,首先我们写一个简单的闭包代码:

1
2
3
4
5
6
const createConfig = (() {
let config;
return () => {
return config;
}
})()

但是我们要实现一个单例模式,就要在再次返回instance前做判断,如果没有 instance 要对他进行赋值。

1
2
3
4
5
6
7
8
9
10
11
const createConfig = (() => {
let config;
return () => {
if (!config) {
config = { /* 创建单例对象的代码 */ };
}
return config;
}
})()

const config = createConfig();

这样就是一个简单的单例模式的代码。

我们不知想要一个简单的单例模式,也想要一个可以设置删除的单例模式,我就的config 就不应该是一个简单的配置对象,而是一个对象其中包含简单的配置对象。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
const createConfig = (() => {
let instance;
return () => {
// 如果不存在 instance 就初始化
if (!instance) {
// 获取 localStorage 信息
const config = JSON.parse(localStorage.getItem('config') ?? '{}')
instance = {
config,
set ( key, value ) {
this.config[key] = value;
localStorage.setItem('config',
JSON.stringify(this.config ?? {})
);
}
};
}
return instance;
}
})();

// 初始化两次
const config = createConfig();
const config2 = createConfig();
console.log(
'config:', config.config,
'config2:', config2.config
);
// 修改 config 观察 config2 信息
config.set('key', 0);
console.log('config2.config', config2.config);

昨天的面试中出现了一个题目:

将 对象 {6: 46, 8: 23, 14: 5, 10: 3} 按照值的顺序排列。

由于做开始他说的一个数组,说这个题的时候我以为是类数组对象,结果是是如上的对象,排序嘛,(不考虑时间复杂度和空间复杂度的话)基本上没什么难度, 但是一直有一个疑惑在脑中,Object 不是无序的吗?

本文主要探讨不同的 Object 定义方式(或者说声明方式)是否影响”Object 顺序“,”Object 顺序“是什么样的,以及常见处理”Object顺序“的方式。

首先再次之前我的认为是”对象是无序的,数组是有序的,如果处理对象的顺序的话,还是使用数组对象[{key, value}]的方式“ 。

ECMA-262_3rd_edition_december_1999 中有提到:

4.3.3 Object
An object is a member of the type Object. It is an unordered collection of properties each of which contains a primitive value, object, or function. A function stored in a property of an object is called a method.

也就是在 ES3 中是 Objiect 是乱序的,但是在 ES6 中逐渐开始在 Object 的中开始添加 Object 部分放的顺序之说。

不同浏览器在处理 for...in 时的解析顺序时不同的,在 Chrome 和 Opera 中遵循的是 ECMA-262 第五版本规范,而在 Firefox 和 Safari遵循的是 ECMA-262 第三版本规范。

在通常情况下,如果要处理对象的排序,我建议使用数组处理,也就是将对象处理为 [{ key, value }] 这样的的数组形式,按照数组排序,因为我还是觉得对象是无序的,尽管他是按照一定的顺序排序的,但为了避免在不同的浏览器中的排序不同还是将他作为数组处理比较好。

参考文献

  1. js能够保证object属性的输出顺序吗? - Jartto’s blog
  2. Does JavaScript guarantee object property order? - Stack Overflow
  3. ECMA-262_3rd_edition_december_1999

当前网页使用 Obsidian 作为编辑器以及知识库,Hexo 作为静态博客框架,也、就是将markdown文档转换为静态 html ,放在 Github 并且部署 Github Pages 上的。

个人需需要将 Obsidian 的 markdown 文档放置在一个私有仓库,将 Hexo Template 放置在共有仓库,github pages 也放在另一个仓库,当然 Hexo Template 可以和 Github Pages 放在相同的仓库中,如果Hexo Template也需要分离的话在将其分离。

静态博客框架仓库和部署仓库分离的好处是如果更换静态博客框架例如Gatsby,VuePress的话,可以不需要修改部署仓库只需要新建新的静态博客框架仓库,调整Obsidian 仓库的 Actions 就可以。

分离的坏处就是需要有一个额外的仓库管理静态博客框架,当然个人觉得好处大于坏处。

在 Obsidian 仓库,和 Hexo 仓库分别设置了两个 Actions,Obsidian 的仓库 Actions 是主 Actions,Hexo 的 Actions 只是为了触发主 Actions。

也可以将主 Actions 放在 Hexo 上,甚至应该放到静态博客框架仓库里面,如果要更换静态博客框架,肯定需要重新修改主 Actions ,而且是大幅修改,而 Obsidian Actions 只需要修改触发的主 Actions 就可以,减少了不必要的 Obsidian 仓库的变动,如果没有仓库洁癖这些问题都是小问题。

本网站的部署 Actions 整体思路大致如下( Local 代表本地仓库):

sequenceDiagram
+participant Local as Local
+participant Obsidian as Obsidian
+participant Hexo as Hexo Template
+participant Github as Github Pages
+opt 推送 Obsidian 触发
+    Local->>Obsidian:推送
+    Obsidian->>Obsidian: 触发
+end
+opt 推送 Hexo template 触发
+    Local->>Hexo: 推送
+    par Hexo Actions
+    Hexo->>Obsidian: 触发
+    end
+end
+par Obsidian Actions
+Hexo->>Obsidian: 拉取
+Obsidian->>Obsidian:生成静态文件
+Obsidian->>Github:推送
+end

CSS优先级是一个值得思考的问题,再次之前我对CSS优先级的理解是:

!important>内联样式>ID选择器>类选择器>类型选择器

相信很多人对CSS优先级的理解也是这样的,但是一篇文章(CSS选择器的优先级(精讲版) (biancheng.net))上面书写了关于CSS 选择器优先级的计算规则的内容,使我开始对CSS优先级进行重新研究。

根据W3C给出关于选择器特异性(specificity,国内一般称优先级)的解释,选择器分为ABC三个等级,其中A为ID选择器,B包括类选择器、属性选择器和伪类,C包括类型选择器和伪元素,当然还存在一个通用选择器,但是通用选择器一般忽略。

等级包含选择器
A计算选择器中 ID 选择器的数量
B计算选择器中类选择器、属性选择器和伪类的数量
C计算选择器中类型选择器和伪元素的数量

优先级的计算,从A级开始到C级结束,如果到C级是两个选择器的优先级还是相等的那么有限选择靠后的选择器。

重复简单选择器

CSS选择器允许重复出现简单选择器,并且简单选择器的重复出现会增加优先级。

1
2
3
4
5
6
7
.class.class{
background-color: red;
}

.class{
background-color: green;
}

也就是说如上代码中第一个选择器重复出现了.class选择器,第二个选择器只出现了一个.class选择器,这两种写法都是正确的,并且第一个选择器.class.class的优先级大于第二个选择器.class,所以结果是背景颜色将呈现红色。

选择器优先级 (A, B, C)
.class.class(0, 2, 0)
.class(0, 1, 0)

在低版本CSS中可能简单重复选择器会被忽略,如在ie8中重复id或被忽略,在ie5中重复的class或被忽略。

拒绝IE,从我做起!

特殊选择器

一些伪类和其他选择器中存在一些特殊的选择器,因此单独定义了这些特殊选择器的特异性。

  1. 选择器:is():not():has()的优先级是选择器列表中最具有复杂性的选择器的优先级取代。
  2. 选择器:nth-child():nth-last-child()的优先级是伪类本身的优先级(计为一个伪类选择器,也就是计为B),再加上选择器列表中最具复杂性的选择器的优先级。
  3. 选择器:where()伪类的优先级被零代替,也就是没有优先级,再优先级计算中不做数。
  4. 通用选择符以及其他选择符在优先级中不计数。

优先级计算

选择器优先级 (A, B, C)
.class(0, 1, 0)
#Red(1, 0, 0)
.container :is(.container>#Red, .container>.class)(1, 2, 0)
.container #Red.class:nth-child(1)(1, 3, 0)
:is(.container>.class.class)(0, 3, 0)
#Red:is(.container>.class)(1, 2, 0)
.container div:nth-child(1)(0, 2, 1)
:is(#Red.class)(1, 1, 0)
#Red.class(1, 1, 0)
#Red.class:nth-child(1)(1, 2, 0)
#Red#Red(2, 0, 0)

代码片段

codepen

specificity求和

在一些其他文档中将讲A、B、C分别比作100,10,1 进行求和,是不准确的,如果按照这样做那么10个class是不是相当于一个id,显然不是。

CSS Level 1Selectors Level 3中也有这样的描述。

在主流浏览器中高等级高于低等级是即使ABC求和相同也不会优先使用后声明的CSS。

造成这样的原因是权重的进制是并不是十进制,CSS 权重进制在 IE6 为 256,后来扩大到了 65536,现代浏览器则采用更大的数量。也可以理解选择器的权值不能进位,或者理解为选择器权值ABC单独计算比较。

关于!important

MDN指出“使用 !important 是一个坏习惯,应该尽量避免”,并给出了使用!important 的情况:

  • 一定要优先考虑使用样式规则的优先级来解决问题而不是 !important
  • 只有在需要覆盖全站或外部 CSS 的特定页面中使用 !important
  • 永远不要在你的插件中使用 !important
  • 永远不要在全站范围的 CSS 代码中使用 !important

以及替代 !important的方法:

  1. 更好地利用 CSS 级联属性
  2. 使用更具体的规则。在您选择的元素之前,增加一个或多个其他元素,使选择器变得更加具体,并获得更高的优先级。
  3. 对于(2)的一种特殊情况,当您无其他要指定的内容时,请复制简单的选择器以增加特异性。

推荐阅读优先级 - CSS(层叠样式表) | MDN (mozilla.org),了解更多!important的使用意见。

其他 CSS 优先规则

CSS 优先规则1: 最近的祖先样式比其他祖先样式优先级高。

CSS 优先规则2: “直接样式”比”祖先样式”优先级高。

CSS 优先规则3: 优先级关系:内联样式 > ID 选择器 > 类选择器 = 属性选择器 = 伪类选择器 > 标签选择器 = 伪元素选择器。

CSS 优先规则4: 计算选择符中 ID 选择器的个数(a),计算选择符中类选择器、属性选择器以及伪类选择器的个数之和(b),计算选择符中标签选择器和伪元素选择器的个数之和(c)。按 a、b、c 的顺序依次比较大小,大的则优先级高,相等则比较下一个。若最后两个的选择符中 a、b、c 都相等,则按照”就近原则”来判断。

CSS 优先规则5: 属性后插有 !important 的属性拥有最高优先级。若同时插有 !important,则再利用规则 3、4 判断优先级。

注意: 文档树中元素的接近度(Proximity of elements)对优先级没有影响。

参考文献

  1. Selectors Level 4
  2. 优先级 - CSS(层叠样式表) | MDN (mozilla.org)
  3. CSS 样式优先级 | 菜鸟教程 (runoob.com)
0%
\ No newline at end of file diff --git a/js/bookmark.js b/js/bookmark.js new file mode 100644 index 0000000..8e3ae6a --- /dev/null +++ b/js/bookmark.js @@ -0,0 +1,56 @@ +/* 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 new file mode 100644 index 0000000..505c21b --- /dev/null +++ b/js/comments-buttons.js @@ -0,0 +1,25 @@ +/* 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 new file mode 100644 index 0000000..4045e8c --- /dev/null +++ b/js/comments.js @@ -0,0 +1,21 @@ +/* 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 new file mode 100644 index 0000000..caa0075 --- /dev/null +++ b/js/config.js @@ -0,0 +1,66 @@ +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/motion.js b/js/motion.js new file mode 100644 index 0000000..aad22db --- /dev/null +++ b/js/motion.js @@ -0,0 +1,140 @@ +/* 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 new file mode 100644 index 0000000..1225fd2 --- /dev/null +++ b/js/next-boot.js @@ -0,0 +1,75 @@ +/* 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 new file mode 100644 index 0000000..f81a6a0 --- /dev/null +++ b/js/pjax.js @@ -0,0 +1,50 @@ +/* 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 new file mode 100644 index 0000000..8f0c26c --- /dev/null +++ b/js/schedule.js @@ -0,0 +1,138 @@ +/* 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 new file mode 100644 index 0000000..ba60b51 --- /dev/null +++ b/js/schemes/muse.js @@ -0,0 +1,60 @@ +/* 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/third-party/addtoany.js b/js/third-party/addtoany.js new file mode 100644 index 0000000..f9009f8 --- /dev/null +++ b/js/third-party/addtoany.js @@ -0,0 +1,8 @@ +/* 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 new file mode 100644 index 0000000..c10e7d0 --- /dev/null +++ b/js/third-party/analytics/baidu-analytics.js @@ -0,0 +1,7 @@ +/* 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 new file mode 100644 index 0000000..2cd128f --- /dev/null +++ b/js/third-party/analytics/google-analytics.js @@ -0,0 +1,35 @@ +/* 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 new file mode 100644 index 0000000..0460833 --- /dev/null +++ b/js/third-party/analytics/growingio.js @@ -0,0 +1,10 @@ +/* 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 new file mode 100644 index 0000000..290a3e0 --- /dev/null +++ b/js/third-party/analytics/matomo.js @@ -0,0 +1,19 @@ +/* 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 new file mode 100644 index 0000000..e495b8e --- /dev/null +++ b/js/third-party/chat/chatra.js @@ -0,0 +1,19 @@ +/* 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/tidio.js b/js/third-party/chat/tidio.js new file mode 100644 index 0000000..bffb918 --- /dev/null +++ b/js/third-party/chat/tidio.js @@ -0,0 +1,10 @@ +/* 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 new file mode 100644 index 0000000..18a1be4 --- /dev/null +++ b/js/third-party/comments/changyan.js @@ -0,0 +1,39 @@ +/* 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 new file mode 100644 index 0000000..4d1ca9e --- /dev/null +++ b/js/third-party/comments/disqus.js @@ -0,0 +1,41 @@ +/* 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 new file mode 100644 index 0000000..d8401ee --- /dev/null +++ b/js/third-party/comments/disqusjs.js @@ -0,0 +1,23 @@ +/* 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 new file mode 100644 index 0000000..08d07f4 --- /dev/null +++ b/js/third-party/comments/gitalk.js @@ -0,0 +1,24 @@ +/* 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 new file mode 100644 index 0000000..2c70601 --- /dev/null +++ b/js/third-party/comments/isso.js @@ -0,0 +1,15 @@ +/* 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 new file mode 100644 index 0000000..c4bcd2e --- /dev/null +++ b/js/third-party/comments/livere.js @@ -0,0 +1,19 @@ +/* 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 new file mode 100644 index 0000000..332ee05 --- /dev/null +++ b/js/third-party/comments/utterances.js @@ -0,0 +1,17 @@ +/* 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 new file mode 100644 index 0000000..178db4b --- /dev/null +++ b/js/third-party/fancybox.js @@ -0,0 +1,35 @@ +/* global Fancybox */ + +document.addEventListener('page:loaded', () => { + + /** + * Wrap images with fancybox. + */ + document.querySelectorAll('.post-body :not(a) > img, .post-body > img').forEach(image => { + const imageLink = image.dataset.src || image.src; + const imageWrapLink = document.createElement('a'); + imageWrapLink.classList.add('fancybox'); + imageWrapLink.href = imageLink; + imageWrapLink.setAttribute('itemscope', ''); + imageWrapLink.setAttribute('itemtype', 'http://schema.org/ImageObject'); + imageWrapLink.setAttribute('itemprop', 'url'); + + let dataFancybox = 'default'; + if (image.closest('.post-gallery') !== null) { + dataFancybox = 'gallery'; + } else if (image.closest('.group-picture') !== null) { + dataFancybox = 'group'; + } + imageWrapLink.dataset.fancybox = dataFancybox; + + const imageTitle = image.title || image.alt; + if (imageTitle) { + imageWrapLink.title = imageTitle; + // Make sure img captions will show correctly in fancybox + imageWrapLink.dataset.caption = imageTitle; + } + image.wrap(imageWrapLink); + }); + + Fancybox.bind('[data-fancybox]'); +}); diff --git a/js/third-party/math/katex.js b/js/third-party/math/katex.js new file mode 100644 index 0000000..ad745b1 --- /dev/null +++ b/js/third-party/math/katex.js @@ -0,0 +1,7 @@ +/* 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 new file mode 100644 index 0000000..fe4d448 --- /dev/null +++ b/js/third-party/math/mathjax.js @@ -0,0 +1,36 @@ +/* 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 new file mode 100644 index 0000000..c22d59f --- /dev/null +++ b/js/third-party/pace.js @@ -0,0 +1,7 @@ +/* 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 new file mode 100644 index 0000000..2543ad1 --- /dev/null +++ b/js/third-party/quicklink.js @@ -0,0 +1,37 @@ +/* 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 new file mode 100644 index 0000000..12a554c --- /dev/null +++ b/js/third-party/search/algolia-search.js @@ -0,0 +1,130 @@ +/* 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 new file mode 100644 index 0000000..92a264d --- /dev/null +++ b/js/third-party/search/local-search.js @@ -0,0 +1,99 @@ +/* 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}
+
+ `; + 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 new file mode 100644 index 0000000..3ea7ba6 --- /dev/null +++ b/js/third-party/statistics/firestore.js @@ -0,0 +1,60 @@ +/* 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 new file mode 100644 index 0000000..8397112 --- /dev/null +++ b/js/third-party/statistics/lean-analytics.js @@ -0,0 +1,107 @@ +/* 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 new file mode 100644 index 0000000..54f6288 --- /dev/null +++ b/js/third-party/tags/mermaid.js @@ -0,0 +1,32 @@ +/* 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.run(); + }); + } +}); diff --git a/js/third-party/tags/pdf.js b/js/third-party/tags/pdf.js new file mode 100644 index 0000000..7e82891 --- /dev/null +++ b/js/third-party/tags/pdf.js @@ -0,0 +1,23 @@ +/* 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 new file mode 100644 index 0000000..ddd9a1d --- /dev/null +++ b/js/third-party/tags/wavedrom.js @@ -0,0 +1,13 @@ +/* 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/utils.js b/js/utils.js new file mode 100644 index 0000000..a2cc0d7 --- /dev/null +++ b/js/utils.js @@ -0,0 +1,461 @@ +/* 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); + }); + }, + + /** + * One-click copy code support. + */ + registerCopyCode: function() { + let figure = document.querySelectorAll('figure.highlight'); + let needWrap = false; + if (figure.length === 0) { + figure = document.querySelectorAll('pre:not(.mermaid)'); + needWrap = true; + } + 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 (needWrap) { + const box = document.createElement('div'); + box.className = 'code-container'; + element.wrap(box); + target = box; + } + target.insertAdjacentHTML('beforeend', '
'); + const button = target.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); + }); + }); + }, + + wrapTableWithBox: function() { + document.querySelectorAll('table').forEach(element => { + const box = document.createElement('div'); + box.className = 'table-container'; + element.wrap(box); + }); + }, + + 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) + '%'; + } + } + }); + }, + + 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--; + } + 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 + }); + }); + }, + + /** + * 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')); + }, + + 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); + }, + + 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)); + }); + }, + + 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; + }); + }); + }, + + 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(); + }, + + registerPostReward: function() { + const button = document.querySelector('.reward-container button'); + if (!button) return; + button.addEventListener('click', () => { + document.querySelector('.post-reward').classList.toggle('active'); + }); + }, + + activateNavByIndex: function(index) { + const nav = document.querySelector('.post-toc:not(.placeholder-toc) .nav'); + if (!nav) return; + + const navItemList = nav.querySelectorAll('.nav-item'); + const target = navItemList[index]; + if (!target || target.classList.contains('active-current')) return; + + const singleHeight = navItemList[navItemList.length - 1].offsetHeight; + + nav.querySelectorAll('.active').forEach(navItem => { + navItem.classList.remove('active', 'active-current'); + }); + target.classList.add('active', 'active-current'); + + let activateEle = target.querySelector('.nav-child') || target.parentElement; + let navChildHeight = 0; + + 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; + } + + // 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 + }); + }, + + 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')); + } + }, + + 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); + } + } + 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); + } + }); + }, + + 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); + }); + } +}; diff --git a/rss2.xml b/rss2.xml new file mode 100644 index 0000000..6385a58 --- /dev/null +++ b/rss2.xml @@ -0,0 +1,130 @@ + + + + 7zMonkey' BLOG + https://www.7zMonkey.tech/ + + + + 7zMonkey' BLOG 记录 7zMonkey 的 BLOG + Thu, 31 Aug 2023 22:28:22 GMT + http://hexo.io/ + + + 单例模式 + https://www.7zmonkey.tech/blog/79b9f490ba99.html + https://www.7zmonkey.tech/blog/79b9f490ba99.html + Fri, 01 Sep 2023 06:23:23 GMT + + + + <p>单例模式,也就是要确保在某一个类在全剧中只有一个实例,并且提供y<br>在前端开发中可以使用闭包实现一个简单的单例模式,将一些配置内容或者一些其他内容放在一个单例中使用其他内容访问。</p> +<p>我们将通过闭包实现一个配置信息的管理,首先我们写一个简单的闭包代码:</p> + + + + + 单例模式,也就是要确保在某一个类在全剧中只有一个实例,并且提供y
在前端开发中可以使用闭包实现一个简单的单例模式,将一些配置内容或者一些其他内容放在一个单例中使用其他内容访问。

我们将通过闭包实现一个配置信息的管理,首先我们写一个简单的闭包代码:

1
2
3
4
5
6
const createConfig = (() {
let config;
return () => {
return config;
}
})()

但是我们要实现一个单例模式,就要在再次返回instance前做判断,如果没有 instance 要对他进行赋值。

1
2
3
4
5
6
7
8
9
10
11
const createConfig = (() => {
let config;
return () => {
if (!config) {
config = { /* 创建单例对象的代码 */ };
}
return config;
}
})()

const config = createConfig();

这样就是一个简单的单例模式的代码。

我们不知想要一个简单的单例模式,也想要一个可以设置删除的单例模式,我就的config 就不应该是一个简单的配置对象,而是一个对象其中包含简单的配置对象。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
const createConfig = (() => {
let instance;
return () => {
// 如果不存在 instance 就初始化
if (!instance) {
// 获取 localStorage 信息
const config = JSON.parse(localStorage.getItem('config') ?? '{}')
instance = {
config,
set ( key, value ) {
this.config[key] = value;
localStorage.setItem('config',
JSON.stringify(this.config ?? {})
);
}
};
}
return instance;
}
})();

// 初始化两次
const config = createConfig();
const config2 = createConfig();
console.log(
'config:', config.config,
'config2:', config2.config
);
// 修改 config 观察 config2 信息
config.set('key', 0);
console.log('config2.config', config2.config);
]]>
+ + + 设计模式 + + + 设计模式 + + + https://www.7zmonkey.tech/blog/79b9f490ba99.html#disqus_thread + +
+ + + 关于 JavaScript 中 Object 顺序的一些探索 + https://www.7zmonkey.tech/blog/f4a8e220f18b.html + https://www.7zmonkey.tech/blog/f4a8e220f18b.html + Fri, 18 Aug 2023 17:47:53 GMT + + + + <p>昨天的面试中出现了一个题目:</p> +<blockquote> +<p>将 对象 <code>&#123;6: 46, 8: 23, 14: 5, 10: 3&#125;</code> 按照值的顺序排列。</p> +</blockquote> +<p>由于做开始他说的一个数组,说 + + + + + 昨天的面试中出现了一个题目:

将 对象 {6: 46, 8: 23, 14: 5, 10: 3} 按照值的顺序排列。

由于做开始他说的一个数组,说这个题的时候我以为是类数组对象,结果是是如上的对象,排序嘛,(不考虑时间复杂度和空间复杂度的话)基本上没什么难度, 但是一直有一个疑惑在脑中,Object 不是无序的吗?

本文主要探讨不同的 Object 定义方式(或者说声明方式)是否影响”Object 顺序“,”Object 顺序“是什么样的,以及常见处理”Object顺序“的方式。

首先再次之前我的认为是”对象是无序的,数组是有序的,如果处理对象的顺序的话,还是使用数组对象[{key, value}]的方式“ 。

ECMA-262_3rd_edition_december_1999 中有提到:

4.3.3 Object
An object is a member of the type Object. It is an unordered collection of properties each of which contains a primitive value, object, or function. A function stored in a property of an object is called a method.

也就是在 ES3 中是 Objiect 是乱序的,但是在 ES6 中逐渐开始在 Object 的中开始添加 Object 部分放的顺序之说。

不同浏览器在处理 for...in 时的解析顺序时不同的,在 Chrome 和 Opera 中遵循的是 ECMA-262 第五版本规范,而在 Firefox 和 Safari遵循的是 ECMA-262 第三版本规范。

在通常情况下,如果要处理对象的排序,我建议使用数组处理,也就是将对象处理为 [{ key, value }] 这样的的数组形式,按照数组排序,因为我还是觉得对象是无序的,尽管他是按照一定的顺序排序的,但为了避免在不同的浏览器中的排序不同还是将他作为数组处理比较好。

参考文献

  1. js能够保证object属性的输出顺序吗? - Jartto’s blog
  2. Does JavaScript guarantee object property order? - Stack Overflow
  3. ECMA-262_3rd_edition_december_1999
]]>
+ + + + + https://www.7zmonkey.tech/blog/f4a8e220f18b.html#disqus_thread + +
+ + + Obsidian + Hexo + Github Pages + Github Actions 自动化部署博客 + https://www.7zmonkey.tech/blog/a871698f1912.html + https://www.7zmonkey.tech/blog/a871698f1912.html + Sat, 29 Jul 2023 12:43:27 GMT + + + + <p>当前网页使用 Obsidian 作为编辑器以及知识库,Hexo 作为静态博客框架,也、就是将markdown文档转换为静态 html ,放在 Github 并且部署 Github Pages 上的。</p> +<p>个人需需要将 Obsidian 的 markdown 文档放 + + + + + 当前网页使用 Obsidian 作为编辑器以及知识库,Hexo 作为静态博客框架,也、就是将markdown文档转换为静态 html ,放在 Github 并且部署 Github Pages 上的。

个人需需要将 Obsidian 的 markdown 文档放置在一个私有仓库,将 Hexo Template 放置在共有仓库,github pages 也放在另一个仓库,当然 Hexo Template 可以和 Github Pages 放在相同的仓库中,如果Hexo Template也需要分离的话在将其分离。

静态博客框架仓库和部署仓库分离的好处是如果更换静态博客框架例如Gatsby,VuePress的话,可以不需要修改部署仓库只需要新建新的静态博客框架仓库,调整Obsidian 仓库的 Actions 就可以。

分离的坏处就是需要有一个额外的仓库管理静态博客框架,当然个人觉得好处大于坏处。

在 Obsidian 仓库,和 Hexo 仓库分别设置了两个 Actions,Obsidian 的仓库 Actions 是主 Actions,Hexo 的 Actions 只是为了触发主 Actions。

也可以将主 Actions 放在 Hexo 上,甚至应该放到静态博客框架仓库里面,如果要更换静态博客框架,肯定需要重新修改主 Actions ,而且是大幅修改,而 Obsidian Actions 只需要修改触发的主 Actions 就可以,减少了不必要的 Obsidian 仓库的变动,如果没有仓库洁癖这些问题都是小问题。

本网站的部署 Actions 整体思路大致如下( Local 代表本地仓库):

sequenceDiagramparticipant Local as Localparticipant Obsidian as Obsidianparticipant Hexo as Hexo Templateparticipant Github as Github Pagesopt 推送 Obsidian 触发    Local->>Obsidian:推送    Obsidian->>Obsidian: 触发endopt 推送 Hexo template 触发    Local->>Hexo: 推送    par Hexo Actions    Hexo->>Obsidian: 触发    endendpar Obsidian ActionsHexo->>Obsidian: 拉取Obsidian->>Obsidian:生成静态文件Obsidian->>Github:推送end
]]>
+ + + DevOps + + + DevOps + + Github Actions + + + https://www.7zmonkey.tech/blog/a871698f1912.html#disqus_thread + +
+ + + 浅谈 CSS 选择器优先级的计算规则 + https://www.7zmonkey.tech/blog/61b7ac3ee9e9.html + https://www.7zmonkey.tech/blog/61b7ac3ee9e9.html + Tue, 31 May 2022 19:39:00 GMT + + + + <p>CSS优先级是一个值得思考的问题,再次之前我对CSS优先级的理解是:</p> +<blockquote> +<p>!important&gt;内联样式&gt;ID选择器&gt;类选择器&gt;类型选择器</p> +</blockquote> +<p>相信很多人对CSS优先级的理解也 + + + + + CSS优先级是一个值得思考的问题,再次之前我对CSS优先级的理解是:

!important>内联样式>ID选择器>类选择器>类型选择器

相信很多人对CSS优先级的理解也是这样的,但是一篇文章(CSS选择器的优先级(精讲版) (biancheng.net))上面书写了关于CSS 选择器优先级的计算规则的内容,使我开始对CSS优先级进行重新研究。

根据W3C给出关于选择器特异性(specificity,国内一般称优先级)的解释,选择器分为ABC三个等级,其中A为ID选择器,B包括类选择器、属性选择器和伪类,C包括类型选择器和伪元素,当然还存在一个通用选择器,但是通用选择器一般忽略。

等级包含选择器
A计算选择器中 ID 选择器的数量
B计算选择器中类选择器、属性选择器和伪类的数量
C计算选择器中类型选择器和伪元素的数量

优先级的计算,从A级开始到C级结束,如果到C级是两个选择器的优先级还是相等的那么有限选择靠后的选择器。

重复简单选择器

CSS选择器允许重复出现简单选择器,并且简单选择器的重复出现会增加优先级。

1
2
3
4
5
6
7
.class.class{
background-color: red;
}

.class{
background-color: green;
}

也就是说如上代码中第一个选择器重复出现了.class选择器,第二个选择器只出现了一个.class选择器,这两种写法都是正确的,并且第一个选择器.class.class的优先级大于第二个选择器.class,所以结果是背景颜色将呈现红色。

选择器优先级 (A, B, C)
.class.class(0, 2, 0)
.class(0, 1, 0)

在低版本CSS中可能简单重复选择器会被忽略,如在ie8中重复id或被忽略,在ie5中重复的class或被忽略。

拒绝IE,从我做起!

特殊选择器

一些伪类和其他选择器中存在一些特殊的选择器,因此单独定义了这些特殊选择器的特异性。

  1. 选择器:is():not():has()的优先级是选择器列表中最具有复杂性的选择器的优先级取代。
  2. 选择器:nth-child():nth-last-child()的优先级是伪类本身的优先级(计为一个伪类选择器,也就是计为B),再加上选择器列表中最具复杂性的选择器的优先级。
  3. 选择器:where()伪类的优先级被零代替,也就是没有优先级,再优先级计算中不做数。
  4. 通用选择符以及其他选择符在优先级中不计数。

优先级计算

选择器优先级 (A, B, C)
.class(0, 1, 0)
#Red(1, 0, 0)
.container :is(.container>#Red, .container>.class)(1, 2, 0)
.container #Red.class:nth-child(1)(1, 3, 0)
:is(.container>.class.class)(0, 3, 0)
#Red:is(.container>.class)(1, 2, 0)
.container div:nth-child(1)(0, 2, 1)
:is(#Red.class)(1, 1, 0)
#Red.class(1, 1, 0)
#Red.class:nth-child(1)(1, 2, 0)
#Red#Red(2, 0, 0)

代码片段

codepen

specificity求和

在一些其他文档中将讲A、B、C分别比作100,10,1 进行求和,是不准确的,如果按照这样做那么10个class是不是相当于一个id,显然不是。

CSS Level 1Selectors Level 3中也有这样的描述。

在主流浏览器中高等级高于低等级是即使ABC求和相同也不会优先使用后声明的CSS。

造成这样的原因是权重的进制是并不是十进制,CSS 权重进制在 IE6 为 256,后来扩大到了 65536,现代浏览器则采用更大的数量。也可以理解选择器的权值不能进位,或者理解为选择器权值ABC单独计算比较。

关于!important

MDN指出“使用 !important 是一个坏习惯,应该尽量避免”,并给出了使用!important 的情况:

  • 一定要优先考虑使用样式规则的优先级来解决问题而不是 !important
  • 只有在需要覆盖全站或外部 CSS 的特定页面中使用 !important
  • 永远不要在你的插件中使用 !important
  • 永远不要在全站范围的 CSS 代码中使用 !important

以及替代 !important的方法:

  1. 更好地利用 CSS 级联属性
  2. 使用更具体的规则。在您选择的元素之前,增加一个或多个其他元素,使选择器变得更加具体,并获得更高的优先级。
  3. 对于(2)的一种特殊情况,当您无其他要指定的内容时,请复制简单的选择器以增加特异性。

推荐阅读优先级 - CSS(层叠样式表) | MDN (mozilla.org),了解更多!important的使用意见。

其他 CSS 优先规则

CSS 优先规则1: 最近的祖先样式比其他祖先样式优先级高。

CSS 优先规则2: “直接样式”比”祖先样式”优先级高。

CSS 优先规则3: 优先级关系:内联样式 > ID 选择器 > 类选择器 = 属性选择器 = 伪类选择器 > 标签选择器 = 伪元素选择器。

CSS 优先规则4: 计算选择符中 ID 选择器的个数(a),计算选择符中类选择器、属性选择器以及伪类选择器的个数之和(b),计算选择符中标签选择器和伪元素选择器的个数之和(c)。按 a、b、c 的顺序依次比较大小,大的则优先级高,相等则比较下一个。若最后两个的选择符中 a、b、c 都相等,则按照”就近原则”来判断。

CSS 优先规则5: 属性后插有 !important 的属性拥有最高优先级。若同时插有 !important,则再利用规则 3、4 判断优先级。

注意: 文档树中元素的接近度(Proximity of elements)对优先级没有影响。

参考文献

  1. Selectors Level 4
  2. 优先级 - CSS(层叠样式表) | MDN (mozilla.org)
  3. CSS 样式优先级 | 菜鸟教程 (runoob.com)
]]>
+ + + CSS + + + CSS + + WEB 标准 + + + https://www.7zmonkey.tech/blog/61b7ac3ee9e9.html#disqus_thread + +
+ +
+
diff --git a/search.xml b/search.xml new file mode 100644 index 0000000..f1459fc --- /dev/null +++ b/search.xml @@ -0,0 +1,99 @@ + + + + + + + 单例模式 + + /blog/79b9f490ba99.html + + 单例模式,也就是要确保在某一个类在全剧中只有一个实例,并且提供y
在前端开发中可以使用闭包实现一个简单的单例模式,将一些配置内容或者一些其他内容放在一个单例中使用其他内容访问。

我们将通过闭包实现一个配置信息的管理,首先我们写一个简单的闭包代码:

1
2
3
4
5
6
const createConfig = (() {
let config;
return () => {
return config;
}
})()

但是我们要实现一个单例模式,就要在再次返回instance前做判断,如果没有 instance 要对他进行赋值。

1
2
3
4
5
6
7
8
9
10
11
const createConfig = (() => {
let config;
return () => {
if (!config) {
config = { /* 创建单例对象的代码 */ };
}
return config;
}
})()

const config = createConfig();

这样就是一个简单的单例模式的代码。

我们不知想要一个简单的单例模式,也想要一个可以设置删除的单例模式,我就的config 就不应该是一个简单的配置对象,而是一个对象其中包含简单的配置对象。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
const createConfig = (() => {
let instance;
return () => {
// 如果不存在 instance 就初始化
if (!instance) {
// 获取 localStorage 信息
const config = JSON.parse(localStorage.getItem('config') ?? '{}')
instance = {
config,
set ( key, value ) {
this.config[key] = value;
localStorage.setItem('config',
JSON.stringify(this.config ?? {})
);
}
};
}
return instance;
}
})();

// 初始化两次
const config = createConfig();
const config2 = createConfig();
console.log(
'config:', config.config,
'config2:', config2.config
);
// 修改 config 观察 config2 信息
config.set('key', 0);
console.log('config2.config', config2.config);
]]>
+ + + + + 设计模式 + + + + + + + 设计模式 + + + +
+ + + + + 关于 JavaScript 中 Object 顺序的一些探索 + + /blog/f4a8e220f18b.html + + 昨天的面试中出现了一个题目:

将 对象 {6: 46, 8: 23, 14: 5, 10: 3} 按照值的顺序排列。

由于做开始他说的一个数组,说这个题的时候我以为是类数组对象,结果是是如上的对象,排序嘛,(不考虑时间复杂度和空间复杂度的话)基本上没什么难度, 但是一直有一个疑惑在脑中,Object 不是无序的吗?

本文主要探讨不同的 Object 定义方式(或者说声明方式)是否影响”Object 顺序“,”Object 顺序“是什么样的,以及常见处理”Object顺序“的方式。

首先再次之前我的认为是”对象是无序的,数组是有序的,如果处理对象的顺序的话,还是使用数组对象[{key, value}]的方式“ 。

ECMA-262_3rd_edition_december_1999 中有提到:

4.3.3 Object
An object is a member of the type Object. It is an unordered collection of properties each of which contains a primitive value, object, or function. A function stored in a property of an object is called a method.

也就是在 ES3 中是 Objiect 是乱序的,但是在 ES6 中逐渐开始在 Object 的中开始添加 Object 部分放的顺序之说。

不同浏览器在处理 for...in 时的解析顺序时不同的,在 Chrome 和 Opera 中遵循的是 ECMA-262 第五版本规范,而在 Firefox 和 Safari遵循的是 ECMA-262 第三版本规范。

在通常情况下,如果要处理对象的排序,我建议使用数组处理,也就是将对象处理为 [{ key, value }] 这样的的数组形式,按照数组排序,因为我还是觉得对象是无序的,尽管他是按照一定的顺序排序的,但为了避免在不同的浏览器中的排序不同还是将他作为数组处理比较好。

参考文献

  1. js能够保证object属性的输出顺序吗? - Jartto’s blog
  2. Does JavaScript guarantee object property order? - Stack Overflow
  3. ECMA-262_3rd_edition_december_1999
]]>
+ + + +
+ + + + + Obsidian + Hexo + Github Pages + Github Actions 自动化部署博客 + + /blog/a871698f1912.html + + 当前网页使用 Obsidian 作为编辑器以及知识库,Hexo 作为静态博客框架,也、就是将markdown文档转换为静态 html ,放在 Github 并且部署 Github Pages 上的。

个人需需要将 Obsidian 的 markdown 文档放置在一个私有仓库,将 Hexo Template 放置在共有仓库,github pages 也放在另一个仓库,当然 Hexo Template 可以和 Github Pages 放在相同的仓库中,如果Hexo Template也需要分离的话在将其分离。

静态博客框架仓库和部署仓库分离的好处是如果更换静态博客框架例如Gatsby,VuePress的话,可以不需要修改部署仓库只需要新建新的静态博客框架仓库,调整Obsidian 仓库的 Actions 就可以。

分离的坏处就是需要有一个额外的仓库管理静态博客框架,当然个人觉得好处大于坏处。

在 Obsidian 仓库,和 Hexo 仓库分别设置了两个 Actions,Obsidian 的仓库 Actions 是主 Actions,Hexo 的 Actions 只是为了触发主 Actions。

也可以将主 Actions 放在 Hexo 上,甚至应该放到静态博客框架仓库里面,如果要更换静态博客框架,肯定需要重新修改主 Actions ,而且是大幅修改,而 Obsidian Actions 只需要修改触发的主 Actions 就可以,减少了不必要的 Obsidian 仓库的变动,如果没有仓库洁癖这些问题都是小问题。

本网站的部署 Actions 整体思路大致如下( Local 代表本地仓库):

sequenceDiagramparticipant Local as Localparticipant Obsidian as Obsidianparticipant Hexo as Hexo Templateparticipant Github as Github Pagesopt 推送 Obsidian 触发    Local->>Obsidian:推送    Obsidian->>Obsidian: 触发endopt 推送 Hexo template 触发    Local->>Hexo: 推送    par Hexo Actions    Hexo->>Obsidian: 触发    endendpar Obsidian ActionsHexo->>Obsidian: 拉取Obsidian->>Obsidian:生成静态文件Obsidian->>Github:推送end
]]>
+ + + + + DevOps + + + + + + + DevOps + + Github Actions + + + +
+ + + + + 浅谈 CSS 选择器优先级的计算规则 + + /blog/61b7ac3ee9e9.html + + CSS优先级是一个值得思考的问题,再次之前我对CSS优先级的理解是:

!important>内联样式>ID选择器>类选择器>类型选择器

相信很多人对CSS优先级的理解也是这样的,但是一篇文章(CSS选择器的优先级(精讲版) (biancheng.net))上面书写了关于CSS 选择器优先级的计算规则的内容,使我开始对CSS优先级进行重新研究。

根据W3C给出关于选择器特异性(specificity,国内一般称优先级)的解释,选择器分为ABC三个等级,其中A为ID选择器,B包括类选择器、属性选择器和伪类,C包括类型选择器和伪元素,当然还存在一个通用选择器,但是通用选择器一般忽略。

等级包含选择器
A计算选择器中 ID 选择器的数量
B计算选择器中类选择器、属性选择器和伪类的数量
C计算选择器中类型选择器和伪元素的数量

优先级的计算,从A级开始到C级结束,如果到C级是两个选择器的优先级还是相等的那么有限选择靠后的选择器。

重复简单选择器

CSS选择器允许重复出现简单选择器,并且简单选择器的重复出现会增加优先级。

1
2
3
4
5
6
7
.class.class{
background-color: red;
}

.class{
background-color: green;
}

也就是说如上代码中第一个选择器重复出现了.class选择器,第二个选择器只出现了一个.class选择器,这两种写法都是正确的,并且第一个选择器.class.class的优先级大于第二个选择器.class,所以结果是背景颜色将呈现红色。

选择器优先级 (A, B, C)
.class.class(0, 2, 0)
.class(0, 1, 0)

在低版本CSS中可能简单重复选择器会被忽略,如在ie8中重复id或被忽略,在ie5中重复的class或被忽略。

拒绝IE,从我做起!

特殊选择器

一些伪类和其他选择器中存在一些特殊的选择器,因此单独定义了这些特殊选择器的特异性。

  1. 选择器:is():not():has()的优先级是选择器列表中最具有复杂性的选择器的优先级取代。
  2. 选择器:nth-child():nth-last-child()的优先级是伪类本身的优先级(计为一个伪类选择器,也就是计为B),再加上选择器列表中最具复杂性的选择器的优先级。
  3. 选择器:where()伪类的优先级被零代替,也就是没有优先级,再优先级计算中不做数。
  4. 通用选择符以及其他选择符在优先级中不计数。

优先级计算

选择器优先级 (A, B, C)
.class(0, 1, 0)
#Red(1, 0, 0)
.container :is(.container>#Red, .container>.class)(1, 2, 0)
.container #Red.class:nth-child(1)(1, 3, 0)
:is(.container>.class.class)(0, 3, 0)
#Red:is(.container>.class)(1, 2, 0)
.container div:nth-child(1)(0, 2, 1)
:is(#Red.class)(1, 1, 0)
#Red.class(1, 1, 0)
#Red.class:nth-child(1)(1, 2, 0)
#Red#Red(2, 0, 0)

代码片段

codepen

specificity求和

在一些其他文档中将讲A、B、C分别比作100,10,1 进行求和,是不准确的,如果按照这样做那么10个class是不是相当于一个id,显然不是。

CSS Level 1Selectors Level 3中也有这样的描述。

在主流浏览器中高等级高于低等级是即使ABC求和相同也不会优先使用后声明的CSS。

造成这样的原因是权重的进制是并不是十进制,CSS 权重进制在 IE6 为 256,后来扩大到了 65536,现代浏览器则采用更大的数量。也可以理解选择器的权值不能进位,或者理解为选择器权值ABC单独计算比较。

关于!important

MDN指出“使用 !important 是一个坏习惯,应该尽量避免”,并给出了使用!important 的情况:

  • 一定要优先考虑使用样式规则的优先级来解决问题而不是 !important
  • 只有在需要覆盖全站或外部 CSS 的特定页面中使用 !important
  • 永远不要在你的插件中使用 !important
  • 永远不要在全站范围的 CSS 代码中使用 !important

以及替代 !important的方法:

  1. 更好地利用 CSS 级联属性
  2. 使用更具体的规则。在您选择的元素之前,增加一个或多个其他元素,使选择器变得更加具体,并获得更高的优先级。
  3. 对于(2)的一种特殊情况,当您无其他要指定的内容时,请复制简单的选择器以增加特异性。

推荐阅读优先级 - CSS(层叠样式表) | MDN (mozilla.org),了解更多!important的使用意见。

其他 CSS 优先规则

CSS 优先规则1: 最近的祖先样式比其他祖先样式优先级高。

CSS 优先规则2: “直接样式”比”祖先样式”优先级高。

CSS 优先规则3: 优先级关系:内联样式 > ID 选择器 > 类选择器 = 属性选择器 = 伪类选择器 > 标签选择器 = 伪元素选择器。

CSS 优先规则4: 计算选择符中 ID 选择器的个数(a),计算选择符中类选择器、属性选择器以及伪类选择器的个数之和(b),计算选择符中标签选择器和伪元素选择器的个数之和(c)。按 a、b、c 的顺序依次比较大小,大的则优先级高,相等则比较下一个。若最后两个的选择符中 a、b、c 都相等,则按照”就近原则”来判断。

CSS 优先规则5: 属性后插有 !important 的属性拥有最高优先级。若同时插有 !important,则再利用规则 3、4 判断优先级。

注意: 文档树中元素的接近度(Proximity of elements)对优先级没有影响。

参考文献

  1. Selectors Level 4
  2. 优先级 - CSS(层叠样式表) | MDN (mozilla.org)
  3. CSS 样式优先级 | 菜鸟教程 (runoob.com)
]]>
+ + + + + CSS + + + + + + + CSS + + WEB 标准 + + + +
+ + + + +
diff --git a/tags/CSS/index.html b/tags/CSS/index.html new file mode 100644 index 0000000..001ab3d --- /dev/null +++ b/tags/CSS/index.html @@ -0,0 +1 @@ +Tag: CSS | 7zMonkey' BLOG
0%
\ No newline at end of file diff --git a/tags/DevOps/index.html b/tags/DevOps/index.html new file mode 100644 index 0000000..396654a --- /dev/null +++ b/tags/DevOps/index.html @@ -0,0 +1 @@ +Tag: DevOps | 7zMonkey' BLOG
0%
\ No newline at end of file diff --git a/tags/Github-Actions/index.html b/tags/Github-Actions/index.html new file mode 100644 index 0000000..5db9e16 --- /dev/null +++ b/tags/Github-Actions/index.html @@ -0,0 +1 @@ +Tag: Github Actions | 7zMonkey' BLOG
0%
\ No newline at end of file diff --git "a/tags/WEB-\346\240\207\345\207\206/index.html" "b/tags/WEB-\346\240\207\345\207\206/index.html" new file mode 100644 index 0000000..ea8709a --- /dev/null +++ "b/tags/WEB-\346\240\207\345\207\206/index.html" @@ -0,0 +1 @@ +Tag: WEB 标准 | 7zMonkey' BLOG
0%
\ No newline at end of file diff --git "a/tags/\350\256\276\350\256\241\346\250\241\345\274\217/index.html" "b/tags/\350\256\276\350\256\241\346\250\241\345\274\217/index.html" new file mode 100644 index 0000000..2535958 --- /dev/null +++ "b/tags/\350\256\276\350\256\241\346\250\241\345\274\217/index.html" @@ -0,0 +1 @@ +Tag: 设计模式 | 7zMonkey' BLOG

设计模式 Tag

2023
0%
\ No newline at end of file