hexo-theme-solitude 3.0.21 → 4.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (142) hide show
  1. package/.github/FUNDING.yml +3 -3
  2. package/README.md +11 -1
  3. package/README_en-US.md +11 -1
  4. package/_config.yml +16 -7
  5. package/languages/default.yml +56 -36
  6. package/languages/en.yml +90 -69
  7. package/languages/es.yml +94 -73
  8. package/languages/zh-CN.yml +56 -34
  9. package/languages/zh-TW.yml +80 -57
  10. package/layout/archive.pug +60 -14
  11. package/layout/category.pug +13 -11
  12. package/layout/includes/body/gadsense.pug +2 -2
  13. package/layout/includes/console.pug +71 -72
  14. package/layout/includes/footer.pug +24 -9
  15. package/layout/includes/head/config.pug +83 -39
  16. package/layout/includes/head/mode.pug +7 -7
  17. package/layout/includes/head/page_config.pug +20 -11
  18. package/layout/includes/inject/body.pug +14 -36
  19. package/layout/includes/keyboard.pug +3 -3
  20. package/layout/includes/loading.pug +1 -37
  21. package/layout/includes/mixins/articleSort.pug +3 -3
  22. package/layout/includes/mixins/common.pug +8 -4
  23. package/layout/includes/mixins/pace.pug +1 -1
  24. package/layout/includes/mixins/pagination.pug +3 -3
  25. package/layout/includes/nav.pug +4 -3
  26. package/layout/includes/page/about.pug +9 -8
  27. package/layout/includes/page/brevity.pug +2 -2
  28. package/layout/includes/page/links.pug +23 -11
  29. package/layout/includes/page/music.pug +0 -5
  30. package/layout/includes/rightmenu.pug +20 -20
  31. package/layout/includes/sidebar.pug +2 -2
  32. package/layout/includes/widgets/aside/asideInfoCard.pug +2 -2
  33. package/layout/includes/widgets/aside/asideTag.pug +2 -2
  34. package/layout/includes/widgets/home/bbTimeList.pug +3 -3
  35. package/layout/includes/widgets/home/categoryBar.pug +30 -15
  36. package/layout/includes/widgets/home/hometop.pug +113 -8
  37. package/layout/includes/widgets/home/postList.pug +1 -2
  38. package/layout/includes/widgets/home/topGroup.pug +3 -7
  39. package/layout/includes/widgets/nav/group.pug +4 -2
  40. package/layout/includes/widgets/nav/menu.pug +6 -4
  41. package/layout/includes/widgets/nav/right.pug +7 -6
  42. package/layout/includes/widgets/page/about/award.pug +67 -38
  43. package/layout/includes/widgets/page/about/hobbies.pug +12 -3
  44. package/layout/includes/widgets/page/banner.pug +1 -1
  45. package/layout/includes/widgets/page/kit/content.pug +2 -2
  46. package/layout/includes/widgets/page/links/banner.pug +30 -19
  47. package/layout/includes/widgets/page/message/artalk.pug +3 -3
  48. package/layout/includes/widgets/page/message/content.pug +3 -3
  49. package/layout/includes/widgets/page/message/twikoo.pug +3 -3
  50. package/layout/includes/widgets/page/message/valine.pug +4 -4
  51. package/layout/includes/widgets/page/message/waline.pug +3 -3
  52. package/layout/includes/widgets/page/recentcomment/artalk.pug +5 -9
  53. package/layout/includes/widgets/page/recentcomment/twikoo.pug +5 -9
  54. package/layout/includes/widgets/page/recentcomment/valine.pug +6 -10
  55. package/layout/includes/widgets/page/recentcomment/waline.pug +5 -9
  56. package/layout/includes/widgets/post/award.pug +1 -28
  57. package/layout/includes/widgets/post/copyright.pug +10 -10
  58. package/layout/includes/widgets/post/postInfo.pug +10 -11
  59. package/layout/includes/widgets/post/postMeta.pug +3 -2
  60. package/layout/includes/widgets/rightside/hide.pug +5 -5
  61. package/layout/includes/widgets/rightside/show.pug +7 -7
  62. package/layout/includes/widgets/third-party/comments/artalk.pug +9 -9
  63. package/layout/includes/widgets/third-party/comments/giscus.pug +2 -2
  64. package/layout/includes/widgets/third-party/comments/twikoo.pug +5 -5
  65. package/layout/includes/widgets/third-party/comments/valine.pug +6 -6
  66. package/layout/includes/widgets/third-party/comments/waline.pug +6 -6
  67. package/layout/includes/widgets/third-party/hot/twikoo.pug +1 -1
  68. package/layout/includes/widgets/third-party/music.pug +9 -5
  69. package/layout/includes/widgets/third-party/news-comment/artalk.pug +6 -6
  70. package/layout/includes/widgets/third-party/news-comment/twikoo.pug +7 -7
  71. package/layout/includes/widgets/third-party/news-comment/valine.pug +8 -8
  72. package/layout/includes/widgets/third-party/news-comment/waline.pug +7 -7
  73. package/layout/includes/widgets/third-party/pjax.pug +16 -10
  74. package/layout/includes/widgets/third-party/search/algolia-search.pug +2 -2
  75. package/layout/includes/widgets/third-party/search/docsearch.pug +3 -3
  76. package/layout/includes/widgets/third-party/search/index.pug +1 -3
  77. package/layout/includes/widgets/third-party/search/local-search.pug +9 -9
  78. package/layout/index.pug +8 -6
  79. package/package.json +8 -7
  80. package/plugins.yml +22 -21
  81. package/scripts/event/cdn.js +11 -6
  82. package/scripts/event/friend_links.js +30 -0
  83. package/scripts/event/merge_config.js +15 -7
  84. package/scripts/filter/comment.js +2 -2
  85. package/scripts/filter/default.js +1 -1
  86. package/scripts/helper/inject_head_js.js +79 -33
  87. package/scripts/helper/related_post.js +16 -21
  88. package/scripts/tags/chart.js +1 -1
  89. package/scripts/tags/typeit.js +1 -1
  90. package/source/css/_components/capsule.styl +259 -168
  91. package/source/css/_components/rightside.styl +98 -242
  92. package/source/css/_global/animation.styl +1 -10
  93. package/source/css/_global/index.styl +27 -1
  94. package/source/css/_layout/aside.styl +7 -1
  95. package/source/css/_layout/console.styl +145 -165
  96. package/source/css/_layout/header.styl +238 -47
  97. package/source/css/_layout/pagination.styl +176 -132
  98. package/source/css/_layout/rightmenu.styl +11 -2
  99. package/source/css/_layout/sidebar.styl +39 -17
  100. package/source/css/_mode/index.styl +7 -1
  101. package/source/css/_page/_about/about.styl +18 -2
  102. package/source/css/_page/_about/buff.styl +1 -1
  103. package/source/css/_page/_about/contentinfo.styl +2 -1
  104. package/source/css/_page/_about/game.styl +57 -1
  105. package/source/css/_page/_about/like.styl +7 -2
  106. package/source/css/_page/_about/maxim.styl +1 -1
  107. package/source/css/_page/_about/reward.styl +226 -108
  108. package/source/css/_page/_about/skills.styl +5 -3
  109. package/source/css/_page/_home/category-bar.styl +162 -88
  110. package/source/css/_page/_home/home-top.styl +309 -263
  111. package/source/css/_page/_home/home.styl +34 -7
  112. package/source/css/_page/archive.styl +412 -0
  113. package/source/css/_page/category.styl +27 -1
  114. package/source/css/_page/index.styl +2 -0
  115. package/source/css/_page/links.styl +190 -0
  116. package/source/css/_page/other.styl +37 -0
  117. package/source/css/_post/index.styl +2 -1
  118. package/source/css/_post/meta.styl +245 -217
  119. package/source/css/_search/local-search.styl +158 -82
  120. package/source/css/var.styl +2 -2
  121. package/source/js/archive-page.js +191 -0
  122. package/source/js/core/actions.js +48 -0
  123. package/source/js/core/api.js +32 -0
  124. package/source/js/core/config.js +13 -0
  125. package/source/js/core/lifecycle.js +52 -0
  126. package/source/js/core/preloader.js +34 -0
  127. package/source/js/core/resources.js +65 -0
  128. package/source/js/covercolor/api.js +13 -103
  129. package/source/js/covercolor/ave.js +14 -77
  130. package/source/js/covercolor/local.js +22 -116
  131. package/source/js/covercolor/shared.js +102 -0
  132. package/source/js/friend_links.js +502 -0
  133. package/source/js/main.js +793 -177
  134. package/source/js/music.js +24 -14
  135. package/source/js/post_ai.js +13 -2
  136. package/source/js/right_menu.js +44 -24
  137. package/source/js/search/algolia.js +29 -21
  138. package/source/js/search/local.js +231 -266
  139. package/source/js/tw_cn.js +37 -12
  140. package/source/js/utils.js +37 -25
  141. package/layout/includes/widgets/randomlink.pug +0 -43
  142. package/scripts/filter/randomPosts.js +0 -16
