Остання активність 10 hours ago

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

Allen2030 ревизій цього gist 10 hours ago. До ревизії

Без змін

Allen2030 ревизій цього gist 10 hours ago. До ревизії

1 file changed, 459 insertions

Twikoo.astro(файл створено)

@@ -0,0 +1,459 @@
1 + ---
2 + interface Props {
3 + path: string;
4 + }
5 +
6 + import { commentConfig } from "@/config";
7 +
8 + const 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 + // 修复表情包定位和点击问题
35 + function 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 加载完成后调用修复函数
130 + window.addEventListener('DOMContentLoaded', function() {
131 + setTimeout(fixTwikooEmoji, 2000); // 给 Twikoo 更多时间初始化
132 + });
133 +
134 + // 在 Twikoo 初始化完成后再执行一次
135 + document.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>
Новіше Пізніше