hexo-theme-solitude 1.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 (173) hide show
  1. package/LICENSE +19 -0
  2. package/README.md +56 -0
  3. package/_config.yml +432 -0
  4. package/languages/en-US.yml +39 -0
  5. package/languages/zh-CN.yml +39 -0
  6. package/layout/404.ejs +34 -0
  7. package/layout/archive.ejs +10 -0
  8. package/layout/category.ejs +18 -0
  9. package/layout/index.ejs +20 -0
  10. package/layout/layout.ejs +35 -0
  11. package/layout/page/about.ejs +13 -0
  12. package/layout/page/categories.ejs +12 -0
  13. package/layout/page/circle.ejs +5 -0
  14. package/layout/page/echarts.ejs +14 -0
  15. package/layout/page/equipment.ejs +2 -0
  16. package/layout/page/links.ejs +57 -0
  17. package/layout/page/page.ejs +3 -0
  18. package/layout/page/rss.ejs +37 -0
  19. package/layout/page/says.ejs +16 -0
  20. package/layout/page/tags.ejs +12 -0
  21. package/layout/page/tlink.ejs +11 -0
  22. package/layout/page.ejs +45 -0
  23. package/layout/partial/body.ejs +8 -0
  24. package/layout/partial/compoment/about/authorinfo.ejs +18 -0
  25. package/layout/partial/compoment/about/award.ejs +57 -0
  26. package/layout/partial/compoment/about/contentinfo.ejs +33 -0
  27. package/layout/partial/compoment/about/hobbies.ejs +44 -0
  28. package/layout/partial/compoment/about/motto.ejs +17 -0
  29. package/layout/partial/compoment/about/other.ejs +68 -0
  30. package/layout/partial/compoment/about/personalities.ejs +11 -0
  31. package/layout/partial/compoment/about/skillsinfo.ejs +62 -0
  32. package/layout/partial/compoment/aside/aside.ejs +28 -0
  33. package/layout/partial/compoment/aside/asideAllInfo.ejs +6 -0
  34. package/layout/partial/compoment/aside/asideArchive.ejs +11 -0
  35. package/layout/partial/compoment/aside/asideFlipCard.ejs +8 -0
  36. package/layout/partial/compoment/aside/asideInfoCard.ejs +39 -0
  37. package/layout/partial/compoment/aside/asideNewestPost.ejs +31 -0
  38. package/layout/partial/compoment/aside/asidePower.ejs +31 -0
  39. package/layout/partial/compoment/aside/asideSwitch.ejs +35 -0
  40. package/layout/partial/compoment/aside/asideTag.ejs +5 -0
  41. package/layout/partial/compoment/aside/asideToc.ejs +11 -0
  42. package/layout/partial/compoment/aside/asideWebInfo.ejs +60 -0
  43. package/layout/partial/compoment/circle/angle.ejs +26 -0
  44. package/layout/partial/compoment/circle/banner.ejs +11 -0
  45. package/layout/partial/compoment/circle/content.ejs +22 -0
  46. package/layout/partial/compoment/dorakika/rightmenu.ejs +115 -0
  47. package/layout/partial/compoment/equipment/list.ejs +37 -0
  48. package/layout/partial/compoment/home/homeCategoryBar.ejs +11 -0
  49. package/layout/partial/compoment/home/postList.ejs +37 -0
  50. package/layout/partial/compoment/hometop/bbTimeList.ejs +15 -0
  51. package/layout/partial/compoment/hometop/categoryGroup.ejs +19 -0
  52. package/layout/partial/compoment/hometop/groupTag.ejs +30 -0
  53. package/layout/partial/compoment/hometop/topGroup.ejs +48 -0
  54. package/layout/partial/compoment/inject/body.ejs +57 -0
  55. package/layout/partial/compoment/inject/head.ejs +19 -0
  56. package/layout/partial/compoment/links/banner.ejs +42 -0
  57. package/layout/partial/compoment/links/linksCard.ejs +27 -0
  58. package/layout/partial/compoment/links/linksItem.ejs +21 -0
  59. package/layout/partial/compoment/mixins/articleSort.ejs +26 -0
  60. package/layout/partial/compoment/mixins/pagination.ejs +11 -0
  61. package/layout/partial/compoment/nav/left.ejs +22 -0
  62. package/layout/partial/compoment/nav/menu.ejs +25 -0
  63. package/layout/partial/compoment/nav/right.ejs +42 -0
  64. package/layout/partial/compoment/post/award.ejs +52 -0
  65. package/layout/partial/compoment/post/copyright.ejs +37 -0
  66. package/layout/partial/compoment/post/postMeta.ejs +83 -0
  67. package/layout/partial/compoment/post/postNav.ejs +41 -0
  68. package/layout/partial/compoment/post/wave.ejs +14 -0
  69. package/layout/partial/compoment/says/banner.ejs +10 -0
  70. package/layout/partial/compoment/says/saysBottom.ejs +18 -0
  71. package/layout/partial/compoment/says/saysContent.ejs +11 -0
  72. package/layout/partial/compoment/third-party/comments/comment.ejs +12 -0
  73. package/layout/partial/compoment/third-party/comments/twikoo.ejs +29 -0
  74. package/layout/partial/compoment/third-party/comments/twikoo_k.ejs +29 -0
  75. package/layout/partial/compoment/third-party/music.ejs +5 -0
  76. package/layout/partial/compoment/third-party/pjax.ejs +31 -0
  77. package/layout/partial/compoment/third-party/search/algolia-search.ejs +20 -0
  78. package/layout/partial/compoment/third-party/search/index.ejs +10 -0
  79. package/layout/partial/compoment/third-party/search/local-search.ejs +22 -0
  80. package/layout/partial/compoment/tlink/banner.ejs +10 -0
  81. package/layout/partial/console.ejs +62 -0
  82. package/layout/partial/footer.ejs +107 -0
  83. package/layout/partial/head.ejs +37 -0
  84. package/layout/partial/header.ejs +6 -0
  85. package/layout/partial/hometop.ejs +15 -0
  86. package/layout/partial/loading.ejs +86 -0
  87. package/layout/partial/nav.ejs +34 -0
  88. package/layout/partial/pwa.ejs +40 -0
  89. package/layout/partial/sidebar.ejs +31 -0
  90. package/layout/post.ejs +36 -0
  91. package/layout/tag.ejs +19 -0
  92. package/package.json +24 -0
  93. package/scripts/event/init.js +22 -0
  94. package/scripts/event/page.js +40 -0
  95. package/scripts/event/welcome.js +15 -0
  96. package/scripts/filter/checkThemeConfig.js +18 -0
  97. package/scripts/filter/default.js +23 -0
  98. package/scripts/filter/katex.js +25 -0
  99. package/scripts/filter/lazyload.js +11 -0
  100. package/scripts/filter/randomPosts.js +9 -0
  101. package/scripts/helper/charts.js +397 -0
  102. package/scripts/helper/getArchiveLength.js +18 -0
  103. package/scripts/helper/randomLinks.js +16 -0
  104. package/scripts/helper/related_post.js +91 -0
  105. package/scripts/helper/themeJsExport.js +77 -0
  106. package/scripts/tags/bvideo.js +42 -0
  107. package/scripts/tags/expand.js +4 -0
  108. package/scripts/tags/fold.js +19 -0
  109. package/scripts/tags/link.js +17 -0
  110. package/scripts/tags/note.js +3 -0
  111. package/scripts/tags/tabs.js +61 -0
  112. package/scripts/tags/timeline.js +35 -0
  113. package/source/css/commentBarrage.css +174 -0
  114. package/source/css/custom.css +901 -0
  115. package/source/css/main.css +16471 -0
  116. package/source/css/search/algolia-search.css +141 -0
  117. package/source/css/search/local-search.css +138 -0
  118. package/source/css/var.css +186 -0
  119. package/source/img/default.png +0 -0
  120. package/source/img/loading.gif +0 -0
  121. package/source/img/power.png +0 -0
  122. package/source/img/pwa/180.png +0 -0
  123. package/source/img/pwa/192.png +0 -0
  124. package/source/img/pwa/512.png +0 -0
  125. package/source/img/pwa/logo.png +0 -0
  126. package/source/img/pwa/siteicon/splash-1125x2436.png +0 -0
  127. package/source/img/pwa/siteicon/splash-1136x640.png +0 -0
  128. package/source/img/pwa/siteicon/splash-1170x2532.png +0 -0
  129. package/source/img/pwa/siteicon/splash-1179x2556.png +0 -0
  130. package/source/img/pwa/siteicon/splash-1242x2208.png +0 -0
  131. package/source/img/pwa/siteicon/splash-1242x2688.png +0 -0
  132. package/source/img/pwa/siteicon/splash-1248x2778.png +0 -0
  133. package/source/img/pwa/siteicon/splash-1290x2796.png +0 -0
  134. package/source/img/pwa/siteicon/splash-1334x750.png +0 -0
  135. package/source/img/pwa/siteicon/splash-1536x2048.png +0 -0
  136. package/source/img/pwa/siteicon/splash-1620x2160.png +0 -0
  137. package/source/img/pwa/siteicon/splash-1668x2224.png +0 -0
  138. package/source/img/pwa/siteicon/splash-1668x2388.png +0 -0
  139. package/source/img/pwa/siteicon/splash-1792x828.png +0 -0
  140. package/source/img/pwa/siteicon/splash-2048x1536.png +0 -0
  141. package/source/img/pwa/siteicon/splash-2048x2732.png +0 -0
  142. package/source/img/pwa/siteicon/splash-2160x1620.png +0 -0
  143. package/source/img/pwa/siteicon/splash-2208x1242.png +0 -0
  144. package/source/img/pwa/siteicon/splash-2224x1668.png +0 -0
  145. package/source/img/pwa/siteicon/splash-2388x1668.png +0 -0
  146. package/source/img/pwa/siteicon/splash-2436x1125.png +0 -0
  147. package/source/img/pwa/siteicon/splash-2532x1170.png +0 -0
  148. package/source/img/pwa/siteicon/splash-2556x1179.png +0 -0
  149. package/source/img/pwa/siteicon/splash-2688x1242.png +0 -0
  150. package/source/img/pwa/siteicon/splash-2732x2048.png +0 -0
  151. package/source/img/pwa/siteicon/splash-2778x1248.png +0 -0
  152. package/source/img/pwa/siteicon/splash-2796x1290.png +0 -0
  153. package/source/img/pwa/siteicon/splash-640x1136.png +0 -0
  154. package/source/img/pwa/siteicon/splash-750x1334.png +0 -0
  155. package/source/img/pwa/siteicon/splash-828x1792.png +0 -0
  156. package/source/img/theme/avatar.png +0 -0
  157. package/source/js/circle.js +1828 -0
  158. package/source/js/commentBarrage.js +148 -0
  159. package/source/js/extend/console/comment.js +99 -0
  160. package/source/js/extend/covercolor/local.js +150 -0
  161. package/source/js/extend/covercolor/web.js +137 -0
  162. package/source/js/extend/search/algolia-search.js +136 -0
  163. package/source/js/extend/search/local-search.js +160 -0
  164. package/source/js/main.js +705 -0
  165. package/source/js/post_ai.js +517 -0
  166. package/source/js/utils.js +153 -0
  167. package/source/lib/bundle.js +27 -0
  168. package/source/lib/friends_post.js +98 -0
  169. package/source/lib/lazyload.js +1 -0
  170. package/source/lib/snackbar.js +16 -0
  171. package/source/lib/snackbar.min.css +1 -0
  172. package/source/lib/view-image.js +13 -0
  173. package/source/lib/waterfall.min.js +1 -0