@@ -1,67 +1,145 @@
1
+ html.search-open
2
+ overflow hidden
3
+
1
4
  .search-dialog
2
5
  position fixed
3
- top 5rem
4
- left 50%
6
+ inset 0
5
7
  z-index 1001
6
8
  display none
7
- margin-left -15rem
9
+ margin auto
8
10
  padding 1rem
9
11
  width 30rem
12
+ height fit-content
13
+ max-height 90dvh
14
+ overflow hidden
10
15
  box-shadow var(--efu-shadow-lightblack)
11
16
  background var(--efu-card-bg)
12
17
  border var(--style-border)
13
- transition 0.3s
14
- border-radius 8px
18
+ border-radius 12px
19
+ transition .3s
15
20
  animation slide-in .6s ease 0s 1 normal none running
16
21
  gap 16px
17
- flex-direction: column
22
+ flex-direction column
18
23
 
19
24
  &:hover
20
25
  border var(--style-border-hover)
21
26
  box-shadow var(--efu-shadow-theme)
22
- +maxWidth768()
23
- border none
24
-
25
- +maxWidth768()
26
- top 0
27
- left 0
28
- margin 0
29
- width 100%
30
- height 100%
31
- border 0
32
27
 
33
28
  .search-dialog-title
34
- font-weight 700
35
29
  color var(--efu-main)
