hexo-theme-particlex 2.1.1 → 2.1.2

Sign up to get free protection for your applications and to get access to all the features.
package/README.md CHANGED
@@ -199,24 +199,13 @@ highlightStyle: github # Highlight style
199
199
 
200
200
  - 搜索
201
201
 
202
- 嵌入到 Archives 中的搜索,搜索数据是用 [Hexo-Generator-Search-Lite](https://github.com/argvchs/hexo-generator-search-lite) 生成,默认关闭,使用需要安装上述插件并**设置 `optimize: true`**
202
+ 嵌入到 Archives 中的搜索,默认关闭
203
203
 
204
- 目前只支持搜索文档的 Title Categories Tags(我太弱了)
205
-
206
- 要同时在主题和根目录的两个 `_config.yml` 添加配置
204
+ 目前只支持搜索文档标题(我太弱了)
207
205
 
208
206
  ```yaml
209
- # Theme config
210
207
  search:
211
208
  enable: false
212
- path: /search.json
213
- ```
214
-
215
- ```yaml
216
- # Site config
217
- search:
218
- path: /search.json
219
- optimize: true
220
209
  ```
221
210
 
222
211
  - Gitalk
package/_config.yml CHANGED
@@ -87,7 +87,6 @@ crypto:
87
87
  # Search
88
88
  search:
89
89
  enable: false
90
- path: /search.json
91
90
 
92
91
  # Gitalk
93
92
  # https://github.com/gitalk/gitalk
@@ -1,12 +1,10 @@
1
1
  <div id="archives">
2
2
  <% if (theme.search.enable) { %>
3
- <div class="search-mask" style="z-index: <%= site.posts.length + 1 %>"></div>
4
- <input class="ipt search-bar" placeholder="搜索..." style="z-index: <%= site.posts.length + 2 %>">
5
- <script src="<%- url_for("/js/searcher.js") %>"></script>
6
- <script>searcher.init("<%- url_for(theme.search.path) %>");</script>
3
+ <div id="search-mask" style="z-index: <%= site.posts.length + 1 %>"></div>
4
+ <input id="search-bar" class="ipt" placeholder="搜索" style="z-index: <%= site.posts.length + 2 %>">
7
5
  <% } %>
8
6
  <% site.posts.forEach((post, id) => { %>
9
- <div class="timeline" path="<%- url_for(post.path) %>" style="z-index: <%= site.posts.length - id %>">
7
+ <div class="timeline" style="z-index: <%= site.posts.length - id %>" data-title="<%- post.title.toLowerCase().replace(/\s+/gm, "") %>">
10
8
  <div class="timeline-tail"></div>
11
9
  <div class="timeline-content">
12
10
  <div class="item-time"><%- date(post.date, "YYYY/M/D") %></div>
@@ -20,7 +20,8 @@
20
20
  <%
21
21
  posts = category.posts;
22
22
  posts.data.sort((a, b) => {
23
- a.top ??= 0, b.top ??= 0;
23
+ if (typeof a.top === "undefined") a.top = 0;
24
+ if (typeof b.top === "undefined") b.top = 0;
24
25
  return a.top == b.top ? b.date - a.date : b.top - a.top
25
26
  });
26
27
  %>
package/layout/layout.ejs CHANGED
@@ -21,8 +21,9 @@
21
21
  title = "Archives | ";
22
22
  title += config.title;
23
23
  site.posts.data.sort((a, b) => {
24
- a.top ??= 0, b.top ??= 0;
25
- return a.top == b.top ? b.date - a.date : b.top - a.top
24
+ if (typeof a.top === "undefined") a.top = 0;
25
+ if (typeof b.top === "undefined") b.top = 0;
26
+ return a.top == b.top ? b.date - a.date : b.top - a.top;
26
27
  });
27
28
  %>
28
29
  <!DOCTYPE html>
package/layout/post.ejs CHANGED
@@ -41,21 +41,19 @@
41
41
  <% if (typeof page.password !== "undefined" && theme.crypto.enable) { %>
42
42
  <%
43
43
  const CryptoJS = crypto();
44
- function SHA(str) {
44
+ function sha(str) {
45
45
  return CryptoJS.SHA256(str).toString(CryptoJS.enc.Base64);
46
46
  }
47
47
  function encrypt(str, key) {
48
- return CryptoJS.AES.encrypt(str, SHA(key), {
48
+ return CryptoJS.AES.encrypt(str, sha(key), {
49
49
  mode: CryptoJS.mode.ECB,
50
50
  padding: CryptoJS.pad.Pkcs7,
51
51
  }).toString();
52
52
  }
53
53
  %>
54
- <input class="ipt crypto" placeholder="文章被加密,请输入密码">
55
- <div class="content" v-pre></div>
56
54
  <script src="https://cdn.staticfile.org/crypto-js/4.1.1/crypto-js.min.js"></script>
57
- <script src="<%- url_for("/js/cryptor.js") %>"></script>
58
- <script>cryptor.init("<%- encrypt(page.content, page.password.toString()) %>", "<%- SHA(page.content) %>")</script>
55
+ <input id="crypto" class="ipt" placeholder="文章被加密,请输入密码" data-encrypt="<%- encrypt(page.content, page.password.toString()) %>" data-check="<%- sha(page.content) %>">
56
+ <div class="content" style="opacity: 0" v-pre></div>
59
57
  <% } else { %>
60
58
  <div class="content" v-pre>
61
59
  <%- page.content %>
package/layout/tags.ejs CHANGED
@@ -20,8 +20,9 @@
20
20
  <%
21
21
  posts = tag.posts;
22
22
  posts.data.sort((a, b) => {
23
- a.top ??= 0, b.top ??= 0;
24
- return a.top == b.top ? b.date - a.date : b.top - a.top
23
+ if (typeof a.top === "undefined") a.top = 0;
24
+ if (typeof b.top === "undefined") b.top = 0;
25
+ return a.top == b.top ? b.date - a.date : b.top - a.top;
25
26
  });
26
27
  %>
27
28
  <% } %>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "hexo-theme-particlex",
3
- "version": "2.1.1",
3
+ "version": "2.1.2",
4
4
  "description": "A concise Hexo theme, based on Particle.",
5
5
  "scripts": {
6
6
  "test": "echo \"Error: no test specified\" && exit 1"
@@ -24,7 +24,6 @@
24
24
  },
25
25
  "homepage": "https://github.com/argvchs/hexo-theme-particlex#readme",
26
26
  "dependencies": {
27
- "hexo-generator-search-lite": "^1.0.8",
28
27
  "hexo-helper-crypto": "^1.0.4",
29
28
  "hexo-renderer-babeljs": "^1.0.7",
30
29
  "hexo-renderer-ejs": "^2.0.0"
@@ -61,6 +61,7 @@ body {
61
61
  margin: 0;
62
62
  padding: 0;
63
63
  word-wrap: break-word;
64
+ word-break: keep-all;
64
65
  scrollbar-width: thin;
65
66
  scrollbar-color: #8ab5ff #e6efff;
66
67
  }
@@ -200,9 +201,6 @@ footer .footer-wrap {
200
201
  color: #333;
201
202
  border-radius: 50%;
202
203
  }
203
- #home-head .home-info .info h1 {
204
- word-break: keep-all;
205
- }
206
204
  #home-head .home-info .loop:nth-child(1) {
207
205
  border-radius: 38% 62% 63% 37%/41% 44% 56% 59%;
208
206
  background: #fff;
@@ -292,8 +290,10 @@ footer .footer-wrap {
292
290
  #home-posts .post .excerpt {
293
291
  color: #1e3e3f;
294
292
  }
293
+ #home-posts .post .post-tags {
294
+ line-height: 1.5;
295
+ }
295
296
  #home-posts .post .post-tags a {
296
- color: #ffbbf4;
297
297
  font-size: 14px;
298
298
  }
299
299
  #home-posts .post .post-tags .tag {
@@ -477,7 +477,6 @@ footer .footer-wrap {
477
477
  #menu .desktop-menu .title {
478
478
  display: inline-block;
479
479
  margin-left: 30px;
480
- margin-right: 20px;
481
480
  font-family: "Lexend", "Noto Sans SC", sans-serif;
482
481
  text-transform: uppercase;
483
482
  color: #555;
@@ -718,16 +717,24 @@ footer .footer-icon {
718
717
  color: #5c6b72;
719
718
  text-decoration: none;
720
719
  }
721
- #archives .tag-icon {
722
- color: #5c6b72;
723
- text-decoration: none;
720
+ #archives .info {
721
+ line-height: 1.5;
724
722
  }
725
723
  #archives .category {
726
- margin-right: 20px;
724
+ display: inline-block;
725
+ margin-right: 10px;
726
+ }
727
+ #archives .tags {
728
+ display: inline-block;
727
729
  }
728
730
  #archives .tags .tag {
731
+ display: inline-block;
729
732
  margin-right: 10px;
730
733
  }
734
+ #archives .tag-icon {
735
+ color: #5c6b72;
736
+ text-decoration: none;
737
+ }
731
738
  #archives h3 {
