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
@@ -0,0 +1,502 @@
1
+ import { Solitude } from "./core/api.js";
2
+
3
+ (() => {
4
+ const config = Solitude.config.friend_links;
5
+ let request = null;
6
+
7
+ const createElement = (tag, className, text) => {
8
+ const element = document.createElement(tag);
9
+ if (className) element.className = className;
10
+ if (text !== undefined && text !== null) {
11
+ element.textContent = String(text);
12
+ }
13
+ return element;
14
+ };
15
+
16
+ const resolveSiteUrl = (value) => {
17
+ if (!value) return "";
18
+ const url = String(value);
19
+ if (/^(?:[a-z][a-z\d+.-]*:|\/\/|#)/i.test(url)) return url;
20
+
21
+ const root = Solitude.config.root || "/";
22
+ if (url.startsWith("/")) {
23
+ return root === "/" ? url : `${root.replace(/\/$/, "")}${url}`;
24
+ }
25
+ return `${root}${url}`;
26
+ };
27
+
28
+ const createImage = (src, alt, className) => {
29
+ const image = createElement("img", className);
30
+ image.alt = alt || "";
31
+ image.loading = "lazy";
32
+ const resolvedSrc = resolveSiteUrl(src);
33
+
34
+ if (Solitude.config.lazyload.enable) {
35
+ image.src = config.placeholder || config.default_avatar;
36
+ image.dataset.lazySrc = resolvedSrc;
37
+ } else {
38
+ image.src = resolvedSrc;
39
+ }
40
+ image.addEventListener(
41
+ "error",
42
+ () => {
43
+ image.removeAttribute("data-lazy-src");
44
+ image.src = config.default_avatar;
45
+ },
46
+ { once: true }
47
+ );
48
+ return image;
49
+ };
50
+
51
+ const shuffle = (items) => {
52
+ const shuffled = [...items];
53
+ for (let index = shuffled.length - 1; index > 0; index -= 1) {
54
+ const randomIndex = Math.floor(Math.random() * (index + 1));
55
+ [shuffled[index], shuffled[randomIndex]] = [
56
+ shuffled[randomIndex],
57
+ shuffled[index],
58
+ ];
59
+ }
60
+ return shuffled;
61
+ };
62
+
63
+ const createTag = (item) => {
64
+ if (!item.tag) return null;
65
+ const tag = createElement("span", "site-card-tag", item.tag);
66
+ if (item.color === "vip" || item.color === "speed") {
67
+ tag.classList.add(item.color);
68
+ tag.append(createElement("i", "light"));
69
+ } else if (item.color) {
70
+ tag.style.backgroundColor = item.color;
71
+ }
72
+ return tag;
73
+ };
74
+
75
+ const createItemLink = (
76
+ item,
77
+ group,
78
+ { disconnected = false, normal = false, defaultOrder = 0 } = {}
79
+ ) => {
80
+ const wrapper = createElement(
81
+ "div",
82
+ `flink-list-item${normal ? " flink-lazy-pending" : ""}`
83
+ );
84
+ if (normal && item.tag) {
85
+ String(item.tag)
86
+ .split(/\s+/)
87
+ .filter(Boolean)
88
+ .forEach((className) => wrapper.classList.add(className));
89
+ }
90
+ if (normal) wrapper.dataset.defaultOrder = String(defaultOrder);
91
+ const tag = disconnected ? null : createTag(item);
92
+ if (tag && !normal) wrapper.append(tag);
93
+
94
+ const anchor = createElement("a", "cf-friends-link");
95
+ anchor.href = disconnected ? "javascript:void(0);" : resolveSiteUrl(item.link);
96
+ anchor.title = item.name || "";
97
+ if (!disconnected) {
98
+ anchor.target = "_blank";
99
+ anchor.rel = "noopener noreferrer nofollow";
100
+ }
101
+ const avatar = disconnected
102
+ ? config.default_avatar
103
+ : `${item.avatar || ""}${group.suffix || ""}`;
104
+ anchor.append(
105
+ createImage(avatar, item.name, "flink-avatar cf-friends-avatar"),
106
+ createElement("div", "img-alt is-center", item.name)
107
+ );
108
+
109
+ const info = createElement("div", "flink-item-info");
110
+ info.append(createElement("span", "flink-item-name cf-friends-name", item.name));
111
+ const description = createElement("span", "flink-item-desc", item.descr);
112
+ description.title = item.descr || "";
113
+ info.append(description);
114
+ anchor.append(info);
115
+ wrapper.append(anchor);
116
+ return wrapper;
117
+ };
118
+
119
+ const createItemGroup = (
120
+ group,
121
+ { disconnected = false, normal = false, id = "", initialSort = "default" } = {}
122
+ ) => {
123
+ const list = createElement(
124
+ "div",
125
+ disconnected
126
+ ? "flink-list cf-friends-lost-contact mini"
127
+ : normal
128
+ ? "flink-list normal-five-row-horizontal js-normal-sortable"
129
+ : "flink-list"
130
+ );
131
+ if (id) list.id = id;
132
+ if (normal) list.dataset.initialSort = initialSort;
133
+ const fragment = document.createDocumentFragment();
134
+ const entries = group.link_list.map((item, defaultOrder) => ({
135
+ item,
136
+ defaultOrder,
137
+ }));
138
+ const orderedEntries = initialSort === "random" ? shuffle(entries) : entries;
139
+ orderedEntries.forEach(({ item, defaultOrder }) =>
140
+ fragment.append(
141
+ createItemLink(item, group, { disconnected, normal, defaultOrder })
142
+ )
143
+ );
144
+ list.append(fragment);
145
+ return list;
146
+ };
147
+
148
+ const createGroupHeading = (group, count) => {
149
+ const heading = createElement(
150
+ "h2",
151
+ "",
152
+ `${group.class_name || ""} (${count})`
153
+ );
154
+ if (!group.title_link || !group.title_link_text) return heading;
155
+
156
+ const titleBar = createElement("div", "power_title_bar");
157
+ const linkWrapper = createElement("div", "title-h2-a-right");
158
+ const link = createElement("a", "", group.title_link_text);
159
+ link.href = resolveSiteUrl(group.title_link);
160
+ linkWrapper.append(link);
161
+ titleBar.append(heading, linkWrapper);
162
+ return titleBar;
163
+ };
164
+
165
+ const createNormalHeading = (group, count, listId, sortMode) => {
166
+ const row = createElement("div", "flink-normal-title-row");
167
+ const content = createElement("div", "flink-normal-title-content");
168
+ content.append(
169
+ createElement("h2", "", `${group.class_name || ""} (${count})`),
170
+ createElement("div", "flink-desc", group.descr || "")
171
+ );
172
+
173
+ const controls = createElement("div", "flink-normal-scroll-controls");
174
+ controls.dataset.target = listId;
175
+ const sort = createElement("button", "flink-normal-sort-btn");
176
+ sort.type = "button";
177
+ sort.dataset.target = listId;
178
+ sort.dataset.sortMode = sortMode;
179
+ sort.setAttribute("aria-label", config.sort_label);
180
+ sort.append(
181
+ createElement(
182
+ "span",
183
+ "",
184
+ sortMode === "default" ? config.default_sort : config.random_sort
185
+ )
186
+ );
187
+ controls.append(sort);
188
+
189
+ [
190
+ ["left", config.scroll_left, "solitude fas fa-chevron-left"],
191
+ ["right", config.scroll_right, "solitude fas fa-chevron-right"],
192
+ ].forEach(([direction, label, iconClass]) => {
193
+ const button = createElement("button", "flink-normal-scroll-btn");
194
+ button.type = "button";
195
+ button.dataset.direction = direction;
196
+ button.setAttribute("aria-label", label);
197
+ button.title = label;
198
+ button.append(createElement("i", iconClass));
199
+ controls.append(button);
200
+ });
201
+
202
+ row.append(content, controls);
203
+ return row;
204
+ };
205
+
206
+ const bindNormalControls = (target) => {
207
+ target.querySelectorAll(".flink-normal-sort-btn").forEach((button) => {
208
+ if (button.dataset.bound === "true") return;
209
+ button.dataset.bound = "true";
210
+ button.addEventListener("click", () => {
211
+ const list = document.getElementById(button.dataset.target);
212
+ if (!list) return;
213
+ const items = Array.from(list.children);
214
+ const nextMode =
215
+ button.dataset.sortMode === "default" ? "random" : "default";
216
+ const orderedItems =
217
+ nextMode === "random"
218
+ ? shuffle(items)
219
+ : items.sort(
220
+ (left, right) =>
221
+ Number(left.dataset.defaultOrder) -
222
+ Number(right.dataset.defaultOrder)
223
+ );
224
+ list.append(...orderedItems);
225
+ list.scrollTo({ left: 0, behavior: "smooth" });
226
+ button.dataset.sortMode = nextMode;
227
+ button.querySelector("span").textContent =
228
+ nextMode === "default" ? config.default_sort : config.random_sort;
229
+ });
230
+ });
231
+
232
+ target.querySelectorAll(".flink-normal-scroll-controls").forEach((controls) => {
233
+ if (controls.dataset.bound === "true") return;
234
+ controls.dataset.bound = "true";
235
+ controls.querySelectorAll(".flink-normal-scroll-btn").forEach((button) => {
236
+ button.addEventListener("click", () => {
237
+ const list = document.getElementById(controls.dataset.target);
238
+ if (!list) return;
239
+ const direction = button.dataset.direction === "left" ? -1 : 1;
240
+ list.scrollBy({
241
+ left: direction * Math.max(280, list.clientWidth * 0.8),
242
+ behavior: "smooth",
243
+ });
244
+ });
245
+ });
246
+ });
247
+ };
248
+
249
+ const createCard = (item, group) => {
250
+ const card = createElement("div", "site-card");
251
+ const tag = createTag(item);
252
+ if (tag) card.append(tag);
253
+
254
+ const imageLink = createElement("a", "img");
255
+ imageLink.href = resolveSiteUrl(item.link);
256
+ imageLink.title = item.name || "";
257
+ imageLink.target = "_blank";
258
+ imageLink.rel = "noopener noreferrer nofollow";
259
+ imageLink.append(
260
+ createImage(`${item.topimg || ""}${group.topimg_suffix || ""}`, item.name, "flink-avatar")
261
+ );
262
+
263
+ const infoLink = createElement("a", "info cf-friends-link");
264
+ infoLink.href = resolveSiteUrl(item.link);
265
+ infoLink.title = item.name || "";
266
+ infoLink.target = "_blank";
267
+ infoLink.rel = "noopener noreferrer nofollow";
268
+ const avatar = createElement("div", "site-card-avatar");
269
+ avatar.append(
270
+ createImage(
271
+ `${item.avatar || ""}${group.suffix || ""}`,
272
+ item.name,
273
+ "flink-avatar cf-friends-avatar"
274
+ ),
275
+ createElement("div", "img-alt is-center", item.name)
276
+ );
277
+ const text = createElement("div", "site-card-text");
278
+ text.append(createElement("span", "title cf-friends-name", item.name));
279
+ const description = createElement("span", "desc", item.descr);
280
+ description.title = item.descr || "";
281
+ text.append(description);
282
+ infoLink.append(avatar, text);
283
+ card.append(imageLink, infoLink);
284
+ return card;
285
+ };
286
+
287
+ const createCardGroup = (group) => {
288
+ const list = createElement("div", "site-card-group");
289
+ const fragment = document.createDocumentFragment();
290
+ group.link_list.forEach((item) => fragment.append(createCard(item, group)));
291
+ list.append(fragment);
292
+ return list;
293
+ };
294
+
295
+ const load = () => {
296
+ if (!request) {
297
+ request = fetch(config.path, { credentials: "same-origin" })
298
+ .then((response) => {
299
+ if (!response.ok) throw new Error(`HTTP ${response.status}`);
300
+ return response.json();
301
+ })
302
+ .then((data) => {
303
+ if (!data || !Array.isArray(data.links)) {
304
+ throw new TypeError("Invalid friend links data");
305
+ }
306
+ return data;
307
+ })
308
+ .catch((error) => {
309
+ request = null;
310
+ throw error;
311
+ });
312
+ }
313
+ return request;
314
+ };
315
+
316
+ const createError = (retry, compact = false) => {
317
+ const status = createElement(
318
+ "div",
319
+ `friend-links-status is-error${compact ? " is-compact" : ""}`
320
+ );
321
+ status.setAttribute("role", "alert");
322
+ status.append(createElement("span", "", config.error));
323
+ const button = createElement("button");
324
+ button.type = "button";
325
+ button.append(
326
+ createElement("i", "solitude fas fa-arrows-rotate"),
327
+ createElement("span", "", config.retry)
328
+ );
329
+ button.addEventListener("click", retry, { once: true });
330
+ status.append(button);
331
+ return status;
332
+ };
333
+
334
+ const refreshLazyload = () => {
335
+ if (!Solitude.config.lazyload.enable) return;
336
+ if (window.lazyLoadInstance) window.lazyLoadInstance.update();
337
+ };
338
+
339
+ const renderList = async (target) => {
340
+ try {
341
+ const data = await load();
342
+ if (!target.isConnected || target.dataset.friendLinksLoaded === "true") return;
343
+ const fragment = document.createDocumentFragment();
344
+ let normalIndex = 0;
345
+ data.links.forEach((group) => {
346
+ const links = Array.isArray(group.link_list) ? group.link_list : [];
347
+ const normalizedGroup = { ...group, link_list: links };
348
+ if (group.type === "card") {
349
+ fragment.append(
350
+ createGroupHeading(group, links.length),
351
+ createElement("div", "flink-desc", group.descr || ""),
352
+ createCardGroup(normalizedGroup)
353
+ );
354
+ } else if (group.type === "item") {
355
+ const listId = `normal-flink-list-${normalIndex}`;
356
+ const initialSort =
357
+ group.sort === "default" || normalIndex === 0 ? "default" : "random";
358
+ fragment.append(
359
+ createNormalHeading(group, links.length, listId, initialSort),
360
+ createItemGroup(normalizedGroup, {
361
+ normal: true,
362
+ id: listId,
363
+ initialSort,
364
+ })
365
+ );
366
+ normalIndex += 1;
367
+ } else if (group.type === "discn") {
368
+ fragment.append(
369
+ createGroupHeading(group, links.length),
370
+ createElement("div", "flink-desc", group.descr || ""),
371
+ createItemGroup(normalizedGroup, { disconnected: true })
372
+ );
373
+ }
374
+ });
375
+ target.replaceChildren(fragment);
376
+ target.dataset.friendLinksLoaded = "true";
377
+ bindNormalControls(target);
378
+ refreshLazyload();
379
+ } catch (error) {
380
+ if (!target.isConnected) return;
381
+ console.error("Unable to load friend links:", error);
382
+ target.replaceChildren(createError(() => renderList(target)));
383
+ }
384
+ };
385
+
386
+ const renderBanner = async (target) => {
387
+ try {
388
+ const data = await load();
389
+ if (!target.isConnected || target.dataset.friendLinksLoaded === "true") return;
390
+ const links = data.links
391
+ .filter((group) => group.type !== "discn")
392
+ .flatMap((group) => (Array.isArray(group.link_list) ? group.link_list : []))
393
+ .slice(0, 30);
394
+ const wrapper = createElement("div", "tags-group-wrapper");
395
+ const pairs = [];
396
+ links.forEach((link, index) => {
397
+ if (index % 2 === 0) pairs.push([link]);
398
+ else pairs[pairs.length - 1].push(link);
399
+ });
400
+ const fragment = document.createDocumentFragment();
401
+ [0, 1].forEach(() => {
402
+ pairs.forEach((pair) => {
403
+ const pairElement = createElement("div", "tags-group-icon-pair");
404
+ pair.forEach((item) => {
405
+ const anchor = createElement("a", "tags-group-icon");
406
+ anchor.href = resolveSiteUrl(item.link);
407
+ anchor.title = item.name || "";
408
+ anchor.append(
409
+ createImage(
410
+ `${item.avatar || ""}${data.banner_suffix || ""}`,
411
+ item.name
412
+ ),
413
+ createElement("span", "tags-group-title", item.name)
414
+ );
415
+ pairElement.append(anchor);
416
+ });
417
+ fragment.append(pairElement);
418
+ });
419
+ });
420
+ wrapper.append(fragment);
421
+ target.replaceChildren(wrapper);
422
+ target.dataset.friendLinksLoaded = "true";
423
+ refreshLazyload();
424
+ } catch (error) {
425
+ if (!target.isConnected) return;
426
+ console.error("Unable to load friend links banner:", error);
427
+ target.replaceChildren(createError(() => renderBanner(target)));
428
+ }
429
+ };
430
+
431
+ const renderFooter = async () => {
432
+ const target = document.getElementById("friend-links-in-footer");
433
+ if (!target) return;
434
+ try {
435
+ const data = await load();
436
+ if (!target.isConnected) return;
437
+ const links = data.links.flatMap((group) =>
438
+ (Array.isArray(group.link_list) ? group.link_list : []).map((item) => ({
439
+ name: item.name,
440
+ link: item.link,
441
+ }))
442
+ );
443
+ const fragment = document.createDocumentFragment();
444
+ const available = [...links];
445
+ const count = Math.min(3, available.length);
446
+ for (let index = 0; index < count; index += 1) {
447
+ const selectedIndex = Solitude.randomNum(available.length);
448
+ const selected = available.splice(selectedIndex, 1)[0];
449
+ const anchor = createElement("a", "footer-item", selected.name);
450
+ anchor.href = resolveSiteUrl(selected.link);
451
+ anchor.target = "_blank";
452
+ anchor.rel = "noopener noreferrer nofollow";
453
+ fragment.append(anchor);
454
+ }
455
+ const more = createElement("a", "footer-item", config.more);
456
+ more.href = config.more_url;
457
+ fragment.append(more);
458
+ target.replaceChildren(fragment);
459
+ } catch (error) {
460
+ if (!target.isConnected) return;
461
+ console.error("Unable to load random friend links:", error);
462
+ target.replaceChildren(createError(renderFooter, true));
463
+ }
464
+ };
465
+
466
+ const init = () => {
467
+ const list = document.querySelector("[data-friend-links-list]");
468
+ const banner = document.querySelector("[data-friend-links-banner]");
469
+ if (list) renderList(list);
470
+ if (banner) renderBanner(banner);
471
+ };
472
+
473
+ Solitude.travelling = async () => {
474
+ try {
475
+ const data = await load();
476
+ const links = data.links.flatMap((group) =>
477
+ (Array.isArray(group.link_list) ? group.link_list : []).map((item) => ({
478
+ name: item.name,
479
+ link: item.link,
480
+ }))
481
+ );
482
+ if (!links.length) throw new Error("No friend links available");
483
+ const link = links[Solitude.randomNum(links.length)];
484
+ Snackbar.show({
485
+ text: config.random.replace(/\$\{name}/, link.name),
486
+ duration: 8000,
487
+ pos: "top-center",
488
+ actionText: config.to,
489
+ onActionClick: (element) => {
490
+ element.style.opacity = 0;
491
+ window.open(resolveSiteUrl(link.link), "_blank");
492
+ },
493
+ });
494
+ } catch (error) {
495
+ console.error("Unable to select a random friend link:", error);
496
+ Snackbar.show({ text: config.error, duration: 3000, pos: "top-center" });
497
+ }
498
+ };
499
+
500
+ Solitude.randomLinksList = renderFooter;
501
+ Solitude.friendLinks = { init, load };
502
+ })();