36
30
  font-size 1.4em
31
+ font-weight 700
37
32
  line-height 1
38
33
 
39
34
  .search-close-button
35
+ display inline-flex
36
+ align-items center
37
+ justify-content center
38
+ flex 0 0 32px
39
+ width 32px
40
+ height 32px
41
+ margin-left 4px
42
+ padding 4px
40
43
  color var(--efu-gray)
44
+ background transparent
45
+ border 0
46
+ border-radius 50%
41
47
  font-size 1.4em
42
48
  line-height 1
43
49
  cursor pointer
44
- transition color .2s ease-in-out 0s
50
+ transition color .2s ease-in-out, background .2s ease-in-out
45
51
 
46
52
  &:hover
47
53
  color var(--efu-main)
54
+ background var(--efu-main-op)
55
+
56
+ .search-wrap
57
+ display flex
58
+ min-height 0
59
+ overflow hidden
60
+ flex-direction column
61
+ gap 16px
62
+
63
+ .search-box
64
+ position relative
65
+ width 100%
66
+ max-width 100%
67
+ margin 0 auto
68
+
69
+ input
70
+ width 100%
71
+ height 44px
72
+ padding .25rem .7rem
73
+ outline 0
74
+ color var(--efu-fontcolor)
75
+ background var(--efu-secondbg)
76
+ border var(--style-border)
77
+ border-radius 12px
78
+ caret-color var(--efu-theme)
79
+
80
+ &:focus
81
+ border var(--style-border-hover)
82
+
83
+ &::placeholder
84
+ color var(--efu-gray)
85
+ opacity .8
86
+
87
+ #search-suggestions
88
+ display flex
89
+ flex-wrap wrap
90
+ gap 8px
91
+
92
+ &[hidden]
93
+ display none
94
+
95
+ .tag-list
96
+ min-height 30px
97
+ padding 3px 8px
98
+ color var(--efu-fontcolor)
99
+ background transparent
100
+ border var(--style-border-always)
101
+ border-radius 8px
102
+ line-height 1.2
103
+ cursor pointer
104
+ transition color .2s ease-in-out, background .2s ease-in-out, border-color .2s ease-in-out
105
+
106
+ &:hover,
107
+ &:focus-visible
108
+ color var(--efu-white)
109
+ background var(--efu-main)
110
+ border-color var(--efu-main)
111
+ outline 0
48
112
 
