From 3e62b5e589cba8271d565a183e476b5e14515ed6 Mon Sep 17 00:00:00 2001 From: Vaica <94612053+zqlit@users.noreply.github.com> Date: Sat, 24 Jan 2026 19:34:32 +0800 Subject: [PATCH] =?UTF-8?q?added=20=E5=BC=95=E7=94=A8=E8=AF=84=E8=AE=BA?= =?UTF-8?q?=E5=8A=9F=E8=83=BD=E5=A2=9E=E5=8A=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- themes/Ying/assets/css/main.css | 104 +++++++++++++++++++++++++ themes/Ying/assets/js/main.js | 133 ++++++++++++++++++++++++++++++++ 2 files changed, 237 insertions(+) diff --git a/themes/Ying/assets/css/main.css b/themes/Ying/assets/css/main.css index 2df7161c..a6cd0dad 100644 --- a/themes/Ying/assets/css/main.css +++ b/themes/Ying/assets/css/main.css @@ -2941,9 +2941,11 @@ img[atk-emoticon] { /* Fix Artalk comment textarea color in light mode */ .atk-textarea { color: #1a1a1a !important; + background: #fff !important; } [data-theme="dark"] .atk-textarea { color: #e5e7eb !important; + background: transparent !important; } /* About Page Card Style */ @@ -2959,6 +2961,108 @@ img[atk-emoticon] { box-shadow: none !important; } +/* Selection Popup (Quote Button) */ +#selection-popup { + position: fixed; + display: none; + background: rgba(30, 30, 30, 0.9); + color: #fff; + padding: 8px 16px; + border-radius: 8px; + font-size: 14px; + cursor: pointer; + z-index: 2147483647; + transform: translate(-50%, -100%); + pointer-events: auto; + /* Removed backdrop-filter for performance */ + box-shadow: 0 4px 12px rgba(0,0,0,0.15); + transition: opacity 0.2s, transform 0.2s; + font-weight: 500; + line-height: 1.4; + user-select: none; + -webkit-user-select: none; + align-items: center; + gap: 6px; + white-space: nowrap; + will-change: transform, opacity; + animation: popIn 0.2s cubic-bezier(0.18, 0.89, 0.32, 1.28); +} + +@keyframes popIn { + 0% { transform: translate(-50%, -80%) scale(0.9); opacity: 0; } + 100% { transform: translate(-50%, -100%) scale(1); opacity: 1; } +} + +#selection-popup i { + font-size: 16px; +} + +#selection-popup::after { + content: ''; + position: absolute; + top: 100%; + left: 50%; + margin-left: -6px; + border-width: 6px; + border-style: solid; + border-color: rgba(30, 30, 30, 0.9) transparent transparent transparent; +} + +#selection-popup:hover { + transform: translate(-50%, -110%) scale(1.05); + background: #000; + box-shadow: 0 6px 16px rgba(0,0,0,0.25); +} + +[data-theme="dark"] #selection-popup { + background: rgba(255, 255, 255, 0.95); + color: #000; + box-shadow: 0 4px 12px rgba(0,0,0,0.3); +} + +[data-theme="dark"] #selection-popup::after { + border-color: rgba(255, 255, 255, 0.95) transparent transparent transparent; +} + +[data-theme="dark"] #selection-popup:hover { + background: #fff; +} + +/* Artalk Comment Blockquote Redesign & Dark Mode Fix */ +.atk-content blockquote { + margin: 10px 0 6px 0; /* Reduced bottom margin for tighter layout */ + padding: 12px 16px; + background-color: var(--bankuai-color); + border-left: 4px solid var(--text-highlight-color); + border-radius: 4px; + color: var(--text-content-color); + position: relative; + font-style: normal; + font-weight: normal; + transition: all 0.3s ease; +} + +.atk-content blockquote + p { + margin-top: 4px !important; /* Reduce spacing for paragraph immediately following blockquote */ +} + +.atk-content blockquote p { + margin: 0; + color: inherit !important; + font-size: 0.95rem !important; + line-height: 1.6 !important; +} + +[data-theme="dark"] .atk-content blockquote { + background-color: rgba(255, 255, 255, 0.05); + border-left-color: #4b5563; /* Use a slightly lighter gray for border in dark mode */ + color: var(--text-content-dark-color); +} + +[data-theme="dark"] .atk-content blockquote p { + color: inherit !important; +} + /* Category Page Styles */ .category-container { max-width: 100%; diff --git a/themes/Ying/assets/js/main.js b/themes/Ying/assets/js/main.js index 4661af95..8854b0ee 100644 --- a/themes/Ying/assets/js/main.js +++ b/themes/Ying/assets/js/main.js @@ -366,8 +366,141 @@ function initFloatingTools() { // Listen for PJAX complete to update visibility document.addEventListener('pjax:complete', function() { updateFloatingToolsVisibility(); + hideSelectionPopup(); }); document.addEventListener('DOMContentLoaded', function() { initFloatingTools(); + initSelectionPopup(); }); + +// Selection Popup Logic +let selectionPopup = null; +let selectionTimeout; + +function initSelectionPopup() { + if (document.getElementById('selection-popup')) return; + + selectionPopup = document.createElement('div'); + selectionPopup.id = 'selection-popup'; + selectionPopup.innerHTML = ' 引用评论'; + document.body.appendChild(selectionPopup); + + // Prevent mousedown on popup from clearing selection + selectionPopup.addEventListener('mousedown', function(e) { + e.preventDefault(); + e.stopPropagation(); + }); + + // Handle click on popup + selectionPopup.addEventListener('click', function(e) { + e.preventDefault(); + e.stopPropagation(); + + let selection = window.getSelection().toString(); + // Optimize: Remove extra whitespace and limit length + if (selection) { + // Collapse multiple empty lines into max 2 newlines, and trim each line + selection = selection.split('\n') + .map(line => line.trim()) + .filter(line => line !== '') + .join('\n'); + + if (selection.length > 500) { + selection = selection.substring(0, 500) + '...'; + } + + scrollToCommentsAndQuote(selection); + hideSelectionPopup(); + window.getSelection().removeAllRanges(); + } + }); + + // Global listeners for selection + document.addEventListener('mouseup', handleSelectionChange); + document.addEventListener('keyup', handleSelectionChange); +} + +function hideSelectionPopup() { + if (selectionPopup) { + selectionPopup.style.display = 'none'; + } +} + +function handleSelectionChange(e) { + // Debounce to avoid rapid firing + clearTimeout(selectionTimeout); + selectionTimeout = setTimeout(() => { + const selection = window.getSelection(); + const text = selection.toString().trim(); + const content = document.querySelector('.post-content'); // Main article container + + if (!selectionPopup || !content) return; + + // Check if selection is empty + if (!text || selection.rangeCount === 0) { + hideSelectionPopup(); + return; + } + + const range = selection.getRangeAt(0); + + // Check if the common ancestor is inside .post-content + // Optimization: Check container existence before heavy contains check + if (!content.contains(range.commonAncestorContainer)) { + hideSelectionPopup(); + return; + } + + // Optimization: Use requestAnimationFrame for UI updates + requestAnimationFrame(() => { + // Show first to ensure measurements if needed (though we set top/left directly) + selectionPopup.style.display = 'flex'; + + // Position logic: Follow mouse if available (mouseup), else fallback to selection rect (keyup) + if (e && e.type === 'mouseup') { + // Optimization: Avoid getBoundingClientRect on mouseup + // Position above the mouse cursor + selectionPopup.style.top = `${e.clientY - 40}px`; + selectionPopup.style.left = `${e.clientX}px`; + } else { + // Fallback for keyboard selection (needs rect) + const rect = range.getBoundingClientRect(); + // Check if visible and valid + if (rect.width === 0 || rect.height === 0) { + hideSelectionPopup(); + return; + } + selectionPopup.style.top = `${rect.top}px`; + selectionPopup.style.left = `${rect.left + rect.width / 2}px`; + } + }); + }, 150); +} + +function scrollToCommentsAndQuote(text) { + const comments = document.getElementById('Comments'); + if (comments) { + comments.scrollIntoView({ behavior: 'smooth' }); + + // Wait for scroll to complete (approximate) + setTimeout(() => { + // Find Artalk textarea + const textarea = document.querySelector('.atk-textarea'); + if (textarea) { + const quote = `> ${text}\n\n`; + // Overwrite existing content as requested + textarea.value = quote; + textarea.focus(); + + // Trigger input event for Vue/React binding (Artalk uses Vue) + textarea.dispatchEvent(new Event('input', { bubbles: true })); + textarea.dispatchEvent(new Event('change', { bubbles: true })); + + if (window.Qmsg) { + Qmsg.success('引用成功'); + } + } + }, 500); + } +}