732
739
  margin: 10px 0;
733
740
  }
@@ -778,6 +785,9 @@ footer .footer-icon {
778
785
  font-weight: bold;
779
786
  margin: 20px 0;
780
787
  }
788
+ .article .info {
789
+ line-height: 1.5;
790
+ }
781
791
  .article .info a {
782
792
  color: #5c6b72;
783
793
  text-decoration: none;
@@ -785,25 +795,18 @@ footer .footer-icon {
785
795
  .article .info .date {
786
796
  color: #5c6b72;
787
797
  display: inline-block;
788
- margin-right: 20px;
789
- }
790
- .article .info .date .tag {
791
798
  margin-right: 10px;
792
799
  }
793
800
  .article .info .category {
794
- color: #5c6b72;
795
801
  display: inline-block;
796
- margin-right: 20px;
797
- }
798
- .article .info .category .tag {
799
802
  margin-right: 10px;
800
803
  }
801
804
  .article .info .tags {
802
- color: #5c6b72;
803
805
  display: inline-block;
804
- margin-right: 20px;
806
+ margin-right: 10px;
805
807
  }
806
808
  .article .info .tags .tag {
809
+ display: inline-block;
807
810
  margin-right: 10px;
808
811
  }
809
812
  .article .content {
@@ -862,24 +865,24 @@ input.ipt:focus {
862
865
  border-color: #0969da;
863
866
  box-shadow: 0 0 0 3px #0969da4d;
864
867
  }
865
- input.crypto {
868
+ #crypto {
866
869
  margin: 50px auto 0;
867
870
  }
868
- input.crypto.fail {
871
+ #crypto.fail {
869
872
  color: #ea4a5a;
870
873
  border-color: #ea4a5a;
871
874
  }
872
- input.crypto.fail:focus {
875
+ #crypto.fail:focus {
873
876
  box-shadow: 0 0 0 3px #ea4a5a4d;
874
877
  }
875
- input.crypto.success {
878
+ #crypto.success {
876
879
  color: #34d058;
877
880
  border-color: #34d058;
878
881
  }
879
- input.search-bar {
882
+ #search-bar {
880
883
  margin: 0 auto 50px;
881
884
  }
882
- .search-mask {
885
+ #search-mask {
883
886
  position: relative;
884
887
  margin: auto;
885
888
  margin-top: -125px;
@@ -888,58 +891,6 @@ input.search-bar {
888
891
  height: 150px;
889
892
  background: #f6f8fa;
890
893
  }
891
- .gt-container *:not(.gt-header-textarea) {
892
- font-family: unset !important;
893
- }
894
- .gt-header-textarea {
895
- font-family: "Fira Code", "Noto Sans SC", monospace;
896
- }
897
- .gt-container .gt-comment-content {
898
- border: 1px solid #0000001a;
899
- border-radius: 5px;
900
- }
901
- .gt-container .gt-comment-content:hover,
902
- .gt-container .gt-header-textarea:hover,
903
- .gt-container .gt-header-textarea:focus {
904
- box-shadow: 0 2px 8px #00000017 !important;
905
- background-color: #fbfbfb;
906
- }
907
- .gt-container .gt-header-textarea:focus {
908
- background-color: #fbfbfb;
909
- }
910
- .gt-container .gt-avatar img {
911
- border-radius: 50% !important;
912
- }
913
- .gt-container .gt-popup {
914
- border-radius: 10px;
915
- }
916
- .gt-container .markdown-body {
917
- color: unset !important;
918
- }
919
- .gt-container .markdown-body p {
920
- margin: 10px 0 !important;
921
- }
922
- .gt-container .markdown-body blockquote {
923
- border-left: unset !important;
924
- padding: 1px 20px !important;
925
- border-left: 3px solid #1e3e3f !important;
926
- color: unset !important;
927
- }
928
- .gt-container .markdown-body code {
929
- font-family: "Fira Code" !important;
930
- background: #bddcf76b;
931
- border-radius: 4px;
932
- font-size: 14px;
933
- color: #4b616b;
934
- }
935
- .gt-container .markdown-body pre {
936
- font-family: "Fira Code" !important;
937
- font-weight: 500;
938
- border: 1px solid #ebeef5;
939
- padding: 20px;
940
- margin: 25px 0;
941
- border-radius: 15px !important;
942
- }
943
894
  #showimg {
944
895
  position: fixed !important;
945
896
  display: flex;
@@ -962,6 +913,9 @@ input.search-bar {
962
913
  max-height: 95%;
963
914
  box-shadow: 0 0 50px 10px #d9d9d980;
964
915
  }
916
+ .math.display .katex {
917
+ overflow: auto;
918
+ }
965
919
  ::-webkit-scrollbar {
966
920
  width: 12px;
967
921
  height: 12px;
@@ -1,12 +1,14 @@
1
- const sleep = ms => new Promise(res => setTimeout(res, ms));
1
+ function sleep(ms) {
2
+ return new Promise(resolve => setTimeout(resolve, ms));
3
+ }
2
4
  let copying = false;
3
5
  function highlight() {
4
6
  hljs.configure({ ignoreUnescapedHTML: true });
5
7
  let codes = document.getElementsByTagName("pre");
6
- for (let code of codes) {
7
- let lang = [...code.classList, ...code.firstChild.classList][0] || "text";
8
- code.innerHTML = `<div class="code-content">${code.innerHTML}</div><div class="language">${lang}</div><div class="copycode"><i class="fa-solid fa-copy fa-fw"></i><i class="fa-solid fa-clone fa-fw"></i></div>`;
9
- let copycode = code.getElementsByClassName("copycode")[0];
8
+ for (let i of codes) {
9
+ let lang = [...i.classList, ...i.firstChild.classList][0] || "text";
10
+ i.innerHTML = `<div class="code-content">${i.innerHTML}</div><div class="language">${lang}</div><div class="copycode"><i class="fa-solid fa-copy fa-fw"></i><i class="fa-solid fa-clone fa-fw"></i></div>`;
11
+ let copycode = i.getElementsByClassName("copycode")[0];
10
12
  copycode.addEventListener("click", async function () {
11
13
  if (copying) return;
12
14
  copying = true;
@@ -16,13 +18,13 @@ function highlight() {
16
18
  this.classList.remove("copied");
17
19
  copying = false;
18
20
  });
19
- hljs.highlightElement(code.getElementsByClassName("code-content")[0]);
21
+ hljs.highlightElement(i.getElementsByClassName("code-content")[0]);
20
22
  }
21
23
  }
22
24
  function showimg() {
23
25
  let wrap = document.getElementById("showimg"),
24
26
  content = document.getElementById("showimg-content"),
25
- imgs = document.querySelectorAll(".article .content img");
27
+ images = document.querySelectorAll(".article .content img");
26
28
  function show(src) {
27
29
  content.setAttribute("src", src);
28
30
  wrap.style.opacity = 1;
@@ -32,12 +34,13 @@ function showimg() {
32
34
  wrap.style.opacity = 0;
33
35
  wrap.style.visibility = "hidden";
34
36
  }
35
- for (let img of imgs)
36
- img.addEventListener("click", function () {
37
+ for (let i of images)
38
+ i.addEventListener("click", function () {
37
39
  show(this.getAttribute("src"));
38
40
  });
39
- wrap.addEventListener("click", () => hide());
40
- window.addEventListener("resize", () => hide());
41
+ wrap.addEventListener("click", hide);
42
+ window.addEventListener("resize", hide);
43
+ window.addEventListener("scroll", hide);
41
44
  }
42
45
  function rendermath() {
43
46
  if (typeof renderMathInElement !== "undefined")
@@ -50,3 +53,17 @@ function rendermath() {
50
53
  ],
51
54
  });
52
55
  }
56
+ function sha(str) {
57
+ return CryptoJS.SHA256(str).toString(CryptoJS.enc.Base64);
58
+ }
59
+ function decrypt(encrypt, key, check) {
60
+ try {
61
+ let res = CryptoJS.AES.decrypt(encrypt, sha(key), {
62
+ mode: CryptoJS.mode.ECB,
63
+ padding: CryptoJS.pad.Pkcs7,
64
+ }).toString(CryptoJS.enc.Utf8);
65
+ return { decrypt: res, check: sha(res) == check };
66
+ } catch {
67
+ return { check: false };
68
+ }
69
+ }
@@ -5,19 +5,41 @@ const app = Vue.createApp({
5
5
  menushow: false,
6
6
  cardtop: 100,
7
7
  barlocal: 0,
8
+ composition: false,
8
9
  };
9
10
  },
10
- created() {
11
- let that = this;
12
- window.addEventListener("load", () => {
13
- that.showpage = true;
14
- document.getElementById("loading").style.opacity = 0;
15
- document.getElementById("loading").style.visibility = "hidden";
16
- });
17
- },
18
11
  mounted() {
12
+ this.showpage = true;
13
+ document.getElementById("loading").style.opacity = 0;
14
+ document.getElementById("loading").style.visibility = "hidden";
19
15
  if (document.getElementById("home-head"))
20
16
  document.getElementById("menu").className += " menu-color";
17
+ if (document.getElementById("crypto")) {
18
+ let input = document.getElementById("crypto");
19
+ input.addEventListener("input", () => {
20
+ if (!this.composition) this.handlecrypto();
21
+ });
22
+ input.addEventListener("compositionstart", () => {
23
+ this.composition = true;
24
+ });
25
+ input.addEventListener("compositionend", () => {
26
+ this.handlecrypto();
27
+ this.composition = false;
28
+ });
29
+ }
30
+ if (document.getElementById("search-bar")) {
31
+ let input = document.getElementById("search-bar");
32
+ input.addEventListener("input", () => {
33
+ if (!this.composition) this.handlesearch();
34
+ });
35
+ input.addEventListener("compositionstart", () => {
36
+ this.composition = true;
37
+ });
38
+ input.addEventListener("compositionend", () => {
39
+ this.handlesearch();
40
+ this.composition = false;
41
+ });
42
+ }
21
43
  window.addEventListener("scroll", this.handlescroll, true);
22
44
  highlight();
23
45
  showimg();
@@ -25,20 +47,16 @@ const app = Vue.createApp({
25
47
  },
26
48
  methods: {
27
49
  homeclick() {
28
- window.scrollTo({
29
- top: window.innerHeight,
30
- behavior: "smooth",
31
- });
50
+ window.scrollTo({ top: window.innerHeight, behavior: "smooth" });
32
51
  },
33
52
  handlescroll() {
34
53
  let newlocal = document.documentElement.scrollTop;
35
54
  let menu = document.getElementById("menu");
36
55
  let wrap = document.getElementById("home-posts-wrap");
37
56
  let footer = document.getElementById("footer");
38
- let that = this;
39
57
  if (this.barlocal < newlocal) {
40
58
  menu.className = "hidden-menu";
41
- that.menushow = false;
59
+ this.menushow = false;
42
60
  } else menu.className = "show-menu";
43
61
  if (wrap) {
44
62
  if (newlocal <= window.innerHeight - 100) menu.className += " menu-color";
@@ -52,6 +70,36 @@ const app = Vue.createApp({
52
70
  }
53
71
  this.barlocal = newlocal;
54
72
  },
73
+ handlecrypto() {
74
+ let input = document.getElementById("crypto"),
75
+ content = document.getElementsByClassName("content")[0];
76
+ let res = decrypt(input.dataset.encrypt, input.value, input.dataset.check);
77
+ if (res.check) {
78
+ input.disabled = true;
79
+ input.classList.remove("fail");
80
+ input.classList.add("success");
81
+ content.innerHTML = res.decrypt;
82
+ content.style.opacity = 1;
83
+ highlight();
84
+ showimg();
85
+ rendermath();
86
+ } else input.classList.add("fail");
87
+ },
88
+ handlesearch() {
89
+ let input = document.getElementById("search-bar"),
90
+ timeline = document.getElementsByClassName("timeline"),
91
+ key = input.value.toLowerCase().replace(/s+/gm, "");
92
+ for (let i of timeline)
93
+ if (!key || i.dataset.title.includes(key)) {
94
+ i.style.opacity = 1;
95
+ i.style.pointerEvents = "";
96
+ i.style.marginTop = "";
97
+ } else {
98
+ i.style.opacity = 0;
99
+ i.style.pointerEvents = "none";
100
+ i.style.marginTop = -i.offsetHeight - 30 + "px";
101
+ }
102
+ },
55
103
  },
56
104
  });
57
105
  app.mount("#layout");
@@ -1,44 +0,0 @@
1
- const cryptor = {
2
- init(enc, sha) {
3
- this.enc = enc;
4
- this.sha = sha;
5
- this.composition = false;
6
- window.addEventListener("load", () => {
7
- this.input = document.getElementsByClassName("crypto")[0];
8
- this.content = document.getElementsByClassName("content")[0];
9
- this.content.style.opacity = 0;
10
- this.input.addEventListener("input", () => this.composition || this.update());
11
- this.input.addEventListener("compositionstart", () => (this.composition = true));
12
- this.input.addEventListener("compositionend", () => {
13
- this.update(), (this.composition = false);
14
- });
15
- });
16
- },
17
- SHA(str) {
18
- return CryptoJS.SHA256(str).toString(CryptoJS.enc.Base64);
19
- },
20
- decrypt(enc, key, sha) {
21
- try {
22
- let res = CryptoJS.AES.decrypt(enc, this.SHA(key), {
23
- mode: CryptoJS.mode.ECB,
24
- padding: CryptoJS.pad.Pkcs7,
25
- }).toString(CryptoJS.enc.Utf8);
26
- return { dec: res, check: this.SHA(res) == sha };
27
- } catch {
28
- return { check: false };
29
- }
30
- },
31
- update() {
32
- let res = this.decrypt(this.enc, this.input.value, this.sha);
33
- if (res.check) {
34
- this.input.disabled = true;
35
- this.input.classList.remove("fail");
36
- this.input.classList.add("success");
37
- this.content.innerHTML = res.dec;
38
- this.content.style.opacity = 1;
39
- highlight();
40
- showimg();
41
- rendermath();
42
- } else this.input.classList.add("fail");
43
- },
44
- };
@@ -1,43 +0,0 @@
1
- const searcher = {
2
- init(path) {
3
- this.composition = false;
4
- window.addEventListener("load", () => {
5
- this.input = document.getElementsByClassName("search-bar")[0];
6
- this.timeline = document.getElementsByClassName("timeline");
7
- this.input.addEventListener("input", () => this.composition || this.update());
8
- this.input.addEventListener("compositionstart", () => (this.composition = true));
9
- this.input.addEventListener("compositionend", () => {
10
- this.update();
11
- this.composition = false;
12
- });
13
- fetch(path)
14
- .then(res => res.json())
15
- .then(data => {
16
- this.data = data;
17
- });
18
- });
19
- },
20
- rstr(s) {
21
- if (!s) return "";
22
- return s.toLowerCase().replace(/\s+/gm, "");
23
- },
24
- match(s, rs) {
25
- return s.indexOf(rs) != -1;
26
- },
27
- update() {
28
- let res = [],
29
- rs = this.rstr(this.input.value);
30
- if (rs) res = this.data.filter(i => this.match(i.odata, rs)).map(i => i.path);
31
- else res = this.data.map(i => i.path);
32
- for (let line of this.timeline)
33
- if (res.indexOf(line.getAttribute("path")) == -1) {
34
- line.style.opacity = 0;
35
- line.style.pointerEvents = "none";
36
- line.style.marginTop = -line.offsetHeight - 30 + "px";
37
- } else {
38
- line.style.opacity = 1;
39
- line.style.pointerEvents = "";
40
- line.style.marginTop = "";
41
- }
42
- },
43
- };