49
113
  #search-results
50
- max-height calc(80vh - 130px)
114
+ display flex
115
+ max-height calc(90dvh - 250px)
116
+ min-height 0
117
+ margin 0
118
+ padding 0
51
119
  overflow-y auto
52
- margin-top 8px
120
+ flex 1
121
+ flex-direction column
122
+ list-style none
123
+
124
+ &[hidden]
125
+ display none
53
126
 
54
127
  .search-result-item
128
+ width 100%
55
129
  padding-left 0
56
130
  font-weight 700
131
+ line-height 1.5
57
132
 
58
133
  .search-result-title
134
+ display block
59
135
  position relative
60
- transition color .2s ease-in-out 0s
136
+ width 100%
137
+ padding 3px 0
138
+ overflow hidden
139
+ color var(--efu-fontcolor)
61
140
  white-space nowrap
62
141
  text-overflow ellipsis
63
- overflow hidden
64
- display block
142
+ transition color .2s ease-in-out
65
143
 
66
144
  &:hover
67
145
  color var(--efu-hovertext)
@@ -70,39 +148,50 @@
70
148
  color var(--efu-theme)
71
149
  font-style normal
72
150
 
73
- +maxWidth768()
74
- max-height 80vh !important
151
+ .search-result-empty,
152
+ .search-result-error,
153
+ .search-result-loading
154
+ color var(--efu-secondtext)
75
155
 
76
156
  #search-pagination
157
+ &:empty
158
+ display none
159
+
77
160
  .pagination-list
161
+ display flex
162
+ justify-content center
163
+ gap 8px
164
+ margin 0
78
165
  padding 0
79
- text-align center
80
-
81
- .pagination-item
82
- color var(--efu-fontcolor)
83
- display inline-block
84
- min-width 1.2rem
85
- height 1.2rem
86
- text-align center
87
- line-height 1.2rem
88
- cursor pointer
89
-
90
- &:hover
91
- color var(--efu-main)
166
+ list-style none
167
+
168
+ .pagination-button
169
+ display inline-flex
170
+ align-items center
171
+ justify-content center
172
+ min-width 1.2rem
173
+ height 1.2rem
174
+ padding 0
175
+ color var(--efu-fontcolor)
176
+ background transparent
177
+ border 0
178
+ border-radius 4px
179
+ cursor pointer
92
180
 
93
- &.select
94
- border-radius 4px
95
- background var(--efu-main)
96
- color var(--efu-white)
97
- cursor default
181
+ &:hover
182
+ color var(--efu-main)
98
183
 
99
- &:hover
100
- color var(--efu-white)
184
+ &.select
185
+ color var(--efu-white)
186
+ background var(--efu-main)
187
+ cursor default
101
188
 
102
189
  #search-tips
190
+ width 100%
103
191
  color var(--efu-secondtext)
104
- right 0
105
- margin auto
192
+
193
+ &:empty
194
+ display none
106
195
 
107
196
  .search-result-count
108
197
  color var(--efu-gray)
@@ -111,51 +200,38 @@
111
200
  b
112
201
  color var(--efu-main)
113
202
 
114
- .search-box
115
- position relative
116
- margin 0 auto
117
- max-width 100%
118
- width 100%
119
-
120
- input
121
- height 100%
122
- width 100%
123
- border-radius 8px
124
- border var(--style-border)
125
- padding 0.25rem 0.7rem;
126
- outline 0
127
- background var(--efu-secondbg)
128
- color var(--efu-fontcolor)
129
- caret-color var(--efu-theme)
130
-
131
- &::placeholder
132
- color var(--efu-gray)
133
- opacity .8
203
+ .search-nav
204
+ display flex
205
+ align-items center
206
+ justify-content space-between
134
207
 
135
208
  #search-mask
136
209
  position fixed
137
210
  inset 0
138
211
  z-index 1000
139
212
  display none
140
- background rgba(0, 0, 0, 0.6)
213
+ background var(--efu-maskbgdeep)
141
214
  backdrop-filter blur(12px)
142
215
  -webkit-backdrop-filter blur(12px)
