From 624d0bda0f873cf9cd38b1c7482f6882611b6c77 Mon Sep 17 00:00:00 2001 From: zqlit Date: Sun, 24 May 2026 14:00:12 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E5=8A=A8=E7=94=BB=E6=8E=89?= =?UTF-8?q?=E5=B8=A7=E7=9A=84=E9=97=AE=E9=A2=98=EF=BC=8C=E5=A2=9E=E5=8A=A0?= =?UTF-8?q?=E6=98=BC=E5=A4=9C=E5=88=87=E6=8D=A2=E7=9A=84=E8=BF=87=E6=B8=A1?= =?UTF-8?q?=E5=8A=A8=E7=94=BB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- themes/Ying/assets/css/main.css | 99 +++++++++++++++++ .../Ying/assets/js/modules/infinite-scroll.js | 18 +-- themes/Ying/assets/js/modules/search.js | 32 ++++-- themes/Ying/assets/js/modules/tiaozhuan.js | 57 ++++------ themes/Ying/assets/js/modules/toast.js | 63 +++++++++++ themes/Ying/assets/js/modules/utils.js | 103 +++++------------- themes/Ying/layouts/partials/footer.html | 5 +- 7 files changed, 246 insertions(+), 131 deletions(-) create mode 100644 themes/Ying/assets/js/modules/toast.js diff --git a/themes/Ying/assets/css/main.css b/themes/Ying/assets/css/main.css index a4734212..e925e6f4 100644 --- a/themes/Ying/assets/css/main.css +++ b/themes/Ying/assets/css/main.css @@ -41,6 +41,32 @@ --card-shadow-dark: 0 18px 45px rgba(0, 0, 0, 0.9), 0 0 0 1px rgba(148, 163, 184, 0.45); } +/* ── Theme switch transition ── */ +::view-transition-old(root), +::view-transition-new(root) { + animation-duration: 0.45s; + animation-timing-function: cubic-bezier(0.4, 0, 0.2, 1); +} + +::view-transition-old(root) { + animation-name: theme-fade-out; +} + +::view-transition-new(root) { + animation-name: theme-fade-in; + animation-delay: 0.04s; +} + +@keyframes theme-fade-out { + from { opacity: 1; } + to { opacity: 0; } +} + +@keyframes theme-fade-in { + from { opacity: 0; } + to { opacity: 1; } +} + [data-theme="dark"] body { color: var(--text-content-dark-color); background-color: var(--background-dark-color); @@ -3736,6 +3762,79 @@ input[type=submit] { } } +/* ── Toast ── */ +.ying-toast-container { + position: fixed; + top: 18px; + left: 50%; + transform: translateX(-50%); + z-index: 10000; + display: flex; + flex-direction: column; + align-items: center; + gap: 8px; + pointer-events: none; +} + +.ying-toast { + padding: 9px 18px; + border-radius: 6px; + font-size: 13px; + font-weight: 500; + line-height: 1.3; + opacity: 0; + transform: translateY(-10px); + transition: opacity 0.28s ease, transform 0.28s ease; + pointer-events: auto; + -webkit-backdrop-filter: blur(20px) saturate(180%); + backdrop-filter: blur(20px) saturate(180%); + will-change: transform, opacity; +} + +.ying-toast--visible { + opacity: 1; + transform: translateY(0); +} + +.ying-toast--success { + background: rgba(34, 197, 94, 0.88); + color: #fff; + box-shadow: 0 8px 32px rgba(34, 197, 94, 0.18); +} + +.ying-toast--error { + background: rgba(239, 68, 68, 0.88); + color: #fff; + box-shadow: 0 8px 32px rgba(239, 68, 68, 0.18); +} + +.ying-toast--warning { + background: rgba(245, 158, 11, 0.88); + color: #fff; + box-shadow: 0 8px 32px rgba(245, 158, 11, 0.18); +} + +.ying-toast--info { + background: rgba(29, 61, 108, 0.85); + color: #f0f4fb; + box-shadow: 0 8px 32px rgba(29, 61, 108, 0.18); +} + +[data-theme="dark"] .ying-toast--info { + background: rgba(159, 196, 244, 0.22); + color: #f3f7fd; + -webkit-backdrop-filter: blur(24px) saturate(180%); + backdrop-filter: blur(24px) saturate(180%); +} + +@media screen and (max-width: 480px) { + .ying-toast { + padding: 8px 14px; + font-size: 12px; + border-radius: 5px; + } +} + .floating-tools a { width: 40px; height: 40px; diff --git a/themes/Ying/assets/js/modules/infinite-scroll.js b/themes/Ying/assets/js/modules/infinite-scroll.js index 96fb5b57..5e33059f 100644 --- a/themes/Ying/assets/js/modules/infinite-scroll.js +++ b/themes/Ying/assets/js/modules/infinite-scroll.js @@ -63,17 +63,21 @@ window.initInfiniteScroll = function() { // 1. Get new posts const newPosts = doc.querySelectorAll(`${containerSelector} ${itemSelector}`); - // 2. Append posts + // 2. Append posts with batch layout read/write to avoid thrashing if (newPosts.length > 0) { - newPosts.forEach(post => { - // Remove UIkit scrollspy attributes to prevent interference and use manual animation + // Phase 1: append all and set initial opacity + newPosts.forEach(function(post) { post.removeAttribute('uk-scrollspy-class'); post.style.opacity = 0; container.appendChild(post); - - // Trigger reflow - void post.offsetWidth; - + }); + + // Phase 2: force single layout read for all new posts + var postArr = Array.prototype.slice.call(newPosts); + postArr.forEach(function(post) { void post.offsetWidth; }); + + // Phase 3: batch animate in + postArr.forEach(function(post) { post.style.transition = 'opacity 0.5s ease'; post.style.opacity = 1; }); diff --git a/themes/Ying/assets/js/modules/search.js b/themes/Ying/assets/js/modules/search.js index cd8942e6..f5a7253a 100644 --- a/themes/Ying/assets/js/modules/search.js +++ b/themes/Ying/assets/js/modules/search.js @@ -5,6 +5,16 @@ window.initSearch = function() { var searchLoadingPromise = null; +function debounce(fn, delay) { + var timer; + return function() { + var context = this; + var args = arguments; + clearTimeout(timer); + timer = setTimeout(function() { fn.apply(context, args); }, delay); + }; +} + function loadSearchIndex() { if (window.searchIndex) { return Promise.resolve(window.searchIndex); @@ -91,7 +101,7 @@ function initSearchModal() { } }); - searchInput.addEventListener('input', function() { + searchInput.addEventListener('input', debounce(function() { var query = this.value.trim().toLowerCase(); if (query.length === 0) { @@ -135,7 +145,7 @@ function initSearchModal() { } else { searchResults.innerHTML = '

未找到相关文章

'; } - }); + }), 200); } } } @@ -224,7 +234,7 @@ function initInlineSearch() { }); } - input.addEventListener('input', function() { + input.addEventListener('input', debounce(function() { var query = this.value.trim().toLowerCase(); var inputGroup = this.closest('.search-input-group'); @@ -285,19 +295,21 @@ function initInlineSearch() { resultsContainer.innerHTML = '
暂无相关内容
'; resultsContainer.classList.add('show'); } - }); + }), 200); // Close on click outside if (!window.inlineSearchClickOutside) { window.inlineSearchClickOutside = function(e) { - var header = document.getElementById('essay-header'); var searchContainer = document.getElementById('search-container'); + if (!searchContainer || searchContainer.style.display !== 'block') return; + var header = document.getElementById('essay-header'); var resultsContainer = document.getElementById('inline-search-results'); - - if (header && searchContainer && resultsContainer && searchContainer.style.display === 'block') { - if (!searchContainer.contains(e.target) && !header.contains(e.target) && !resultsContainer.contains(e.target)) { - window.toggleSearchMode(false); - } + + if (header && resultsContainer && + !searchContainer.contains(e.target) && + !header.contains(e.target) && + !resultsContainer.contains(e.target)) { + window.toggleSearchMode(false); } }; document.addEventListener('click', window.inlineSearchClickOutside); diff --git a/themes/Ying/assets/js/modules/tiaozhuan.js b/themes/Ying/assets/js/modules/tiaozhuan.js index e3f3b38d..68452d10 100644 --- a/themes/Ying/assets/js/modules/tiaozhuan.js +++ b/themes/Ying/assets/js/modules/tiaozhuan.js @@ -1,35 +1,24 @@ -function checkParent(element, classNames) { - while (element) { - if (element.classList && classNames.some(cn => element.classList.contains(cn))) { - return true; - } - element = element.parentElement; - } - return false; - } - var excludedClasses = ['talks_comments','tiaozhuan-button', 'floating-tools']; // 添加需要排除的a标签类名class - window.addEventListener('load', (event) => { - document.body.addEventListener('click', function(e) { - let target = e.target; - while (target && target.nodeName !== 'A') { - target = target.parentNode; - } - if (target && target.nodeName === 'A' && - !checkParent(target, excludedClasses) && - !target.href.startsWith('javascript:') && - !target.href.includes('usj.cc') && - !target.href.includes('douban.com') && - !target.href.includes('bilibili.com') && - !target.href.includes('github.com') && - !target.href.includes('zql.im') && - !target.href.includes('5b2.cn') && - !target.href.includes('https://beian.miit.gov.cn') && - !target.href.includes('https://www.upyun.com') && - target.hostname !== window.location.hostname) { +var excludedClasses = ['talks_comments','tiaozhuan-button', 'floating-tools']; +window.addEventListener('load', function() { + document.body.addEventListener('click', function(e) { + var target = e.target.closest('a'); + if (!target) return; + + if (target.href.startsWith('javascript:') || + target.hostname === window.location.hostname || + target.href.includes('usj.cc') || + target.href.includes('douban.com') || + target.href.includes('bilibili.com') || + target.href.includes('github.com') || + target.href.includes('zql.im') || + target.href.includes('5b2.cn') || + target.href.includes('beian.miit.gov.cn') || + target.href.includes('upyun.com')) return; + + if (excludedClasses.some(function(cn) { return target.classList.contains(cn); })) return; + e.preventDefault(); - let encodedUrl = btoa(target.href); // Base64 encode the URL - let url = '/tiaozhuan.html?target=' + encodedUrl; - window.open(url, '_blank'); - } - }); - }); \ No newline at end of file + var encodedUrl = btoa(target.href); + window.open('/tiaozhuan.html?target=' + encodedUrl, '_blank'); + }); +}); \ No newline at end of file diff --git a/themes/Ying/assets/js/modules/toast.js b/themes/Ying/assets/js/modules/toast.js new file mode 100644 index 00000000..4d652263 --- /dev/null +++ b/themes/Ying/assets/js/modules/toast.js @@ -0,0 +1,63 @@ +/** + * Toast — lightweight notification + * Usage: window.Toast.success('msg'), Toast.error('msg'), Toast.warning('msg'), Toast.info('msg') + * window.Toast.show('msg', { type: 'info', duration: 2500 }) + */ +(function() { + var container = null; + var counter = 0; + + function ensureContainer() { + if (container) return; + container = document.createElement('div'); + container.className = 'ying-toast-container'; + document.body.appendChild(container); + } + + function remove(el, delay) { + setTimeout(function() { + el.classList.remove('ying-toast--visible'); + el.addEventListener('transitionend', function onEnd(e) { + if (e.propertyName !== 'opacity') return; + el.removeEventListener('transitionend', onEnd); + if (el.parentNode) el.parentNode.removeChild(el); + }); + }, delay); + } + + window.Toast = { + show: function(text, options) { + options = options || {}; + var type = options.type || 'info'; + var duration = options.duration || 2200; + + ensureContainer(); + + var el = document.createElement('div'); + el.className = 'ying-toast ying-toast--' + type; + el.textContent = text; + el.setAttribute('role', 'status'); + el.setAttribute('aria-live', 'polite'); + container.appendChild(el); + + /* Force layout then animate in */ + void el.offsetWidth; + el.classList.add('ying-toast--visible'); + + remove(el, duration); + }, + + success: function(text, duration) { + this.show(text, { type: 'success', duration: duration }); + }, + error: function(text, duration) { + this.show(text, { type: 'error', duration: duration }); + }, + warning: function(text, duration) { + this.show(text, { type: 'warning', duration: duration }); + }, + info: function(text, duration) { + this.show(text, { type: 'info', duration: duration }); + } + }; +})(); diff --git a/themes/Ying/assets/js/modules/utils.js b/themes/Ying/assets/js/modules/utils.js index 86e1126f..39fdaa54 100644 --- a/themes/Ying/assets/js/modules/utils.js +++ b/themes/Ying/assets/js/modules/utils.js @@ -9,69 +9,13 @@ window.isMobile = function() { // Toast Message Helper window.showToast = function(text, type) { - type = type || 'info'; // default to info - if (typeof Qmsg !== 'undefined') { - // Map common types to Qmsg methods - if (typeof Qmsg[type] === 'function') { - Qmsg[type](text); + if (window.Toast) { + var t = type || 'info'; + if (typeof window.Toast[t] === 'function') { + window.Toast[t](text); } else { - Qmsg.info(text); + window.Toast.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, background-color 0.3s; - pointer-events: none; - display: flex; - align-items: center; - gap: 8px; - `; - - // Add dark mode support class - toast.setAttribute('data-toast', 'true'); - document.body.appendChild(toast); - } - - // Update toast style based on theme - const isDark = document.documentElement.getAttribute('data-theme') === 'dark'; - if (isDark) { - toast.style.background = 'rgba(255, 255, 255, 0.9)'; - toast.style.color = '#333'; - toast.style.boxShadow = '0 4px 12px rgba(0,0,0,0.3)'; - } else { - toast.style.background = 'rgba(0,0,0,0.8)'; - toast.style.color = 'white'; - toast.style.boxShadow = '0 4px 12px rgba(0,0,0,0.1)'; - } - - // Simple icon mapping for fallback - let icon = ''; - if (type === 'success') icon = '✅ '; - else if (type === 'error') icon = '❌ '; - else if (type === 'warning') icon = '⚠️ '; - - toast.innerText = icon + text; - toast.style.opacity = '1'; - - if (window.toastTimeout) clearTimeout(window.toastTimeout); - window.toastTimeout = setTimeout(() => { - toast.style.opacity = '0'; - }, 2500); } }; @@ -85,25 +29,30 @@ window.initThemeToggle = function() { 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' ? '深色' : '浅色') + '模式', 'success'); - // Sync Artalk Theme - if (window.artalk && typeof window.artalk.setDarkMode === 'function') { - window.artalk.setDarkMode(newTheme === 'dark'); + var html = document.documentElement; + var currentTheme = html.getAttribute('data-theme'); + var newTheme = currentTheme === 'dark' ? 'light' : 'dark'; + + var apply = function() { + html.setAttribute('data-theme', newTheme); + localStorage.setItem('theme', newTheme); + + window.showToast('已切换至 ' + (newTheme === 'dark' ? '深色' : '浅色') + '模式', 'success'); + + if (window.artalk && typeof window.artalk.setDarkMode === 'function') { + window.artalk.setDarkMode(newTheme === 'dark'); + } + + window.dispatchEvent(new Event('resize')); + }; + + if (document.startViewTransition) { + document.startViewTransition(function() { apply(); }); + } else { + apply(); } - - // Trigger generic resize event to force charts/etc to redraw if needed - window.dispatchEvent(new Event('resize')); }); }; diff --git a/themes/Ying/layouts/partials/footer.html b/themes/Ying/layouts/partials/footer.html index a082a469..27b1c866 100644 --- a/themes/Ying/layouts/partials/footer.html +++ b/themes/Ying/layouts/partials/footer.html @@ -48,7 +48,6 @@ {{ $iconfont := resources.Get "js/libs/iconfont.js" }} -{{ $lightgallery := resources.Get "js/libs/lightgallery.umd.min.js" }} {{ $uikit := resources.Get "js/libs/uikit.min.js" }} {{ $easylazyload := resources.Get "js/modules/lazyload.js" }} {{ $viewimage := resources.Get "js/libs/view-image.min.js" }} @@ -56,8 +55,8 @@ {{ $main := resources.Get "js/main.js" }} {{ $mypjax := resources.Get "js/modules/mypjax.js" }} {{ $nprogress := resources.Get "js/libs/nprogress.js" }} -{{ $message := resources.Get "js/libs/message.min.js" }} {{ $tiaozhuan := resources.Get "js/modules/tiaozhuan.js" }} +{{ $toast := resources.Get "js/modules/toast.js" }} {{ $reward := resources.Get "js/modules/reward.js" }} {{ $echarts := resources.Get "js/libs/echarts.min.js" }} {{ $artalk := resources.Get "js/libs/Artalk.js" }} @@ -73,7 +72,7 @@ {{ $artalkModule := resources.Get "js/modules/artalk.js" }} {{ $pjaxLib := resources.Get "js/libs/pjax.js" }} -{{ $scripts := slice $iconfont $lightgallery $uikit $easylazyload $viewimage $barrage $utils $loader $cache $search $floatingTools $paragraphComments $infiniteScroll $artalkModule $nprogress $pjaxLib $main $mypjax $message $tiaozhuan $reward $echarts $artalk | resources.Concat "js/bundle.js" | resources.Minify | resources.Fingerprint }} +{{ $scripts := slice $iconfont $uikit $easylazyload $viewimage $barrage $utils $loader $cache $search $floatingTools $paragraphComments $infiniteScroll $artalkModule $nprogress $pjaxLib $main $mypjax $toast $tiaozhuan $reward $echarts $artalk | resources.Concat "js/bundle.js" | resources.Minify | resources.Fingerprint }}