fixed bug

This commit is contained in:
Vaica committed 2026-01-31 21:27:06 +08:00
1 parent 54b18039e3
commit 8c3232fadf
16 files changed
+1134 -97

No files matched your search

+20 -10
View File
@@ -33,7 +33,7 @@ window.initArtalk = function() {
<div class="atk-main" uk-scrollspy="cls: uk-animation-slide-right-small; delay: 300">
<div class="atk-header">
<span class="atk-nick" uk-scrollspy="cls: uk-animation-slide-top-small; delay: 350">${ai.name}</span>
<span class="atk-badge">AI</span>
<span class="atk-badge">${window.artalkConfig.aiBadge || 'AI'}</span>
<span class="atk-date">${ai.date}</span>
</div>
<div class="atk-body">
@@ -71,20 +71,30 @@ window.initArtalk = function() {
});
// Find all avatars in the comment list
var avatars = document.querySelectorAll('.atk-list .atk-avatar img');
avatars.forEach(function(img) {
var src = img.getAttribute('src');
if (src) {
img.setAttribute('data-lazy-src', src);
img.removeAttribute('src');
img.classList.add('lazyload');
}
});
// We let lazyload.js handle the avatars automatically by calling lazyLoadAgain()
// which will find these new images, set the placeholder, and observe them.
// Trigger lazy load update
if (window.lazyLoadAgain) {
window.lazyLoadAgain();
}
// Force hide "No Comment" if AI comment exists
if (window.artalkConfig.aiComment) {
var checkNoComment = setInterval(function() {
var noComment = document.querySelector('.atk-list-no-comment');
var aiComment = document.querySelector('.atk-ai-comment');
if (aiComment && noComment && noComment.style.display !== 'none') {
noComment.style.display = 'none';
}
if (!document.querySelector('#Comments')) {
clearInterval(checkNoComment);
}
}, 500);
// Clear interval after 10 seconds to save resources
setTimeout(function() { clearInterval(checkNoComment); }, 10000);
}
});
} catch (e) {
+22 -14
View File
@@ -12,26 +12,34 @@
function init(setting) {
defaults = Object.assign({}, defaults, setting || {});
// Initialize IntersectionObserver
if (!observer) {
observer = new IntersectionObserver(function(entries, self) {
entries.forEach(function(entry) {
if (entry.isIntersecting) {
var img = entry.target;
showImg(img);
self.unobserve(img);
}
});
}, {
rootMargin: defaults.offsetTopm + "px 0px " + defaults.offsetBottom + "px 0px"
});
// Reset observer for PJAX support
if (observer) {
observer.disconnect();
observer = null;
}
// Initialize IntersectionObserver
observer = new IntersectionObserver(function(entries, self) {
entries.forEach(function(entry) {
if (entry.isIntersecting) {
var img = entry.target;
showImg(img);
self.unobserve(img);
}
});
}, {
rootMargin: defaults.offsetTopm + "px 0px " + defaults.offsetBottom + "px 0px"
});
processImages();
}
function processImages() {
var images = document.querySelectorAll('img:not(.blog-img):not([data-comp])');
if (!observer) {
init(); // Auto-initialize if not ready
}
// Expanded selector to catch more images
var images = document.querySelectorAll('img.lazyload, img:not(.blog-img):not([data-comp])');
images.forEach(function(img) {
// Check if already processed or has data-lazy-src
if (img.getAttribute('data-lazy-src')) {
@@ -0,0 +1,564 @@
// Paragraph Comments Module
(function() {
var POPUP_ID = 'selection-popup';
// CSS for Paragraph Comments
var style = document.createElement('style');
style.innerHTML = `
/* Selection Popup */
#${POPUP_ID} {
position: absolute;
display: none;
background: var(--artalk-editer-color, #333);
color: #fff;
padding: 6px 12px;
border-radius: 6px;
font-size: 13px;
cursor: pointer;
z-index: 2147483647;
transform: translate(-50%, -120%);
box-shadow: 0 4px 12px rgba(0,0,0,0.15);
transition: opacity 0.2s, transform 0.2s;
pointer-events: auto;
align-items: center;
gap: 6px;
white-space: nowrap;
animation: popIn 0.2s cubic-bezier(0.18, 0.89, 0.32, 1.28);
}
#${POPUP_ID}::after {
content: '';
position: absolute;
top: 100%;
left: 50%;
margin-left: -5px;
border-width: 5px;
border-style: solid;
border-color: var(--artalk-editer-color, #333) transparent transparent transparent;
}
@keyframes popIn {
0% { transform: translate(-50%, -100%) scale(0.9); opacity: 0; }
100% { transform: translate(-50%, -120%) scale(1); opacity: 1; }
}
/* Paragraph Comment Marker - Pure Text, No Styles */
.paragraph-comment-marker {
text-decoration: none !important;
border-bottom: none !important;
background: transparent !important;
box-shadow: none !important;
cursor: text;
color: inherit;
}
/* Comment Count Bubble - Minimal SVG Style */
.paragraph-comment-count {
display: inline-flex;
align-items: center;
justify-content: center;
height: 16px;
padding: 0 4px;
background: transparent;
color: var(--erji-color, #999);
border-radius: 4px;
font-size: 11px;
font-weight: 500;
margin-left: 2px;
vertical-align: top;
line-height: 1;
cursor: pointer;
position: relative;
top: 2px;
user-select: none;
z-index: 10;
transition: all 0.2s ease;
gap: 2px;
}
.paragraph-comment-count svg {
width: 12px;
height: 12px;
fill: currentColor;
opacity: 0.7;
}
.paragraph-comment-count:hover {
color: var(--theme-color, #4a90e2);
background: rgba(0,0,0,0.03);
}
.paragraph-comment-count:hover svg {
opacity: 1;
transform: scale(1.1);
}
/* Comments Preview Modal */
.pc-modal {
position: fixed;
top: 0; left: 0; width: 100%; height: 100%;
background: rgba(0,0,0,0.5);
z-index: 9999;
display: flex;
justify-content: center;
align-items: center;
opacity: 0;
pointer-events: none;
transition: opacity 0.3s;
}
.pc-modal.show {
opacity: 1;
pointer-events: auto;
}
.pc-modal-content {
background: var(--background-color, #fff);
width: 90%;
max-width: 500px;
max-height: 80vh;
border-radius: 12px;
display: flex;
flex-direction: column;
box-shadow: 0 10px 25px rgba(0,0,0,0.2);
transform: translateY(20px);
transition: transform 0.3s;
}
.pc-modal.show .pc-modal-content {
transform: translateY(0);
}
.pc-header {
padding: 15px;
border-bottom: 1px solid var(--frame-color, #eee);
display: flex;
justify-content: space-between;
align-items: center;
font-weight: bold;
}
.pc-body {
flex: 1;
overflow-y: auto;
padding: 15px;
}
.pc-close {
cursor: pointer;
font-size: 20px;
opacity: 0.6;
}
.pc-close:hover { opacity: 1; }
.pc-quote-text {
padding: 10px;
background: var(--bankuai-color, #f6f6f6);
border-left: 3px solid var(--text-highlight-color, #666);
margin-bottom: 15px;
font-size: 0.9em;
color: var(--erji-color, #666);
}
.pc-comment-item {
margin-bottom: 15px;
padding-bottom: 15px;
border-bottom: 1px solid var(--frame-color, #eee);
}
.pc-comment-item:last-child { border-bottom: none; margin-bottom: 0; }
.pc-user { font-weight: bold; font-size: 0.9em; margin-bottom: 5px; }
.pc-content { font-size: 0.95em; }
[data-theme="dark"] .pc-modal-content {
background: var(--frame-dark-color, #1f2933);
color: var(--text-content-dark-color, #d1d5db);
}
[data-theme="dark"] .pc-quote-text {
background: rgba(0,0,0,0.2);
}
`;
document.head.appendChild(style);
// Create Popup Element
var popup = document.createElement('div');
popup.id = POPUP_ID;
popup.innerHTML = '<span class="icon" style="width:14px;height:14px;fill:currentColor;display:inline-block;"><svg viewBox="0 0 1024 1024" width="100%" height="100%"><path d="M576 128v298.666667h298.666667c0 164.906667-133.76 298.666667-298.666667 298.666666v170.666667c259.2 0 469.333333-210.133333 469.333333-469.333333H576zM149.333333 725.333333c0-164.906667 133.76-298.666667 298.666667-298.666666V128C188.8 128 0 338.133333 0 597.333333h469.333333v-298.666666H170.666667A298.666667 298.666667 0 0 0 149.333333 725.333333z"></path></svg></span> 评论';
document.body.appendChild(popup);
// Create Modal Element
var modal = document.createElement('div');
modal.className = 'pc-modal';
modal.innerHTML = `
<div class="pc-modal-content">
<div class="pc-header">
<span>引用评论</span>
<span class="pc-close">&times;</span>
</div>
<div class="pc-body"></div>
</div>
`;
document.body.appendChild(modal);
// State
var selectedText = '';
var range = null;
var isInitialized = false;
// Initialization
function init() {
if (!isInitialized) {
document.addEventListener('mouseup', handleSelection);
document.addEventListener('touchend', handleSelection); // Mobile support
popup.addEventListener('click', handleQuoteClick);
modal.querySelector('.pc-close').addEventListener('click', closeModal);
modal.addEventListener('click', function(e) {
if (e.target === modal) closeModal();
});
isInitialized = true;
}
// Hook into Artalk
tryHookArtalk();
}
function tryHookArtalk() {
if (window.artalk) {
setupArtalkHook(window.artalk);
} else {
// Performance: Check less frequently (1s) and stop after 10s
var checkCount = 0;
var checkArtalk = setInterval(function() {
checkCount++;
if (window.artalk) {
clearInterval(checkArtalk);
setupArtalkHook(window.artalk);
}
if (checkCount > 10) clearInterval(checkArtalk); // 10s timeout
}, 1000);
}
}
function setupArtalkHook(artalk) {
// Initial load
var list = artalk.getCommentList();
if (list && list.length > 0) {
processComments();
}
// Listen for updates
artalk.on('list-loaded', function() {
setTimeout(processComments, 500);
});
}
// Performance Optimized: Build Index Once
function buildDocumentIndex(root) {
var textNodes = [];
var allText = "";
var nodeOffsets = [];
var walker = document.createTreeWalker(root, NodeFilter.SHOW_TEXT, null, false);
var node;
while (node = walker.nextNode()) {
if (node.parentNode && (
node.parentNode.tagName === 'SCRIPT' ||
node.parentNode.tagName === 'STYLE' ||
node.parentNode.classList.contains('paragraph-comment-marker') ||
node.parentNode.classList.contains('paragraph-comment-count')
)) continue;
// Skip code blocks
if (node.parentElement.closest('pre') || node.parentElement.closest('code')) continue;
nodeOffsets.push(allText.length);
textNodes.push(node);
allText += node.nodeValue;
}
return { textNodes, allText, nodeOffsets };
}
function processComments() {
if (!window.artalk) return;
// Cleanup old markers
document.querySelectorAll('.paragraph-comment-marker').forEach(el => {
var parent = el.parentNode;
if (parent) {
parent.replaceChild(document.createTextNode(el.innerText), el);
parent.normalize();
}
});
document.querySelectorAll('.paragraph-comment-count').forEach(el => el.remove());
var comments = window.artalk.getCommentList();
var quoteMap = {};
// Group comments by quote
comments.forEach(c => {
var content = c.content_marked || c.content;
// Extract text from HTML blockquotes
var tempDiv = document.createElement('div');
tempDiv.innerHTML = content;
var blockquotes = tempDiv.querySelectorAll('blockquote');
blockquotes.forEach(bq => {
var quoteText = bq.textContent.trim();
if (quoteText) {
if (!quoteMap[quoteText]) quoteMap[quoteText] = [];
if (!quoteMap[quoteText].some(existing => existing.id === c.id)) {
quoteMap[quoteText].push(c);
}
}
});
// Regex fallback
if (!blockquotes.length && c.content && !c.content.includes('<')) {
var match = c.content.match(/^> (.*?)(?:\n|$)/gm);
if (match) {
match.forEach(m => {
var q = m.replace(/^> /, '').trim();
if (q) {
if (!quoteMap[q]) quoteMap[q] = [];
if (!quoteMap[q].some(existing => existing.id === c.id)) {
quoteMap[q].push(c);
}
}
});
}
}
});
var postContent = document.querySelector('.post-content') ||
document.querySelector('.article-content') ||
document.querySelector('article');
if (!postContent) return;
// Build Index ONCE
var docData = buildDocumentIndex(postContent);
Object.keys(quoteMap).forEach(quote => {
if (quote.length < 2) return;
highlightQuote(docData, quote, quoteMap[quote]);
});
}
// Improved Highlight Logic using Pre-built Index
function highlightQuote(docData, quote, comments) {
if (!quote || quote.length < 5) return;
var { allText, textNodes, nodeOffsets } = docData;
// Fuzzy Match
var cleanQuote = quote.trim().replace(/\s+/g, ' ');
var cleanAllText = allText.replace(/\s+/g, ' ');
var matchIndex = cleanAllText.indexOf(cleanQuote);
if (matchIndex === -1) {
matchIndex = allText.indexOf(quote);
if (matchIndex === -1) matchIndex = allText.indexOf(quote.trim());
}
if (matchIndex === -1) return;
// Map back to raw index
var currentCleanIndex = 0;
var rawStart = -1;
var rawEnd = -1;
var inWhitespace = false;
for (var i = 0; i < allText.length; i++) {
var char = allText[i];
var isSpace = /\s/.test(char);
if (isSpace) {
if (!inWhitespace) {
if (currentCleanIndex === matchIndex) rawStart = i;
currentCleanIndex++;
}
inWhitespace = true;
} else {
if (currentCleanIndex === matchIndex) rawStart = i;
currentCleanIndex++;
inWhitespace = false;
}
if (rawStart !== -1 && currentCleanIndex === matchIndex + cleanQuote.length) {
rawEnd = i + 1;
break;
}
}
if (rawStart !== -1 && rawEnd !== -1) {
rangeFromOffsets(textNodes, nodeOffsets, rawStart, rawEnd - rawStart, comments);
}
}
function rangeFromOffsets(textNodes, nodeOffsets, start, length, comments) {
var end = start + length;
var range = document.createRange();
var startNode = null, startOffset = 0;
var endNode = null, endOffset = 0;
for (var i = 0; i < textNodes.length; i++) {
var nodeStart = nodeOffsets[i];
var nodeEnd = nodeStart + textNodes[i].nodeValue.length;
if (!startNode && start >= nodeStart && start < nodeEnd) {
startNode = textNodes[i];
startOffset = start - nodeStart;
}
if (!endNode && end > nodeStart && end <= nodeEnd) {
endNode = textNodes[i];
endOffset = end - nodeStart;
}
if (startNode && endNode) break;
}
if (startNode && endNode) {
try {
range.setStart(startNode, startOffset);
range.setEnd(endNode, endOffset);
wrapRange(range, comments);
} catch (e) {
// Ignore
}
}
}
function wrapRange(range, comments) {
try {
var contents = range.extractContents();
var span = document.createElement('span');
span.className = 'paragraph-comment-marker';
span.appendChild(contents);
range.insertNode(span);
addBubble(span, comments.length, comments);
} catch (e) {
// Ignore
}
}
function addBubble(marker, count, comments) {
var bubble = document.createElement('sup');
bubble.className = 'paragraph-comment-count';
// Simple Chat Icon SVG
bubble.innerHTML = `<svg viewBox="0 0 24 24"><path d="M20 2H4c-1.1 0-2 .9-2 2v18l4-4h14c1.1 0 2-.9 2-2V4c0-1.1-.9-2-2-2z"></path></svg> <span>${count}</span>`;
marker.appendChild(bubble);
bubble.addEventListener('click', function(e) {
e.stopPropagation();
showCommentsModal(null, comments);
});
}
function handleSelection(e) {
setTimeout(function() {
var sel = window.getSelection();
if (sel.rangeCount > 0) {
var r = sel.getRangeAt(0);
var text = sel.toString().trim();
var postContent = document.querySelector('.post-content') ||
document.querySelector('.article-content') ||
document.querySelector('article');
if (!postContent) return;
var isInside = postContent.contains(r.commonAncestorContainer);
if (text.length > 0 && isInside) {
selectedText = text;
range = r;
showPopup(r);
} else {
hidePopup();
}
} else {
hidePopup();
}
}, 10);
}
function showPopup(r) {
var rect = r.getBoundingClientRect();
popup.style.display = 'flex';
var top = rect.top + window.scrollY - 40;
var left = rect.left + window.scrollX + (rect.width / 2);
popup.style.top = top + 'px';
popup.style.left = left + 'px';
popup.style.opacity = 1;
}
function hidePopup() {
popup.style.display = 'none';
popup.style.opacity = 0;
}
function handleQuoteClick() {
if (!window.artalk) return;
var commentsEl = document.getElementById('Comments');
if (commentsEl) commentsEl.scrollIntoView({ behavior: 'smooth' });
var quoteContent = `> ${selectedText}\n\n`;
var editor = window.artalk.editor;
if (editor && typeof editor.setContent === 'function' && typeof editor.getContent === 'function') {
editor.setContent(quoteContent + editor.getContent());
editor.focus();
} else {
var textarea = document.querySelector('.atk-textarea');
if (textarea) {
textarea.value = quoteContent + textarea.value;
textarea.focus();
textarea.dispatchEvent(new Event('input', { bubbles: true }));
textarea.dispatchEvent(new Event('change', { bubbles: true }));
}
}
hidePopup();
window.getSelection().removeAllRanges();
}
function showCommentsModal(quote, comments) {
var body = modal.querySelector('.pc-body');
body.innerHTML = '';
if (!quote && comments.length > 0) {
var firstContent = comments[0].content_marked || comments[0].content;
var match = firstContent.match(/<blockquote>(.*?)<\/blockquote>/) || firstContent.match(/^> (.*?)(?:\n|$)/);
if (match) quote = match[1];
}
if (quote) {
body.innerHTML += `<div class="pc-quote-text">${quote}</div>`;
}
comments.forEach(c => {
var item = document.createElement('div');
item.className = 'pc-comment-item';
var cleanContent = c.content.replace(/^> .*?\n+/g, '').replace(/<blockquote>.*?<\/blockquote>/g, '');
var badgeHtml = '';
if (c.badge) {
badgeHtml = `<span style="background:${c.badge_color || '#000'};color:#fff;padding:1px 4px;border-radius:4px;font-size:0.8em;margin-left:5px;">${c.badge}</span>`;
}
item.innerHTML = `
<div class="pc-user">${c.nick}${badgeHtml} <span style="font-weight:normal;font-size:0.8em;color:#999;float:right">${c.date}</span></div>
<div class="pc-content">${cleanContent}</div>
`;
body.appendChild(item);
});
modal.classList.add('show');
}
function closeModal() {
modal.classList.remove('show');
}
window.initParagraphComments = init;
init();
})();
+49 -3
View File
@@ -121,12 +121,24 @@ function initInlineSearch() {
searchContainer.style.display = 'block';
input.focus();
loadSearchIndex();
// Add Esc key listener
document.addEventListener('keydown', escListener);
} else {
header.style.display = 'inline-flex';
searchContainer.style.display = 'none';
input.value = '';
// Remove .has-value class
searchContainer.querySelector('.search-input-group').classList.remove('has-value');
resultsContainer.classList.remove('show');
resultsContainer.innerHTML = '';
// Remove Esc key listener
document.removeEventListener('keydown', escListener);
}
};
var escListener = function(e) {
if (e.key === 'Escape') {
window.toggleSearchMode(false);
}
};
@@ -137,8 +149,41 @@ function initInlineSearch() {
input.parentNode.replaceChild(newInput, input);
input = newInput;
// Handle Close Icon Logic
var closeIcon = searchContainer.querySelector('.close-icon');
if (closeIcon) {
// Remove inline onclick handler if present
closeIcon.onclick = null;
closeIcon.removeAttribute('onclick');
// Add new custom handler
closeIcon.addEventListener('click', function(e) {
e.preventDefault();
e.stopPropagation();
if (input.value.trim().length > 0) {
// Clear input
input.value = '';
input.focus();
// Trigger input event to update UI
input.dispatchEvent(new Event('input'));
} else {
// Close search
window.toggleSearchMode(false);
}
});
}
input.addEventListener('input', function() {
var query = this.value.trim().toLowerCase();
var inputGroup = this.closest('.search-input-group');
// Toggle has-value class
if (this.value.length > 0) {
inputGroup.classList.add('has-value');
} else {
inputGroup.classList.remove('has-value');
}
if (query.length === 0) {
resultsContainer.classList.remove('show');
@@ -156,10 +201,11 @@ function initInlineSearch() {
if (results.length > 0) {
var html = results.slice(0, 5).map(function(item) {
return `
<a href="${item.permalink}" class="search-result-item" style="display:block; text-decoration:none;" onclick="window.toggleSearchMode(false)">
<a href="${item.permalink}" class="search-result-item" onclick="window.toggleSearchMode(false)">
<span class="search-result-title">${item.title}</span>
<div style="display:flex; align-items:center;">
<div class="search-result-meta">
<span class="search-result-date">${item.date}</span>
<span class="search-result-summary-text">${item.summary || ''}</span>
</div>
</a>
`;
@@ -173,7 +219,7 @@ function initInlineSearch() {
window.pjax.refresh(resultsContainer);
}
} else {
resultsContainer.innerHTML = '<div class="search-result-item" style="text-align:center;color:#999;cursor:default;">无搜索结果</div>';
resultsContainer.innerHTML = '<div class="search-result-item no-result">暂无相关内容</div>';
resultsContainer.classList.add('show');
}
});
+170
View File
@@ -6,3 +6,173 @@ window.isMobile = function() {
return false; // PC端
}
};
// Toast Message Helper
window.showToast = function(text) {
if (typeof Qmsg !== 'undefined') {
Qmsg.info(text);
} else {
// Fallback custom toast
let toast = document.querySelector('.custom-toast');
if (!toast) {
toast = document.createElement('div');
toast.className = 'custom-toast';
toast.style.cssText = `
position: fixed;
top: 20px;
left: 50%;
transform: translateX(-50%);
background: rgba(0,0,0,0.8);
color: white;
padding: 8px 16px;
border-radius: 4px;
z-index: 9999;
font-size: 14px;
opacity: 0;
transition: opacity 0.3s;
pointer-events: none;
`;
document.body.appendChild(toast);
}
toast.innerText = text;
toast.style.opacity = '1';
if (window.toastTimeout) clearTimeout(window.toastTimeout);
window.toastTimeout = setTimeout(() => {
toast.style.opacity = '0';
}, 2000);
}
};
// Theme Toggle Logic
window.initThemeToggle = function() {
const avatar = document.getElementById('theme-toggle-avatar');
if (!avatar) return;
// Remove existing listeners to avoid duplicates (if any)
const newAvatar = avatar.cloneNode(true);
avatar.parentNode.replaceChild(newAvatar, avatar);
newAvatar.addEventListener('click', function(e) {
// Prevent if clicking on something interactive inside (unlikely for avatar)
e.preventDefault();
const html = document.documentElement;
const currentTheme = html.getAttribute('data-theme');
const newTheme = currentTheme === 'dark' ? 'light' : 'dark';
html.setAttribute('data-theme', newTheme);
localStorage.setItem('theme', newTheme);
window.showToast('已切换至 ' + (newTheme === 'dark' ? '深色' : '浅色') + '模式');
// Trigger generic resize event to force charts/etc to redraw if needed
window.dispatchEvent(new Event('resize'));
});
};
// Code Folding Logic
window.initCodeFold = function() {
const codeBlocks = document.querySelectorAll('.highlight');
codeBlocks.forEach(block => {
// Check height (approx 300px)
if (block.offsetHeight > 360) {
// Check if already processed
if (block.parentNode.classList.contains('code-fold-container')) return;
// Wrap
const wrapper = document.createElement('div');
wrapper.className = 'code-fold-container collapsed';
block.parentNode.insertBefore(wrapper, block);
wrapper.appendChild(block);
// Add Button
const btn = document.createElement('button');
btn.className = 'code-expand-btn';
btn.innerHTML = '<span><i class="ri-arrow-down-s-line" style="margin-right:4px;"></i> 展开代码 / Expand</span>';
btn.onclick = function() {
wrapper.classList.remove('collapsed');
// Hide button after expand, or change to collapse?
// Usually just expand is enough.
btn.style.display = 'none';
};
wrapper.appendChild(btn);
}
});
};
// Code Copy Logic
window.initCodeCopy = function() {
// Target pre tags that contain code, or .highlight containers
const codeBlocks = document.querySelectorAll('pre');
codeBlocks.forEach(pre => {
// Avoid double init
if (pre.parentNode.querySelector('.copy-code-btn')) return;
if (pre.querySelector('.copy-code-btn')) return;
// Check if it's inside a highlight block (Hugo usually does .highlight > pre)
// We attach button to the pre or the highlight container?
// Attaching to pre is safer for positioning.
const btn = document.createElement('button');
btn.className = 'copy-code-btn';
btn.innerHTML = '<i class="ri-file-copy-line"></i>';
btn.title = "复制代码 / Copy Code";
btn.style.cssText = `
position: absolute;
top: 5px;
right: 5px;
background: transparent;
border: none;
color: #888;
cursor: pointer;
padding: 5px;
border-radius: 4px;
transition: all 0.2s;
opacity: 0;
z-index: 10;
`;
// Show on hover
pre.addEventListener('mouseenter', () => btn.style.opacity = '1');
pre.addEventListener('mouseleave', () => btn.style.opacity = '0');
// Handle touch devices
if (window.isMobile()) {
btn.style.opacity = '1';
}
btn.addEventListener('click', (e) => {
e.stopPropagation();
const code = pre.querySelector('code') ? pre.querySelector('code').innerText : pre.innerText;
if (navigator.clipboard) {
navigator.clipboard.writeText(code).then(() => {
window.showToast('代码已复制 / Copied!');
btn.innerHTML = '<i class="ri-check-line"></i>';
setTimeout(() => btn.innerHTML = '<i class="ri-file-copy-line"></i>', 2000);
}).catch(err => {
console.error('Copy failed', err);
window.showToast('复制失败 / Failed');
});
} else {
// Fallback
const textarea = document.createElement('textarea');
textarea.value = code;
document.body.appendChild(textarea);
textarea.select();
document.execCommand('copy');
document.body.removeChild(textarea);
window.showToast('代码已复制 / Copied!');
}
});
// Ensure relative positioning
if (getComputedStyle(pre).position === 'static') {
pre.style.position = 'relative';
}
pre.appendChild(btn);
});
};