143
216
  transform translateZ(0)
144
- background var(--efu-maskbgdeep)
145
217
 
146
- #search-hits
147
- .tag-list
148
- padding 4px 8px
149
- border-radius 8px
150
- margin-right 0.5rem
151
- margin-top 0.5rem
152
- border var(--style-border-always)
218
+ @media screen and (max-width: 767px)
219
+ .search-dialog
220
+ inset 0
221
+ width 100vw
222
+ height var(--search-height, 100dvh)
223
+ max-height none
224
+ margin 0
225
+ border 0
226
+ border-radius 0
153
227
 
154
228
  &:hover
155
- background var(--efu-main)
156
- color var(--efu-white)
229
+ border 0
230
+ box-shadow var(--efu-shadow-lightblack)
157
231
 
158
- .search-nav
159
- display: flex
160
- justify-content: space-between
161
- align-content: center
232
+ .search-wrap
233
+ flex 1
234
+
235
+ #search-results
236
+ max-height none
237
+ flex 1
@@ -6,12 +6,12 @@ $dark_theme = convert(hexo-config('theme_color.dark'))
6
6
  $dark_theme_op = convert(hexo-config('theme_color.dark') + '23')
7
7
  $dark_theme_op_deep = convert(hexo-config('theme_color.dark') + 'dd')
8
8
  $dark_theme_none = convert(hexo-config('theme_color.dark') + '00')
9
+ $dark_nav_hover_text = convert(hexo-config('theme_color.nav_hover_text.dark'))
9
10
  $light_theme = convert(hexo-config('theme_color.light'))
10
11
  $light_theme_op = convert(hexo-config('theme_color.light') + '23')
11
12
  $light_theme_op_deep = convert(hexo-config('theme_color.light') + 'dd')
12
13
  $light_theme_none = convert(hexo-config('theme_color.light') + '00')
13
-
14
- $todayCardColor = convert(hexo-config('hometop.recommendList.color'))
14
+ $light_nav_hover_text = convert(hexo-config('theme_color.nav_hover_text.light'))
15
15
 
16
16
  // code
17
17
  if hexo-config('highlight.theme') == 'default'
