fixed bug
This commit is contained in:
1 parent
54b18039e3
commit
8c3232fadf
16 files changed
+1134
-97
No files matched your search
@@ -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">×</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();
|
||||
|
||||
})();
|
||||
Reference in new issue
Block a user