@@ -0,0 +1,517 @@
1
+ function ChucklePostAI(AI_option) {
2
+ // 如果有则删除
3
+ const box = document.querySelector(".post-ai");
4
+ if (box) {
5
+ box.parentElement.removeChild(box);
6
+ }
7
+ const currentURL = window.location.href;
8
+ // 排除页面
9
+ if (AI_option.eliminate && AI_option.eliminate.length && AI_option.eliminate.some(item => currentURL.includes(item))) {
10
+ return;
11
+ }
12
+ if (AI_option.whitelist && AI_option.whitelist.length && !AI_option.whitelist.some(item => currentURL.includes(item))) {
13
+ return;
14
+ }
15
+ // 获取挂载元素,即文章内容所在的容器元素
16
+ let targetElement = "";
17
+ // 若el配置不存在则自动获取,如果auto_mount配置为真也自动获取
18
+ if (!AI_option.auto_mount && AI_option.el) {
19
+ targetElement = document.querySelector(AI_option.el ? AI_option.el : '#post #article-container');
20
+ } else {
21
+ targetElement = getArticleElements();
22
+ }
23
+ // 获取文章标题,默认获取网页标题
24
+ const post_title = document.querySelector(AI_option.title_el) ? document.querySelector(AI_option.title_el).textContent : document.title;
25
+ if (!targetElement) {
26
+ return;
27
+ }
28
+ ;
29
+ const interface = {
30
+ name: "GPT",
31
+ introduce: "我是文章辅助AI,点击下方的按钮,让我生成本文简介、推荐相关文章等。",
32
+ version: "GPT 4",
33
+ ...AI_option.interface
34
+ }
35
+ const post_ai_box = document.createElement('div');
36
+ post_ai_box.className = 'post-ai';
37
+ post_ai_box.setAttribute('id', 'post-ai');
38
+ targetElement.insertBefore(post_ai_box, targetElement.firstChild);
39
+ post_ai_box.innerHTML = `<div class="ai-title">
40
+ <i class="ri-robot-2-fill"></i>
41
+ <div class="ai-title-text">${interface.name}</div>
42
+ <div class="ai-tag">${interface.version}</div>
43
+ </div>
44
+ <div class="ai-explanation">${interface.name}初始化中...</div>
45
+ <div class="ai-btn-box">
46
+ <div class="ai-btn-item">介绍自己</div>
47
+ <div class="ai-btn-item">推荐相关文章</div>
48
+ <div class="ai-btn-item">生成AI简介</div>
49
+ </div>`;
50
+ // ai主体业务逻辑
51
+ let animationRunning = true; // 标志变量,控制动画函数的运行
52
+ let explanation = document.querySelector('.ai-explanation');
53
+ let post_ai = document.querySelector('.post-ai');
54
+ let ai_btn_item = document.querySelectorAll('.ai-btn-item');
55
+ let ai_str = '';
56
+ let ai_str_length = '';
57
+ let delay_init = 600;
58
+ let i = 0;
59
+ let j = 0;
60
+ let sto = [];
61
+ let elapsed = 0;
62
+ let completeGenerate = false;
63
+ let controller = new AbortController();//控制fetch
64
+ let signal = controller.signal;
65
+ let visitorId = ""; // 标识访客ID
66
+ const choiceApi = true;
67
+ const tlReferer = `https://${window.location.host}/`;
68
+ const tlKey = AI_option.key ? AI_option.key : '123456';
69
+ //-----------------------------------------------
70
+ const animate = (timestamp) => {
71
+ if (!animationRunning) {
72
+ return; // 动画函数停止运行
73
+ }
74
+ if (!animate.start) animate.start = timestamp;
75
+ elapsed = timestamp - animate.start;
76
+ if (elapsed >= 20) {
77
+ animate.start = timestamp;
78
+ if (i < ai_str_length - 1) {
79
+ let char = ai_str.charAt(i + 1);
80
+ let delay = /[,.,。!?!?]/.test(char) ? 150 : 20;
81
+ if (explanation.firstElementChild) {
82
+ explanation.removeChild(explanation.firstElementChild);
83
+ }
84
+ explanation.innerHTML += char;
85
+ let div = document.createElement('div');
86
+ div.className = "ai-cursor";
87
+ explanation.appendChild(div);
88
+ i++;
89
+ if (i === ai_str_length - 1) {
90
+ observer.disconnect();// 暂停监听
91
+ explanation.removeChild(explanation.firstElementChild);
92
+ }
93
+ sto[0] = setTimeout(() => {
94
+ requestAnimationFrame(animate);
95
+ }, delay);
96
+ }
97
+ } else {
98
+ requestAnimationFrame(animate);
99
+ }
100
+ };
101
+ const observer = new IntersectionObserver((entries) => {
102
+ let isVisible = entries[0].isIntersecting;
103
+ animationRunning = isVisible; // 标志变量更新
104
+ if (animationRunning) {
105
+ delay_init = i === 0 ? 200 : 20;
106
+ sto[1] = setTimeout(() => {
107
+ if (j) {
108
+ i = 0;
109
+ j = 0;
110
+ }
111
+ if (i === 0) {
112
+ explanation.innerHTML = ai_str.charAt(0);
113
+ }
114
+ requestAnimationFrame(animate);
115
+ }, delay_init);
116
+ }
117
+ }, {threshold: 0});
118
+
119
+ function clearSTO() {
120
+ if (sto.length) {
121
+ sto.forEach((item) => {
122
+ if (item) {
123
+ clearTimeout(item);
124
+ }
125
+ });
126
+ }
127
+ }
128
+
129
+ function resetAI(df = true) {
130
+ i = 0;//重置计数器
131
+ j = 1;
132
+ clearSTO();
133
+ animationRunning = false;
134
+ elapsed = 0;
135
+ if (df) {
136
+ explanation.innerHTML = '生成中. . .';
137
+ } else {
138
+ explanation.innerHTML = '请等待. . .';
139
+ }
140
+ if (!completeGenerate) {
141
+ controller.abort();
142
+ }
143
+ ai_str = '';
144
+ ai_str_length = '';
145
+ observer.disconnect();// 暂停上一次监听
146
+ }
147
+
148
+ function startAI(str, df = true) {
149
+ resetAI(df);
150
+ ai_str = str;
151
+ ai_str_length = ai_str.length;
152
+ observer.observe(post_ai);//启动新监听
153
+ }
154
+
155
+ function aiIntroduce() {
156
+ startAI(interface.introduce);
157
+ }
158
+
159
+ function aiRecommend() {
160
+ resetAI();
161
+ sto[2] = setTimeout(async () => {
162
+ let info = await recommendList();
163
+ if (info === "") {
164
+ startAI(`${interface.name}未能找到任何可推荐的文章。`);
165
+ } else if (info) {
166
+ explanation.innerHTML = info;
167
+ }
168
+ }, 200);
169
+ }
170
+
171
+ async function aiGenerateAbstract() {
172
+ resetAI();
173
+ const ele = targetElement
174
+ const content = getTextContent(ele);
175
+ const response = await getGptResponse(content, choiceApi);
176
+ if (response) {
177
+ startAI(response);
178
+ }
179
+ }
180
+
181
+ async function recommendList() {
182
+ completeGenerate = false;
183
+ controller = new AbortController();
184
+ signal = controller.signal;
185
+ let response = '';
186
+ let info = '';
187
+ let data = '';
188
+ const options = {
189
+ signal,
190
+ method: 'GET',
191
+ headers: {'content-type': 'application/x-www-form-urlencoded'},
192
+ };
193
+ // 利用sessionStorage缓存推荐列表,有则缓存中读取,无则获取后缓存
194
+ if (sessionStorage.getItem('recommendList')) {
195
+ data = JSON.parse(sessionStorage.getItem('recommendList'));
196
+ } else {
197
+ try {
198
+ response = await fetch(`https://summary.tianli0.top/recommends?url=${encodeURIComponent(window.location.href)}&author=${AI_option.rec_method ? AI_option.rec_method : 'all'}`, options);
199
+ completeGenerate = true;
200
+ if (response.status === 429) {
201
+ startAI('请求过于频繁,请稍后再请求AI。');
202
+ }
203
+ if (!response.ok) {
204
+ throw new Error('Response not ok');
205
+ }
206
+ // 处理响应
207
+ } catch (error) {
208
+ if (error.name === "AbortError") {
209
+ // console.log("请求已被中止");
210
+ } else {
211
+ console.error('Error occurred:', error);
212
+ startAI("获取推荐出错了,请稍后再试。");
213
+ }
214
+ completeGenerate = true;
215
+ return false;
216
+ }
217
+ // 解析响应并返回结果
218
+ data = await response.json();
219
+ sessionStorage.setItem('recommendList', JSON.stringify(data));
220
+ }
221
+ if (data.hasOwnProperty("success") && !data.success) {
222
+ return false;
223
+ } else {
224
+ info = `推荐文章:<br />`;
225
+ info += '<div class="ai-recommend">';
226
+ data.forEach((item, index) => {
227
+ info += `<div class="ai-recommend-item"><span>推荐${index + 1}:</span><a target="_blank" href="${item.url}" title="${item.title ? item.title : "未获取到题目"}">${item.title ? item.title : "未获取到题目"}</a></div>`;
228
+ });
229
+ info += '</div>'
230
+ }
231
+ return info;
232
+ }
233
+
234
+ //ai首屏初始化,绑定按钮注册事件
235
+ async function ai_init() {
236
+ // 清除缓存
237
+ sessionStorage.removeItem('recommendList');
238
+ sessionStorage.removeItem('summary');
239
+ explanation = document.querySelector('.ai-explanation');
240
+ post_ai = document.querySelector('.post-ai');
241
+ ai_btn_item = document.querySelectorAll('.ai-btn-item');
242
+ const funArr = [aiIntroduce, aiRecommend, aiGenerateAbstract];
243
+ ai_btn_item.forEach((item, index) => {
244
+ item.addEventListener('click', () => {
245
+ funArr[index]();
246
+ });
247
+ });
248
+ if (AI_option.summary_directly) {
249
+ aiGenerateAbstract();
250
+ } else {
251
+ aiIntroduce();
252
+ }
253
+ // 获取或生成访客ID
254
+ visitorId = localStorage.getItem('visitorId') || await generateVisitorID();
255
+ }
256
+
257
+ async function generateVisitorID() {
258
+ try {
259
+ const FingerprintJS = await import('https://openfpcdn.io/fingerprintjs/v4');
260
+ const fp = await FingerprintJS.default.load();
261
+ const result = await fp.get();
262
+ const visitorId = result.visitorId;
263
+ localStorage.setItem('visitorId', visitorId);
264
+ return visitorId;
265
+ } catch (error) {
266
+ console.error("生成ID失败:", error);
267
+ return null;
268
+ }
269
+ }
270
+
271
+ //获取某个元素内的所有纯文本,并按顺序拼接返回
272
+ function getText(element) {
273
+ //需要排除的元素及其子元素
274
+ const excludeClasses = AI_option.exclude ? AI_option.exclude : ['highlight', 'Copyright-Notice', 'post-ai', 'post-series', 'mini-sandbox'];
275
+ let textContent = '';
276
+ for (let node of element.childNodes) {
277
+ if (node.nodeType === Node.TEXT_NODE) {
278
+ //如果是纯文本节点则获取内容拼接
279
+ textContent += node.textContent.trim();
280
+ } else if (node.nodeType === Node.ELEMENT_NODE) {
281
+ let hasExcludeClass = false;//跟踪元素是否包含需要排除的类名
282
+ //遍历类名
283
+ for (let className of node.classList) {
284
+ //如果包含则hasExcludeClass设为true,且break跳出循环
285
+ if (excludeClasses.includes(className)) {
286
+ hasExcludeClass = true;
287
+ break;
288
+ }
289
+ }
290
+ //如果hasExcludeClass为false,即该标签不包含需要排除的类,可以继续向下遍历子元素
291
+ if (!hasExcludeClass) {
292
+ let innerTextContent = getText(node);
293
+ textContent += innerTextContent;
294
+ }
295
+ }
296
+ }
297
+ //返回纯文本节点的内容
298
+ return textContent.replace(/\s+/g, '');
299
+ }
300
+
301
+ //获取各级标题
302
+ function extractHeadings(element) {
303
+ const headings = element.querySelectorAll('h1, h2, h3, h4');
304
+ const result = [];
305
+ for (let i = 0; i < headings.length; i++) {
306
+ const heading = headings[i];
307
+ const headingText = heading.textContent.trim();
308
+ result.push(headingText);
309
+ const childHeadings = extractHeadings(heading);
310
+ result.push(...childHeadings);
311
+ }
312
+ return result.join(";");
313
+ }
314
+
315
+ //按比例切割字符串
316
+ function extractString(str) {
317
+ // 截取前500个字符
318
+ var first500 = str.substring(0, 500);
319
+ // 截取末尾200个字符
320
+ var last200 = str.substring(str.length - 200);
321
+ // 截取中间300个字符
322
+ var midStartIndex = (str.length - 300) / 2; // 计算中间部分的起始索引
323
+ var middle300 = str.substring(midStartIndex, midStartIndex + 300);
324
+ // 将三个部分拼接在一起
325
+ var result = first500 + middle300 + last200;
326
+ // 返回截取后的字符串
327
+ return result;
328
+ }
329
+
330
+ //获得字符串,默认进行切割,false返回原文纯文本
331
+ function getTextContent(element, i = true) {
332
+ let content;
333
+ if (i) {
334
+ content = `文章的各级标题:${extractHeadings(element)}。文章内容的截取:${extractString(getText(element))}`;
335
+ } else {
336
+ content = `${getText(element)}`;
337
+ }
338
+ return content;
339
+ }
340
+
341
+ //发送请求获得简介
342
+ async function getGptResponse(content, i = true) {
343
+ if (!tlKey) {
344
+ return "没有获取到key,代码可能没有安装正确。如果你需要在tianli_gpt文件引用前定义tianliGPT_key变量。详细请查看文档。";
345
+ }
346
+ if (tlKey === "123456") {
347
+ return "请购买 key 使用,如果你能看到此条内容,则说明代码安装正确。";
348
+ }
349
+ completeGenerate = false;
350
+ controller = new AbortController();
351
+ signal = controller.signal;
352
+ let response = '';
353
+ if (sessionStorage.getItem('summary')) {
354
+ return sessionStorage.getItem('summary');
355
+ }
356
+ try {
357
+ response = await fetch('https://summary.tianli0.top/', {
358
+ signal: signal,
359
+ method: "POST",
360
+ headers: {
361
+ "Content-Type": "application/json",
362
+ "Referer": tlReferer
363
+ },
364
+ body: JSON.stringify({
365
+ content: content,
366
+ key: tlKey,
367
+ title: post_title,
368
+ url: window.location.href,
369
+ user_openid: visitorId
370
+ })
371
+ });
372
+ completeGenerate = true;
373
+ if (response.status === 429) {
374
+ startAI('请求过于频繁,请稍后再请求AI。');
375
+ }
376
+ if (!response.ok) {
377
+ throw new Error('Response not ok');
378
+ }
379
+ // 处理响应
380
+ } catch (error) {
381
+ if (error.name === "AbortError") {
382
+ // console.log("请求已被中止");
383
+ } else if (window.location.hostname === "localhost" || window.location.hostname === "127.0.0.1") {
384
+ startAI(`${interface.name}请求出错了,你正在本地进行调试.`);
385
+ } else {
386
+ startAI(`${interface.name}请求出错了,请稍后再试。`);
387
+ }
388
+ completeGenerate = true;
389
+ return "";
390
+ }
391
+ // 解析响应并返回结果
392
+ const data = await response.json();
393
+ const outputText = data.summary;
394
+ sessionStorage.setItem('summary', outputText);
395
+ return outputText;
396
+ }
397
+
398
+ // 实验性功能,自动获取文章内容所在容器元素
399
+ function getArticleElements() {
400
+ // 计算元素的后代元素总个数
401
+ function countDescendants(element) {
402
+ let count = 1;
403
+ for (const child of element.children) {
404
+ count += countDescendants(child);
405
+ }
406
+ return count;
407
+ }
408
+
409
+ // 判断是否有要排除的元素
410
+ function judgeElement(element) {
411
+ const excludedTags = ['IFRAME', 'FOOTER', 'HEADER', 'BLOCKQUOTE']; // 添加要排除的标签
412
+ if (excludedTags.includes(element.tagName)) {
413
+ return true;
414
+ }
415
+ const exclusionStrings = ['aplayer', 'comment']; // 排除包含其中字符串的className
416
+ return Array.from(element.classList).some(className => exclusionStrings.some(exclusion => className.includes(exclusion)));
417
+ }
418
+
419
+ // 深度搜索,找到得分最高的父元素
420
+ function findMaxHeadingParentElement(element) {
421
+ const tagScores = {
422
+ 'H1': 1.5,
423
+ 'H2': 1,
424
+ 'H3': 0.5,
425
+ 'P': 1
426
+ };
427
+ let maxScore = 0;
428
+ let maxHeadingParentElement = null;
429
+
430
+ function dfs(element) {
431
+ if (judgeElement(element)) {
432
+ return;
433
+ }
434
+ let score = 0;
435
+ for (const child of element.children) {
436
+ if (child.tagName in tagScores) {
437
+ score += tagScores[child.tagName];
438
+ }
439
+ }
440
+ if (score > maxScore) {
441
+ maxScore = score;
442
+ maxHeadingParentElement = element;
443
+ }
444
+ for (const child of element.children) {
445
+ dfs(child);
446
+ }
447
+ }
448
+
449
+ dfs(element);
450
+ return maxHeadingParentElement;
451
+ }
452
+
453
+ // 广度优先搜索,标记所有元素,并找到得分最高的父元素
454
+ function findArticleContentElement() {
455
+ const queue = [document.body];
456
+ let maxDescendantsCount = 0;
457
+ let articleContentElement = null;
458
+ while (queue.length > 0) {
459
+ const currentElement = queue.shift();
460
+ // 判断当前元素是否要排除
461
+ if (judgeElement(currentElement)) {
462
+ continue;
463
+ }
464
+ const descendantsCount = countDescendants(currentElement);
465
+ if (descendantsCount > maxDescendantsCount) {
466
+ maxDescendantsCount = descendantsCount;
467
+ articleContentElement = currentElement;
468
+ }
469
+ for (const child of currentElement.children) {
470
+ queue.push(child);
471
+ }
472
+ }
473
+ return findMaxHeadingParentElement(articleContentElement);
474
+ }
475
+
476
+ // 返回文章内容所在的容器元素
477
+ return findArticleContentElement();
478
+ }
479
+
480
+ // 请求个性化推荐
481
+ async function personalizedRecommend() {
482
+ completeGenerate = false;
483
+ controller = new AbortController();
484
+ signal = controller.signal;
485
+ const options = {
486
+ signal,
487
+ method: 'GET',
488
+ headers: {'content-type': 'application/x-www-form-urlencoded'},
489
+ };
490
+ try {
491
+ const response = await fetch(`https://summary.tianli0.top/personalized_recommends?openid=${visitorId}`, options);
492
+ completeGenerate = true;
493
+ const data = await response.json();
494
+ return data;
495
+ } catch {
496
+ startAI(`${interface.name}获取个性化推荐出错了,请稍后再试。`);
497
+ completeGenerate = true;
498
+ return null;
499
+ }
500
+ }
501
+
502
+ ai_init();
503
+ }
504
+
505
+ // AI构造函数
506
+ new ChucklePostAI({
507
+ /* 必须配置 */
508
+ // 文章内容所在的元素属性的选择器,也是AI挂载的容器,AI将会挂载到该容器的最前面
509
+ el: '#post #article-container',
510
+ // 驱动AI所必须的key,即是tianliGPT后端服务所必须的key
511
+ key: 'Qi7QIXLOrxLCyKBW0bie',
512
+ /* 非必须配置,但最好根据自身需要进行配置 */
513
+ // 文章标题所在的元素属性的选择器,默认获取当前网页的标题
514
+ title_el: '.post-title',
515
+ // 文章推荐方式,all:匹配数据库内所有文章进行推荐,web:仅当前站内的文章,默认all
516
+ rec_method: 'web',
517
+ })
@@ -0,0 +1,153 @@
1
+ const utils = {
2
+ debounce: function (func, wait, immediate) {
3
+ let timeout
4
+ return function () {
5
+ const context = this
6
+ const args = arguments
7
+ const later = function () {
8
+ timeout = null
9
+ if (!immediate) func.apply(context, args)
10
+ }
11
+ const callNow = immediate && !timeout
12
+ clearTimeout(timeout)
13
+ timeout = setTimeout(later, wait)
14
+ if (callNow) func.apply(context, args)
15
+ }
16
+ },
17
+
18
+ throttle: function (func, wait, options) {
19
+ let timeout, context, args
20
+ let previous = 0
21
+ if (!options) options = {}
22
+
23
+ const later = function () {
24
+ previous = options.leading === false ? 0 : new Date().getTime()
25
+ timeout = null
26
+ func.apply(context, args)
27
+ if (!timeout) context = args = null
28
+ }
29
+
30
+ const throttled = function () {
31
+ const now = new Date().getTime()
32
+ if (!previous && options.leading === false) previous = now
33
+ const remaining = wait - (now - previous)
34
+ context = this
35
+ args = arguments
36
+ if (remaining <= 0 || remaining > wait) {
37
+ if (timeout) {
38
+ clearTimeout(timeout)
39
+ timeout = null
40
+ }
41
+ previous = now
42
+ func.apply(context, args)
43
+ if (!timeout) context = args = null
44
+ } else if (!timeout && options.trailing !== false) {
45
+ timeout = setTimeout(later, remaining)
46
+ }
47
+ }
48
+
49
+ return throttled
50
+ },
51
+
52
+ fadeIn: (ele, time) => {
53
+ ele.style.cssText = `display:block;animation: to_show ${time}s`
54
+ },
55
+
56
+ fadeOut: (ele, time) => {
57
+ ele.addEventListener('animationend', function f() {
58
+ ele.style.cssText = "display: none; animation: '' "
59
+ ele.removeEventListener('animationend', f)
60
+ })
61
+ ele.style.animation = `to_hide ${time}s`
62
+ },
63
+
64
+ sidebarPaddingR: () => {
65
+ const innerWidth = window.innerWidth
66
+ const clientWidth = document.body.clientWidth
67
+ const paddingRight = innerWidth - clientWidth
68
+ if (innerWidth !== clientWidth) {
69
+ document.body.style.paddingRight = paddingRight + 'px'
70
+ }
71
+ },
72
+
73
+ snackbarShow: (text, showAction, duration) => {
74
+ const sa = (typeof showAction !== 'undefined') ? showAction : false
75
+ const dur = (typeof duration !== 'undefined') ? duration : 5000
76
+ document.styleSheets[0].addRule(':root', '--sco-snackbar-time:' + dur + 'ms!important')
77
+ Snackbar.show({
78
+ text: text,
79
+ showAction: sa,
80
+ duration: dur,
81
+ pos: 'top-center'
82
+ })
83
+ },
84
+
85
+ copy: async (text) => {
86
+ try {
87
+ await navigator.clipboard.writeText(text)
88
+ utils.snackbarShow(GLOBALCONFIG.lang.copy.success, false, 2000)
89
+ } catch (err) {
90
+ utils.snackbarShow(GLOBALCONFIG.lang.copy.error, false, 2000)
91
+ }
92
+ },
93
+
94
+ getEleTop: ele => {
95
+ let actualTop = ele.offsetTop
96
+ let current = ele.offsetParent
97
+
98
+ while (current !== null) {
99
+ actualTop += current.offsetTop
100
+ current = current.offsetParent
101
+ }
102
+
103
+ return actualTop
104
+ },
105
+
106
+ randomNum: (length) => {
107
+ return Math.floor(Math.random() * length)
108
+ },
109
+
110
+ timeDiff: (timeObj, today) => {
111
+ var timeDiff = today.getTime() - timeObj.getTime();
112
+ return Math.floor(timeDiff / (1000 * 3600 * 24));
113
+ },
114
+
115
+ scrollToDest: (pos, time = 500) => {
116
+ const currentPos = window.pageYOffset
117
+ const isNavFixed = document.getElementById('page-header').classList.contains('nav-fixed')
118
+ if (currentPos > pos || isNavFixed) pos = pos - 70
119
+ if ('scrollBehavior' in document.documentElement.style) {
120
+ window.scrollTo({
121
+ top: pos,
122
+ behavior: 'smooth'
123
+ })
124
+ return
125
+ }
126
+ let start = null
127
+ pos = + pos
128
+ window.requestAnimationFrame(function step(currentTime) {
129
+ start = !start ? currentTime : start
130
+ const progress = currentTime - start
131
+ if (currentPos < pos) {
132
+ window.scrollTo(0, ((pos - currentPos) * progress / time) + currentPos)
133
+ } else {
134
+ window.scrollTo(0, currentPos - ((currentPos - pos) * progress / time))
135
+ }
136
+ if (progress < time) {
137
+ window.requestAnimationFrame(step)
138
+ } else {
139
+ window.scrollTo(0, pos)
140
+ }
141
+ })
142
+ },
143
+
144
+ siblings: (ele, selector) => {
145
+ return [...ele.parentNode.children].filter((child) => {
146
+ if (selector) {
147
+ return child !== ele && child.matches(selector)
148
+ }
149
+ return child !== ele
150
+ })
151
+ },
152
+ isMobile: () => /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent),
153
+ }