From d73ec8bdc1b0ce025de7665d0301ac10bcf9def5 Mon Sep 17 00:00:00 2001 From: Vaica <94612053+zqlit@users.noreply.github.com> Date: Sat, 31 Jan 2026 21:27:06 +0800 Subject: [PATCH] fixed bug --- data/album.yml | 36 ++ hugo.toml | 15 +- themes/Ying/assets/css/main.css | 238 ++++++-- themes/Ying/assets/js/main.js | 20 +- themes/Ying/assets/js/modules/artalk.js | 30 +- themes/Ying/assets/js/modules/lazyload.js | 36 +- .../assets/js/modules/paragraph-comments.js | 564 ++++++++++++++++++ themes/Ying/assets/js/modules/search.js | 52 +- themes/Ying/assets/js/modules/utils.js | 170 ++++++ themes/Ying/assets/js/mypjax.js | 5 + .../_default/_markup/render-image.html | 2 +- themes/Ying/layouts/_default/circles.html | 18 +- themes/Ying/layouts/partials/artalk.html | 27 + themes/Ying/layouts/partials/footer.html | 3 +- themes/Ying/layouts/partials/head.html | 13 + themes/Ying/layouts/partials/header.html | 2 +- 16 files changed, 1134 insertions(+), 97 deletions(-) create mode 100644 themes/Ying/assets/js/modules/paragraph-comments.js diff --git a/data/album.yml b/data/album.yml index ef4c1d30..ec32f165 100644 --- a/data/album.yml +++ b/data/album.yml @@ -2,4 +2,40 @@ - title: "青梅酒不错" image: "/album/青梅酒.jpg" date: "2026-01-29" + description: "青梅酒还不错" +- title: "青梅酒不错" + image: "/album/青梅酒.jpg" + date: "2026-01-29" + description: "青梅酒还不错" +- title: "青梅酒不错" + image: "/album/青梅酒.jpg" + date: "2026-01-29" + description: "青梅酒还不错" +- title: "青梅酒不错" + image: "/album/青梅酒.jpg" + date: "2026-01-29" + description: "青梅酒还不错" +- title: "青梅酒不错" + image: "/album/青梅酒.jpg" + date: "2026-01-29" + description: "青梅酒还不错" +- title: "青梅酒不错" + image: "/album/青梅酒.jpg" + date: "2026-01-29" + description: "青梅酒还不错" +- title: "青梅酒不错" + image: "/album/青梅酒.jpg" + date: "2026-01-29" + description: "青梅酒还不错" +- title: "青梅酒不错" + image: "https://immmmm.com/goods/mxkeys.webp" + date: "2026-01-29" + description: "青梅酒还不错" +- title: "青梅酒不错" + image: "/album/青梅酒.jpg" + date: "2026-01-29" + description: "青梅酒还不错" +- title: "青梅酒不错" + image: "https://immmmm.com/goods/mxkeys.webp" + date: "2026-01-29" description: "青梅酒还不错" \ No newline at end of file diff --git a/hugo.toml b/hugo.toml index 9272273c..e4fb3cc3 100644 --- a/hugo.toml +++ b/hugo.toml @@ -87,13 +87,6 @@ hasCJKLanguage = true # 站点参数配置 (Site Parameters) # ============================================================================== [params] - # ---------------------------------------------------------------------------- - # 关于页面配置 (About Page Configuration) - # ---------------------------------------------------------------------------- - [params.about] - enableheatmap = false # 开启热力图 (Enable Heatmap) - enableskills = false # 开启技能栈 (Enable Skills) - # ---------------------------------------------------------------------------- # 社交链接 (Social Links) # ---------------------------------------------------------------------------- @@ -103,6 +96,13 @@ hasCJKLanguage = true { title = "Rss订阅", url = "/rss.xml", icon = "ri-rss-fill" } ] + # ---------------------------------------------------------------------------- + # 关于页面配置 (About Page Configuration) + # ---------------------------------------------------------------------------- + [params.about] + enableheatmap = false # 开启热力图 (Enable Heatmap) + enableskills = false # 开启技能栈 (Enable Skills) + # ---------------------------------------------------------------------------- # 作者信息 (Author Info) # ---------------------------------------------------------------------------- @@ -126,6 +126,7 @@ hasCJKLanguage = true [params.artalk] server = "https://artalk.usj.cc" site = "优世界" + ai_badge = "捧场王" # 自定义 AI 评论标识 emoticons = "/emotion/OwO.json" gravatar = "https://cravatar.cn/avatar/" diff --git a/themes/Ying/assets/css/main.css b/themes/Ying/assets/css/main.css index 4deb6336..fed7a591 100644 --- a/themes/Ying/assets/css/main.css +++ b/themes/Ying/assets/css/main.css @@ -46,10 +46,11 @@ } [data-theme="dark"] .main { - background: rgba(15, 23, 42, 0.96); + background: rgba(15, 23, 42, 0.99); /* Increased opacity for readability */ box-shadow: var(--card-shadow-dark); - backdrop-filter: blur(10px); - -webkit-backdrop-filter: blur(10px); + /* Removed glassmorphism blur as requested */ + backdrop-filter: none; + -webkit-backdrop-filter: none; } [data-theme="dark"] .header nav { @@ -505,7 +506,7 @@ ul { overflow: hidden; margin: 60px auto; padding: 50px; - background: rgba(255, 255, 255, 0.95); + background: rgba(255, 255, 255, 0.8); box-shadow: var(--card-shadow-light); border-radius: 8px; z-index: 10; @@ -740,93 +741,153 @@ ul { animation: fadeIn 0.3s ease; } +/* Simplified Search Styles for Essay/Shuoshuo */ .search-input-group { position: relative; display: flex; align-items: center; width: 100%; - border-bottom: 2px solid #e5e7eb; - transition: border-color 0.3s ease; + border: 1px solid rgba(0, 0, 0, 0.08); /* Very subtle border for minimalism */ + border-radius: 99px; /* Fully rounded pill */ + padding: 2px 15px; + background: transparent; /* No background by default */ + transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1); +} + +.search-input-group:hover { + border-color: rgba(0, 0, 0, 0.15); + background: rgba(0, 0, 0, 0.01); } .search-input-group:focus-within { - border-color: #111827; + background: #fff; + border-color: transparent; + box-shadow: 0 8px 30px rgba(0, 0, 0, 0.08); /* Soft diffuse shadow */ + transform: translateY(-1px); +} + +[data-theme="dark"] .search-input-group { + border-color: rgba(255, 255, 255, 0.1); + background: transparent; +} + +[data-theme="dark"] .search-input-group:hover { + border-color: rgba(255, 255, 255, 0.2); + background: rgba(255, 255, 255, 0.02); } [data-theme="dark"] .search-input-group:focus-within { - border-color: #e5e7eb; + background: #1f2933; + border-color: transparent; + box-shadow: 0 8px 30px rgba(0, 0, 0, 0.4); } .search-input-group input { width: 100%; border: none; background: transparent; - padding: 8px 35px 8px 45px; /* Increased left padding to separate from icon */ - font-size: 1rem; + padding: 10px 10px 10px 30px; + font-size: 0.95rem; color: var(--text-title-color); outline: none; } -[data-theme="dark"] .search-input-group input { - color: var(--text-title-dark-color); -} - -.search-icon, .close-icon { - position: absolute; - font-size: 1.2rem; - color: #999; -} - .search-icon { - left: 5px; + position: absolute; + left: 15px; + font-size: 1.1rem; + color: #9ca3af; + pointer-events: none; + transition: color 0.3s; +} + +.search-input-group:focus-within .search-icon { + color: #4b5563; +} + +[data-theme="dark"] .search-input-group:focus-within .search-icon { + color: #d1d5db; } .close-icon { - right: 5px; + position: absolute; + right: 15px; + font-size: 1.1rem; + color: #9ca3af; cursor: pointer; - transition: color 0.2s; + transition: all 0.2s; + opacity: 0; + transform: scale(0.8); +} + +.search-input-group.has-value .close-icon { + opacity: 1; + transform: scale(1); } .close-icon:hover { - color: var(--text-title-color); -} - -[data-theme="dark"] .close-icon:hover { - color: var(--text-title-dark-color); + color: #ef4444; } .search-dropdown { position: absolute; top: 100%; left: 0; - width: 100%; - background: rgba(255, 255, 255, 0.98); - backdrop-filter: blur(10px); - border-radius: 8px; - box-shadow: 0 10px 25px rgba(0,0,0,0.1); - z-index: 100; - max-height: 400px; - overflow-y: auto; - margin-top: 10px; - display: none; /* Hidden by default */ -} - -[data-theme="dark"] .search-dropdown { - background: rgba(30, 41, 59, 0.98); - box-shadow: 0 10px 25px rgba(0,0,0,0.5); + right: 0; + background: rgba(255, 255, 255, 0.9); + backdrop-filter: blur(12px); + -webkit-backdrop-filter: blur(12px); + border-radius: 12px; + box-shadow: 0 10px 40px -10px rgba(0, 0, 0, 0.1); + border: 1px solid rgba(255, 255, 255, 0.2); + margin-top: 12px; + overflow: hidden; + z-index: 50; + opacity: 0; + transform: translateY(10px); + visibility: hidden; + transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1); } .search-dropdown.show { - display: block; - animation: slideDown 0.2s ease; + opacity: 1; + transform: translateY(0); + visibility: visible; +} + +[data-theme="dark"] .search-dropdown { + background: rgba(31, 41, 51, 0.9); + border: 1px solid rgba(255, 255, 255, 0.05); + box-shadow: 0 10px 40px -10px rgba(0, 0, 0, 0.5); } .search-result-item { - padding: 12px 15px; - border-bottom: 1px solid rgba(0,0,0,0.05); - transition: background 0.2s; - text-align: left; /* Align text left */ - cursor: pointer; + padding: 10px 15px; + border-bottom: 1px solid rgba(0,0,0,0.03); + display: flex; + flex-direction: column; +} + +.search-result-meta { + display: flex; + justify-content: space-between; + font-size: 0.8rem; + color: #999; + margin-top: 4px; +} + +.search-result-summary-text { + max-width: 70%; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} + +.no-result { + text-align: center; + color: #999; + padding: 15px; + cursor: default; } [data-theme="dark"] .search-result-item { @@ -1001,6 +1062,83 @@ ul { transition: background-color 0.3s ease, color 0.3s ease, border-color 0.3s ease; } +/* Code Block Controls */ +.code-controls { + position: absolute; + top: 5px; + right: 10px; + display: flex; + gap: 8px; + z-index: 10; + opacity: 0; + transition: opacity 0.3s ease; +} + +.highlight:hover .code-controls { + opacity: 1; +} + +.code-btn { + background: rgba(0,0,0,0.5); + color: white; + border: none; + border-radius: 4px; + padding: 4px 8px; + font-size: 12px; + cursor: pointer; + transition: background 0.2s; +} + +.code-btn:hover { + background: rgba(0,0,0,0.7); +} + +/* Expandable Code Blocks */ +.highlight.collapsed { + max-height: 300px; + position: relative; +} + +.highlight.collapsed::after { + content: ""; + position: absolute; + bottom: 0; + left: 0; + width: 100%; + height: 80px; + background: linear-gradient(to bottom, transparent, #f6f8fa); + pointer-events: none; +} + +[data-theme="dark"] .highlight.collapsed::after { + background: linear-gradient(to bottom, transparent, #282a36); +} + +.expand-btn-container { + text-align: center; + position: absolute; + bottom: 0; + width: 100%; + padding: 10px; + z-index: 5; + display: none; +} + +.highlight.collapsed .expand-btn-container { + display: block; +} + +.expand-btn { + background: var(--link-color); + color: white; + border: none; + padding: 5px 15px; + border-radius: 20px; + cursor: pointer; + font-size: 12px; + box-shadow: 0 2px 5px rgba(0,0,0,0.2); +} + [data-theme="dark"] .highlight { background: #282a36; /* Night Mode: Dark BG (Dracula) */ color: #f8f8f2; /* Night Mode: Light Text */ diff --git a/themes/Ying/assets/js/main.js b/themes/Ying/assets/js/main.js index 172135b3..d04d5a8e 100644 --- a/themes/Ying/assets/js/main.js +++ b/themes/Ying/assets/js/main.js @@ -5,13 +5,7 @@ document.addEventListener('DOMContentLoaded', function() { ViewImage.init('.post-content img'); } - // 初始化各个模块 - if (window.initLoader) window.initLoader(); - if (window.initFloatingTools) window.initFloatingTools(); - if (window.initSearch) window.initSearch(); - if (window.initArtalk) window.initArtalk(); - - // 文章图片懒加载 + // 文章图片懒加载 (优先初始化,防止其他模块调用时出错) if (typeof lazyLoadInit === 'function') { lazyLoadInit({ offsetBottom: 800, @@ -21,4 +15,16 @@ document.addEventListener('DOMContentLoaded', function() { } else { console.warn('lazyLoadInit is not defined'); } + + // 初始化各个模块 + if (window.initLoader) window.initLoader(); + if (window.initFloatingTools) window.initFloatingTools(); + if (window.initSearch) window.initSearch(); + if (window.initArtalk) window.initArtalk(); + + // New Utils Init + if (window.initThemeToggle) window.initThemeToggle(); + if (window.initCodeFold) window.initCodeFold(); + if (window.initCodeCopy) window.initCodeCopy(); + if (window.initParagraphComments) window.initParagraphComments(); }); diff --git a/themes/Ying/assets/js/modules/artalk.js b/themes/Ying/assets/js/modules/artalk.js index e8541590..9d4959a9 100644 --- a/themes/Ying/assets/js/modules/artalk.js +++ b/themes/Ying/assets/js/modules/artalk.js @@ -33,7 +33,7 @@ window.initArtalk = function() {
${ai.name} - AI + ${window.artalkConfig.aiBadge || 'AI'} ${ai.date}
@@ -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) { diff --git a/themes/Ying/assets/js/modules/lazyload.js b/themes/Ying/assets/js/modules/lazyload.js index 21dd858c..1a23f555 100644 --- a/themes/Ying/assets/js/modules/lazyload.js +++ b/themes/Ying/assets/js/modules/lazyload.js @@ -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')) { diff --git a/themes/Ying/assets/js/modules/paragraph-comments.js b/themes/Ying/assets/js/modules/paragraph-comments.js new file mode 100644 index 00000000..8f5e72fe --- /dev/null +++ b/themes/Ying/assets/js/modules/paragraph-comments.js @@ -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 = ' 评论'; + document.body.appendChild(popup); + + // Create Modal Element + var modal = document.createElement('div'); + modal.className = 'pc-modal'; + modal.innerHTML = ` +
+
+ 引用评论 + × +
+
+
+ `; + 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 = ` ${count}`; + + 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>/) || firstContent.match(/^> (.*?)(?:\n|$)/); + if (match) quote = match[1]; + } + + if (quote) { + body.innerHTML += `
${quote}
`; + } + + comments.forEach(c => { + var item = document.createElement('div'); + item.className = 'pc-comment-item'; + var cleanContent = c.content.replace(/^> .*?\n+/g, '').replace(/
.*?<\/blockquote>/g, ''); + + var badgeHtml = ''; + if (c.badge) { + badgeHtml = `${c.badge}`; + } + + item.innerHTML = ` +
${c.nick}${badgeHtml} ${c.date}
+
${cleanContent}
+ `; + body.appendChild(item); + }); + + modal.classList.add('show'); + } + + function closeModal() { + modal.classList.remove('show'); + } + + window.initParagraphComments = init; + init(); + +})(); diff --git a/themes/Ying/assets/js/modules/search.js b/themes/Ying/assets/js/modules/search.js index 76cd50ba..2f6e9406 100644 --- a/themes/Ying/assets/js/modules/search.js +++ b/themes/Ying/assets/js/modules/search.js @@ -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 ` - + ${item.title} -
+
${item.date} + ${item.summary || ''}
`; @@ -173,7 +219,7 @@ function initInlineSearch() { window.pjax.refresh(resultsContainer); } } else { - resultsContainer.innerHTML = '
无搜索结果
'; + resultsContainer.innerHTML = '
暂无相关内容
'; resultsContainer.classList.add('show'); } }); diff --git a/themes/Ying/assets/js/modules/utils.js b/themes/Ying/assets/js/modules/utils.js index fac84034..153a4d1b 100644 --- a/themes/Ying/assets/js/modules/utils.js +++ b/themes/Ying/assets/js/modules/utils.js @@ -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 = ' 展开代码 / Expand'; + 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 = ''; + 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 = ''; + setTimeout(() => btn.innerHTML = '', 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); + }); +}; diff --git a/themes/Ying/assets/js/mypjax.js b/themes/Ying/assets/js/mypjax.js index 67786787..b5dba738 100644 --- a/themes/Ying/assets/js/mypjax.js +++ b/themes/Ying/assets/js/mypjax.js @@ -94,6 +94,11 @@ document.addEventListener("pjax:complete", function () { // 初始化代码折叠 if (typeof initCodeFold === 'function') initCodeFold(); + // 初始化段落评论 + if (typeof window.initParagraphComments === 'function') { + window.initParagraphComments(); + } + // 初始化友链状态 if (typeof window.initLinkStatus === 'function') { window.initLinkStatus(); diff --git a/themes/Ying/layouts/_default/_markup/render-image.html b/themes/Ying/layouts/_default/_markup/render-image.html index f77d3c77..6d144e2f 100644 --- a/themes/Ying/layouts/_default/_markup/render-image.html +++ b/themes/Ying/layouts/_default/_markup/render-image.html @@ -7,4 +7,4 @@ {{- $src = $src | relURL -}} {{- end -}} {{- end -}} -{{ .Text }} \ No newline at end of file +{{ .Text }} \ No newline at end of file diff --git a/themes/Ying/layouts/_default/circles.html b/themes/Ying/layouts/_default/circles.html index cfd6c347..504daaf9 100644 --- a/themes/Ying/layouts/_default/circles.html +++ b/themes/Ying/layouts/_default/circles.html @@ -39,9 +39,9 @@ height: 160px; justify-content: space-between; background: rgba(255, 255, 255, 0.4); - /* Removed backdrop-filter for performance */ - /* backdrop-filter: blur(16px) saturate(180%); */ - /* -webkit-backdrop-filter: blur(16px) saturate(180%); */ + /* Enhanced Glassmorphism for non-mobile */ + backdrop-filter: blur(12px) saturate(120%); + -webkit-backdrop-filter: blur(12px) saturate(120%); border: 1px solid #111827; box-shadow: none; transition: @@ -55,6 +55,18 @@ overflow: hidden; /* 防止背景图溢出 */ } + /* Mobile Optimization: Remove expensive effects */ + @media (max-width: 767px) { + .fc-item { + backdrop-filter: none !important; + -webkit-backdrop-filter: none !important; + background: rgba(255, 255, 255, 0.8); /* Higher opacity fallback */ + } + [data-theme="dark"] .fc-item { + background: rgba(30, 30, 30, 0.8); + } + } + .friend-circle-list a { text-decoration: none; } diff --git a/themes/Ying/layouts/partials/artalk.html b/themes/Ying/layouts/partials/artalk.html index defc61bb..fcfcd38b 100644 --- a/themes/Ying/layouts/partials/artalk.html +++ b/themes/Ying/layouts/partials/artalk.html @@ -4,6 +4,17 @@ .atk-loading{ background: #FFFFFF; } + .atk-loading-editor-box { + margin-bottom: 30px; + border: 1px solid var(--frame-color); + border-radius: 4px; + padding: 10px; + background: #fff; + } + [data-theme="dark"] .atk-loading-editor-box { + border-color: var(--frame-dark-color); + background: transparent; + } /*对artalk评论暗夜模式进行微调*/ @media (prefers-color-scheme: dark) { .atk-pinned-badge { @@ -20,6 +31,21 @@
+ +
+
+
+
+
+
+
+
+
+
+
+
+ +
@@ -45,6 +71,7 @@ el: '#Comments', server: '{{ .Site.Params.artalk.server }}', site: '{{ .Site.Params.artalk.site }}', + aiBadge: '{{ .Site.Params.artalk.ai_badge | default "AI" }}', pageKey: '{{ .RelPermalink }}', emoticons: '{{ .Site.Params.artalk.emoticons | relURL }}', aiComment: {{ if .Params.ai_comment }} diff --git a/themes/Ying/layouts/partials/footer.html b/themes/Ying/layouts/partials/footer.html index 6f643e80..3d15f65c 100644 --- a/themes/Ying/layouts/partials/footer.html +++ b/themes/Ying/layouts/partials/footer.html @@ -66,10 +66,11 @@ {{ $loader := resources.Get "js/modules/loader.js" }} {{ $search := resources.Get "js/modules/search.js" }} {{ $floatingTools := resources.Get "js/modules/floating-tools.js" }} +{{ $paragraphComments := resources.Get "js/modules/paragraph-comments.js" }} {{ $artalkModule := resources.Get "js/modules/artalk.js" }} {{ $pjaxLib := resources.Get "js/pjax.js" }} -{{ $scripts := slice $iconfont $lightgallery $uikit $easylazyload $viewimage $artalk $barrage $utils $loader $search $floatingTools $artalkModule $nprogress $pjaxLib $main $mypjax $message $echarts $tiaozhuan | resources.Concat "js/bundle.js" | resources.Minify | resources.Fingerprint }} +{{ $scripts := slice $iconfont $lightgallery $uikit $easylazyload $viewimage $artalk $barrage $utils $loader $search $floatingTools $paragraphComments $artalkModule $nprogress $pjaxLib $main $mypjax $message $echarts $tiaozhuan | resources.Concat "js/bundle.js" | resources.Minify | resources.Fingerprint }} diff --git a/themes/Ying/layouts/partials/head.html b/themes/Ying/layouts/partials/head.html index d560f794..4d4bb912 100644 --- a/themes/Ying/layouts/partials/head.html +++ b/themes/Ying/layouts/partials/head.html @@ -52,6 +52,19 @@ } } document.documentElement.setAttribute('data-theme', theme); + + // Listen for system changes + if (window.matchMedia) { + window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', event => { + // Only switch if user hasn't manually set a preference + // Or maybe we should respect system change if user hasn't explicitly locked it? + // Common behavior: manual override sticks. + if (!localStorage.getItem('theme')) { + var newTheme = event.matches ? "dark" : "light"; + document.documentElement.setAttribute('data-theme', newTheme); + } + }); + } } catch (e) {} })(); diff --git a/themes/Ying/layouts/partials/header.html b/themes/Ying/layouts/partials/header.html index 18e133cf..28afcdc0 100644 --- a/themes/Ying/layouts/partials/header.html +++ b/themes/Ying/layouts/partials/header.html @@ -4,7 +4,7 @@
-
+
{{ .Site.Title }}
点击头像切换昼夜