文字回應功能實作-這個系統允許讀者選擇文章中的任意文字段落進行評論,並即時顯示評論數量。

請ChatGpt,Gmini,claude協助完成,可惜不是自架站,不然就可以嘗試將回應資料上傳伺服器了 。


這是文章標題

這是一段可以留言的文章內容。你可以選擇其中的任何句子來留言。。

這是另一段文章內容,同樣可以被選取與留言。

這是另一段文章內容,同樣可以被選取與留言。

這是另一段文章內容,同樣可以被選取與留言。

這是另一段文章內容,同樣可以被選取與留言。

這是另一段文章內容,同樣可以被選取與留言。

這是另一段文章內容,同樣可以被選取與留言。

這是另一段文章內容,同樣可以被選取與留言。

這是另一段文章內容,同樣可以被選取與留言。

這是另一段文章內容,同樣可以被選取與留言。

這是另一段文章內容,同樣可以被選取與留言。

這是另一段文章內容,同樣可以被選取與留言。

這是另一段文章內容,同樣可以被選取與留言。

這是另一段文章內容,同樣可以被選取與留言。

這是另一段文章內容,同樣可以被選取與留言。

這是另一段文章內容,同樣可以被選取與留言。

這是另一段文章內容,同樣可以被選取與留言。

這是另一段文章內容,同樣可以被選取與留言。

這是另一段文章內容,同樣可以被選取與留言。

這是另一段文章內容,同樣可以被選取與留言。

這是另一段文章內容,同樣可以被選取與留言。

這是另一段文章內容,同樣可以被選取與留言。

這是另一段文章內容,同樣可以被選取與留言。

這是另一段文章內容,同樣可以被選取與留言。

這是另一段文章內容,同樣可以被選取與留言。

這是另一段文章內容,同樣可以被選取與留言。

這是另一段文章內容,同樣可以被選取與留言。

這是另一段文章內容,同樣可以被選取與留言。

這是另一段文章內容,同樣可以被選取與留言。

這是另一段文章內容,同樣可以被選取與留言。

這是另一段文章內容,同樣可以被選取與留言。

這是另一段文章內容,同樣可以被選取與留言。

這是另一段文章內容,同樣可以被選取與留言。

這是另一段文章內容,同樣可以被選取與留言。


底下是網頁原始檔:


<meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0">
<title></title>
<style type="text/css">* {
            margin: 0;
            padding: 0;
            box-sizing: border-box;
        }

        body {
            font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'PingFang SC', 'Hiragino Sans GB', sans-serif;
            line-height: 1.6;
            background: #f5f5f5;
            color: #333;
        }

        .container {
            max-width: 1200px;
            margin: 0 auto;
            display: flex;
            gap: 20px;
            padding: 20px;
        }

        .article-section {
            flex: 2;
            background: white;
            border-radius: 12px;
            box-shadow: 0 2px 12px rgba(0,0,0,0.1);
            padding: 40px;
        }

        .comments-section {
            flex: 1;
            background: white;
            border-radius: 12px;
            box-shadow: 0 2px 12px rgba(0,0,0,0.1);
            padding: 20px;
            height: fit-content;
            position: sticky;
            top: 20px;
        }

        .article-title {
            font-size: 24px;
            font-weight: bold;
            margin-bottom: 20px;
            color: #1a1a1a;
        }

        .article-content {
            font-size: 16px;
            line-height: 1.8;
            color: #333;
        }

        .article-content p {
            margin-bottom: 20px;
            position: relative;
        }

        /* 選中文字的樣式 */
        .selected-text {
            background: linear-gradient(120deg, #a8e6cf 0%, #dcedc1 100%);
            padding: 2px 4px;
            border-radius: 4px;
            position: relative;
            cursor: pointer;
        }

        /* 評論數量標記 */
        .comment-count {
            display: inline-block;
            background: #ff6b6b;
            color: white;
            font-size: 12px;
            padding: 2px 6px;
            border-radius: 10px;
            margin-left: 5px;
            font-weight: bold;
            cursor: pointer;
            animation: pulse 0.3s ease-in-out;
        }

        @keyframes pulse {
            0% { transform: scale(1); }
            50% { transform: scale(1.1); }
            100% { transform: scale(1); }
        }

        /* 評論彈窗 */
        .comment-popup {
            position: fixed;
            background: white;
            border: 1px solid #e0e0e0;
            border-radius: 8px;
            box-shadow: 0 4px 20px rgba(0,0,0,0.15);
            padding: 15px;
            width: 400px;
            z-index: 1000;
            display: none;
        }

        .comment-popup textarea {
            width: 100%;
            height: 100px;
            border: 1px solid #e0e0e0;
            border-radius: 6px;
            padding: 10px;
            font-size: 14px;
            resize: vertical;
        }

        .comment-popup-buttons {
            display: flex;
            gap: 10px;
            margin-top: 10px;
            justify-content: flex-end;
        }

        .btn {
            padding: 8px 16px;
            border: none;
            border-radius: 6px;
            cursor: pointer;
            font-size: 14px;
            transition: all 0.2s;
        }

        .btn-primary {
            background: #4CAF50;
            color: white;
        }

        .btn-primary:hover {
            background: #45a049;
        }

        .btn-secondary {
            background: #f5f5f5;
            color: #666;
        }

        .btn-secondary:hover {
            background: #e0e0e0;
        }

        /* 評論區域 */
        .comments-header {
            font-size: 16px;
            font-weight: bold;
            margin-bottom: 20px;
            color: #1a1a1a;
            border-bottom: 2px solid #f0f0f0;
            padding-bottom: 10px;
        }

        .comment-item {
            margin-bottom: 20px;
            padding: 15px;
            background: #f9f9f9;
            border-radius: 8px;
            border-left: 4px solid #4CAF50;
            animation: slideIn 0.3s ease-out;
        }

        @keyframes slideIn {
            from {
                opacity: 0;
                transform: translateY(20px);
            }
            to {
                opacity: 1;
                transform: translateY(0);
            }
        }

        .comment-user {
            font-weight: bold;
            color: #2c3e50;
            margin-bottom: 5px;
        }

        .comment-quote {
            background: white;
            padding: 8px 12px;
            border-radius: 6px;
            font-style: italic;
            color: #666;
            border-left: 3px solid #ddd;
            margin-bottom: 8px;
        }

        .comment-text {
            color: #333;
            line-height: 1.5;
        }

        .comment-time {
            font-size: 12px;
            color: #999;
            margin-top: 8px;
        }

        /* 使用者登錄區域 */
        .user-login {
            margin-bottom: 20px;
            padding: 15px;
            background: #e8f5e8;
            border-radius: 8px;
        }

        .user-login input {
            width: 100%;
            padding: 8px 12px;
            border: 1px solid #ddd;
            border-radius: 6px;
            font-size: 14px;
        }

        /* 選擇提示 */
        .selection-hint {
            position: fixed;
            background: #333;
            color: white;
            padding: 8px 12px;
            border-radius: 6px;
            font-size: 12px;
            z-index: 999;
            display: none;
            pointer-events: none;
        }

        /* 回應式設計 */
        @media (max-width: 768px) {
            .container {
                flex-direction: column;
                padding: 10px;
            }
            
            .article-section {
                padding: 20px;
            }
            
            .comments-section {
                position: static;
            }
        }
</style>
<div class="container">
<div class="article-section">
<h1 class="article-title">這是文章標題</h1>

<div class="article-content" id="articleContent">
<p>這是一段可以留言的文章內容。你可以選擇其中的任何句子來留言。。</p>

<p>這是另一段文章內容,同樣可以被選取與留言。</p>

<p>這是另一段文章內容,同樣可以被選取與留言。</p>

<p>這是另一段文章內容,同樣可以被選取與留言。</p>

<p>這是另一段文章內容,同樣可以被選取與留言。</p>

<p>這是另一段文章內容,同樣可以被選取與留言。</p>

<p>這是另一段文章內容,同樣可以被選取與留言。</p>

<p>這是另一段文章內容,同樣可以被選取與留言。</p>

<p>這是另一段文章內容,同樣可以被選取與留言。</p>

<p>這是另一段文章內容,同樣可以被選取與留言。</p>

<p>這是另一段文章內容,同樣可以被選取與留言。</p>

<p>這是另一段文章內容,同樣可以被選取與留言。</p>

<p>這是另一段文章內容,同樣可以被選取與留言。</p>

<p>這是另一段文章內容,同樣可以被選取與留言。</p>

<p>這是另一段文章內容,同樣可以被選取與留言。</p>

<p>這是另一段文章內容,同樣可以被選取與留言。</p>

<p>這是另一段文章內容,同樣可以被選取與留言。</p>

<p>這是另一段文章內容,同樣可以被選取與留言。</p>

<p>這是另一段文章內容,同樣可以被選取與留言。</p>

<p>這是另一段文章內容,同樣可以被選取與留言。</p>

<p>這是另一段文章內容,同樣可以被選取與留言。</p>

<p>這是另一段文章內容,同樣可以被選取與留言。</p>

<p>這是另一段文章內容,同樣可以被選取與留言。</p>

<p>這是另一段文章內容,同樣可以被選取與留言。</p>

<p>這是另一段文章內容,同樣可以被選取與留言。</p>

<p>這是另一段文章內容,同樣可以被選取與留言。</p>

<p>這是另一段文章內容,同樣可以被選取與留言。</p>

<p>這是另一段文章內容,同樣可以被選取與留言。</p>

<p>這是另一段文章內容,同樣可以被選取與留言。</p>

<p>這是另一段文章內容,同樣可以被選取與留言。</p>

<p>這是另一段文章內容,同樣可以被選取與留言。</p>

<p>這是另一段文章內容,同樣可以被選取與留言。</p>

<p>這是另一段文章內容,同樣可以被選取與留言。</p>

<p>這是另一段文章內容,同樣可以被選取與留言。</p>

<p>&nbsp;</p>
</div>
</div>

<div class="comments-section">
<div class="user-login"><input id="username" placeholder="請輸入您的用戶名" type="text" value="訪客用戶" /></div>

<div class="comments-header">評論區 (<span id="commentCount">0</span>)</div>

<div id="commentsList"><!-- 評論將在這裡顯示 --></div>
</div>
</div>
<!-- 評論彈窗 -->

<div class="comment-popup" id="commentPopup"><textarea id="commentText" placeholder="寫下你的想法..."></textarea>

<div class="comment-popup-buttons"><button class="btn btn-secondary">取消</button><button class="btn btn-primary">發表</button></div>
</div>
<!-- 選擇提示 -->

<div class="selection-hint" id="selectionHint">選擇文字後點擊添加評論</div>
<script>
let comments = [];
let currentSelection = null;
let commentIdCounter = 1;

// 初始化
window.addEventListener('DOMContentLoaded', () => {
    initializeParagraphIds();
    initializeTextSelection();
    updateCommentCount();
});

// 為每段文字加上唯一 ID
function initializeParagraphIds() {
    const paragraphs = document.querySelectorAll('#articleContent p');
    paragraphs.forEach((p, index) => {
        p.setAttribute('data-para-id', index);
    });
}

// 選取文字時處理
function initializeTextSelection() {
    const articleContent = document.getElementById('articleContent');

    articleContent.addEventListener('mouseup', handleTextSelection);
    articleContent.addEventListener('touchend', handleTextSelection);

    document.addEventListener('click', function(e) {
        if (!e.target.closest('.comment-popup') && !e.target.closest('#articleContent')) {
            clearSelection();
        }
    });
}

function handleTextSelection(e) {
    const selection = window.getSelection();
    const selectedText = selection.toString().trim();

    if (!selectedText) return;

    const range = selection.getRangeAt(0);
    let para = range.startContainer;
    while (para && para.tagName !== 'P') {
        para = para.parentNode;
    }
    if (!para) return;

    const paraId = para.dataset.paraId;
    currentSelection = { text: selectedText, paraId: paraId };

    // 改成智慧浮動定位
    showCommentPopupFromSelection();
}


function showCommentPopupFromSelection() {
    const popup = document.getElementById('commentPopup');
    const textarea = document.getElementById('commentText');
    const width = 600;
    const height = 300;

    const selection = window.getSelection();
    if (selection.rangeCount === 0) return;

    const range = selection.getRangeAt(0);
    const rect = range.getBoundingClientRect();

    if (!rect) return;

    // 計算理想彈窗位置
    let left = rect.left ;
    let top = rect.bottom ;

    // 避免超出畫面邊界(右邊與下方)
    if (left + width > window.innerWidth + window.scrollX) {
        left = window.innerWidth + window.scrollX - width - 20;
    }
    if (top + height > window.innerHeight + window.scrollY) {
        top = rect.top + window.scrollY - height - 10;
    }

    // 保底:避免超出上方與左邊
    if (left < 0) left = 10;
    if (top < 0) top = 10;

    // 顯示彈窗
    popup.style.left = left + 'px';
    popup.style.top = top + 'px';
    popup.style.display = 'block';
    textarea.focus();
}


function closeCommentPopup() {
    document.getElementById('commentPopup').style.display = 'none';
    document.getElementById('commentText').value = '';
    clearSelection();
}

function clearSelection() {
    window.getSelection().removeAllRanges();
    currentSelection = null;
}

function submitComment() {
    const text = document.getElementById('commentText').value.trim();
    const user = document.getElementById('username').value.trim() || '匿名使用者';
    const { text: quote, paraId } = currentSelection;

    if (!text || !quote || paraId == null) return;

    const key = `${paraId}::${quote}`;
    const comment = {
        id: commentIdCounter++,
        user: user,
        quote: quote,
        text: text,
        time: new Date().toLocaleString('zh-CN'),
        paraId: paraId,
        key: key
    };

    comments.push(comment);
    highlightTextInParagraph(paraId, quote, key);
    updateCommentsDisplay();
    updateCommentCount();
    closeCommentPopup();
}

function highlightTextInParagraph(paraId, quoteText, key) {
    const p = document.querySelector(`p[data-para-id="${paraId}"]`);
    if (!p) return;

    const html = p.innerHTML;
    if (html.includes(`data-highlight="${key}"`)) {
        const countSpan = p.querySelector(`.selected-text[data-highlight="${key}"] .comment-count`);
        if (countSpan) {
            const count = parseInt(countSpan.textContent);
            countSpan.textContent = count + 1;
        }
        return;
    }

    const safeQuote = quoteText.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
    const pattern = new RegExp(`(${safeQuote})`);
    const replaced = html.replace(pattern, `<span class="selected-text" data-highlight="${key}">$1 <span class="comment-count">1</span></span>`);

    p.innerHTML = replaced;

    const span = p.querySelector(`.selected-text[data-highlight="${key}"]`);
    if (span) {
        span.onclick = () => scrollToComment(commentIdCounter - 1);
    }
}

function updateCommentsDisplay() {
    const list = document.getElementById('commentsList');
    list.innerHTML = '';

    comments.slice().reverse().forEach(comment => {
        const item = document.createElement('div');
        item.className = 'comment-item';
        item.setAttribute('data-comment-id', comment.id);
        item.innerHTML = `
            <div class="comment-user">${comment.user}</div>
            <div class="comment-quote">"${comment.quote}"</div>
            <div class="comment-text">${comment.text}</div>
            <div class="comment-time">${comment.time}</div>
        `;
        list.appendChild(item);
    });
}

function scrollToComment(commentId) {
    const el = document.querySelector(`.comment-item[data-comment-id="${commentId}"]`);
    if (el) {
        el.scrollIntoView({ behavior: 'smooth', block: 'center' });
        el.style.animation = 'pulse 0.6s ease-in-out';
        setTimeout(() => el.style.animation = '', 600);
    }
}

function updateCommentCount() {
    document.getElementById('commentCount').textContent = comments.length;
}

// 快捷鍵支援
document.querySelector('.btn-primary').addEventListener('click', submitComment);
document.querySelector('.btn-secondary').addEventListener('click', closeCommentPopup);
document.addEventListener('keydown', function(e) {
    if (e.key === 'Escape') closeCommentPopup();
    if (e.ctrlKey && e.key === 'Enter' && document.getElementById('commentPopup').style.display === 'block') submitComment();
});
</script>

 

評論區 (0)
選擇文字後點擊添加評論

沒有留言:

張貼留言

請問墊腳尖和拉筋的好,處像小朋友一樣握拳頭再鬆開的好處。

我: 請問墊腳尖和拉筋的好處 像小朋友一樣握拳頭再鬆開的好處   ________________________________________ Claude : 墊腳尖和拉筋都是很好的運動習慣,各有不同的健康益處: 墊腳尖的好處 皮膚增強 ...