@@ -0,0 +1,191 @@
1
+ import { Solitude } from "./core/api.js";
2
+
3
+ export const archivePageController = (() => {
4
+ const createElement = (tag, className, text) => {
5
+ const element = document.createElement(tag);
6
+ if (className) element.className = className;
7
+ if (text !== undefined) element.textContent = text;
8
+ return element;
9
+ };
10
+
11
+ const controller = {
12
+ init() {
13
+ const shell = document.getElementById('archives-page');
14
+ const dataElement = document.getElementById('archive-page-data');
15
+ if (!shell || !dataElement || shell.dataset.archiveInitialized === 'true') return;
16
+
17
+ let posts;
18
+ try {
19
+ posts = JSON.parse(dataElement.content?.textContent || dataElement.textContent || '[]');
20
+ } catch (error) {
21
+ this.renderState(shell, '文章数据加载失败,请稍后再试。');
22
+ console.error('Failed to parse archive data:', error);
23
+ return;
24
+ }
25
+
26
+ shell.dataset.archiveInitialized = 'true';
27
+ this.shell = shell;
28
+ this.list = shell.querySelector('#archives-page-list');
29
+ this.yearList = shell.querySelector('#archives-year-filter-list');
30
+ this.pagination = shell.querySelector('#pagination .pagination');
31
+ this.paginationSection = shell.querySelector('.archive-page-section-pagination');
32
+ this.posts = Array.isArray(posts) ? posts : [];
33
+ this.perPage = Math.max(Number(shell.dataset.perPage) || 10, 1);
34
+ this.years = [...new Set(this.posts.map(post => String(post.year)))].sort((a, b) => Number(b) - Number(a));
35
+ this.activeYear = this.years.includes(shell.dataset.initialYear) ? shell.dataset.initialYear : 'all';
36
+ this.currentPage = Math.max(Number(shell.dataset.initialPage) || 1, 1);
37
+
38
+ this.renderYears();
39
+ this.bindEvents();
40
+ this.render();
41
+ },
42
+
43
+ bindEvents() {
44
+ this.shell.addEventListener('click', event => {
45
+ const yearButton = event.target.closest('.archive-year-button');
46
+ if (yearButton) {
47
+ this.activeYear = yearButton.dataset.year;
48
+ this.currentPage = 1;
49
+ this.render();
50
+ return;
51
+ }
52
+
53
+ const pageButton = event.target.closest('[data-archive-page]');
54
+ if (!pageButton || pageButton.disabled) return;
55
+ this.currentPage = Number(pageButton.dataset.archivePage);
56
+ this.render();
57
+ this.shell.scrollIntoView({ behavior: 'smooth', block: 'start' });
58
+ });
59
+ },
60
+
61
+ renderYears() {
62
+ this.yearList.replaceChildren();
63
+ this.years.forEach(year => {
64
+ const button = createElement('button', 'archive-year-button', year);
65
+ button.type = 'button';
66
+ button.dataset.year = year;
67
+ button.setAttribute('aria-pressed', 'false');
68
+ this.yearList.appendChild(button);
69
+ });
70
+ },
71
+
72
+ render() {
73
+ const filteredPosts = this.activeYear === 'all'
74
+ ? this.posts
75
+ : this.posts.filter(post => String(post.year) === this.activeYear);
76
+ const totalPages = Math.max(Math.ceil(filteredPosts.length / this.perPage), 1);
77
+ this.currentPage = Math.min(this.currentPage, totalPages);
78
+
79
+ this.shell.querySelectorAll('.archive-year-button').forEach(button => {
80
+ const active = button.dataset.year === this.activeYear;
81
+ button.classList.toggle('is-active', active);
82
+ button.setAttribute('aria-pressed', String(active));
83
+ });
84
+
85
+ this.list.replaceChildren();
86
+ if (!filteredPosts.length) {
87
+ this.renderState(this.shell, '当前年份下暂无文章。');
88
+ } else {
89
+ const start = (this.currentPage - 1) * this.perPage;
90
+ filteredPosts.slice(start, start + this.perPage).forEach(post => {
91
+ this.list.appendChild(this.createPostItem(post));
92
+ });
93
+ }
94
+
95
+ this.renderPagination(totalPages);
96
+ },
97
+
98
+ createPostItem(post) {
99
+ const item = createElement('a', 'archive-page-item');
100
+ item.href = post.url;
101
+ item.title = post.title;
102
+
103
+ const thumb = createElement('div', 'archive-page-thumb');
104
+ const image = createElement('img');
105
+ image.src = post.cover;
106
+ image.alt = post.title;
107
+ image.loading = 'lazy';
108
+ image.addEventListener('error', () => thumb.classList.add('is-fallback'), { once: true });
109
+ const fallback = createElement('span', 'archive-page-thumb-fallback', (post.title || '文').trim().charAt(0) || '文');
110
+ thumb.append(image, fallback);
111
+
112
+ const main = createElement('div', 'archive-page-item-main');
113
+ const title = createElement('div', 'archive-page-item-title', post.title);
114
+ const meta = createElement('div', 'archive-page-item-meta');
115
+ meta.append(
116
+ createElement('span', 'archive-page-item-category', post.primaryCategory || '未分类'),
117
+ createElement('span', 'archive-page-item-divider', '/'),
118
+ createElement('span', 'archive-page-item-date', post.dateLabel)
119
+ );
120
+ main.append(title, meta);
121
+
122
+ const arrow = createElement('div', 'archive-page-item-arrow');
123
+ const arrowIcon = createElement('i', 'solitude fas fa-chevron-right');
124
+ arrow.setAttribute('aria-hidden', 'true');
125
+ arrow.appendChild(arrowIcon);
126
+ item.append(thumb, main, arrow);
127
+ return item;
128
+ },
129
+
130
+ renderPagination(totalPages) {
131
+ this.pagination.replaceChildren();
132
+ this.paginationSection.hidden = totalPages <= 1;
133
+ if (totalPages <= 1) return;
134
+
135
+ this.pagination.appendChild(this.createPageButton(this.currentPage - 1, '上一页', 'archive-page-extend prev', this.currentPage === 1, 'fa-chevron-left'));
136
+
137
+ this.getPageRange(totalPages).forEach(page => {
138
+ if (page === 'space') {
139
+ this.pagination.appendChild(createElement('span', 'archive-page-space', '...'));
140
+ return;
141
+ }
142
+ const button = this.createPageButton(page, String(page), 'archive-page-number', false);
143
+ if (page === this.currentPage) {
144
+ button.classList.add('is-current');
145
+ button.setAttribute('aria-current', 'page');
146
+ }
147
+ this.pagination.appendChild(button);
148
+ });
149
+
150
+ this.pagination.appendChild(this.createPageButton(this.currentPage + 1, '下一页', 'archive-page-extend next', this.currentPage === totalPages, 'fa-chevron-right'));
151
+ },
152
+
153
+ createPageButton(page, label, className, disabled, iconName) {
154
+ const button = createElement('button', className);
155
+ button.type = 'button';
156
+ button.disabled = disabled;
157
+ button.dataset.archivePage = String(page);
158
+ button.setAttribute('aria-label', label);
159
+ if (iconName === 'fa-chevron-left') button.appendChild(createElement('i', `solitude fas ${iconName}`));
160
+ button.appendChild(createElement('span', '', label));
161
+ if (iconName === 'fa-chevron-right') button.appendChild(createElement('i', `solitude fas ${iconName}`));
162
+ return button;
163
+ },
164
+
165
+ getPageRange(totalPages) {
166
+ if (totalPages <= 7) return Array.from({ length: totalPages }, (_, index) => index + 1);
167
+ if (this.currentPage <= 4) return [1, 2, 3, 4, 5, 'space', totalPages];
168
+ if (this.currentPage >= totalPages - 3) return [1, 'space', totalPages - 4, totalPages - 3, totalPages - 2, totalPages - 1, totalPages];
169
+ return [1, 'space', this.currentPage - 1, this.currentPage, this.currentPage + 1, 'space', totalPages];
170
+ },
171
+
172
+ renderState(shell, message) {
173
+ const list = shell.querySelector('#archives-page-list');
174
+ if (!list) return;
175
+ list.replaceChildren(createElement('div', 'archive-page-state', message));
176
+ },
177
+
178
+ destroy() {
179
+ this.shell = null;
180
+ this.list = null;
181
+ this.yearList = null;
182
+ this.pagination = null;
183
+ this.paginationSection = null;
184
+ this.posts = [];
185
+ }
186
+ };
187
+
188
+ return controller;
189
+ })();
190
+
191
+ export const initArchivePage = () => archivePageController.init();
@@ -0,0 +1,48 @@
1
+ export const initActionDelegation = (api) => {
2
+ if (document.documentElement.dataset.solitudeActions === "true") return;
3
+ document.documentElement.dataset.solitudeActions = "true";
4
+
5
+ const dispatch = (event) => {
6
+ if (!(event.target instanceof Element)) return;
7
+ const element = event.target.closest("[data-solitude-action]");
8
+ if (!element) return;
9
+ const action = api[element.dataset.solitudeAction];
10
+ if (typeof action !== "function") return;
11
+
12
+ if (element.dataset.solitudePrevent === "true") event.preventDefault();
13
+ if (element.dataset.solitudeStop === "true") event.stopPropagation();
14
+
15
+ let argument;
16
+ if (element.dataset.solitudeTarget) argument = element.dataset.solitudeTarget;
17
+ else if (element.dataset.solitudeUrl) argument = element.dataset.solitudeUrl;
18
+ else if (element.dataset.solitudeValue) argument = element.dataset.solitudeValue;
19
+ else if (element.dataset.solitudeEvent === "true") argument = event;
20
+
21
+ action.call(api, argument, event, element);
22
+ };
23
+
24
+ document.addEventListener("click", dispatch);
25
+ document.addEventListener("keydown", (event) => {
26
+ if (event.key !== "Enter" && event.key !== " ") return;
27
+ if (!(event.target instanceof Element)) return;
28
+ const element = event.target.closest("[data-solitude-action]");
29
+ if (!element || /^(A|BUTTON|INPUT)$/.test(element.tagName)) return;
30
+ event.preventDefault();
31
+ element.click();
32
+ });
33
+
34
+ document.addEventListener("error", (event) => {
35
+ const element = event.target;
36
+ if (!(element instanceof Element)) return;
37
+ if (element.matches("[data-solitude-hide-ads]")) {
38
+ document
39
+ .querySelectorAll(".google-ads-warp")
40
+ .forEach((item) => { item.style.display = "none"; });
41
+ }
42
+ const fallback = element.dataset.solitudeFallback;
43
+ if (fallback && element.getAttribute("src") !== fallback) {
44
+ element.removeAttribute("data-solitude-fallback");
45
+ element.setAttribute("src", fallback);
46
+ }
47
+ }, true);
48
+ };
@@ -0,0 +1,32 @@
1
+ import { getConfig, getPageConfig } from "./config.js";
2
+ import { lifecycle } from "./lifecycle.js";
3
+ import { loadScript, loadStyle } from "./resources.js";
4
+
5
+ const api = window.Solitude || {};
6
+
7
+ Object.defineProperties(api, {
8
+ config: { configurable: true, get: getConfig },
9
+ page: { configurable: true, get: getPageConfig },
10
+ });
11
+
12
+ Object.assign(api, {
13
+ loadScript(url, options) {
14
+ if (/barrage(?:\.min)?\.js(?:\?|$)/.test(url)) api.installLegacyAdapter?.();
15
+ return loadScript(url, options);
16
+ },
17
+ loadStyle,
18
+ on: lifecycle.on.bind(lifecycle),
19
+ listen: lifecycle.listen.bind(lifecycle),
20
+ onPageCleanup: lifecycle.add.bind(lifecycle),
21
+ disposePage: lifecycle.disposePage.bind(lifecycle),
22
+ navigate(url) {
23
+ if (!url) return;
24
+ const instance = api.pjax;
25
+ if (instance?.loadUrl) instance.loadUrl(url);
26
+ else window.location.assign(url);
27
+ },
28
+ });
29
+
30
+ window.Solitude = api;
31
+
32
+ export { api as Solitude };
@@ -0,0 +1,13 @@
1
+ const parseConfig = (id, fallback = {}) => {
2
+ const element = document.getElementById(id);
3
+ if (!element) return fallback;
4
+ try {
5
+ return JSON.parse(element.content?.textContent || element.textContent || "{}");
6
+ } catch (error) {
7
+ console.error(`Invalid Solitude configuration in #${id}:`, error);
8
+ return fallback;
9
+ }
10
+ };
11
+
12
+ export const getConfig = () => parseConfig("site-config");
13
+ export const getPageConfig = () => parseConfig("config-diff");
@@ -0,0 +1,52 @@
1
+ const EVENT_PREFIX = "solitude:";
2
+
3
+ class Lifecycle {
4
+ #pageController = new AbortController();
5
+ #disposers = new Set();
6
+
7
+ get signal() {
8
+ return this.#pageController.signal;
9
+ }
10
+
11
+ add(disposer) {
12
+ if (typeof disposer !== "function") return () => {};
13
+ this.#disposers.add(disposer);
14
+ return () => this.#disposers.delete(disposer);
15
+ }
16
+
17
+ listen(target, type, handler, options = {}) {
18
+ if (!target?.addEventListener) return () => {};
19
+ const normalized =
20
+ typeof options === "boolean" ? { capture: options } : { ...options };
21
+ normalized.signal ??= this.signal;
22
+ target.addEventListener(type, handler, normalized);
23
+ return () => target.removeEventListener(type, handler, normalized);
24
+ }
25
+
26
+ disposePage() {
27
+ this.#pageController.abort();
28
+ this.#disposers.forEach((dispose) => {
29
+ try {
30
+ dispose();
31
+ } catch (error) {
32
+ console.error("Failed to dispose a Solitude page resource:", error);
33
+ }
34
+ });
35
+ this.#disposers.clear();
36
+ this.#pageController = new AbortController();
37
+ }
38
+
39
+ emit(type, detail) {
40
+ document.dispatchEvent(
41
+ new CustomEvent(`${EVENT_PREFIX}${type}`, { detail })
42
+ );
43
+ }
44
+
45
+ on(type, handler) {
46
+ const eventName = `${EVENT_PREFIX}${type}`;
47
+ document.addEventListener(eventName, handler);
48
+ return () => document.removeEventListener(eventName, handler);
49
+ }
50
+ }
51
+
52
+ export const lifecycle = new Lifecycle();