/*! * Argon 主题核心 JavaScript */ // ========== 兼容性修复 ========== // 确保 Prism 和 autoloader 存在 if (typeof window.Prism === 'undefined') { window.Prism = { highlightAll: function() {}, highlightElement: function() {}, plugins: {} }; } if (typeof window.Prism.plugins === 'undefined') { window.Prism.plugins = {}; } if (typeof window.Prism.plugins.autoloader === 'undefined') { window.Prism.plugins.autoloader = { languages_path: '', use_minified: true }; } // 确保 Zoomify 存在 if (typeof window.Zoomify === 'undefined') { window.Zoomify = function() {}; window.Zoomify.DEFAULTS = {}; } // 确保 jQuery 插件存在 if (typeof jQuery !== 'undefined') { (function($) { // 确保 easing 函数存在(防止其他插件覆盖 jQuery 后丢失) if (typeof $.easing === 'undefined') { $.easing = {}; } if (typeof $.easing.easeOutCirc === 'undefined') { $.easing.easeOutCirc = function(x) { return Math.sqrt(1 - Math.pow(x - 1, 2)); }; } if (typeof $.easing.easeOutExpo === 'undefined') { $.easing.easeOutExpo = function(x) { return x === 1 ? 1 : 1 - Math.pow(2, -10 * x); }; } // 确保 zoomify 插件存在 if (typeof $.fn.zoomify === 'undefined') { $.fn.zoomify = function() { return this; }; } // 确保 fancybox 存在 if (typeof $.fancybox === 'undefined') { $.fancybox = { defaults: { transitionEffect: 'slide', buttons: [], lang: 'zh_CN', i18n: {} }, open: function() {}, close: function() {} }; } if (typeof $.fancybox.defaults === 'undefined') { $.fancybox.defaults = { transitionEffect: 'slide', buttons: [], lang: 'zh_CN', i18n: {} }; } // 确保 pjax 存在 if (typeof $.pjax === 'undefined') { $.pjax = function() {}; $.pjax.defaults = { timeout: 10000, container: [], fragment: [] }; } if (typeof $.pjax.defaults === 'undefined') { $.pjax.defaults = { timeout: 10000, container: [], fragment: [] }; } })(jQuery); } // ========================================================================== // 性能优化模块引入 // ========================================================================== // 注意:argon-performance.js 需要在此文件之前加载 // 在 functions.php 中通过 wp_enqueue_script 确保加载顺序 // // 提供的优化功能: // - ArgonDOMCache: DOM 元素缓存系统 // - ArgonEventManager: 事件节流和防抖 // - ArgonResourceLoader: 资源按需加载 // - ArgonRenderOptimizer: 渲染优化和 GPU 加速 // - ArgonMemoryManager: 内存管理和清理 // - ArgonPerformanceMonitor: 性能监控和分析 // ========================================================================== // ========== 原有代码 ========== if (typeof(argonConfig) == "undefined"){ var argonConfig = {}; } if (typeof(argonConfig.wp_path) == "undefined"){ argonConfig.wp_path = "/"; } // ========================================================================== // 性能优化模块实例(全局变量) // ========================================================================== var argonDOMCache = null; // DOM 缓存实例 var argonEventManager = null; // 事件管理实例 var argonResourceLoader = null; // 资源加载实例 var argonRenderOptimizer = null; // 渲染优化实例 var argonMemoryManager = null; // 内存管理实例 var argonPerformanceMonitor = null; // 性能监控实例 // ========================================================================== // 调试控制台(由 argon-performance.js 引入) // ========================================================================== // 如果 ArgonDebug 未定义,创建一个简单的替代实现 if (typeof ArgonDebug === 'undefined') { window.ArgonDebug = { enabled: false, init() { if (typeof argonConfig !== 'undefined' && argonConfig.debug_mode) { this.enabled = true; } }, log(...args) { if (this.enabled) console.log('[Argon]', ...args); }, warn(...args) { if (this.enabled) console.warn('[Argon]', ...args); }, error(...args) { if (this.enabled) console.error('[Argon]', ...args); }, info(...args) { if (this.enabled) console.info('[Argon]', ...args); } }; ArgonDebug.init(); } /*Cookies 操作*/ function setCookie(cname, cvalue, exdays) { let d = new Date(); d.setTime(d.getTime() + (exdays * 24 * 60 * 60 * 1000)); let expires = "expires=" + d.toUTCString(); document.cookie = cname + "=" + cvalue + ";" + expires + ";path=/"; } function getCookie(cname) { let name = cname + "="; let decodedCookie = decodeURIComponent(document.cookie); let ca = decodedCookie.split(';'); for (let i = 0; i < ca.length; i++) { let c = ca[i]; while (c.charAt(0) == ' ') { c = c.substring(1); } if (c.indexOf(name) == 0) { return c.substring(name.length, c.length); } } return ""; } /*多语言支持*/ var translation = {}; translation['en_US'] = { "确定": "OK", "清除": "Clear", "恢复博客默认": "Set To Default", "评论内容不能为空": "Comment content cannot be empty", "昵称不能为空": "Name cannot be empty", "邮箱或QQ号格式错误": "Incorrect email or QQ format", "邮箱格式错误": "Incorrect email format", "网站格式错误 (不是 http(s):// 开头)": "Website URL format error", "验证码未输入": "CAPTCHA cannot be empty", "验证码格式错误": "Incorrect CAPTCHA format", "请完成验证码验证": "Please complete CAPTCHA verification", "评论格式错误": "Comment format error", "发送中": "Sending", "正在发送": "Sending", "评论正在发送中...": "Comment is sending...", "发送": "Send", "评论发送失败": "Comment failed", "发送成功": "Success", "您的评论已发送": "Your comment has been sent", "评论": "Comments", "未知原因": "Unknown Error", "编辑中": "Editing", "正在编辑": "Editing", "评论正在编辑中...": "Comment is being edited...", "编辑": "Edit", "评论编辑失败": "Comment editing failed", "已编辑": "Edited", "编辑成功": "Success", "您的评论已编辑": "Your comment has been edited", "评论 #": "Comment #", "的编辑记录": "- Edit History", "加载失败": "Failed to load", "展开": "Show", "没有更多了": "No more comments", "找不到该 Repo": "Can't find the repository", "获取 Repo 信息失败": "Failed to get repository information", "点赞失败": "Vote failed", "Hitokoto 获取失败": "Failed to get Hitokoto", "复制成功": "Copied", "代码已复制到剪贴板": "Code has been copied to the clipboard", "复制失败": "Failed", "请手动复制代码": "Please copy the code manually", "刚刚": "Now", "分钟前": "minutes ago", "小时前": "hours ago", "昨天": "Yesterday", "前天": "The day before yesterday", "天前": "days ago", "隐藏行号": "Hide Line Numbers", "显示行号": "Show Line Numbers", "开启折行": "Enable Break Line", "关闭折行": "Disable Break Line", "复制": "Copy", "全屏": "Fullscreen", "退出全屏": "Exit Fullscreen", "置顶评论": "Pin Comment", "取消置顶评论": "Unpin Comment", "是否要取消置顶评论 #": "Do you want to unpin the comment #", "是否要置顶评论 #": "Do you want to pin the comment #", "确认": "Confirm", "取消": "Cancel", "置顶": "Pin", "取消置顶": "Unpin", "置顶成功": "Pinned", "取消置顶成功": "Unpinned", "该评论已置顶": "The comment has been pinned", "该评论已取消置顶": "The comment has been unpinned", "置顶失败": "Failed to pin", "取消置顶失败": "Failed to unpin", }; translation['ru_RU'] = { "确定": "ОК", "清除": "Очистить", "恢复博客默认": "Восстановить по умолчанию", "评论内容不能为空": "Содержимое комментария не может быть пустым", "昵称不能为空": "Имя не может быть пустым", "邮箱或QQ号格式错误": "Неверный формат электронной почты или QQ", "邮箱格式错误": "Неправильный формат электронной почты", "网站格式错误 (不是 http(s):// 开头)": "Сайт ошибка формата URL-адреса ", "验证码未输入": "Вы не решили капчу", "验证码格式错误": "Ошибка проверки капчи", "评论格式错误": "Неправильный формат комментария", "发送中": "Отправка", "正在发送": "Отправка", "评论正在发送中...": "Комментарий отправляется...", "发送": "Отправить", "评论发送失败": "Не удалось отправить комментарий", "发送成功": "Комментарий отправлен", "您的评论已发送": "Ваш комментарий был отправлен", "评论": "Комментарии", "未知原因": "Неизвестная ошибка", "编辑中": "Редактируется", "正在编辑": "Редактируется", "评论正在编辑中...": "Комментарий редактируется", "编辑": "Редактировать", "评论编辑失败": "Не удалось отредактировать комментарий", "已编辑": "Изменено", "编辑成功": "Успешно", "您的评论已编辑": "Ваш комментарий был изменен", "评论 #": "Комментарий #", "的编辑记录": "- История изменений", "加载失败": "Ошибка загрузки", "展开": "Показать", "没有更多了": "Комментариев больше нет", "找不到该 Repo": "Невозможно найти репозиторий", "获取 Repo 信息失败": "Неудалось получить информацию репозитория", "点赞失败": "Ошибка голосования", "Hitokoto 获取失败": "Проблемы с вызовом Hitokoto", "复制成功": "Скопировано", "代码已复制到剪贴板": "Код скопирован в буфер обмена", "复制失败": "Неудалось", "请手动复制代码": "Скопируйте код вручную", "刚刚": "Сейчас", "分钟前": "минут назад", "小时前": "часов назад", "昨天": "Вчера", "前天": "Позавчера", "天前": "дней назад", "隐藏行号": "Скрыть номера строк", "显示行号": "Показать номера строк", "开启折行": "Включить перенос строк", "关闭折行": "Выключить перенос строк", "复制": "Скопировать", "全屏": "Полноэкранный режим", "退出全屏": "Выход из полноэкранного режима", }; translation['zh_TW'] = { "确定": "確定", "清除": "清除", "恢复博客默认": "恢復博客默認", "评论内容不能为空": "評論內容不能為空", "昵称不能为空": "昵稱不能為空", "邮箱或QQ号格式错误": "郵箱或QQ號格式錯誤", "邮箱格式错误": "郵箱格式錯誤", "网站格式错误 (不是 http(s):// 开头)": "網站格式錯誤 (不是 http(s):// 開頭)", "验证码未输入": "驗證碼未輸入", "验证码格式错误": "驗證碼格式錯誤", "评论格式错误": "評論格式錯誤", "发送中": "發送中", "正在发送": "正在發送", "评论正在发送中...": "評論正在發送中...", "发送": "發送", "评论发送失败": "評論發送失敗", "发送成功": "發送成功", "您的评论已发送": "您的評論已發送", "评论": "評論", "未知原因": "未知原因", "编辑中": "編輯中", "正在编辑": "正在編輯", "评论正在编辑中...": "評論正在編輯中...", "编辑": "編輯", "评论编辑失败": "評論編輯失敗", "已编辑": "已編輯", "编辑成功": "編輯成功", "您的评论已编辑": "您的評論已編輯", "评论 #": "評論 #", "的编辑记录": "的編輯記錄", "加载失败": "加載失敗", "展开": "展開", "没有更多了": "沒有更多了", "找不到该 Repo": "找不到該 Repo", "获取 Repo 信息失败": "獲取 Repo 信息失敗", "点赞失败": "點贊失敗", "Hitokoto 获取失败": "Hitokoto 獲取失敗", "复制成功": "復制成功", "代码已复制到剪贴板": "代碼已復制到剪貼板", "复制失败": "復制失敗", "请手动复制代码": "請手動復制代碼", "刚刚": "剛剛", "分钟前": "分鐘前", "小时前": "小時前", "昨天": "昨天", "前天": "前天", "天前": "天前", "隐藏行号": "隱藏行號", "显示行号": "顯示行號", "开启折行": "開啟折行", "关闭折行": "關閉折行", "复制": "復制", "全屏": "全屏", "退出全屏": "退出全屏", }; function __(text){ let lang = argonConfig.language; if (typeof(translation[lang]) == "undefined"){ return text; } if (typeof(translation[lang][text]) == "undefined"){ return text; } return translation[lang][text]; } /*根据滚动高度改变顶栏透明度 */ !function(){ let toolbar = document.getElementById("navbar-main"); let $bannerContainer = $("#banner_container"); let $content = $("#content"); let startTransitionHeight; let endTransitionHeight; let maxOpacity = 0.85; startTransitionHeight = $bannerContainer.offset().top - 75; endTransitionHeight = $content.offset().top - 75; $(window).resize(function(){ startTransitionHeight = $bannerContainer.offset().top - 75; endTransitionHeight = $content.offset().top - 75; }); function changeToolbarTransparency(){ let scrollTop = document.documentElement.scrollTop || document.body.scrollTop; if (scrollTop < startTransitionHeight){ toolbar.style.setProperty('background-color', 'rgba(var(--toolbar-color), 0)', 'important'); toolbar.style.setProperty('box-shadow', 'none'); if (argonConfig.toolbar_blur){ toolbar.style.setProperty('backdrop-filter', 'blur(0px)'); } toolbar.classList.add("navbar-ontop"); return; } if (scrollTop > endTransitionHeight){ toolbar.style.setProperty('background-color', 'rgba(var(--toolbar-color), ' + maxOpacity + ')', 'important'); toolbar.style.setProperty('box-shadow', ''); if (argonConfig.toolbar_blur){ toolbar.style.setProperty('backdrop-filter', 'blur(16px)'); } toolbar.classList.remove("navbar-ontop"); return; } let transparency = (scrollTop - startTransitionHeight) / (endTransitionHeight - startTransitionHeight) * maxOpacity; toolbar.style.setProperty('background-color', 'rgba(var(--toolbar-color), ' + transparency, 'important'); toolbar.style.setProperty('box-shadow', ''); if (argonConfig.toolbar_blur){ if ((scrollTop - startTransitionHeight) / (endTransitionHeight - startTransitionHeight) > 0.3){ toolbar.style.setProperty('backdrop-filter', 'blur(16px)'); }else{ toolbar.style.setProperty('backdrop-filter', 'blur(0px)'); } } toolbar.classList.remove("navbar-ontop"); } function changeToolbarOnTopClass(){ let scrollTop = document.documentElement.scrollTop || document.body.scrollTop; if (scrollTop < 30){ toolbar.classList.add("navbar-no-blur"); }else{ toolbar.classList.remove("navbar-no-blur"); } } if ($("html").hasClass("no-banner")) { changeToolbarOnTopClass(); // 使用节流优化滚动事件性能 const throttledChangeToolbarOnTopClass = argonEventManager ? argonEventManager.throttle(changeToolbarOnTopClass, 16) : changeToolbarOnTopClass; document.addEventListener("scroll", throttledChangeToolbarOnTopClass, {passive: true}); return; } if (argonConfig.headroom == "absolute") { toolbar.classList.add("navbar-ontop"); return; } if ($("html").hasClass("toolbar-blur")) { argonConfig.toolbar_blur = true; maxOpacity = 0.65; }else{ argonConfig.toolbar_blur = false; } changeToolbarTransparency(); // 使用节流优化滚动事件性能 const throttledChangeToolbarTransparency = argonEventManager ? argonEventManager.throttle(changeToolbarTransparency, 16) : changeToolbarTransparency; document.addEventListener("scroll", throttledChangeToolbarTransparency, {passive: true}); }(); /*搜索*/ function searchPosts(word){ if ($(".search-result").length > 0){ let url = new URL(window.location.href); url.searchParams.set("s", word); $.pjax({ url: url.href }); }else{ $.pjax({ url: argonConfig.wp_path + "?s=" + encodeURI(word) }); } } /*顶栏搜索*/ $(document).on("click" , "#navbar_search_input_container" , function(){ $(this).addClass("open"); $("#navbar_search_input").focus(); }); $(document).on("blur" , "#navbar_search_input_container" , function(){ // 如果有文字则保持has-text类 if ($("#navbar_search_input").val().trim() !== "") { $(this).addClass("has-text"); } else { $(this).removeClass("has-text"); } $(this).removeClass("open"); }); // 监听输入变化来切换has-text类 $(document).on("input" , "#navbar_search_input" , function(){ var container = $("#navbar_search_input_container"); if ($(this).val().trim() !== "") { container.addClass("has-text"); } else { container.removeClass("has-text"); } }); $(document).on("keydown" , "#navbar_search_input_container #navbar_search_input" , function(e){ if (e.keyCode != 13){ return; } let word = $(this).val(); if (word == ""){ $("#navbar_search_input_container").blur(); return; } let scrolltop = $(document).scrollTop(); searchPosts(word); }); /*顶栏搜索 (Mobile)*/ $(document).on("keydown" , "#navbar_search_input_mobile" , function(e){ if (e.keyCode != 13){ return; } let word = $(this).val(); $("#navbar_global .collapse-close button").click(); if (word == ""){ return; } let scrolltop = $(document).scrollTop(); searchPosts(word); }); /*侧栏搜索*/ $(document).on("click" , "#leftbar_search_container" , function(){ $(".leftbar-search-button").addClass("open"); $("#leftbar_search_input").removeAttr("readonly").focus(); $("#leftbar_search_input").focus(); $("#leftbar_search_input").select(); return false; }); $(document).on("blur" , "#leftbar_search_container" , function(){ $(".leftbar-search-button").removeClass("open"); $("#leftbar_search_input").attr("readonly", "readonly"); }); $(document).on("keydown" , "#leftbar_search_input" , function(e){ if (e.keyCode != 13){ return; } let word = $(this).val(); if (word == ""){ $("#leftbar_search_container").blur(); return; } $("html").removeClass("leftbar-opened"); searchPosts(word); }); /*搜索过滤器*/ $(document).on("change" , ".search-filter" , function(e){ if (pjaxLoading){ $(this).prop("checked", !$(this).prop("checked")); e.preventDefault(); return; } pjaxLoading = true; let postTypes = []; $(".search-filter:checked").each(function(){ postTypes.push($(this).attr("name")); }); if (postTypes.length == 0){ postTypes = ["none"]; } let url = new URL(document.location.href); url.searchParams.set("post_type", postTypes.join(",")); url.pathname = url.pathname.replace(/\/page\/\d+$/, ''); $.pjax({ url: url.href }); }); /*左侧栏随页面滚动浮动*/ !function(){ if ($("#leftbar").length == 0){ let contentOffsetTop = $('#content').offset().top; function changeLeftbarStickyStatusWithoutSidebar(){ let scrollTop = document.documentElement.scrollTop || document.body.scrollTop; if( contentOffsetTop - 10 - scrollTop <= 20 ){ document.body.classList.add('leftbar-can-headroom'); }else{ document.body.classList.remove('leftbar-can-headroom'); } } changeLeftbarStickyStatusWithoutSidebar(); // 使用节流优化滚动事件性能 const throttledChangeLeftbarStickyStatusWithoutSidebar = argonEventManager ? argonEventManager.throttle(changeLeftbarStickyStatusWithoutSidebar, 16) : changeLeftbarStickyStatusWithoutSidebar; document.addEventListener("scroll", throttledChangeLeftbarStickyStatusWithoutSidebar, {passive: true}); $(window).resize(function(){ contentOffsetTop = $('#content').offset().top; changeLeftbarStickyStatusWithoutSidebar(); }); return; } let $leftbarPart1 = $('#leftbar_part1'); let $leftbarPart2 = $('#leftbar_part2'); let leftbarPart1 = document.getElementById('leftbar_part1'); let leftbarPart2 = document.getElementById('leftbar_part2'); let leftbarPart3 = document.getElementById('leftbar_part3'); let part1OffsetTop = $('#leftbar_part1').offset().top; let part1OuterHeight = $('#leftbar_part1').outerHeight(); function changeLeftbarStickyStatus(){ let scrollTop = document.documentElement.scrollTop || document.body.scrollTop; if( part1OffsetTop + part1OuterHeight + 10 - scrollTop <= (argonConfig.headroom != "absolute" ? 90 : 18) ){ //滚动条在页面中间浮动状态 leftbarPart2.classList.add('sticky'); if (leftbarPart3) { leftbarPart3.classList.add('sticky'); // 计算可用空间并分配给 part2 和 part3 let viewportHeight = window.innerHeight; let topOffset = argonConfig.headroom !== 'absolute' ? 90 : 20; let availableHeight = viewportHeight - topOffset - 20; // 20px 底部边距 // 获取 part3 的自然高度(不受限制时的高度) leftbarPart3.style.maxHeight = 'none'; let part3NaturalHeight = leftbarPart3.scrollHeight; leftbarPart3.style.maxHeight = ''; // 最小高度限制 let minPart2Height = 150; let minPart3Height = 100; // 计算分配 let part2Height, part3MaxHeight; if (part3NaturalHeight + minPart2Height <= availableHeight) { // part3 可以完全显示 part3MaxHeight = part3NaturalHeight; part2Height = availableHeight - part3NaturalHeight - 10; } else { // 需要按比例分配,part2 占 60%,part3 占 40% part2Height = Math.max(minPart2Height, availableHeight * 0.6); part3MaxHeight = Math.max(minPart3Height, availableHeight - part2Height - 10); } document.documentElement.style.setProperty('--leftbar-part2-height', leftbarPart2.offsetHeight + 10 + 'px'); document.documentElement.style.setProperty('--leftbar-part3-height', part3MaxHeight + 'px'); document.documentElement.style.setProperty('--leftbar-part3-max-height', part3MaxHeight + 'px'); } }else{ //滚动条在顶部 不浮动状态 leftbarPart2.classList.remove('sticky'); if (leftbarPart3) leftbarPart3.classList.remove('sticky'); } if( part1OffsetTop + part1OuterHeight + 10 - scrollTop <= 20 ){//侧栏下部分是否可以随 Headroom 一起向上移动 document.body.classList.add('leftbar-can-headroom'); }else{ document.body.classList.remove('leftbar-can-headroom'); } } changeLeftbarStickyStatus(); // 使用节流优化滚动事件性能 const throttledChangeLeftbarStickyStatus = argonEventManager ? argonEventManager.throttle(changeLeftbarStickyStatus, 16) : changeLeftbarStickyStatus; document.addEventListener("scroll", throttledChangeLeftbarStickyStatus, {passive: true}); $(window).resize(function(){ part1OffsetTop = $leftbarPart1.offset().top; part1OuterHeight = $leftbarPart1.outerHeight(); changeLeftbarStickyStatus(); }); new MutationObserver(function(){ part1OffsetTop = $leftbarPart1.offset().top; part1OuterHeight = $leftbarPart1.outerHeight(); changeLeftbarStickyStatus(); }).observe(leftbarPart1, {attributes: true, childList: true, subtree: true}); }(); /*Headroom*/ if (argonConfig.headroom == "true"){ var headroom = new Headroom(document.querySelector("body"),{ "tolerance" : { up : 0, down : 0 }, "offset": 0, "classes": { "initial": "with-headroom", "pinned": "headroom---pinned", "unpinned": "headroom---unpinned", "top": "headroom---top", "notTop": "headroom---not-top", "bottom": "headroom---bottom", "notBottom": "headroom---not-bottom", "frozen": "headroom---frozen" } }).init(); } /*瀑布流布局*/ function waterflowInit() { if (argonConfig.waterflow_columns == "1") { return; } $("#main.article-list img").each(function(index, ele){ ele.onload = function(){ waterflowInit(); } }); let columns; if (argonConfig.waterflow_columns == "2and3") { if ($("#main").outerWidth() > 1000) { columns = 3; } else { columns = 2; } }else{ columns = parseInt(argonConfig.waterflow_columns); } if ($("#main").outerWidth() < 650 && columns == 2) { columns = 1; }else if ($("#main").outerWidth() < 800 && columns == 3) { columns = 1; } let heights = [0, 0, 0]; function getMinHeightPosition(){ let res = 0, minn = 2147483647; for (var i = 0; i < columns; i++) { if (heights[i] < minn) { minn = heights[i]; res = i; } } return res; } function getMaxHeight(){ let res = 0; for (let i in heights) { res = Math.max(res, heights[i]); } return res; } $("#primary").css("transition", "none") .addClass("waterflow"); let $container = $("#main.article-list"); if (!$container.length){ return; } let $items = $container.find("article.post:not(.no-results), .shuoshuo-preview-container"); columns = Math.max(Math.min(columns, $items.length), 1); if (columns == 1) { $container.removeClass("waterflow"); $items.css("transition", "").css("position", "").css("width", "").css("top", "").css("left", "").css("margin", ""); $(".waterflow-placeholder").remove(); }else{ $container.addClass("waterflow"); $items.each(function(index, item) { let $item = $(item); $item.css("transition", "none") .css("position", "absolute") .css("width", "calc(" + (100 / columns) + "% - " + (10 * (columns - 1) / columns) + "px)").css("margin", 0); let itemHeight = $item.outerHeight() + 10; let pos = getMinHeightPosition(); $item.css("top", heights[getMinHeightPosition()] + "px") .css("left", (pos * $item.outerWidth() + 10 * pos) + "px"); heights[pos] += itemHeight; }); } if ($(".waterflow-placeholder").length) { $(".waterflow-placeholder").css("height", getMaxHeight() + "px"); }else{ $container.prepend("
"); } } waterflowInit(); if (argonConfig.waterflow_columns != "1") { $(window).resize(function(){ waterflowInit(); }); new MutationObserver(function(mutations, observer){ waterflowInit(); }).observe(document.querySelector("#primary"), { 'childList': true }); } /*移动端文章列表布局切换*/ !function(){ var mobileLayout = argonConfig.article_list_layout_mobile || "1"; var isMobile = window.innerWidth <= 900; function applyMobileLayout() { var nowMobile = window.innerWidth <= 900; if (nowMobile) { $("html").addClass("mobile-layout-" + mobileLayout); } else { $("html").removeClass("mobile-layout-1 mobile-layout-2 mobile-layout-3"); } } applyMobileLayout(); $(window).resize(function(){ applyMobileLayout(); }); }(); /*浮动按钮栏相关(回顶部)*/ !function(){ // 确保 DOM 和 jQuery 已加载 if (typeof jQuery === 'undefined') { setTimeout(arguments.callee, 50); return; } let $fabtns = $('#float_action_buttons'); if ($fabtns.length === 0) { setTimeout(arguments.callee, 50); return; } let $backToTopBtn = $('#fabtn_back_to_top'); let $toggleSidesBtn = $('#fabtn_toggle_sides'); let $toggleDarkmode = $('#fabtn_toggle_darkmode'); let $toggleAmoledMode = $('#blog_setting_toggle_darkmode_and_amoledarkmode'); let $toggleBlogSettings = $('#fabtn_toggle_blog_settings_popup'); let $goToComment = $('#fabtn_go_to_comment'); let $readingProgressBtn = $('#fabtn_reading_progress'); let $readingProgressBar = $('#fabtn_reading_progress_bar'); let $readingProgressDetails = $('#fabtn_reading_progress_details'); $backToTopBtn.on("click" , function(){ $("body,html").stop().animate({ scrollTop: 0 }, 600); }); $toggleDarkmode.on("click" , function(){ toggleDarkmode(); }); $toggleAmoledMode.on("click" , function(){ toggleAmoledDarkMode(); }) if ($("#post_comment").length > 0){ $("#fabtn_go_to_comment").removeClass("fabtn-hidden"); }else{ $("#fabtn_go_to_comment").addClass("fabtn-hidden"); } $goToComment.on("click" , function(){ var commentsArea = $("#comments"); var postCommentArea = $("#post_comment"); var wasCollapsed = commentsArea.hasClass("comments-collapsed"); var toggleBtn = $("#comments_toggle"); if (wasCollapsed && toggleBtn.length > 0) { // 折叠状态:先滚动到评论切换按钮位置,再展开 $("body,html").stop().animate({ scrollTop: toggleBtn.offset().top - 80 }, 600); toggleBtn.find("i").removeClass("fa-comments").addClass("fa-comment-o"); toggleBtn.find(".btn-inner--text").text("折叠评论"); toggleBtn.addClass("expanded"); commentsArea.removeClass("comments-collapsed"); setTimeout(function() { postCommentArea.removeClass("comments-collapsed"); $("#post_comment_content").focus(); }, 150); } else { // 已展开或无切换按钮:直接滚动到评论区 $("body,html").stop().animate({ scrollTop: postCommentArea.offset().top - 80 }, 600); $("#post_comment_content").focus(); } }); if (localStorage['Argon_fabs_Floating_Status'] == "left"){ $fabtns.addClass("fabtns-float-left"); } $toggleSidesBtn.on("click" , function(){ $fabtns.addClass("fabtns-unloaded"); setTimeout(function(){ $fabtns.toggleClass("fabtns-float-left"); if ($fabtns.hasClass("fabtns-float-left")){ localStorage['Argon_fabs_Floating_Status'] = "left"; }else{ localStorage['Argon_fabs_Floating_Status'] = "right"; } $fabtns.removeClass("fabtns-unloaded"); } , 300); }); //博客设置 $toggleBlogSettings.on("click" , function(){ $("#float_action_buttons").toggleClass("blog_settings_opened"); }); $("#close_blog_settings").on("click" , function(){ $("#float_action_buttons").removeClass("blog_settings_opened"); }); $("#blog_setting_darkmode_switch .custom-toggle-slider").on("click" , function(){ toggleDarkmode(); }); //字体 $("#blog_setting_font_sans_serif").on("click" , function(){ $("html").removeClass("use-serif"); localStorage['Argon_Use_Serif'] = "false"; }); $("#blog_setting_font_serif").on("click" , function(){ $("html").addClass("use-serif"); localStorage['Argon_Use_Serif'] = "true"; }); // 字体设置已在 header.php 中预加载,此处无需重复应用 //阴影 $("#blog_setting_shadow_small").on("click" , function(){ $("html").removeClass("use-big-shadow"); localStorage['Argon_Use_Big_Shadow'] = "false"; }); $("#blog_setting_shadow_big").on("click" , function(){ $("html").addClass("use-big-shadow"); localStorage['Argon_Use_Big_Shadow'] = "true"; }); // 阴影设置已在 header.php 中预加载,此处无需重复应用 //滤镜 function setBlogFilter(name){ if (name == undefined || name == ""){ name = "off"; } if (!$("html").hasClass("filter-" + name)){ $("html").removeClass("filter-sunset filter-darkness filter-grayscale"); if (name != "off"){ $("html").addClass("filter-" + name); } } $("#blog_setting_filters .blog-setting-filter-btn").removeClass("active"); $("#blog_setting_filters .blog-setting-filter-btn[filter-name='" + name + "']").addClass("active"); localStorage['Argon_Filter'] = name; } // 滤镜设置已在 header.php 中预加载,此处只需设置按钮状态 let currentFilter = localStorage['Argon_Filter'] || 'off'; $("#blog_setting_filters .blog-setting-filter-btn[filter-name='" + currentFilter + "']").addClass("active"); $(".blog-setting-filter-btn").on("click" , function(){ setBlogFilter(this.getAttribute("filter-name")); }); //UI 样式切换 (玻璃拟态/新拟态) function setUIStyle(style){ if (style == undefined || style == ""){ style = "default"; } $("html").removeClass("style-glass style-neumorphism"); if (style != "default"){ $("html").addClass("style-" + style); } $(".blog-setting-style-btn").removeClass("active"); $(".blog-setting-style-btn[style-name='" + style + "']").addClass("active"); localStorage['Argon_UI_Style'] = style; } // UI 样式设置已在 header.php 中预加载,此处只需设置按钮状态 let currentUIStyle = localStorage['Argon_UI_Style'] || 'default'; $(".blog-setting-style-btn[style-name='" + currentUIStyle + "']").addClass("active"); $(".blog-setting-style-btn").on("click" , function(){ setUIStyle(this.getAttribute("style-name")); }); let $window = $(window); function changefabtnDisplayStatus(){ //阅读进度 function hideReadingProgress(){ $readingProgressBtn.addClass("fabtn-hidden"); } function setReadingProgress(percent){ $readingProgressBtn.removeClass("fabtn-hidden"); $readingProgressDetails.html((percent * 100).toFixed(0) + "%"); $readingProgressBar.css("width" , (percent * 100).toFixed(0) + "%"); } if ($("article.post.post-full").length == 0){ hideReadingProgress(); }else{ let a = $window.scrollTop() - ($("article.post.post-full").offset().top - 80); let b = $("article.post.post-full").outerHeight() + 50 - $window.height(); if (b <= 0){ hideReadingProgress(); }else{ readingProgress = a / b; if (isNaN(readingProgress) || readingProgress < 0 || readingProgress > 1){ hideReadingProgress(); }else{ setReadingProgress(readingProgress); } } } //是否显示回顶 if ($(window).scrollTop() >= 400){ $backToTopBtn.removeClass("fabtn-hidden"); }else{ $backToTopBtn.addClass("fabtn-hidden"); } } changefabtnDisplayStatus(); $(window).scroll(function(){ changefabtnDisplayStatus(); }); $(window).resize(function(){ changefabtnDisplayStatus(); }); $fabtns.removeClass("fabtns-unloaded"); }(); /*卡片圆角大小调整*/ !function(){ function setCardRadius(radius, setcookie){ document.documentElement.style.setProperty('--card-radius', radius + "px"); if (setcookie){ setCookie("argon_card_radius", radius, 365); } } let slider = document.getElementById('blog_setting_card_radius'); noUiSlider.create(slider, { start: [$("meta[name='theme-card-radius']").attr("content")], step: 0.5, connect: [true, false], range: { 'min': [0], 'max': [30] } }); slider.noUiSlider.on('update', function (values){ let value = values[0]; setCardRadius(value, false); }); slider.noUiSlider.on('set', function (values){ let value = values[0]; setCardRadius(value, true); }); $(document).on("click" , "#blog_setting_card_radius_to_default" , function(){ slider.noUiSlider.set($("meta[name='theme-card-radius-origin']").attr("content")); setCardRadius($("meta[name='theme-card-radius-origin']").attr("content"), false); setCookie("argon_card_radius", $("meta[name='theme-card-radius-origin']").attr("content"), 0); }); }(); /*评论区 & 发送评论*/ !function(){ //回复评论 let replying = false , replyID = 0; /** * 显示回复框 * @param {number} commentID - 评论ID */ function reply(commentID){ cancelEdit(false); replying = true; replyID = commentID; let nameEl = $("#comment-" + commentID + " .comment-item-title > .comment-name")[0]; let textEl = $("#comment-" + commentID + " .comment-item-text")[0]; let sourceEl = $("#comment-" + commentID + " .comment-item-source")[0]; if (nameEl) { $("#post_comment_reply_name").text(nameEl.textContent); } let preview = textEl ? textEl.textContent : ''; if (sourceEl && sourceEl.innerHTML !== '') { preview = sourceEl.textContent; } $("#post_comment_reply_preview").text(preview); if ($("#comment-" + commentID + " .comment-item-title .badge-private-comment").length > 0){ $("#post_comment").addClass("post-comment-force-privatemode-on"); }else{ $("#post_comment").addClass("post-comment-force-privatemode-off"); } // 滚动到评论框(使用原生 scrollTo 避免 jQuery easing 依赖问题) let postComment = $('#post_comment'); if (postComment.length > 0 && postComment.offset()) { let targetTop = postComment.offset().top - 100; window.scrollTo({ top: targetTop, behavior: 'smooth' }); } // 使用 CSS 动画显示回复框 let replyInfo = $('#post_comment_reply_info'); if (replyInfo.length > 0) { replyInfo.removeClass('reply-leaving').css('display', 'block'); // 触发重排以确保动画生效 replyInfo[0].offsetHeight; replyInfo.addClass('reply-entering'); } setTimeout(function(){ $("#post_comment_content").focus(); }, 300); } /** * 取消回复 */ function cancelReply(){ replying = false; replyID = 0; let replyInfo = $('#post_comment_reply_info'); replyInfo.removeClass('reply-entering').addClass('reply-leaving'); setTimeout(function(){ replyInfo.css('display', 'none').removeClass('reply-leaving'); }, 200); $("#post_comment").removeClass("post-comment-force-privatemode-on post-comment-force-privatemode-off"); } $(document).on("click" , ".comment-reply" , function(){ reply(this.getAttribute("data-id")); }); $(document).on("click pjax:click" , "#post_comment_reply_cancel" , function(){ cancelReply(); }); $(document).on("pjax:click" , function(){ replying = false; replyID = 0; $('#post_comment_reply_info').css("display", "none").removeClass('reply-entering reply-leaving'); $("#post_comment").removeClass("post-comment-force-privatemode-on post-comment-force-privatemode-off"); }); //编辑评论 let editing = false , editID = 0; function edit(commentID){ cancelReply(); editing = true; editID = commentID; $('#post_comment').addClass("editing"); $("#post_comment_content").val($("#comment-" + editID + " .comment-item-source").text()); $("#post_comment_content").trigger("change"); if ($("#comment-" + editID).data("use-markdown") == true && document.getElementById("comment_post_use_markdown") != null){ document.getElementById("comment_post_use_markdown").checked = true; }else{ document.getElementById("comment_post_use_markdown").checked = false; } if ($("#comment-" + commentID + " .comment-item-title .badge-private-comment").length > 0){ $("#post_comment").addClass("post-comment-force-privatemode-on"); }else{ $("#post_comment").addClass("post-comment-force-privatemode-off"); } // 使用原生 scrollTo 避免 jQuery easing 依赖问题 let postCommentEl = document.getElementById('post_comment'); if (postCommentEl) { window.scrollTo({ top: postCommentEl.getBoundingClientRect().top + window.pageYOffset - 100, behavior: 'smooth' }); } $("#post_comment_content").focus(); } function cancelEdit(clear){ editing = false; editID = 0; $("#post_comment").removeClass("post-comment-force-privatemode-on post-comment-force-privatemode-off"); if (clear == true) $("#post_comment_content").val(""); $("#post_comment_content").trigger("change"); $('#post_comment').removeClass("editing"); } $(document).on("click", ".comment-edit", function(){ edit(this.getAttribute("data-id")); }); $(document).on("click", "#post_comment_edit_cancel", function(){ // 使用原生 scrollTo 避免 jQuery easing 依赖问题 let commentEl = document.getElementById("comment-" + editID); if (commentEl) { window.scrollTo({ top: commentEl.getBoundingClientRect().top + window.pageYOffset - 100, behavior: 'smooth' }); } cancelEdit(true); }); $(document).on("pjax:click", function(){ cancelEdit(true); }); $(document).on("click", ".comment-pin, .comment-unpin", function(){ toogleCommentPin(this.getAttribute("data-id"), !this.classList.contains("comment-pin")); }); $(document).on("click", ".comment-delete", function(){ deleteComment(this.getAttribute("data-id")); }); $(document).on("mouseenter", ".comment-parent-info", function(){ $("#comment-" + this.getAttribute("data-parent-id")).addClass("highlight"); }); $(document).on("mouseleave", ".comment-parent-info", function(){ $("#comment-" + this.getAttribute("data-parent-id")).removeClass("highlight"); }); //切换评论置顶状态 function toogleCommentPin(commentID, pinned){ $("#comment_pin_comfirm_dialog .modal-title").html(pinned ? __("取消置顶评论") : __("置顶评论")); $("#comment_pin_comfirm_dialog .modal-body").html(pinned ? __("是否要取消置顶评论 #") + commentID + "?" : __("是否要置顶评论 #") + commentID + "?"); $("#comment_pin_comfirm_dialog .btn-comfirm").html(__("确认")).attr("disabled", false); $("#comment_pin_comfirm_dialog .btn-dismiss").html(__("取消")).attr("disabled", false); $("#comment_pin_comfirm_dialog .btn-comfirm").off("click").on("click", function(){ $("#comment_pin_comfirm_dialog .btn-dismiss").attr("disabled", true) $("#comment_pin_comfirm_dialog .btn-comfirm").attr("disabled", true).prepend(__(``)); $.ajax({ type: 'POST', url: argonConfig.wp_path + "wp-admin/admin-ajax.php", dataType : "json", data: { action: "pin_comment", id: commentID, pinned: pinned ? "false" : "true" }, success: function(result){ $("#comment_pin_comfirm_dialog").modal('hide'); if (result.status == "success"){ if (pinned){ $("#comment-" + commentID + " .comment-name .badge-pinned").remove(); $("#comment-" + commentID + " .comment-unpin").removeClass("comment-unpin").addClass("comment-pin").html(__("置顶")); }else{ $("#comment-" + commentID + " .comment-name").append(`${__("置顶")}`); $("#comment-" + commentID + " .comment-pin").removeClass("comment-pin").addClass("comment-unpin").html(__("取消置顶")); } iziToast.show({ title: pinned ? __("取消置顶成功") : __("置顶成功"), message: pinned ? __("该评论已取消置顶") : __("该评论已置顶"), class: 'shadow-sm', position: 'topRight', backgroundColor: '#2dce89', titleColor: '#ffffff', messageColor: '#ffffff', iconColor: '#ffffff', progressBarColor: '#ffffff', icon: 'fa fa-check', timeout: 5000 }); } else { iziToast.show({ title: pinned ? __("取消置顶失败") : __("置顶失败"), message: result.msg, class: 'shadow-sm', position: 'topRight', backgroundColor: '#f5365c', titleColor: '#ffffff', messageColor: '#ffffff', iconColor: '#ffffff', progressBarColor: '#ffffff', icon: 'fa fa-close', timeout: 5000 }); } }, error: function(result){ $("#comment_pin_comfirm_dialog").modal('hide'); iziToast.show({ title: pinned ? __("取消置顶失败") : __("置顶失败"), message: __("未知错误"), class: 'shadow-sm', position: 'topRight', backgroundColor: '#f5365c', titleColor: '#ffffff', messageColor: '#ffffff', iconColor: '#ffffff', progressBarColor: '#ffffff', icon: 'fa fa-close', timeout: 5000 }); } }); }); $("#comment_pin_comfirm_dialog").modal(null); } //删除评论 function deleteComment(commentID) { if (!confirm(__('确定要删除评论 #') + commentID + '?')) { return; } $.ajax({ type: 'POST', url: argonConfig.wp_path + 'wp-admin/admin-ajax.php', dataType: 'json', data: { action: 'frontend_delete_comment', id: commentID }, success: function(result) { if (result.status === 'success') { // 移除评论元素 $('#comment-' + commentID).fadeOut(300, function() { $(this).next('.comment-divider').remove(); $(this).remove(); }); iziToast.show({ title: __('删除成功'), message: result.msg, class: 'shadow-sm', position: 'topRight', backgroundColor: '#2dce89', titleColor: '#ffffff', messageColor: '#ffffff', iconColor: '#ffffff', progressBarColor: '#ffffff', icon: 'fa fa-check', timeout: 5000 }); } else { iziToast.show({ title: __('删除失败'), message: result.msg, class: 'shadow-sm', position: 'topRight', backgroundColor: '#f5365c', titleColor: '#ffffff', messageColor: '#ffffff', iconColor: '#ffffff', progressBarColor: '#ffffff', icon: 'fa fa-close', timeout: 5000 }); } }, error: function() { iziToast.show({ title: __('删除失败'), message: __('未知错误'), class: 'shadow-sm', position: 'topRight', backgroundColor: '#f5365c', titleColor: '#ffffff', messageColor: '#ffffff', iconColor: '#ffffff', progressBarColor: '#ffffff', icon: 'fa fa-close', timeout: 5000 }); } }); } //显示/隐藏额外输入框(评论者网址) $(document).on("click" , "#post_comment_toggle_extra_input" , function(){ $("#post_comment").toggleClass("show-extra-input"); if ($("#post_comment").hasClass("show-extra-input")){ $("#post_comment_extra_input").slideDown(250, 'easeOutCirc'); }else{ $("#post_comment_extra_input").slideUp(200, 'easeOutCirc'); } }); //输入框细节 $(document).on("change input keydown keyup propertychange" , "#post_comment_content" , function(){ $("#post_comment_content_hidden")[0].innerText = $("#post_comment_content").val() + "\n"; $("#post_comment_content").css("height" , $("#post_comment_content_hidden").outerHeight()); }); $(document).on("focus" , "#post_comment_link" , function(){ $(".post-comment-link-container").addClass("active"); }); $(document).on("blur" , "#post_comment_link" , function(){ $(".post-comment-link-container").removeClass("active"); }); $(document).on("focus" , "#post_comment_captcha" , function(){ $(".post-comment-captcha-container").addClass("active"); }); $(document).on("blur" , "#post_comment_captcha" , function(){ $(".post-comment-captcha-container").removeClass("active"); }); //发送评论 window.postComment = function postComment(){ let commentContent = $("#post_comment_content").val(); let commentName = $("#post_comment_name").val(); let commentEmail = $("#post_comment_email").val(); let commentLink = $("#post_comment_link").val(); let commentCaptcha = $("#post_comment_captcha").val(); let useMarkdown = false; let privateMode = false; let mailNotice = false; if ($("#comment_post_use_markdown").length > 0){ useMarkdown = $("#comment_post_use_markdown")[0].checked; } if ($("#comment_post_privatemode").length > 0){ privateMode = $("#comment_post_privatemode")[0].checked; } if ($("#comment_post_mailnotice").length > 0){ mailNotice = $("#comment_post_mailnotice")[0].checked; } let postID = $("#post_comment_post_id").val(); let commentCaptchaSeed = $("#post_comment_captcha_seed").val(); let isError = false; let errorMsg = ""; //检查表单合法性 if (commentContent.match(/^\s*$/)){ isError = true; errorMsg += __("评论内容不能为空") + ""; } if (!$("#post_comment").hasClass("no-need-name-email")){ if (commentName.match(/^\s*$/)){ isError = true; errorMsg += __("昵称不能为空") + ""; } if ($("#post_comment").hasClass("enable-qq-avatar")){ if (!(/^\w+((-\w+)|(\.\w+))*\@[A-Za-z0-9]+((\.|-)[A-Za-z0-9]+)*\.[A-Za-z0-9]+$/).test(commentEmail) && !(/^[1-9][0-9]{4,10}$/).test(commentEmail)){ isError = true; errorMsg += __("邮箱或QQ 号格式错误") + ""; } }else{ if (!(/^\w+((-\w+)|(\.\w+))*\@[A-Za-z0-9]+((\.|-)[A-Za-z0-9]+)*\.[A-Za-z0-9]+$/).test(commentEmail)){ isError = true; errorMsg += __("邮箱格式错误") + ""; } } }else{ if (commentEmail.length || (document.getElementById("comment_post_mailnotice") != null && document.getElementById("comment_post_mailnotice").checked == true)){ if ($("#post_comment").hasClass("enable-qq-avatar")){ if (!(/^\w+((-\w+)|(\.\w+))*\@[A-Za-z0-9]+((\.|-)[A-Za-z0-9]+)*\.[A-Za-z0-9]+$/).test(commentEmail) && !(/^[1-9][0-9]{4,10}$/).test(commentEmail)){ isError = true; errorMsg += __("邮箱或QQ 号格式错误") + ""; } }else{ if (!(/^\w+((-\w+)|(\.\w+))*\@[A-Za-z0-9]+((\.|-)[A-Za-z0-9]+)*\.[A-Za-z0-9]+$/).test(commentEmail)){ isError = true; errorMsg += __("邮箱格式错误") + ""; } } } } if (commentLink != "" && !(/https?:\/\//).test(commentLink)){ isError = true; errorMsg += __("网站格式错误 (不是 http(s):// 开头)") + ""; } if (!$("#post_comment").hasClass("no-need-captcha")){ // 检查是否使用Geetest验证码 if ($("#geetest-captcha").length > 0) { // 检查Geetest库是否加载成功 if (typeof window.geetestLoadFailed !== 'undefined' && window.geetestLoadFailed) { isError = true; errorMsg += __("验证码服务不可用,请刷新页面重试"); } else if (typeof window.geetestCaptcha === 'undefined' || !window.geetestCaptcha) { isError = true; errorMsg += __("验证码未初始化,请稍后重试");; } else { // Geetest验证码检查 let lotNumber = $("#geetest_lot_number").val(); let captchaOutput = $("#geetest_captcha_output").val(); let passToken = $("#geetest_pass_token").val(); let genTime = $("#geetest_gen_time").val(); if (!lotNumber || !captchaOutput || !passToken || !genTime) { isError = true; errorMsg += __("请完成验证码验证"); } } } else { // 原有的数学验证码验证 if (commentCaptcha == ""){ isError = true; errorMsg += __("验证码未输入"); } if (commentCaptcha != "" && !(/^[0-9]+$/).test(commentCaptcha)){ isError = true; errorMsg += __("验证码格式错误"); } } } if (isError){ // 确保按钮和表单元素处于可用状态 $("#post_comment_send").removeAttr("disabled"); $("#post_comment_content").removeAttr("disabled"); $("#post_comment_name").removeAttr("disabled"); $("#post_comment_email").removeAttr("disabled"); $("#post_comment_link").removeAttr("disabled"); $("#post_comment_captcha").removeAttr("disabled"); $("#post_comment_reply_cancel").removeAttr("disabled"); $("#post_comment").removeClass("sending"); $("#post_comment_send .btn-inner--icon.hide-on-comment-editing").html(""); $("#post_comment_send .btn-inner--text.hide-on-comment-editing").html(__("发送")); iziToast.show({ title: __("评论格式错误"), message: errorMsg, class: 'shadow-sm', position: 'topRight', backgroundColor: '#f5365c', titleColor: '#ffffff', messageColor: '#ffffff', iconColor: '#ffffff', progressBarColor: '#ffffff', icon: 'fa fa-close', timeout: 5000 }); return; } //增加 disabled 属性和其他的表单提示 $("#post_comment").addClass("sending"); $("#post_comment_content").attr("disabled","disabled"); $("#post_comment_name").attr("disabled","disabled"); $("#post_comment_email").attr("disabled","disabled"); $("#post_comment_captcha").attr("disabled","disabled"); $("#post_comment_link").attr("disabled","disabled"); $("#post_comment_send").attr("disabled","disabled"); $("#post_comment_reply_cancel").attr("disabled","disabled"); $("#post_comment_send .btn-inner--icon.hide-on-comment-editing").html(""); $("#post_comment_send .btn-inner--text.hide-on-comment-editing").html(__("发送中")); iziToast.show({ title: __("正在发送"), message: __("评论正在发送中..."), class: 'shadow-sm iziToast-noprogressbar', position: 'topRight', backgroundColor: 'var(--themecolor)', titleColor: '#ffffff', messageColor: '#ffffff', iconColor: '#ffffff', progressBarColor: '#ffffff', icon: 'fa fa-spinner fa-spin', close: false, timeout: 999999999 }); // 准备数据 var ajaxData = { action: "ajax_post_comment", comment: commentContent, author: commentName, email: commentEmail, url: commentLink, comment_post_ID: postID, comment_parent: replyID, "wp-comment-cookies-consent": "yes", use_markdown: useMarkdown, private_mode: privateMode, enable_mailnotice: mailNotice, argon_nonce: $("#argon_comment_nonce").val() }; // 根据验证码类型添加相应参数 if ($("#geetest-captcha").length > 0) { // 检查Geetest库加载状态 if (typeof window.geetestLoadFailed !== 'undefined' && window.geetestLoadFailed) { // 重新启用按钮和表单元素 $("#post_comment").removeClass("sending"); $("#post_comment_content").removeAttr("disabled"); $("#post_comment_name").removeAttr("disabled"); $("#post_comment_email").removeAttr("disabled"); $("#post_comment_captcha").removeAttr("disabled"); $("#post_comment_link").removeAttr("disabled"); $("#post_comment_send").removeAttr("disabled"); $("#post_comment_reply_cancel").removeAttr("disabled"); $("#post_comment_send .btn-inner--icon.hide-on-comment-editing").html(""); $("#post_comment_send .btn-inner--text.hide-on-comment-editing").html(__("发送")); setTimeout(function() { try { iziToast.destroy(); iziToast.error({ title: __('评论发送失败'), message: __('验证码服务不可用,请刷新页面重试'), position: 'topRight' }); } catch (e) { ArgonDebug.warn('iziToast error:', e); } }, 0); return false; } // Geetest验证码参数 - 使用后端期望的参数名 ajaxData.lot_number = $("#geetest_lot_number").val(); ajaxData.captcha_output = $("#geetest_captcha_output").val(); ajaxData.pass_token = $("#geetest_pass_token").val(); ajaxData.gen_time = $("#geetest_gen_time").val(); // 验证Geetest参数是否完整 if (!ajaxData.lot_number || !ajaxData.captcha_output || !ajaxData.pass_token || !ajaxData.gen_time) { // 重新启用按钮和表单元素 $("#post_comment").removeClass("sending"); $("#post_comment_content").removeAttr("disabled"); $("#post_comment_name").removeAttr("disabled"); $("#post_comment_email").removeAttr("disabled"); $("#post_comment_captcha").removeAttr("disabled"); $("#post_comment_link").removeAttr("disabled"); $("#post_comment_send").removeAttr("disabled"); $("#post_comment_reply_cancel").removeAttr("disabled"); $("#post_comment_send .btn-inner--icon.hide-on-comment-editing").html(""); $("#post_comment_send .btn-inner--text.hide-on-comment-editing").html(__("发送")); // 使用 setTimeout 确保 iziToast 操作在下一个事件循环中执行 setTimeout(function() { try { iziToast.destroy(); iziToast.error({ title: __('评论发送失败'), message: __('请完成验证码验证'), position: 'topRight' }); } catch (e) { ArgonDebug.warn('iziToast error:', e); } }, 0); return false; } } else { // 原有数学验证码参数 ajaxData.comment_captcha_seed = commentCaptchaSeed; ajaxData.comment_captcha = commentCaptcha; } $.ajax({ type: 'POST', url: argonConfig.wp_path + "wp-admin/admin-ajax.php", dataType : "json", data: ajaxData, success: function(result){ $("#post_comment").removeClass("sending"); $("#post_comment_content").removeAttr("disabled"); $("#post_comment_name").removeAttr("disabled"); $("#post_comment_email").removeAttr("disabled"); $("#post_comment_link").removeAttr("disabled"); $("#post_comment_send").removeAttr("disabled"); $("#post_comment_reply_cancel").removeAttr("disabled"); $("#post_comment_send .btn-inner--icon.hide-on-comment-editing").html(""); $("#post_comment_send .btn-inner--text.hide-on-comment-editing").html(__("发送")); $("#post_comment").removeClass("show-extra-input post-comment-force-privatemode-on post-comment-force-privatemode-off"); if (!result.isAdmin){ $("#post_comment_captcha").removeAttr("disabled"); } //判断是否有错误 if (result.status == "failed"){ // 使用 setTimeout 确保 iziToast 操作在下一个事件循环中执行 setTimeout(function() { try { iziToast.destroy(); iziToast.show({ title: __("评论发送失败"), message: result.msg, class: 'shadow-sm', position: 'topRight', backgroundColor: '#f5365c', titleColor: '#ffffff', messageColor: '#ffffff', iconColor: '#ffffff', progressBarColor: '#ffffff', icon: 'fa fa-close', timeout: 5000 }); } catch (e) { ArgonDebug.warn('iziToast error:', e); } }, 0); return; } //发送成功 // 先复位评论表单(确保无论后续操作是否成功都能重置表单) cancelReply(); $("#post_comment_content").val(""); // 重置数学验证码 $("#post_comment_captcha_seed").val(result.newCaptchaSeed); $("#post_comment_captcha + style").html(".post-comment-captcha-container:before{content: '" + result.newCaptcha + "';}"); $("#post_comment_captcha").val(""); // 清空Geetest验证码隐藏字段并重置验证码实例 if ($("#geetest-captcha").length > 0) { $("#geetest_lot_number").val(""); $("#geetest_captcha_output").val(""); $("#geetest_pass_token").val(""); $("#geetest_gen_time").val(""); // 重置验证状态标志位 window.geetestVerified = false; // 重置自动提交标记 window.geetestAutoSubmitting = false; // 重置 Geetest 实例,确保下一次可以重新验证 if (window.geetestCaptcha) { try { window.geetestCaptcha.reset(); } catch (e) { ArgonDebug.warn('Geetest reset error:', e); } } } // 显示成功提示 setTimeout(function() { try { iziToast.destroy(); iziToast.show({ title: __("发送成功"), message: __("您的评论已发送"), class: 'shadow-sm', position: 'topRight', backgroundColor: '#2dce89', titleColor: '#ffffff', messageColor: '#ffffff', iconColor: '#ffffff', progressBarColor: '#ffffff', icon: 'fa fa-check', timeout: 5000 }); } catch (e) { ArgonDebug.warn('iziToast error:', e); } }, 0); // 插入新评论 try { result.html = result.html.replace(/<(\/).noscript>/g, ""); let parentID = result.parentID; if (parentID == "" || parentID == null){ parentID = 0; } parentID = parseInt(parentID); if (parentID == 0){ if ($("#comments > .card-body > ol.comment-list").length == 0){ $("#comments > .card-body").html(" 转换为
* 这样可以避免代码高亮干扰 Mermaid 渲染
*/
function convertMermaidCodeblocks(){
// 支持多种代码块格式
const selectors = [
'pre > code.language-mermaid', // 标准 Markdown 格式(最常见)
'pre > code.mermaid', // 简化格式
'code.language-mermaid', // 无 pre 包裹
'pre[data-lang="mermaid"]' // 自定义属性格式
];
let processedCount = 0;
selectors.forEach(selector => {
document.querySelectorAll(selector).forEach(element => {
// 避免重复处理
if (element.dataset.mermaidProcessed) {
return;
}
// 提取代码
let code = element.textContent;
// 清理代码:移除前后空白,但保留内部换行
code = code.trim();
// 如果代码为空,跳过
if (!code) {
return;
}
// 移除每行开头的统一缩进(保持相对缩进)
const lines = code.split('\n');
if (lines.length > 1) {
// 找到最小缩进
let minIndent = Infinity;
lines.forEach(line => {
if (line.trim().length > 0) {
const indent = line.match(/^\s*/)[0].length;
if (indent < minIndent) {
minIndent = indent;
}
}
});
// 移除最小缩进
if (minIndent > 0 && minIndent !== Infinity) {
code = lines.map(line => {
if (line.trim().length === 0) {
return '';
}
return line.substring(minIndent);
}).join('\n');
}
}
// 创建容器
const container = document.createElement('div');
container.className = 'mermaid-from-codeblock';
container.textContent = code;
container.dataset.processed = 'true';
// 调试日志
if (typeof console !== 'undefined' && console.log) {
console.log('[Mermaid 转换] 代码长度:', code.length);
console.log('[Mermaid 转换] 代码内容:', code.substring(0, 200));
console.log('[Mermaid 转换] 容器 textContent:', container.textContent.substring(0, 200));
}
// 替换元素
const targetElement = element.closest('pre') || element;
if (targetElement.parentNode) {
targetElement.parentNode.replaceChild(container, targetElement);
processedCount++;
}
// 标记已处理
element.dataset.mermaidProcessed = 'true';
});
});
if (processedCount > 0 && typeof console !== 'undefined' && console.log) {
console.log('[Mermaid] 转换了 ' + processedCount + ' 个代码块');
}
}
function highlightJsRender(){
// 在代码高亮之前,先处理 Mermaid 代码块
convertMermaidCodeblocks();
if (typeof(hljs) == "undefined"){
return;
}
if (typeof(argonConfig.code_highlight.enable) == "undefined"){
return;
}
if (!argonConfig.code_highlight.enable){
return;
}
$("article pre.code").each(function(index, block) {
if ($(block).hasClass("no-hljs")){
return;
}
$(block).html("" + $(block).html() + "");
});
$("article pre > code").each(function(index, block) {
if ($(block).hasClass("no-hljs")){
return;
}
// 跳过 mermaid 代码块(避免代码高亮干扰 mermaid 渲染)
if ($(block).hasClass("language-mermaid") || $(block).hasClass("mermaid")){
return;
}
// 跳过在 .mermaid 容器内的代码块
if ($(block).closest('.mermaid').length > 0){
return;
}
$(block).parent().attr("id", randomString());
hljs.highlightBlock(block);
hljs.lineNumbersBlock(block, {singleLine: true});
$(block).parent().addClass("hljs-codeblock");
if (argonConfig.code_highlight.hide_linenumber){
$(block).parent().addClass("hljs-hide-linenumber");
}
if (argonConfig.code_highlight.break_line){
$(block).parent().addClass("hljs-break-line");
}
if (argonConfig.code_highlight.transparent_linenumber){
$(block).parent().addClass("hljs-transparent-linenumber");
}
$(block).attr("hljs-codeblock-inner", "");
let copyBtnID = "copy_btn_" + randomString();
$(block).parent().append(`
`);
let clipboard = new ClipboardJS("#" + copyBtnID, {
text: function(trigger) {
return getCodeFromBlock($(block).parent()[0]);
}
});
clipboard.on('success', function(e) {
iziToast.show({
title: __("复制成功"),
message: __("代码已复制到剪贴板"),
class: 'shadow',
position: 'topRight',
backgroundColor: '#2dce89',
titleColor: '#ffffff',
messageColor: '#ffffff',
iconColor: '#ffffff',
progressBarColor: '#ffffff',
icon: 'fa fa-check',
timeout: 5000
});
});
clipboard.on('error', function(e) {
iziToast.show({
title: __("复制失败"),
message: __("请手动复制代码"),
class: 'shadow',
position: 'topRight',
backgroundColor: '#f5365c',
titleColor: '#ffffff',
messageColor: '#ffffff',
iconColor: '#ffffff',
progressBarColor: '#ffffff',
icon: 'fa fa-close',
timeout: 5000
});
});
});
}
$(document).ready(function(){
// ==========================================================================
// 初始化性能优化模块
// ==========================================================================
if (typeof initArgonPerformance === 'function') {
// 调用性能优化模块的初始化函数
initArgonPerformance();
// 创建各个优化模块的实例
try {
// 1. 性能监控模块(最先初始化,用于监控其他模块)
if (typeof ArgonPerformanceMonitor !== 'undefined') {
argonPerformanceMonitor = new ArgonPerformanceMonitor();
ArgonDebug.info('✓ 性能监控模块已初始化');
}
// 2. DOM 缓存模块
if (typeof ArgonDOMCache !== 'undefined') {
argonDOMCache = new ArgonDOMCache();
// 设置性能监控器引用
if (argonPerformanceMonitor) {
argonDOMCache.setPerformanceMonitor(argonPerformanceMonitor);
}
// 初始化缓存
argonDOMCache.init();
ArgonDebug.info('✓ DOM 缓存模块已初始化');
}
// 3. 事件管理模块
if (typeof ArgonEventManager !== 'undefined') {
argonEventManager = new ArgonEventManager();
ArgonDebug.info('✓ 事件管理模块已初始化');
}
// 4. 资源加载模块
if (typeof ArgonResourceLoader !== 'undefined') {
argonResourceLoader = new ArgonResourceLoader();
ArgonDebug.info('✓ 资源加载模块已初始化');
}
// 5. 渲染优化模块
if (typeof ArgonRenderOptimizer !== 'undefined') {
argonRenderOptimizer = new ArgonRenderOptimizer();
ArgonDebug.info('✓ 渲染优化模块已初始化');
}
// 6. 内存管理模块
if (typeof ArgonMemoryManager !== 'undefined') {
argonMemoryManager = new ArgonMemoryManager();
ArgonDebug.info('✓ 内存管理模块已初始化');
}
ArgonDebug.info('🚀 Argon 性能优化模块全部初始化完成');
} catch (error) {
ArgonDebug.error('性能优化模块初始化失败:', error);
}
} else {
ArgonDebug.warn('性能优化模块未加载,请确保 argon-performance.js 已正确引入');
}
// ==========================================================================
// 原有初始化代码
// ==========================================================================
highlightJsRender();
waterflowInit();
});
$(document).on("click" , ".hljs-control-fullscreen" , function(){
let block = $(this).parent().parent();
block.toggleClass("hljs-codeblock-fullscreen");
if (block.hasClass("hljs-codeblock-fullscreen")){
$("html").addClass("noscroll codeblock-fullscreen");
}else{
$("html").removeClass("noscroll codeblock-fullscreen");
}
});
$(document).on("click" , ".hljs-control-toggle-break-line" , function(){
let block = $(this).parent().parent();
block.toggleClass("hljs-break-line");
});
$(document).on("click" , ".hljs-control-toggle-linenumber" , function(){
let block = $(this).parent().parent();
block.toggleClass("hljs-hide-linenumber");
});
/*时间差计算/
function addPreZero(num, n) {
var len = num.toString().length;
while(len < n) {
num = "0" + num;
len++;
}
return num;
}
function humanTimeDiff(time){
let now = new Date();
time = new Date(time);
let delta = now - time;
if (delta < 0){
delta = 0;
}
if (delta < 1000 * 60){
return __("刚刚");
}
if (delta < 1000 * 60 * 60){
return parseInt(delta / (1000 * 60)) + " " + __("分钟前);
}
if (delta < 1000 * 60 * 60 * 24){
return parseInt(delta / (1000 * 60 * 60)) + " " + __("小时前);
}
let yesterday = new Date(now - 1000 * 60 * 60 * 24);
yesterday.setHours(0);
yesterday.setMinutes(0);
yesterday.setSeconds(0);
yesterday.setMilliseconds(0);
if (time > yesterday){
return __("昨天") + " " + time.getHours() + ":" + addPreZero(time.getMinutes(), 2);
}
let theDayBeforeYesterday = new Date(now - 1000 * 60 * 60 * 24 * 2);
theDayBeforeYesterday.setHours(0);
theDayBeforeYesterday.setMinutes(0);
theDayBeforeYesterday.setSeconds(0);
theDayBeforeYesterday.setMilliseconds(0);
if (time > theDayBeforeYesterday && argonConfig.language.indexOf("zh") == 0){
return __("前天") + " " + time.getHours() + ":" + addPreZero(time.getMinutes(), 2);
}
if (delta < 1000 * 60 * 60 * 24 * 30){
return parseInt(delta / (1000 * 60 * 60 * 24)) + " " + __("天前");
}
let theFirstDayOfThisYear = new Date(now);
theFirstDayOfThisYear.setMonth(0);
theFirstDayOfThisYear.setDate(1);
theFirstDayOfThisYear.setHours(0);
theFirstDayOfThisYear.setMinutes(0);
theFirstDayOfThisYear.setSeconds(0);
theFirstDayOfThisYear.setMilliseconds(0);
if (time > theFirstDayOfThisYear){
if (argonConfig.dateFormat == "YMD" || argonConfig.dateFormat == "MDY"){
return (time.getMonth() + 1) + "-" + time.getDate();
}else{
return time.getDate() + "-" + (time.getMonth() + 1);
}
}
if (argonConfig.dateFormat == "YMD"){
return time.getFullYear() + "-" + (time.getMonth() + 1) + "-" + time.getDate();
}else if (argonConfig.dateFormat == "MDY"){
return time.getDate() + "-" + (time.getMonth() + 1) + "-" + time.getFullYear();
}else if (argonConfig.dateFormat == "DMY"){
return time.getDate() + "-" + (time.getMonth() + 1) + "-" + time.getFullYear();
}
}
function calcHumanTimesOnPage(){
$(".human-time").each(function(){
$(this).text(humanTimeDiff(parseInt($(this).data("time")) * 1000));
});
}
calcHumanTimesOnPage();
setInterval(function(){
calcHumanTimesOnPage()
}, 15000);
/*Console*/
!function(){
void 0;
}();
/* ========== Modern UI Enhancements - 现代化交互动画增强========== */
(function() {
'use strict';
// 1. 图片加载动画
function initImageLoadAnimation() {
var images = document.querySelectorAll('article img[loading="lazy"], .post-thumbnail img');
images.forEach(function(img) {
if (img.dataset.loadAnimInit) return;
img.dataset.loadAnimInit = 'true';
if (img.complete) { img.classList.add('loaded'); }
else { img.addEventListener('load', function() { this.classList.add('loaded'); }); }
});
}
// 3. 滚动入场动画
function initScrollAnimations() {
if (!('IntersectionObserver' in window)) return;
var animatedElements = document.querySelectorAll('.article-list article.post, .comment-item, .timeline-item, .friend-link-item, #leftbar .card, #rightbar .card');
var observer = new IntersectionObserver(function(entries) {
entries.forEach(function(entry) {
if (entry.isIntersecting) { entry.target.classList.add('animate-in'); observer.unobserve(entry.target); }
});
}, { threshold: 0.1, rootMargin: '0px 0px -50px 0px' });
animatedElements.forEach(function(el) { if (!el.classList.contains('animate-in')) observer.observe(el); });
}
// 4. 平滑滚动
function initSmoothScroll() {
document.querySelectorAll('a[href^="#"]').forEach(function(anchor) {
if (anchor.dataset.smoothScrollInit) return;
anchor.dataset.smoothScrollInit = 'true';
anchor.addEventListener('click', function(e) {
var targetId = this.getAttribute('href');
if (targetId === '#') return;
var target = document.querySelector(targetId);
if (target) { e.preventDefault(); target.scrollIntoView({ behavior: 'smooth', block: 'start' }); }
});
});
}
// 5. 页面加载进度条
function initLoadingBar() {
if (document.getElementById('page-loading-bar')) return;
var bar = document.createElement('div');
bar.id = 'page-loading-bar';
bar.style.width = '0%';
document.body.appendChild(bar);
var progress = 0;
var interval = setInterval(function() {
progress += Math.random() * 10;
if (progress >= 90) { clearInterval(interval); progress = 90; }
bar.style.width = progress + '%';
}, 100);
window.addEventListener('load', function() {
clearInterval(interval);
bar.style.width = '100%';
setTimeout(function() { bar.style.opacity = '0'; setTimeout(function() { bar.remove(); }, 300); }, 200);
});
}
// 6. PJAX 加载动画
function initPjaxAnimations() {
if (typeof jQuery === 'undefined') return;
jQuery(document).on('pjax:start', function() {
jQuery('#primary').addClass('pjax-loading');
var bar = document.getElementById('page-loading-bar');
if (!bar) { bar = document.createElement('div'); bar.id = 'page-loading-bar'; document.body.appendChild(bar); }
bar.style.opacity = '1'; bar.style.width = '30%';
setTimeout(function() { bar.style.width = '60%'; }, 200);
});
jQuery(document).on('pjax:end', function() {
jQuery('#primary').removeClass('pjax-loading');
var bar = document.getElementById('page-loading-bar');
if (bar) { bar.style.width = '100%'; setTimeout(function() { bar.style.opacity = '0'; setTimeout(function() { bar.remove(); }, 300); }, 200); }
setTimeout(function() { initImageLoadAnimation(); initScrollAnimations(); initSmoothScroll(); }, 100);
});
}
// 7. 主题切换动画 - 已在 header.php 中通过 setDarkmode 函数处理,此处不再重复
function initThemeTransition() {
// 移除 MutationObserver 避免无限循环导致内存泄漏
// 主题切换过渡效果已在 setDarkmode() 函数中实现
}
// 8. 减少动画偏好检查
function checkReducedMotion() {
if (window.matchMedia('(prefers-reduced-motion: reduce)').matches) {
document.documentElement.classList.add('reduced-motion');
}
}
// 初始化
function init() {
checkReducedMotion();
initImageLoadAnimation();
initScrollAnimations();
initSmoothScroll();
initPjaxAnimations();
initThemeTransition();
}
if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', init); }
else { init(); }
if (document.readyState !== 'complete') { initLoadingBar(); }
})();
/* ========== End of Modern UI Enhancements ========== */
// ========== jQuery Easing 备用(确保在所有脚本加载后仍可用)==========
(function() {
if (typeof jQuery === 'undefined') return;
var $ = jQuery;
if (!$.easing) $.easing = {};
if (!$.easing.easeOutCirc) {
$.easing.easeOutCirc = function(x) {
return Math.sqrt(1 - Math.pow(x - 1, 2));
};
}
if (!$.easing.easeOutExpo) {
$.easing.easeOutExpo = function(x) {
return x === 1 ? 1 : 1 - Math.pow(2, -10 * x);
};
}
})();
// ==========================================================================
// Mermaid 图表渲染引擎
// ==========================================================================
(function() {
'use strict';
// ---------- 配置和状态管理 ----------
/**
* Mermaid 渲染引擎配置
*/
const MermaidRenderer = {
initialized: false,
rendered: new Set(), // 已渲染的图表 ID 集合
config: null,
/**
* 初始化 Mermaid 配置
*/
initConfig() {
// 检查 Mermaid 库是否已加载
if (typeof window.mermaid === 'undefined') {
this.logDebug('Mermaid 库未加载');
return false;
}
// 检查配置是否存在
if (typeof window.argonMermaidConfig === 'undefined') {
this.logDebug('Mermaid 配置未找到,使用默认配置');
this.config = {
enabled: true,
theme: 'auto',
debugMode: false,
fallbackUrls: [],
libraryLoadedByPlugin: false
};
} else {
this.config = window.argonMermaidConfig;
}
// 如果库由插件加载,记录日志
if (this.config.libraryLoadedByPlugin) {
this.logDebug('Mermaid 库由插件加载,主题只提供样式增强');
}
// 获取当前主题
const theme = this.getMermaidTheme();
// 配置 Mermaid
try {
window.mermaid.initialize({
startOnLoad: false, // 手动控制渲染时机
theme: theme,
securityLevel: 'loose', // 允许 HTML 标签
logLevel: this.config.debugMode ? 'debug' : 'error',
flowchart: {
useMaxWidth: true,
htmlLabels: true,
curve: 'basis'
},
sequence: {
useMaxWidth: true,
wrap: true
},
gantt: {
useMaxWidth: true
}
});
this.initialized = true;
this.logDebug('Mermaid 配置初始化成功', { theme });
return true;
} catch (error) {
this.logError('Mermaid 配置初始化失败', error);
return false;
}
},
/**
* 获取当前主题对应的 Mermaid 主题
* @returns {string} Mermaid 主题名称
*/
getMermaidTheme() {
// 如果配置了固定主题(非 auto),直接返回
if (this.config && this.config.theme !== 'auto') {
return this.config.theme;
}
// 根据页面主题模式自动选择
const isDarkMode = document.documentElement.classList.contains('darkmode');
return isDarkMode ? 'dark' : 'default';
},
// ---------- 代码块检测器 ----------
/**
* 检测所有 Mermaid 代码块
* @returns {Array} Mermaid 代码块元素数组
*/
detectMermaidBlocks() {
const blocks = [];
// 检测规则(优先级从高到低)
const selectors = [
'div.mermaid-shortcode', // Shortcode 格式(推荐)
'div.mermaid-from-codeblock', // 代码块魔改格式(新增)
'div.mermaid', // 标准格式
'pre code.language-mermaid', // Markdown 格式(降级)
'pre[data-lang="mermaid"]', // 自定义属性格式
'code.mermaid' // 简化格式
];
selectors.forEach(selector => {
const elements = document.querySelectorAll(selector);
elements.forEach(element => {
// 避免重复添加
if (!blocks.includes(element)) {
// 检查是否在注释中
if (!this.isInComment(element)) {
blocks.push(element);
}
}
});
});
// 检测 Markdown 容器语法的 Mermaid 代码块
// 格式: ::: mermaid ... :::
this.detectContainerBlocks(blocks);
this.logDebug(`检测到 ${blocks.length} 个 Mermaid 代码块`);
return blocks;
},
/**
* 检查元素是否在 HTML 注释中
* @param {HTMLElement} element - 要检查的元素
* @returns {boolean} 是否在注释中
*/
isInComment(element) {
let node = element.parentNode;
while (node) {
if (node.nodeType === Node.COMMENT_NODE) {
return true;
}
node = node.parentNode;
}
return false;
},
/**
* 检测 Markdown 容器语法的 Mermaid 代码块
* 格式: ::: mermaid ... :::
* @param {Array} blocks - 代码块数组
*/
detectContainerBlocks(blocks) {
// 查找所有包含 ::: mermaid 的元素
const allElements = document.querySelectorAll('p, pre, code, div');
const processedElements = new Set();
allElements.forEach(element => {
// 跳过已处理的元素
if (processedElements.has(element)) {
return;
}
const text = element.textContent.trim();
// 检查是否是开始标记
if (text.startsWith('::: mermaid') || text === '::: mermaid') {
this.logDebug('找到容器语法开始标记');
// 收集所有内容直到结束标记
const container = this.extractContainerContent(element, processedElements);
if (container && !blocks.includes(container)) {
blocks.push(container);
this.logDebug('检测到 Markdown 容器语法的 Mermaid 代码块');
}
}
});
},
/**
* 提取 Markdown 容器语法的内容
* @param {HTMLElement} startElement - 包含开始标记的元素
* @param {Set} processedElements - 已处理的元素集合
* @returns {HTMLElement|null} 包含代码的容器元素
*/
extractContainerContent(startElement, processedElements) {
let codeLines = [];
let currentElement = startElement;
let foundStart = false;
let foundEnd = false;
// 标记开始元素为已处理
processedElements.add(startElement);
// 处理开始元素
// 使用 innerHTML 来获取原始内容,包括
标签
let startHTML = startElement.innerHTML;
let startText = startElement.textContent.trim();
this.logDebug('检查元素,textContent: ' + startText.substring(0, 50));
this.logDebug('innerHTML: ' + startHTML.substring(0, 100));
if (startText.startsWith('::: mermaid')) {
foundStart = true;
this.logDebug('找到容器语法开始标记');
// 检查是否整个内容都在一个元素中
if (startText.includes(':::') && startText.lastIndexOf(':::') > 10) {
// 整个容器语法在一个元素中
this.logDebug('检测到单元素容器语法');
// 使用 htmlToText 转换整个 HTML
let fullText = this.htmlToText(startHTML);
this.logDebug('转换后的完整文本: ' + fullText.substring(0, 200));
// 移除开始和结束标记
fullText = fullText.replace(/^:::\s*mermaid\s*/i, '').trim();
fullText = fullText.replace(/:::\s*$/, '').trim();
this.logDebug('移除标记后的代码: ' + fullText.substring(0, 200));
if (fullText) {
// 创建容器
const container = document.createElement('div');
container.className = 'mermaid-container-block';
container.textContent = fullText;
container.dataset.containerBlock = 'true';
// 替换开始元素
startElement.parentNode.replaceChild(container, startElement);
return container;
}
return null;
}
// 多元素容器语法(原有逻辑)
// 移除开始标记,保留同一元素中的其他内容
startText = startText.replace(/^:::\s*mermaid\s*/i, '').trim();
if (startText && !startText.startsWith(':::')) {
// 如果开始标记后有内容,需要从 HTML 中提取
// 将
转换为换行符
let contentHTML = startHTML.replace(/^:::\s*mermaid\s*
/i, '');
let contentText = this.htmlToText(contentHTML);
if (contentText.trim()) {
codeLines.push(contentText);
}
}
// 检查是否在同一元素中就有结束标记
if (startText.endsWith(':::')) {
foundEnd = true;
// 移除结束标记
if (codeLines.length > 0) {
let lastLine = codeLines[codeLines.length - 1];
codeLines[codeLines.length - 1] = lastLine.replace(/:::\s*$/, '').trim();
}
}
}
// 如果还没找到结束标记,继续查找后续兄弟元素
if (foundStart && !foundEnd) {
currentElement = startElement.nextElementSibling;
while (currentElement) {
processedElements.add(currentElement);
let text = currentElement.textContent.trim();
// 检查是否是结束标记
if (text === ':::' || text.endsWith(':::')) {
foundEnd = true;
// 如果结束标记前还有内容,保留它
if (text !== ':::') {
text = text.replace(/:::\s*$/, '').trim();
if (text) {
// 将 HTML 转换为文本,保留换行符
let contentText = this.htmlToText(currentElement.innerHTML);
codeLines.push(contentText);
}
}
break;
}
// 添加当前元素的内容
// 将 HTML 转换为文本,保留换行符
let contentText = this.htmlToText(currentElement.innerHTML);
if (contentText.trim()) {
codeLines.push(contentText);
} else {
// 空元素,添加空行
codeLines.push('');
}
currentElement = currentElement.nextElementSibling;
}
}
// 如果没有找到完整的容器语法,返回 null
if (!foundStart || !foundEnd) {
this.logDebug('容器语法不完整,跳过');
return null;
}
// 合并所有行
let code = codeLines.join('\n').trim();
this.logDebug('提取的完整代码: ' + code.substring(0, 200));
if (!code) {
return null;
}
// 创建一个新的容器来存储代码
const container = document.createElement('div');
container.className = 'mermaid-container-block';
container.textContent = code;
container.dataset.containerBlock = 'true';
// 替换开始元素
startElement.parentNode.replaceChild(container, startElement);
return container;
},
/**
* 将 HTML 转换为纯文本,保留换行符
* @param {string} html - HTML 字符串
* @returns {string} 纯文本
*/
htmlToText(html) {
// 将
和
转换为换行符
let text = html.replace(/
/gi, '\n');
// 移除其他 HTML 标签
text = text.replace(/<[^>]+>/g, '');
// 解码 HTML 实体
text = text
.replace(/ /g, ' ')
.replace(/</g, '<')
.replace(/>/g, '>')
.replace(/&/g, '&')
.replace(/"/g, '"')
.replace(/'/g, "'")
.replace(/–/g, '-') // EN DASH
.replace(/—/g, '--') // EM DASH
.replace(/→/g, '->') // RIGHTWARDS ARROW
.replace(/–/g, '-') // EN DASH (named entity)
.replace(/—/g, '--') // EM DASH (named entity)
.replace(/→/g, '->'); // RIGHTWARDS ARROW (named entity)
// 转换 Unicode 字符(WordPress 可能直接输出 Unicode)
text = text
.replace(/–/g, '-') // U+2013 EN DASH
.replace(/—/g, '--') // U+2014 EM DASH
.replace(/→/g, '->'); // U+2192 RIGHTWARDS ARROW
return text;
},
/**
* 提取代码块内容
* @param {HTMLElement} element - 代码块元素
* @returns {string} Mermaid 代码
*/
extractMermaidCode(element) {
let code = '';
// 处理 Shortcode 格式(推荐)
if (element.classList.contains('mermaid-shortcode')) {
code = element.textContent;
this.logDebug('从 Shortcode 格式提取代码,长度: ' + code.length);
this.logDebug('代码内容: ' + code.substring(0, 200));
}
// 处理代码块魔改格式(新增)
else if (element.classList.contains('mermaid-from-codeblock')) {
code = element.textContent;
this.logDebug('从代码块魔改格式提取代码,长度: ' + code.length);
this.logDebug('代码内容: ' + code.substring(0, 200));
this.logDebug('元素 HTML: ' + element.outerHTML.substring(0, 300));
}
// 处理 Markdown 容器语法格式
else if (element.classList.contains('mermaid-container-block')) {
code = element.textContent;
this.logDebug('从 Markdown 容器语法提取代码,长度: ' + code.length);
this.logDebug('代码内容: ' + code.substring(0, 200));
}
// 根据不同的元素类型提取代码
else if (element.tagName === 'DIV' && element.classList.contains('mermaid')) {
// 检查是否包含 WP-Markdown 生成的 script 标签
const scriptTag = element.querySelector('script');
if (scriptTag) {
// 提取 document.write() 中的内容
const scriptContent = scriptTag.textContent || scriptTag.innerText;
this.logDebug('检测到 script 标签,原始内容: ' + scriptContent.substring(0, 100));
// 使用更精确的正则:匹配 document.write() 中的字符串内容
// 支持双引号和单引号,支持转义字符
// 使用贪婪匹配 [\s\S]* 来匹配包括换行在内的所有字符
let match = scriptContent.match(/document\.write\s*\(\s*["']([\s\S]*?)["']\s*\)/);
if (match && match[1]) {
code = match[1];
this.logDebug('从 document.write() 提取到代码,长度: ' + code.length);
} else {
// 如果没有匹配到 document.write(),尝试直接提取引号内的内容
match = scriptContent.match(/["']([\s\S]*?)["']/);
if (match && match[1]) {
code = match[1];
this.logDebug('从引号内提取到代码,长度: ' + code.length);
} else {
// 最后的降级方案:获取除 script 标签外的文本内容
const clonedElement = element.cloneNode(true);
const scripts = clonedElement.querySelectorAll('script');
scripts.forEach(script => script.remove());
code = clonedElement.textContent;
this.logDebug('使用降级方案提取代码');
}
}
// 重要:移除 script 标签,避免重复渲染
// WP-Markdown 会在 script 后面再输出一次代码
scriptTag.remove();
} else {
code = element.textContent;
this.logDebug('从纯文本提取代码');
}
} else if (element.tagName === 'CODE') {
code = element.textContent;
} else if (element.tagName === 'PRE') {
const codeElement = element.querySelector('code');
code = codeElement ? codeElement.textContent : element.textContent;
}
// 解码 HTML 实体(WordPress 可能会转义 HTML)
code = code
.replace(/</g, '<')
.replace(/>/g, '>')
.replace(/&/g, '&')
.replace(/"/g, '"')
.replace(/'/g, "'");
// 解码转义字符(必须在 HTML 实体解码之后)
code = code
.replace(/\\n/g, '\n')
.replace(/\\t/g, '\t')
.replace(/\\r/g, '\r')
.replace(/\\"/g, '"')
.replace(/\\'/g, "'")
.replace(/\\\\/g, '\\');
this.logDebug('最终提取的代码: ' + code.substring(0, 100) + (code.length > 100 ? '...' : ''));
return code.trim();
},
// ---------- 渲染引擎 ----------
/**
* 批量渲染所有 Mermaid 图表
* 需求 18.1: 使用批量渲染,避免阻塞主线程
* 需求 18.4: 延迟渲染视口外的图表,优先渲染可见图表
*/
renderAllCharts() {
if (!this.initialized) {
this.logDebug('Mermaid 未初始化,跳过渲染');
return;
}
// 检测所有代码块(一次 DOM 遍历)
const blocks = this.detectMermaidBlocks();
if (blocks.length === 0) {
this.logDebug('未找到 Mermaid 代码块');
return;
}
this.logDebug(`准备批量渲染 ${blocks.length} 个图表`);
// 需求 18.4: 将图表分为视口内和视口外两组
const visibleBlocks = [];
const invisibleBlocks = [];
blocks.forEach(block => {
if (this.isInViewport(block)) {
visibleBlocks.push(block);
} else {
invisibleBlocks.push(block);
}
});
this.logDebug(`视口内图表: ${visibleBlocks.length}, 视口外图表: ${invisibleBlocks.length}`);
// 优先渲染视口内的图表
let currentIndex = 0;
const batchSize = 3; // 每批渲染 3 个图表
const renderBatch = (blockList, isVisible) => {
if (currentIndex >= blockList.length) {
// 当前列表渲染完成
if (isVisible && invisibleBlocks.length > 0) {
// 视口内图表渲染完成,开始渲染视口外图表
this.logDebug('视口内图表渲染完成,开始渲染视口外图表');
currentIndex = 0;
requestAnimationFrame(() => renderBatch(invisibleBlocks, false));
} else {
this.logDebug(`完成渲染 ${blocks.length} 个图表`);
}
return;
}
const endIndex = Math.min(currentIndex + batchSize, blockList.length);
// 渲染当前批次
for (let i = currentIndex; i < endIndex; i++) {
const globalIndex = blocks.indexOf(blockList[i]);
this.renderChart(blockList[i], globalIndex);
}
currentIndex = endIndex;
// 继续下一批
requestAnimationFrame(() => renderBatch(blockList, isVisible));
};
// 开始渲染(优先渲染视口内图表)
if (visibleBlocks.length > 0) {
requestAnimationFrame(() => renderBatch(visibleBlocks, true));
} else if (invisibleBlocks.length > 0) {
// 如果没有视口内图表,直接渲染视口外图表
requestAnimationFrame(() => renderBatch(invisibleBlocks, false));
}
},
/**
* 检查元素是否在视口内
* @param {HTMLElement} element - 要检查的元素
* @returns {boolean} 是否在视口内
*/
isInViewport(element) {
const rect = element.getBoundingClientRect();
const windowHeight = window.innerHeight || document.documentElement.clientHeight;
const windowWidth = window.innerWidth || document.documentElement.clientWidth;
// 元素至少部分可见
return (
rect.top < windowHeight &&
rect.bottom > 0 &&
rect.left < windowWidth &&
rect.right > 0
);
},
/**
* 渲染单个图表
* 需求 18.5: 图表渲染失败不阻塞其他图表的渲染
* @param {HTMLElement} element - 代码块元素
* @param {number} index - 图表索引
*/
renderChart(element, index) {
const chartId = `mermaid-chart-${Date.now()}-${index}`;
// 需求 18.5: 使用 try-catch 包裹整个渲染过程
try {
// 检查是否已经是错误容器(避免重复处理错误)
if (element.classList && element.classList.contains('mermaid-error-container')) {
this.logDebug(`元素已经是错误容器,跳过: ${chartId}`);
return;
}
// 检查是否已经是渲染成功的容器(避免重复渲染)
if (element.classList && element.classList.contains('mermaid-container') && element.dataset.mermaidCode) {
this.logDebug(`图表已成功渲染,跳过: ${chartId}`);
return;
}
// 检查是否已渲染(避免重复渲染)
if (this.rendered.has(element)) {
this.logDebug(`图表已渲染,跳过: ${chartId}`);
return;
}
// 提取代码
const code = this.extractMermaidCode(element);
if (!code) {
this.logDebug(`代码块为空,跳过: ${chartId}`);
return;
}
this.logDebug(`准备渲染图表: ${chartId}, 代码长度: ${code.length}`);
this.logDebug(`代码内容: ${code.substring(0, 200)}`);
// 需求 18.2: 创建加载动画容器
const loadingContainer = document.createElement('div');
loadingContainer.className = 'mermaid-loading';
loadingContainer.innerHTML = `
正在渲染图表...
`;
// 替换原始元素为加载动画
if (element.parentNode) {
element.parentNode.replaceChild(loadingContainer, element);
}
// 创建最终容器
const container = document.createElement('div');
container.className = 'mermaid-container';
container.id = chartId;
// 检查 Mermaid API
if (typeof window.mermaid === 'undefined') {
this.logError('Mermaid 库未加载');
this.handleRenderError(loadingContainer, new Error('Mermaid 库未加载'), code);
return;
}
if (typeof window.mermaid.render !== 'function') {
this.logError('Mermaid.render 方法不存在');
this.handleRenderError(loadingContainer, new Error('Mermaid.render 方法不存在'), code);
return;
}
// 渲染图表
const renderPromise = window.mermaid.render(`mermaid-svg-${chartId}`, code);
// 检查返回值是否是 Promise
if (!renderPromise || typeof renderPromise.then !== 'function') {
this.logError('Mermaid.render 没有返回 Promise,可能是版本不兼容');
// 尝试使用旧版 API
this.renderChartLegacy(loadingContainer, code, container, chartId);
return;
}
renderPromise.then(result => {
// 渲染成功
container.innerHTML = result.svg;
// 保存原始代码(用于主题切换时重新渲染)
container.dataset.mermaidCode = code;
container.dataset.currentTheme = this.getMermaidTheme();
// 替换加载动画为渲染结果
if (loadingContainer.parentNode) {
loadingContainer.parentNode.replaceChild(container, loadingContainer);
}
// 标记为已渲染
this.rendered.add(container);
// 应用样式增强(包含淡入动画)
this.applyStyles(container);
this.logDebug(`图表渲染成功: ${chartId}`);
}).catch(error => {
// 需求 18.5: 渲染失败时记录错误但不抛出异常
this.logError(`图表渲染失败: ${chartId}`, error);
this.handleRenderError(loadingContainer, error, code);
});
} catch (error) {
// 需求 18.5: 捕获所有异常,确保不影响其他图表
this.logError(`渲染异常: ${chartId}`, error);
// 尝试显示错误信息
try {
this.handleRenderError(element, error, '');
} catch (e) {
// 如果错误处理也失败,只记录日志
this.logError('错误处理失败', e);
}
}
},
/**
* 使用旧版 Mermaid API 渲染(兼容 Mermaid 8.x)
* @param {HTMLElement} element - 原始代码块元素
* @param {string} code - Mermaid 代码
* @param {HTMLElement} container - 容器元素
* @param {string} chartId - 图表 ID
*/
renderChartLegacy(element, code, container, chartId) {
try {
this.logDebug('尝试使用旧版 Mermaid API');
// Mermaid 8.x 使用 mermaidAPI.render
if (typeof window.mermaidAPI !== 'undefined' && typeof window.mermaidAPI.render === 'function') {
window.mermaidAPI.render(`mermaid-svg-${chartId}`, code, (svgCode) => {
container.innerHTML = svgCode;
container.dataset.mermaidCode = code;
container.dataset.currentTheme = this.getMermaidTheme();
element.parentNode.replaceChild(container, element);
this.rendered.add(container);
this.applyStyles(container);
this.logDebug(`图表渲染成功(旧版 API): ${chartId}`);
});
} else {
// 最后的降级方案:直接使用 mermaid.init()
container.className = 'mermaid';
container.textContent = code;
element.parentNode.replaceChild(container, element);
if (typeof window.mermaid.init === 'function') {
window.mermaid.init(undefined, container);
this.rendered.add(container);
this.logDebug(`图表渲染成功(init 方法): ${chartId}`);
} else {
this.handleRenderError(element, new Error('无可用的 Mermaid 渲染方法'), code);
}
}
} catch (error) {
this.logError('旧版 API 渲染失败', error);
this.handleRenderError(element, error, code);
}
},
/**
* 处理渲染错误
* @param {HTMLElement} element - 原始代码块元素
* @param {Error} error - 错误对象
* @param {string} code - 原始代码
*/
handleRenderError(element, error, code) {
this.logError('图表渲染失败', error);
// 如果元素已经是错误容器,避免重复嵌套
if (element.classList && element.classList.contains('mermaid-error-container')) {
this.logDebug('元素已经是错误容器,跳过重复处理');
return;
}
// 提取原始代码(优先使用传入的 code,其次从元素中提取)
let originalCode = code;
if (!originalCode) {
// 尝试从不同类型的元素中提取代码
if (element.dataset && element.dataset.mermaidCode) {
originalCode = element.dataset.mermaidCode;
} else if (element.textContent) {
originalCode = element.textContent.trim();
}
}
// 创建错误提示容器
const errorContainer = document.createElement('div');
errorContainer.className = 'mermaid-error-container';
errorContainer.dataset.errorHandled = 'true'; // 标记已处理
// 提取错误信息
const errorMessage = error.message || '未知错误';
const errorType = this.getErrorType(errorMessage);
const errorLine = this.extractErrorLine(errorMessage);
// 创建错误显示结构
const errorHeader = document.createElement('div');
errorHeader.className = 'mermaid-error-header';
errorHeader.innerHTML = `
Mermaid 图表渲染失败
`;
const errorBody = document.createElement('div');
errorBody.className = 'mermaid-error-body';
let errorBodyHTML = `
错误类型: ${errorType}
`;
// 如果提取到错误行号,显示它
if (errorLine) {
errorBodyHTML += `错误位置: 第 ${errorLine} 行
`;
}
errorBody.innerHTML = errorBodyHTML;
// 创建代码查看区域
const codeDetails = document.createElement('details');
codeDetails.className = 'mermaid-error-code';
const codeSummary = document.createElement('summary');
codeSummary.textContent = '📄 查看原始代码';
const codeBlock = document.createElement('pre');
const codeElement = document.createElement('code');
codeElement.className = 'language-mermaid';
codeElement.textContent = originalCode || '(无法提取代码)';
codeBlock.appendChild(codeElement);
codeDetails.appendChild(codeSummary);
codeDetails.appendChild(codeBlock);
// 组装错误容器
errorContainer.appendChild(errorHeader);
errorContainer.appendChild(errorBody);
errorContainer.appendChild(codeDetails);
// 替换原始代码块
if (element.parentNode) {
element.parentNode.replaceChild(errorContainer, element);
}
},
/**
* 获取错误类型
* @param {string} errorMessage - 错误信息
* @returns {string} 错误类型
*/
getErrorType(errorMessage) {
if (errorMessage.includes('Syntax') || errorMessage.includes('Parse')) {
return '语法错误';
} else if (errorMessage.includes('Lexical')) {
return '词法错误';
} else if (errorMessage.includes('Expecting')) {
return '格式错误';
} else if (errorMessage.includes('Unknown')) {
return '未知图表类型';
}
return '渲染错误';
},
/**
* 从错误信息中提取行号
* @param {string} errorMessage - 错误信息
* @returns {string|null} 行号或 null
*/
extractErrorLine(errorMessage) {
// 尝试匹配常见的行号格式
const linePatterns = [
/line (\d+)/i,
/at line (\d+)/i,
/on line (\d+)/i,
/第 (\d+) 行/,
];
for (const pattern of linePatterns) {
const match = errorMessage.match(pattern);
if (match && match[1]) {
return match[1];
}
}
return null;
},
/**
* HTML 转义
* @param {string} text - 要转义的文本
* @returns {string} 转义后的文本
*/
escapeHtml(text) {
const div = document.createElement('div');
div.textContent = text;
return div.innerHTML;
},
// ---------- 样式增强 ----------
/**
* 应用容器样式并添加缩放控制
* 需求 18.3: 使用淡入动画,提升视觉体验
* @param {HTMLElement} container - 图表容器
*/
applyStyles(container) {
// 需求 18.3: 添加淡入动画
container.style.opacity = '0';
// 使用 requestAnimationFrame 确保动画流畅
requestAnimationFrame(() => {
container.style.transition = 'opacity 0.4s ease-in';
container.style.opacity = '1';
// 动画完成后清理过渡样式
setTimeout(() => {
container.style.transition = '';
}, 400);
});
// 确保 SVG 响应式
const svg = container.querySelector('svg');
if (svg) {
svg.style.width = '100%';
svg.style.height = 'auto';
}
// 添加缩放功能
this.addZoomControls(container);
},
/**
* 添加缩放控制按钮
* @param {HTMLElement} container - 图表容器
*/
addZoomControls(container) {
// 创建内部容器包裹 SVG
const svg = container.querySelector('svg');
if (!svg) return;
// 创建内部容器
const inner = document.createElement('div');
inner.className = 'mermaid-container-inner';
// 将 SVG 移到内部容器
container.appendChild(inner);
inner.appendChild(svg);
// 创建缩放控制
// 需求 14.1: 添加全屏按钮
// 需求 15.1: 添加导出按钮和菜单
const controls = document.createElement('div');
controls.className = 'mermaid-zoom-controls';
controls.innerHTML = `
100%
`;
container.appendChild(controls);
// 需求 15.1: 创建导出菜单
const exportMenu = document.createElement('div');
exportMenu.className = 'mermaid-export-menu';
exportMenu.innerHTML = `
`;
container.appendChild(exportMenu);
// 创建提示文本
const hint = document.createElement('div');
hint.className = 'mermaid-hint';
hint.textContent = '按住 Ctrl+滚轮缩放 | 拖拽移动';
container.appendChild(hint);
// 缩放状态
let scale = 1;
const minScale = 0.5;
const maxScale = 3;
const step = 0.25;
// 更新缩放显示
// 需求 12.2, 12.3: 使用 CSS transform 实现硬件加速和平滑过渡
// 需求 12.4, 20.3: 更新按钮状态
const updateZoom = (useTransition = true) => {
// 控制是否使用过渡动画
if (!useTransition) {
inner.style.transition = 'none';
} else {
inner.style.transition = 'transform 0.3s ease';
}
inner.style.transform = `scale(${scale})`;
controls.querySelector('.mermaid-zoom-level').textContent = Math.round(scale * 100) + '%';
// 更新按钮状态
const zoomInBtn = controls.querySelector('[data-action="zoom-in"]');
const zoomOutBtn = controls.querySelector('[data-action="zoom-out"]');
// 禁用/启用放大按钮
if (scale >= maxScale) {
zoomInBtn.disabled = true;
zoomInBtn.style.opacity = '0.4';
zoomInBtn.style.cursor = 'not-allowed';
} else {
zoomInBtn.disabled = false;
zoomInBtn.style.opacity = '1';
zoomInBtn.style.cursor = 'pointer';
}
// 禁用/启用缩小按钮
if (scale <= minScale) {
zoomOutBtn.disabled = true;
zoomOutBtn.style.opacity = '0.4';
zoomOutBtn.style.cursor = 'not-allowed';
} else {
zoomOutBtn.disabled = false;
zoomOutBtn.style.opacity = '1';
zoomOutBtn.style.cursor = 'pointer';
}
// 恢复过渡(用于下次按钮点击)
if (!useTransition) {
requestAnimationFrame(() => {
inner.style.transition = 'transform 0.3s ease';
});
}
};
// 绑定按钮事件
// 需求 14.1, 14.2, 14.3: 全屏按钮功能
// 需求 15.1: 导出按钮功能
controls.addEventListener('click', (e) => {
const btn = e.target.closest('.mermaid-zoom-btn');
if (!btn || btn.disabled) return; // 忽略禁用的按钮
const action = btn.dataset.action;
if (action === 'zoom-in' && scale < maxScale) {
scale = Math.min(scale + step, maxScale);
updateZoom(true); // 按钮点击使用平滑过渡
} else if (action === 'zoom-out' && scale > minScale) {
scale = Math.max(scale - step, minScale);
updateZoom(true); // 按钮点击使用平滑过渡
} else if (action === 'zoom-reset') {
scale = 1;
updateZoom(true); // 重置使用平滑过渡
} else if (action === 'fullscreen') {
// 需求 14.1: 点击全屏按钮
toggleFullscreen();
} else if (action === 'export') {
// 需求 15.1: 点击导出按钮,显示/隐藏导出菜单
toggleExportMenu();
}
});
// ==========================================================================
// 需求 15: Mermaid 导出功能
// ==========================================================================
/**
* 切换导出菜单显示/隐藏
* 需求 15.1: 点击导出按钮显示导出选项
*/
const toggleExportMenu = () => {
const isVisible = exportMenu.classList.contains('visible');
if (isVisible) {
exportMenu.classList.remove('visible');
} else {
exportMenu.classList.add('visible');
}
};
// 点击导出选项
exportMenu.addEventListener('click', (e) => {
const option = e.target.closest('.mermaid-export-option');
if (!option) return;
const format = option.dataset.format;
exportMenu.classList.remove('visible');
if (format === 'png') {
exportAsPNG();
} else if (format === 'svg') {
exportAsSVG();
}
});
// 点击容器外部关闭导出菜单
document.addEventListener('click', (e) => {
if (!container.contains(e.target)) {
exportMenu.classList.remove('visible');
}
});
/**
* 导出为 PNG
* 需求 15.2: 将图表转换为 PNG 图片并下载
* 需求 15.4: 保持图表当前的缩放级别和样式
*/
const exportAsPNG = async () => {
try {
const svgElement = inner.querySelector('svg');
if (!svgElement) {
throw new Error('未找到 SVG 元素');
}
// 克隆 SVG 以避免修改原始元素
const clonedSvg = svgElement.cloneNode(true);
// 需求 15.4: 应用当前缩放级别
const svgWidth = svgElement.getBoundingClientRect().width * scale;
const svgHeight = svgElement.getBoundingClientRect().height * scale;
clonedSvg.setAttribute('width', svgWidth);
clonedSvg.setAttribute('height', svgHeight);
// 将 SVG 转换为 data URL
const svgData = new XMLSerializer().serializeToString(clonedSvg);
const svgBlob = new Blob([svgData], { type: 'image/svg+xml;charset=utf-8' });
const svgUrl = URL.createObjectURL(svgBlob);
// 创建图片元素
const img = new Image();
img.onload = () => {
// 创建 canvas
const canvas = document.createElement('canvas');
canvas.width = svgWidth;
canvas.height = svgHeight;
const ctx = canvas.getContext('2d');
// 设置白色背景(PNG 默认透明)
ctx.fillStyle = '#ffffff';
ctx.fillRect(0, 0, canvas.width, canvas.height);
// 绘制图片
ctx.drawImage(img, 0, 0, svgWidth, svgHeight);
// 转换为 PNG 并下载
canvas.toBlob((blob) => {
if (blob) {
downloadFile(blob, 'mermaid-chart.png');
}
URL.revokeObjectURL(svgUrl);
}, 'image/png');
};
// 需求 15.5: 添加导出错误处理
img.onerror = () => {
URL.revokeObjectURL(svgUrl);
throw new Error('图片加载失败');
};
img.src = svgUrl;
} catch (error) {
// 需求 15.5: 显示友好的错误提示
handleExportError('PNG', error);
}
};
/**
* 导出为 SVG
* 需求 15.3: 将 SVG 代码保存为文件并下载
* 需求 15.4: 保持图表当前的缩放级别和样式
*/
const exportAsSVG = () => {
try {
const svgElement = inner.querySelector('svg');
if (!svgElement) {
throw new Error('未找到 SVG 元素');
}
// 克隆 SVG
const clonedSvg = svgElement.cloneNode(true);
// 需求 15.4: 应用当前缩放级别
if (scale !== 1) {
const svgWidth = svgElement.getBoundingClientRect().width * scale;
const svgHeight = svgElement.getBoundingClientRect().height * scale;
clonedSvg.setAttribute('width', svgWidth);
clonedSvg.setAttribute('height', svgHeight);
}
// 添加 XML 声明和命名空间
const svgData = new XMLSerializer().serializeToString(clonedSvg);
const svgWithDeclaration = '\n' + svgData;
// 创建 Blob 并下载
const blob = new Blob([svgWithDeclaration], { type: 'image/svg+xml;charset=utf-8' });
downloadFile(blob, 'mermaid-chart.svg');
} catch (error) {
// 需求 15.5: 显示友好的错误提示
handleExportError('SVG', error);
}
};
/**
* 下载文件
* @param {Blob} blob - 文件内容
* @param {string} filename - 文件名
*/
const downloadFile = (blob, filename) => {
const url = URL.createObjectURL(blob);
const link = document.createElement('a');
link.href = url;
link.download = filename;
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
URL.revokeObjectURL(url);
};
/**
* 处理导出错误
* 需求 15.5: 显示友好的错误提示
* @param {string} format - 导出格式
* @param {Error} error - 错误对象
*/
const handleExportError = (format, error) => {
console.error(`导出 ${format} 失败:`, error);
// 显示错误提示
const errorMsg = document.createElement('div');
errorMsg.className = 'mermaid-export-error';
errorMsg.textContent = `导出 ${format} 失败: ${error.message}`;
container.appendChild(errorMsg);
// 3秒后自动移除错误提示
setTimeout(() => {
if (errorMsg.parentNode) {
errorMsg.parentNode.removeChild(errorMsg);
}
}, 3000);
};
// 鼠标滚轮缩放(按住 Ctrl)
// 需求 12.1: 以鼠标为中心进行缩放
container.addEventListener('wheel', (e) => {
if (e.ctrlKey || e.metaKey) {
e.preventDefault();
const delta = e.deltaY > 0 ? -step : step;
const newScale = Math.max(minScale, Math.min(maxScale, scale + delta));
if (newScale !== scale) {
// 获取鼠标相对于容器的位置
const rect = container.getBoundingClientRect();
const mouseX = e.clientX - rect.left;
const mouseY = e.clientY - rect.top;
// 计算鼠标相对于内容的位置(缩放前)
const scrollLeft = container.scrollLeft;
const scrollTop = container.scrollTop;
const offsetX = (scrollLeft + mouseX) / scale;
const offsetY = (scrollTop + mouseY) / scale;
// 更新缩放(滚轮缩放不使用过渡动画,更流畅)
scale = newScale;
updateZoom(false);
// 调整滚动位置,使鼠标位置保持不变
container.scrollLeft = offsetX * scale - mouseX;
container.scrollTop = offsetY * scale - mouseY;
}
}
}, { passive: false });
// 拖拽功能
let isDragging = false;
let startX = 0;
let startY = 0;
let scrollLeft = 0;
let scrollTop = 0;
let rafId = null;
let pendingX = 0;
let pendingY = 0;
// 需求 13.5: 智能启用拖拽 - 检查图表是否需要拖拽
const checkDragEnabled = () => {
const svgElement = inner.querySelector('svg');
if (!svgElement) return false;
// 如果图表未缩放且完全可见,禁用拖拽
const containerRect = container.getBoundingClientRect();
const svgRect = svgElement.getBoundingClientRect();
const isFullyVisible = svgRect.width <= containerRect.width &&
svgRect.height <= containerRect.height;
const isNotZoomed = Math.abs(scale - 1) < 0.01;
return !(isFullyVisible && isNotZoomed);
};
// 需求 13.3: 使用 requestAnimationFrame 优化拖拽性能
const updateDragPosition = () => {
if (!isDragging) {
rafId = null;
return;
}
inner.scrollLeft = scrollLeft - pendingX;
inner.scrollTop = scrollTop - pendingY;
rafId = requestAnimationFrame(updateDragPosition);
};
inner.addEventListener('mousedown', (e) => {
// 只响应左键
if (e.button !== 0) return;
// 需求 13.5: 检查是否需要启用拖拽
if (!checkDragEnabled()) return;
isDragging = true;
// 需求 13.1: 改变鼠标光标为抓手样式
inner.classList.add('dragging');
startX = e.pageX - inner.offsetLeft;
startY = e.pageY - inner.offsetTop;
scrollLeft = inner.scrollLeft;
scrollTop = inner.scrollTop;
// 需求 13.2: 禁用文本选择
e.preventDefault();
// 启动 RAF 循环
if (!rafId) {
rafId = requestAnimationFrame(updateDragPosition);
}
});
document.addEventListener('mousemove', (e) => {
if (!isDragging) return;
e.preventDefault();
const x = e.pageX - inner.offsetLeft;
const y = e.pageY - inner.offsetTop;
// 更新待处理的位置,由 RAF 循环处理
pendingX = (x - startX) * 2;
pendingY = (y - startY) * 2;
});
document.addEventListener('mouseup', () => {
if (isDragging) {
isDragging = false;
// 需求 13.4: 恢复正常光标
inner.classList.remove('dragging');
// 取消 RAF 循环
if (rafId) {
cancelAnimationFrame(rafId);
rafId = null;
}
}
});
// 防止拖拽时选中文本
inner.addEventListener('dragstart', (e) => {
e.preventDefault();
});
// 需求 13.2: 禁用文本选择
inner.addEventListener('selectstart', (e) => {
if (isDragging) {
e.preventDefault();
}
});
// 需求 12.5: 双击智能缩放
// 双击图表时,如果当前是默认大小则放大到 2 倍,否则重置到 1 倍
inner.addEventListener('dblclick', (e) => {
e.preventDefault();
// 获取双击位置
const rect = container.getBoundingClientRect();
const mouseX = e.clientX - rect.left;
const mouseY = e.clientY - rect.top;
// 计算鼠标相对于内容的位置(缩放前)
const scrollLeft = container.scrollLeft;
const scrollTop = container.scrollTop;
const offsetX = (scrollLeft + mouseX) / scale;
const offsetY = (scrollTop + mouseY) / scale;
// 智能缩放:如果接近默认大小则放大,否则重置
const targetScale = Math.abs(scale - 1) < 0.1 ? 2 : 1;
scale = targetScale;
updateZoom(true); // 使用平滑过渡
// 调整滚动位置,使双击位置保持在视野中心
setTimeout(() => {
container.scrollLeft = offsetX * scale - mouseX;
container.scrollTop = offsetY * scale - mouseY;
}, 50);
});
// ==========================================================================
// 需求 14: Mermaid 全屏模式
// ==========================================================================
// 全屏状态
let isFullscreen = false;
let savedScale = 1;
let savedScrollLeft = 0;
let savedScrollTop = 0;
/**
* 切换全屏模式
* 需求 14.1: 点击全屏按钮将图表全屏显示
* 需求 14.2, 14.3: 全屏模式下保持缩放和拖拽功能可用
* 需求 14.5: 退出全屏恢复图表原始状态
*/
const toggleFullscreen = () => {
if (!isFullscreen) {
// 进入全屏
enterFullscreen();
} else {
// 退出全屏
exitFullscreen();
}
};
/**
* 进入全屏模式
*/
const enterFullscreen = () => {
// 保存当前状态
savedScale = scale;
savedScrollLeft = container.scrollLeft;
savedScrollTop = container.scrollTop;
// 添加全屏类
container.classList.add('mermaid-fullscreen');
isFullscreen = true;
// 更新全屏按钮
const fullscreenBtn = controls.querySelector('[data-action="fullscreen"]');
if (fullscreenBtn) {
fullscreenBtn.textContent = '⛶';
fullscreenBtn.title = '退出全屏';
fullscreenBtn.classList.add('active');
}
// 需求 14.3: 全屏模式下显示退出全屏按钮(已在工具栏中)
// 工具栏在全屏模式下仍然可见
// 禁用页面滚动
document.body.style.overflow = 'hidden';
this.logDebug('进入全屏模式');
};
/**
* 退出全屏模式
* 需求 14.5: 恢复图表原始状态(缩放级别和位置)
*/
const exitFullscreen = () => {
// 移除全屏类
container.classList.remove('mermaid-fullscreen');
isFullscreen = false;
// 更新全屏按钮
const fullscreenBtn = controls.querySelector('[data-action="fullscreen"]');
if (fullscreenBtn) {
fullscreenBtn.textContent = '⛶';
fullscreenBtn.title = '全屏';
fullscreenBtn.classList.remove('active');
}
// 恢复原始状态
scale = savedScale;
updateZoom(false);
// 恢复滚动位置
setTimeout(() => {
container.scrollLeft = savedScrollLeft;
container.scrollTop = savedScrollTop;
}, 50);
// 恢复页面滚动
document.body.style.overflow = '';
this.logDebug('退出全屏模式');
};
// 需求 14.4: 按 ESC 键退出全屏模式
const handleEscKey = (e) => {
if (e.key === 'Escape' && isFullscreen) {
exitFullscreen();
}
};
// 绑定 ESC 键事件
document.addEventListener('keydown', handleEscKey);
// 清理函数(在容器销毁时调用)
container.dataset.cleanupFullscreen = 'registered';
container._fullscreenCleanup = () => {
document.removeEventListener('keydown', handleEscKey);
if (isFullscreen) {
exitFullscreen();
}
};
// ==========================================================================
// 需求 16.2-16.4: 移动端触摸手势支持
// ==========================================================================
// 触摸状态
let touchStartDistance = 0;
let touchStartScale = 1;
let touchStartX = 0;
let touchStartY = 0;
let isTouchDragging = false;
let touchScrollLeft = 0;
let touchScrollTop = 0;
/**
* 计算两个触摸点之间的距离
* @param {Touch} touch1 - 第一个触摸点
* @param {Touch} touch2 - 第二个触摸点
* @returns {number} 距离
*/
const getTouchDistance = (touch1, touch2) => {
const dx = touch2.clientX - touch1.clientX;
const dy = touch2.clientY - touch1.clientY;
return Math.sqrt(dx * dx + dy * dy);
};
/**
* 获取两个触摸点的中心位置
* @param {Touch} touch1 - 第一个触摸点
* @param {Touch} touch2 - 第二个触摸点
* @returns {Object} 中心位置 {x, y}
*/
const getTouchCenter = (touch1, touch2) => {
return {
x: (touch1.clientX + touch2.clientX) / 2,
y: (touch1.clientY + touch2.clientY) / 2
};
};
// 需求 16.2: 双指缩放手势
inner.addEventListener('touchstart', (e) => {
if (e.touches.length === 2) {
// 双指触摸 - 缩放模式
e.preventDefault();
touchStartDistance = getTouchDistance(e.touches[0], e.touches[1]);
touchStartScale = scale;
// 记录缩放中心点
const center = getTouchCenter(e.touches[0], e.touches[1]);
const rect = container.getBoundingClientRect();
touchStartX = center.x - rect.left;
touchStartY = center.y - rect.top;
} else if (e.touches.length === 1) {
// 需求 16.3: 单指拖拽移动
// 检查是否需要启用拖拽
if (!checkDragEnabled()) return;
isTouchDragging = true;
inner.classList.add('dragging');
const touch = e.touches[0];
touchStartX = touch.clientX;
touchStartY = touch.clientY;
touchScrollLeft = container.scrollLeft;
touchScrollTop = container.scrollTop;
}
}, { passive: false });
// 需求 16.4: 优化触摸事件响应速度
inner.addEventListener('touchmove', (e) => {
if (e.touches.length === 2) {
// 双指缩放
e.preventDefault();
const currentDistance = getTouchDistance(e.touches[0], e.touches[1]);
const scaleChange = currentDistance / touchStartDistance;
const newScale = Math.max(minScale, Math.min(maxScale, touchStartScale * scaleChange));
if (newScale !== scale) {
// 获取缩放中心点
const center = getTouchCenter(e.touches[0], e.touches[1]);
const rect = container.getBoundingClientRect();
const centerX = center.x - rect.left;
const centerY = center.y - rect.top;
// 计算缩放前的内容位置
const scrollLeft = container.scrollLeft;
const scrollTop = container.scrollTop;
const offsetX = (scrollLeft + centerX) / scale;
const offsetY = (scrollTop + centerY) / scale;
// 更新缩放(不使用过渡动画,更流畅)
scale = newScale;
updateZoom(false);
// 调整滚动位置,使缩放中心保持不变
requestAnimationFrame(() => {
container.scrollLeft = offsetX * scale - centerX;
container.scrollTop = offsetY * scale - centerY;
});
}
} else if (e.touches.length === 1 && isTouchDragging) {
// 单指拖拽
e.preventDefault();
const touch = e.touches[0];
const deltaX = touch.clientX - touchStartX;
const deltaY = touch.clientY - touchStartY;
// 使用 requestAnimationFrame 优化性能
requestAnimationFrame(() => {
container.scrollLeft = touchScrollLeft - deltaX;
container.scrollTop = touchScrollTop - deltaY;
});
}
}, { passive: false });
inner.addEventListener('touchend', (e) => {
if (isTouchDragging) {
isTouchDragging = false;
inner.classList.remove('dragging');
}
// 重置触摸状态
if (e.touches.length === 0) {
touchStartDistance = 0;
touchStartScale = 1;
}
}, { passive: true });
inner.addEventListener('touchcancel', () => {
if (isTouchDragging) {
isTouchDragging = false;
inner.classList.remove('dragging');
}
touchStartDistance = 0;
touchStartScale = 1;
}, { passive: true });
},
// ---------- 主题切换监听 ----------
/**
* 监听主题切换事件
*/
listenThemeSwitch() {
// 如果配置了固定主题,不需要监听切换
if (this.config && this.config.theme !== 'auto') {
return;
}
// 监听 Argon 主题切换事件
document.addEventListener('argon:theme-switched', () => {
this.reRenderCharts();
});
// 使用 MutationObserver 监听 darkmode class 变化
const observer = new MutationObserver((mutations) => {
mutations.forEach((mutation) => {
if (mutation.attributeName === 'class') {
const isDarkMode = document.documentElement.classList.contains('darkmode');
const currentTheme = isDarkMode ? 'dark' : 'default';
// 检查主题是否真的改变了
const firstChart = document.querySelector('.mermaid-container');
if (firstChart && firstChart.dataset.currentTheme !== currentTheme) {
this.reRenderCharts();
}
}
});
});
observer.observe(document.documentElement, {
attributes: true,
attributeFilter: ['class']
});
this.logDebug('主题切换监听器已启动');
},
/**
* 重新渲染所有图表(主题切换时)
* 需求 17.3: 保持图表的缩放级别和位置
* 需求 17.4, 17.5: 使用淡入淡出过渡效果
*/
reRenderCharts() {
// 只选择成功渲染的图表容器,排除错误容器
const charts = document.querySelectorAll('.mermaid-container:not(.mermaid-error-container)');
if (charts.length === 0) {
return;
}
this.logDebug(`重新渲染 ${charts.length} 个图表`);
// 更新 Mermaid 主题配置
const newTheme = this.getMermaidTheme();
try {
window.mermaid.initialize({
startOnLoad: false,
theme: newTheme,
securityLevel: 'loose',
logLevel: this.config.debugMode ? 'debug' : 'error',
flowchart: {
useMaxWidth: true,
htmlLabels: true,
curve: 'basis'
},
sequence: {
useMaxWidth: true,
wrap: true
},
gantt: {
useMaxWidth: true
}
});
// 重新渲染每个图表
charts.forEach((chart, index) => {
const code = chart.dataset.mermaidCode;
if (!code) {
this.logDebug('图表缺少原始代码,跳过重新渲染');
return;
}
// 检查主题是否真的需要更新
if (chart.dataset.currentTheme === newTheme) {
this.logDebug('图表主题未改变,跳过重新渲染');
return;
}
// 需求 17.3: 保存当前的缩放级别和滚动位置
const inner = chart.querySelector('.mermaid-container-inner');
let savedState = null;
if (inner) {
const transform = window.getComputedStyle(inner).transform;
const scrollLeft = inner.scrollLeft;
const scrollTop = inner.scrollTop;
// 提取缩放比例
let scale = 1;
if (transform && transform !== 'none') {
const matrix = transform.match(/matrix\(([^)]+)\)/);
if (matrix) {
const values = matrix[1].split(',').map(v => parseFloat(v.trim()));
scale = values[0]; // scaleX
}
}
savedState = {
scale: scale,
scrollLeft: scrollLeft,
scrollTop: scrollTop
};
this.logDebug('保存图表状态', savedState);
}
const chartId = `mermaid-chart-rerender-${Date.now()}-${index}`;
// 需求 17.4: 添加淡出动画
chart.style.transition = 'opacity 0.2s ease-out';
chart.style.opacity = '0';
// 等待淡出完成后重新渲染
setTimeout(() => {
window.mermaid.render(`mermaid-svg-${chartId}`, code).then(result => {
// 保存旧的内部容器引用
const oldInner = chart.querySelector('.mermaid-container-inner');
// 更新 SVG 内容
if (oldInner) {
// 只替换 SVG,保留容器结构
const oldSvg = oldInner.querySelector('svg');
if (oldSvg) {
// 创建临时容器解析新的 SVG
const temp = document.createElement('div');
temp.innerHTML = result.svg;
const newSvg = temp.querySelector('svg');
if (newSvg) {
// 替换 SVG
oldSvg.replaceWith(newSvg);
// 确保 SVG 响应式
newSvg.style.width = '100%';
newSvg.style.height = 'auto';
}
}
} else {
// 如果没有内部容器,直接替换内容
chart.innerHTML = result.svg;
// 确保 SVG 响应式
const svg = chart.querySelector('svg');
if (svg) {
svg.style.width = '100%';
svg.style.height = 'auto';
}
}
// 更新主题标记
chart.dataset.currentTheme = newTheme;
// 需求 17.3: 恢复缩放级别和滚动位置
if (savedState && oldInner) {
// 恢复缩放
oldInner.style.transform = `scale(${savedState.scale})`;
// 恢复滚动位置
oldInner.scrollLeft = savedState.scrollLeft;
oldInner.scrollTop = savedState.scrollTop;
// 更新缩放显示
const zoomLevel = chart.querySelector('.mermaid-zoom-level');
if (zoomLevel) {
zoomLevel.textContent = Math.round(savedState.scale * 100) + '%';
}
this.logDebug('恢复图表状态', savedState);
}
// 需求 17.5: 添加淡入动画
requestAnimationFrame(() => {
chart.style.transition = 'opacity 0.3s ease-in';
chart.style.opacity = '1';
// 动画完成后清理过渡样式
setTimeout(() => {
chart.style.transition = '';
}, 300);
});
this.logDebug(`图表重新渲染成功: ${chartId}`);
}).catch(error => {
this.logError('图表重新渲染失败', error);
// 重新渲染失败时,恢复显示
chart.style.opacity = '1';
chart.style.transition = '';
// 不替换为错误容器,保持原样
// 因为之前已经成功渲染过,只是主题切换失败
});
}, 200); // 等待淡出动画完成
});
} catch (error) {
this.logError('重新渲染失败', error);
}
},
// ---------- 日志工具 ----------
/**
* 调试日志
* @param {string} message - 日志信息
* @param {*} data - 附加数据
*/
logDebug(message, data) {
if (this.config && this.config.debugMode) {
if (data) {
console.log(`[Argon Mermaid] ${message}`, data);
} else {
console.log(`[Argon Mermaid] ${message}`);
}
}
},
/**
* 错误日志
* @param {string} message - 错误信息
* @param {Error} error - 错误对象
*/
logError(message, error) {
console.error(`[Argon Mermaid] ${message}`, error);
},
// ---------- 初始化 ----------
/**
* 初始化渲染引擎
*/
init() {
// 检查是否启用
if (typeof window.argonMermaidConfig !== 'undefined' && !window.argonMermaidConfig.enabled) {
this.logDebug('Mermaid 支持未启用');
return;
}
// 检查 Mermaid 库是否加载
if (typeof window.mermaid === 'undefined') {
this.logDebug('Mermaid 库未加载,等待加载...');
// 等待库加载(最多等待 5 秒)
let attempts = 0;
const checkInterval = setInterval(() => {
attempts++;
if (typeof window.mermaid !== 'undefined') {
clearInterval(checkInterval);
this.logDebug('Mermaid 库加载完成');
this.initAndRender();
} else if (attempts >= 50) {
clearInterval(checkInterval);
this.logError('Mermaid 库加载超时');
}
}, 100);
return;
}
this.initAndRender();
},
/**
* 初始化并渲染
*/
initAndRender() {
// 初始化配置
if (!this.initConfig()) {
return;
}
// 渲染所有图表
this.renderAllCharts();
// 监听主题切换
this.listenThemeSwitch();
this.logDebug('Mermaid 渲染引擎初始化完成');
}
};
// ---------- 启动渲染引擎 ----------
// 暴露到全局作用域(用于库加载失败时的降级处理)
window.MermaidRenderer = MermaidRenderer;
// 在 DOM 加载完成后初始化
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', () => {
MermaidRenderer.init();
});
} else {
// DOM 已加载完成,直接初始化
MermaidRenderer.init();
}
// 暴露到全局(用于 PJAX 等场景)
window.ArgonMermaidRenderer = MermaidRenderer;
})();
/* ========== End of Mermaid 图表渲染引擎 ========== */