Zuletzt aktiv 8 hours ago

Astro-theme-fuwari的Twikoo评论区样式修复

Twikoo.astro Originalformat
1---
2interface Props {
3 path: string;
4}
5
6import { commentConfig } from "@/config";
7
8const config = {
9 el: "#twikoo",
10 path: Astro.props.path,
11 envId: commentConfig.twikoo?.envId,
12 lang: commentConfig.twikoo?.lang || "zh-CN",
13 region: commentConfig.twikoo?.region,
14 visitor: true, // 启用访问统计
15};
16---
17
18<!-- 简化了配置部分的代码,实际上可以把配置文件统一写入fuwari的配置文件统一读取 -->
19<div id="twikoo"></div>
20<script define:vars={{ config }}>
21 function loadTwikoo() {
22 console.log('loadTwikoo function called');
23 // 不输出包含敏感信息的配置
24 const script = document.createElement("script");
25 script.src =
26 "https://registry.npmmirror.com/twikoo/1.6.44/files/dist/twikoo.min.js";
27 script.defer = true;
28 script.onload = () => {
29 console.log('Twikoo script loaded, initializing...');
30 // 不输出包含敏感信息的配置
31
32 // 尝试多种环境ID格式// 在现有的脚本中添加以下修复代码
33
34// 修复表情包定位和点击问题
35function fixTwikooEmoji() {
36 // 等待 Twikoo 完全加载
37 const checkExist = setInterval(function() {
38 const buttons = document.querySelectorAll('#twikoo .tk-emoji-btn');
39 if (buttons && buttons.length) {
40 clearInterval(checkExist);
41
42 // 注入样式(id 防重复)
43 if (!document.getElementById('twikoo-emoji-fix-style')) {
44 const style = document.createElement('style');
45 style.id = 'twikoo-emoji-fix-style';
46 style.textContent = `
47 #twikoo .tk-emoji-popover {
48 position: fixed !important;
49 z-index: 99999 !important;
50 transform: none !important;
51 display: none !important;
52 will-change: transform, top, left;
53 }
54
55 #twikoo .tk-emoji-popover.tk-emoji-popover-show {
56 display: block !important;
57 }
58
59 /* 避免父容器溢出剪裁 */
60 #twikoo {
61 overflow: visible !important;
62 }
63 `;
64 document.head.appendChild(style);
65 }
66
67 // 用于重定位的辅助函数
68 const positionPopover = (btn, popover) => {
69 if (!btn || !popover) return;
70 const rect = btn.getBoundingClientRect();
71 const left = Math.max(8, rect.left);
72 const top = rect.bottom + 6; // 与按钮下方稍微留隙
73 popover.style.left = left + 'px';
74 popover.style.top = top + 'px';
75 };
76
77 // 处理每个按钮,确保点击时定位到视口
78 buttons.forEach((btn) => {
79 // 去重,避免重复绑定
80 if (btn.__twikooEmojiBound) return;
81 btn.__twikooEmojiBound = true;
82
83 btn.addEventListener('click', function(e) {
84 e.stopPropagation();
85 const popover = document.querySelector('#twikoo .tk-emoji-popover');
86 if (!popover) return;
87
88 // 切换显示
89 const shown = popover.classList.toggle('tk-emoji-popover-show');
90 if (shown) {
91 positionPopover(btn, popover);
92 }
93 });
94 });
95
96 // 点击空白区域关闭
97 document.addEventListener('click', function(e) {
98 if (!e.target.closest('#twikoo .tk-emoji-btn') && !e.target.closest('#twikoo .tk-emoji-popover')) {
99 const popover = document.querySelector('#twikoo .tk-emoji-popover');
100 if (popover) popover.classList.remove('tk-emoji-popover-show');
101 }
102 });
103
104 // 在滚动或调整大小时重定位当前打开的 popover
105 const repositionIfShown = () => {
106 const popover = document.querySelector('#twikoo .tk-emoji-popover');
107 if (!popover || !popover.classList.contains('tk-emoji-popover-show')) return;
108 // 找到与之对应的按钮(靠近左上角的按钮)
109 const buttons = Array.from(document.querySelectorAll('#twikoo .tk-emoji-btn'));
110 let closest = null;
111 let minDist = Infinity;
112 const popRect = popover.getBoundingClientRect();
113 buttons.forEach((b) => {
114 const r = b.getBoundingClientRect();
115 const dx = r.left - popRect.left;
116 const dy = r.top - popRect.top;
117 const d = Math.hypot(dx, dy);
118 if (d < minDist) { minDist = d; closest = b; }
119 });
120 if (closest) positionPopover(closest, popover);
121 };
122
123 window.addEventListener('resize', repositionIfShown);
124 window.addEventListener('scroll', repositionIfShown, { passive: true });
125 }
126 }, 100);
127}
128
129// 在 DOM 加载完成后调用修复函数
130window.addEventListener('DOMContentLoaded', function() {
131 setTimeout(fixTwikooEmoji, 2000); // 给 Twikoo 更多时间初始化
132});
133
134// 在 Twikoo 初始化完成后再执行一次
135document.addEventListener("loadComment", () => {
136 setTimeout(fixTwikooEmoji, 1000);
137}, { once: true });
138 const originalEnvId = config.envId;
139 const envIdWithoutProtocol = originalEnvId ? originalEnvId.replace(/^https?:\/\//, '') : '';
140
141 // 尝试使用原始URL
142 try {
143 twikoo.init({
144 ...config,
145 envId: originalEnvId
146 });
147 } catch (error) {
148 // 尝试使用不带协议的URL
149 try {
150 twikoo.init({
151 ...config,
152 envId: envIdWithoutProtocol
153 });
154 } catch (error) {
155 // 最后尝试使用一个简单的字符串
156 try {
157 // 从原始envId中提取域名部分作为简单ID
158 const simpleEnvId = originalEnvId ?
159 originalEnvId.replace(/^https?:\/\//, '').split('.')[0] :
160 'twikoo-env';
161
162 twikoo.init({
163 ...config,
164 envId: simpleEnvId
165 });
166 } catch (error) {
167 // 初始化失败,静默处理
168 }
169 }
170 }
171
172 // 全面改进的移动端点击处理
173 // 存储触摸开始的信息
174 let touchStartInfo = {
175 target: null,
176 x: 0,
177 y: 0,
178 time: 0,
179 isScrolling: false
180 };
181
182 // 添加触摸移动事件处理
183 const handleTouchMove = function(e) {
184 if (!touchStartInfo.target) return;
185
186 // 计算移动距离
187 const touchMoveX = e.touches[0].clientX;
188 const touchMoveY = e.touches[0].clientY;
189 const moveDistance = Math.sqrt(
190 Math.pow(touchMoveX - touchStartInfo.x, 2) +
191 Math.pow(touchMoveY - touchStartInfo.y, 2)
192 );
193
194 // 如果移动距离超过阈值,标记为滑动操作
195 if (moveDistance > 10) {
196 touchStartInfo.isScrolling = true;
197 }
198 };
199
200 const preventDefaultForTwikoo = function(e) {
201 const target = e.target.closest('#twikoo button, #twikoo a');
202 if (target) {
203 // 特别处理href="#"的链接,这些链接会导致页面跳转到顶部
204 if (target.tagName === 'A' &&
205 (target.getAttribute('href') === '#' ||
206 target.getAttribute('href') === '' ||
207 target.getAttribute('href') === 'javascript:void(0)' ||
208 target.getAttribute('href') === 'javascript:;')) {
209 // 替换href属性,防止导航到页面顶部
210 if (target.getAttribute('original-href') === null) {
211 target.setAttribute('original-href', target.getAttribute('href'));
212 }
213 target.setAttribute('href', 'javascript:void(0);');
214 }
215
216 // 处理触摸开始事件
217 if (e.type === 'touchstart') {
218 // 记录触摸开始的目标元素和位置
219 touchStartInfo.target = target;
220 touchStartInfo.x = e.touches[0].clientX;
221 touchStartInfo.y = e.touches[0].clientY;
222 touchStartInfo.time = Date.now();
223 touchStartInfo.isScrolling = false;
224
225 // 只对空链接阻止默认行为,允许按钮正常工作
226 if (target.tagName === 'A' &&
227 (target.getAttribute('href') === '#' ||
228 target.getAttribute('href') === '' ||
229 target.getAttribute('href') === 'javascript:void(0)' ||
230 target.getAttribute('href') === 'javascript:;')) {
231 e.preventDefault();
232 }
233 }
234
235 // 处理触摸结束事件
236 if (e.type === 'touchend') {
237 // 如果被标记为滑动操作,不触发点击
238 if (touchStartInfo.isScrolling) {
239 // 重置触摸信息
240 touchStartInfo = {
241 target: null,
242 x: 0,
243 y: 0,
244 time: 0,
245 isScrolling: false
246 };
247 return;
248 }
249
250 // 计算触摸的移动距离和时间
251 const touchEndX = e.changedTouches[0].clientX;
252 const touchEndY = e.changedTouches[0].clientY;
253 const touchEndTime = Date.now();
254
255 const moveDistance = Math.sqrt(
256 Math.pow(touchEndX - touchStartInfo.x, 2) +
257 Math.pow(touchEndY - touchStartInfo.y, 2)
258 );
259 const touchDuration = touchEndTime - touchStartInfo.time;
260
261 // 检查触摸结束时的元素是否与开始时相同
262 const isSameTarget = touchStartInfo.target === target;
263
264 // 设置阈值:移动距离小于10px且触摸时间小于300ms视为点击
265 const isValidTap = moveDistance < 10 && touchDuration < 300;
266
267 // 只有当触摸结束时的元素与开始时相同,且移动距离小于阈值时,才触发点击
268 if (isSameTarget && isValidTap) {
269 // 只对空链接阻止默认行为,允许按钮正常工作
270 if (target.tagName === 'A' &&
271 (target.getAttribute('href') === '#' ||
272 target.getAttribute('href') === '' ||
273 target.getAttribute('href') === 'javascript:void(0)' ||
274 target.getAttribute('href') === 'javascript:;')) {
275 e.preventDefault();
276
277 // 如果链接有onclick处理函数,手动触发它
278 if (typeof target.onclick === 'function') {
279 target.onclick.call(target);
280 }
281 }
282 // 不再阻止按钮的默认行为
283 }
284
285 // 重置触摸信息
286 touchStartInfo = {
287 target: null,
288 x: 0,
289 y: 0,
290 time: 0,
291 isScrolling: false
292 };
293 }
294 }
295 };
296
297 // 添加事件监听器
298 document.addEventListener('touchstart', preventDefaultForTwikoo, {passive: false, capture: true});
299 document.addEventListener('touchmove', handleTouchMove, {passive: true, capture: true});
300 document.addEventListener('touchend', preventDefaultForTwikoo, {passive: false, capture: true});
301 document.addEventListener('click', function(e) {
302 const target = e.target.closest('#twikoo a[href="#"]');
303 if (target) {
304 e.preventDefault();
305 e.stopPropagation();
306 }
307 }, {capture: true});
308
309 // 在Twikoo初始化完成后添加事件处理
310 setTimeout(() => {
311 setupCommentEventHandlers();
312 }, 1000); // 给Twikoo一些时间来渲染评论区
313 };
314 script.onerror = (error) => {
315 // Twikoo 脚本加载失败,静默处理
316 };
317 document.body.appendChild(script);
318 }
319
320 // 监听加载评论事件
321 document.addEventListener("loadComment", () => {
322 loadTwikoo();
323 }, { once: true });
324
325 // 页面加载完成后自动加载Twikoo(作为备用方案)
326 window.addEventListener('DOMContentLoaded', () => {
327 console.log('DOMContentLoaded event triggered');
328 // 检查是否已经加载过Twikoo
329 if (!document.querySelector('script[src*="twikoo.min.js"]')) {
330 console.log('Twikoo not loaded yet, loading now...');
331 loadTwikoo();
332 }
333 });
334
335 console.log('Twikoo component initialized');
336
337 // 设置评论区事件处理函数
338 function setupCommentEventHandlers() {
339 console.log('Setting up comment event handlers');
340
341 // 移除之前可能存在的事件监听器
342 document.removeEventListener('click', handleCommentClick);
343
344 // 添加新的事件监听器
345 document.addEventListener('click', handleCommentClick);
346
347 // 使用MutationObserver监听DOM变化
348 const observer = new MutationObserver((mutations) => {
349 // 当DOM变化时,确保所有新添加的按钮都被正确处理
350 console.log('DOM mutations detected in comment area');
351 });
352
353 // 开始观察评论区的DOM变化
354 const twikooElement = document.getElementById('twikoo');
355 if (twikooElement) {
356 observer.observe(twikooElement, {
357 childList: true,
358 subtree: true,
359 attributes: true,
360 attributeFilter: ['href', 'onclick']
361 });
362 console.log('MutationObserver started for #twikoo element');
363 }
364
365 // 直接修改所有评论区内的链接和按钮
366 fixAllCommentButtons();
367 }
368
369 // 处理评论区内的点击事件
370 function handleCommentClick(event) {
371 // 检查点击事件是否发生在评论区内
372 const twikooElement = event.target.closest('#twikoo');
373 if (!twikooElement) return;
374
375 // 检查点击的元素是否是按钮或链接
376 const clickedElement = event.target.closest('button, a');
377 if (!clickedElement) return;
378
379 // 获取原始点击事件的目标元素
380 const originalTarget = event.target;
381
382 // 检查是否是评论提交按钮或其他特殊按钮
383 const isSubmitButton = clickedElement.classList.contains('tk-submit') ||
384 clickedElement.classList.contains('el-button--primary') ||
385 clickedElement.getAttribute('type') === 'submit';
386
387 // 检查是否是有效的外部链接
388 const href = clickedElement.getAttribute('href');
389 const isExternalLink = href && href.startsWith('http') && !href.includes('#');
390
391 // 只阻止空链接的默认行为,允许其他按钮正常工作
392 if (clickedElement.tagName === 'A' &&
393 (href === '#' || href === '' || href === 'javascript:void(0)' || href === 'javascript:;')) {
394 event.preventDefault();
395 console.log('Prevented default action for empty link in comment area');
396
397 // 如果元素有onclick属性或事件监听器,手动触发点击逻辑但不导航
398 if (typeof clickedElement.onclick === 'function') {
399 console.log('Element has onclick handler, executing custom logic without navigation');
400 clickedElement.onclick.call(clickedElement);
401 }
402 }
403 }
404
405 // 修复所有评论区内的按钮
406 function fixAllCommentButtons() {
407 console.log('Fixing all comment buttons');
408 const twikooElement = document.getElementById('twikoo');
409 if (!twikooElement) return;
410
411 // 只修复空链接,避免干扰其他正常链接
412 const links = twikooElement.querySelectorAll('a');
413 links.forEach(link => {
414 const href = link.getAttribute('href');
415 if (href === '#' || href === '' || href === 'javascript:void(0)') {
416 // 替换href属性,防止导航到页面顶部
417 link.setAttribute('href', 'javascript:void(0);');
418 link.addEventListener('click', (e) => {
419 e.preventDefault();
420 console.log('Prevented navigation for empty link');
421
422 // 如果链接有onclick处理函数,手动触发它
423 if (typeof link.onclick === 'function') {
424 link.onclick.call(link);
425 }
426 });
427 }
428 });
429
430 // 不再阻止按钮的默认行为,让它们正常工作
431
432 console.log('Fixed all comment buttons');
433 }
434
435 // 初始的事件监听器设置 - 只处理空链接
436 document.addEventListener('click', function(event) {
437 // 检查点击事件是否发生在评论区内
438 if (event.target.closest('#twikoo')) {
439 // 检查点击的元素是否是链接
440 const clickedElement = event.target.closest('a');
441 if (clickedElement) {
442 // 如果是链接且href为#或javascript:void(0)等,阻止默认行为
443 if (clickedElement.getAttribute('href') === '#' ||
444 clickedElement.getAttribute('href') === 'javascript:void(0)' ||
445 clickedElement.getAttribute('href') === '') {
446 event.preventDefault();
447 console.log('Prevented default action for empty link in comment area');
448
449 // 如果链接有onclick处理函数,手动触发它
450 if (typeof clickedElement.onclick === 'function') {
451 clickedElement.onclick.call(clickedElement);
452 }
453 }
454 }
455
456 // 不再阻止按钮的默认行为,让它们正常工作
457 }
458 });
459</script>