跳转到内容

MediaWiki:Gadget-UnihanTooltips.js:修订间差异

勤求古训,博采众方
创建页面,内容为“本小工具可以將[[Template:僻字]]的提示由原來的title提示改為元素式彈出提示,使觸控式裝置可以觀看有關提示:​ (function() { // 使用MediaWiki的Cookie方法 const dontLoad = mw.util.getParamValue("UTdontload"); if (dontLoad && !isNaN(dontLoad)) { mw.cookie.set("UTdontload", "1", { path: "/", expires: parseInt(dontLoad) }); } if (mw.cookie.get("UTdontload") === "1") return; (function()…”
 
// Edit via Wikiplus
 
(未显示同一用户的4个中间版本)
第1行: 第1行:
/*
/*
   本小工具可以將[[Template:僻字]]的提示由原來的title提示改為元素式彈出提示,使觸控式裝置可以觀看有關提示
   本小工具可以將[[Template:僻字]]的提示由原來的title提示改為元素式彈出提示,使觸控式裝置可以觀看有關提示
  适配说明:兼容MediaWiki 1.43+,修复模块依赖错误,移除jquery.ui.animate依赖,优化动画逻辑
*/
*/
// 显式传入jQuery和mediaWiki,避免$冲突
(function($, mw) {
    "use strict";


(function() {
    /*===== 初始化检查:控制工具是否加载 =====*/
     // 使用MediaWiki的Cookie方法
     // 获取URL参数"UTdontload"(0=清除不加载状态,数字=设置不加载状态)
     const dontLoad = mw.util.getParamValue("UTdontload");
     const dontLoadParam = mw.util.getParamValue("UTdontload");
     if (dontLoad && !isNaN(dontLoad)) {
     if (dontLoadParam !== null) {
        mw.cookie.set("UTdontload", "1", { path: "/", expires: parseInt(dontLoad) });
        const paramVal = parseInt(dontLoadParam, 10);
        if (!isNaN(paramVal)) {
            paramVal === 0 ? mw.storage.remove("UTdontload") : mw.storage.set("UTdontload", "1");
        }
    }
    // 若本地存储存在"不加载"状态,直接终止
    if (mw.storage.get("UTdontload") === "1") {
        return;
     }
     }
    if (mw.cookie.get("UTdontload") === "1") return;


     (function() {
     /*===== 主初始化:命名空间与容器 =====*/
        // 使用MediaWiki提供的content容器
    const canonicalNamespace = mw.config.get("wgCanonicalNamespace");
        const bodyContent = mw.util.$content || document.body;
    const allowedNamespaces = ["", "Project", "Help"]; // 主/项目/帮助命名空间
        const canonicalNamespace = mw.config.get('wgCanonicalNamespace');
    if (!allowedNamespaces.includes(canonicalNamespace)) {
       
        return;
        // 只在这些命名空间运行
    }
        if (!['', 'Project', 'Help'].includes(canonicalNamespace)) return;


        // 更可靠的触摸检测
    // 获取内容容器(优先mw-content-text,兼容旧版本)
        const isTouchscreen = mw.config.get('wgDisplayResolution') === 'mobile' ||  
    const $bodyContent = $("#mw-content-text").length ? $("#mw-content-text") : $("body");
                            'ontouchstart' in document.documentElement;
    const bodyContent = $bodyContent[0];
        const timerLength = isTouchscreen ? 0 : 200;
 
    /*===== 设备类型检测 =====*/
    const isTouchscreen = window.matchMedia("(hover: none), (pointer: coarse)").matches ||
                        mw.config.get("wgDisplayResolution") === "mobile" ||
                        "ontouchstart" in document.documentElement;
    const hoverDelay = isTouchscreen ? 0 : 200; // 触摸设备无延迟
 
    /*===== 工具函数:弹窗核心逻辑 =====*/
    /**
    * 初始化元素数据(存储到元素data中)
    */
    function initElementData(element) {
        let tooltipData = $(element).data("tooltipData");
        if (!tooltipData) {
            const originalTitle = element.title || "";
            element.title = ""; // 清空原生title提示
            $(element).addClass("UHTarget"); // 添加高亮类
            tooltipData = {
                originalTitle: originalTitle,
                tooltipNode: null,
                hideTimer: null,
                showTimer: null
            };
            $(element).data("tooltipData", tooltipData);
        }
        return tooltipData;
    }


        // 添加CSS样式
    /**
         mw.util.addCSS(`
    * 创建弹窗内容(支持多行文本分割)
             .unihantooltip {
    */
                position: absolute;
    function createContentElement(originalTitle) {
                z-index: 999;
         const $contentContainer = $("<div>");
                 background: #fff;
        originalTitle.split("\n").forEach(line => {
                border: 1px solid #ccc;
             const trimmedLine = line.trim();
                padding: 5px;
            if (trimmedLine) {
                border-radius: 3px;
                 $contentContainer.append($("<p>").text(trimmedLine));
                box-shadow: 0 2px 5px rgba(0,0,0,0.2);
                opacity: 0;
                transition: opacity 0.1s;
             }
             }
             .unihantooltip.UHflipped {
        });
                 top: auto !important;
        return $contentContainer[0];
                 bottom: 12px;
    }
             }
 
         `);
    /**
    * 创建弹窗DOM结构(包含内容区和箭头)
    */
    function createTooltipNode(originalTitle) {
        const tooltipNode = document.createElement("ul");
        tooltipNode.className = "unihantooltip";
 
        // 内容区(包含提示文本和设置图标)
        const $contentLi = $("<li>");
        const contentElement = createContentElement(originalTitle);
        const $settingsIcon = $("<div>")
             .addClass("UHsettings")
            .attr("title", "设置")
            .on("click", (e) => {
                 e.stopPropagation();
                // 示例:设置功能入口
                alert("僻字提示设置(可扩展)");
            });
 
        // 组装内容区和箭头
        $contentLi.append(contentElement).append($settingsIcon);
        const $arrowLi = $("<li>");
        $(tooltipNode).append($contentLi).append($arrowLi);
 
        // 非触摸设备:绑定弹窗悬停事件
        if (!isTouchscreen) {
            const tooltipData = { hideTimer: null, showTimer: null };
            $(tooltipNode).data("tooltipData", tooltipData)
                .on("mouseenter", function() {
                    showTooltip($(this).data("tooltipData"), this);
                })
                .on("mouseleave", function() {
                    hideTooltip($(this).data("tooltipData"), this);
                 });
        }
 
        return tooltipNode;
    }
 
    /**
    * 弹窗定位(避免超出视口,精准对齐)
    */
    function positionTooltip(element, tooltipNode) {
        const $element = $(element);
        const $tooltip = $(tooltipNode);
 
        // 离线计算尺寸(避免布局抖动)
        $tooltip.css({ visibility: "hidden", display: "block" }).appendTo(bodyContent);
 
        // 元素与视口信息
        const elementOffset = $element.offset();
        const elementWidth = $element.outerWidth();
        const elementHeight = $element.outerHeight();
        const tooltipWidth = $tooltip.outerWidth();
        const contentHeight = $tooltip.find("li:first-child").outerHeight();
        const arrowHeight = 12; // 与CSS箭头高度一致
        const viewport = {
            top: $(window).scrollTop(),
            bottom: $(window).scrollTop() + $(window).height(),
            left: 0,
            right: $(window).width()
        };
 
        // 垂直定位(优先上方,不足则翻转)
        let top = elementOffset.top - contentHeight - arrowHeight;
        const needFlip = top < viewport.top || (elementOffset.top + contentHeight + arrowHeight) > viewport.bottom;
        $tooltip.toggleClass("UHflipped", needFlip);
        if (needFlip) {
             top = elementOffset.top + elementHeight + arrowHeight - 2; // 适配CSS翻转间距
        }
         top = Math.max(viewport.top, Math.min(top, viewport.bottom - contentHeight));


         $(".inline-unihan").each(function() {
         // 水平定位(居中对齐,留边距)
            let tooltipNode = null;
        let left = elementOffset.left + (elementWidth - tooltipWidth) / 2;
            let hideTimer = null;
        left = Math.max(viewport.left + 10, Math.min(left, viewport.right - tooltipWidth - 10));
            let showTimer = null;


            function hide(refLink) {
        // 箭头定位(指向元素中心)
                if (tooltipNode && tooltipNode.parentNode === bodyContent) {
        const arrowOffset = elementOffset.left - left + elementWidth / 2 - 7;
                    hideTimer = setTimeout(() => {
        $tooltip.find("li:last-child").css("margin-inline-start", `${arrowOffset}px`);
                        $(tooltipNode).animate({
                            opacity: 0
                        }, 100, function() {
                            if (tooltipNode && tooltipNode.parentNode) {
                                tooltipNode.parentNode.removeChild(tooltipNode);
                            }
                        });
                    }, isTouchscreen ? 16 : 100);
                }
            }


            function show() {
        // 应用定位并显示
                if (!tooltipNode.parentNode || tooltipNode.parentNode.nodeType === 11) {
        $tooltip.css({ top: `${top}px`, left: `${left}px`, visibility: "visible", display: "" });
                    bodyContent.appendChild(tooltipNode);
    }
                }
                $(tooltipNode).stop().animate({
                    opacity: 1
                }, 100);
                clearTimeout(hideTimer);
            }


             // 保存原始title并清空
    /**
             const originalTitle = this.title;
    * 显示弹窗(依赖CSS过渡,无JS动画依赖)
             this.title = "";
    */
    function showTooltip(tooltipData, tooltipNode) {
        const $tooltip = $(tooltipNode);
        // 确保弹窗已插入DOM
        if (!tooltipNode.parentNode || tooltipNode.parentNode.nodeType === 11) {
             bodyContent.appendChild(tooltipNode);
        }
        // 添加可见类(触发CSS渲染)
        $tooltip.addClass("is-visible");
        // 直接设置透明度(CSS过渡自动处理动画)
        $tooltip.stop().css("opacity", 1);
        // 清除隐藏定时器
        if (tooltipData.hideTimer) {
             clearTimeout(tooltipData.hideTimer);
             tooltipData.hideTimer = null;
        }
    }


            // 使用jQuery的on方法代替hover
    /**
            $(this).on(isTouchscreen ? 'click' : 'mouseenter mouseleave', function(e) {
    * 隐藏弹窗(依赖CSS过渡,无JS动画依赖)
                const element = this;
    */
               
    function hideTooltip(tooltipData, tooltipNode) {
                if (isTouchscreen && e.type === 'click') {
        const $tooltip = $(tooltipNode);
                    e.preventDefault();
        // 清除显示定时器
                   
        if (tooltipData.showTimer) {
                    if (!tooltipNode || tooltipNode.parentNode !== bodyContent) {
            clearTimeout(tooltipData.showTimer);
                        requestAnimationFrame(() => {
            tooltipData.showTimer = null;
                            $(bodyContent).on("click touchstart", function closeHandler(e) {
        }
                                const target = e.target || e.srcElement;
        // 延迟隐藏(防误触)
                                let currentElement = target;
        tooltipData.hideTimer = setTimeout(() => {
                               
            // 触发淡出(CSS过渡处理)
                                while (currentElement && !currentElement.classList.contains("unihantooltip")) {
            $tooltip.stop().css("opacity", 0);
                                    currentElement = currentElement.parentNode;
            // 过渡结束后清理
                                }
            setTimeout(() => {
                               
                $tooltip.removeClass("is-visible");
                                if (!currentElement) {
                if (bodyContent.contains(tooltipNode)) {
                                    clearTimeout(showTimer);
                    bodyContent.removeChild(tooltipNode);
                                    hide(element);
                                    $(bodyContent).off("click touchstart", closeHandler);
                                }
                            });
                        });
                    }
                 }
                 }
            }, 150); // 与CSS transition时长一致
        }, isTouchscreen ? 16 : 100);
    }


                if (e.type === 'mouseleave') {
    /**
                    clearTimeout(showTimer);
    * 触摸设备:点击外部关闭弹窗
                    hide(this);
    */
                    return;
    function setupClickOutsideHandler(tooltipData, tooltipNode, element) {
                }
        const handler = function(e) {
            if (!tooltipNode.contains(e.target) && e.target !== element) {
                hideTooltip(tooltipData, tooltipNode);
                $(document).off("click touchstart", handler); // 移除事件避免泄漏
                if (e.type === "touchstart") e.preventDefault(); // 防点击穿透
            }
        };
        $(document).on("click touchstart", handler);
    }


                showTimer && clearTimeout(showTimer);
    /**
                showTimer = setTimeout(() => {
    * 触发弹窗显示
                    const tooltipContent = $("<ul class='reflist'></ul>");
    */
                    const lines = originalTitle.split("\n");
    function triggerTooltip(element, tooltipData) {
                   
        if (!tooltipData.originalTitle.trim()) return;
                    lines.forEach(line => {
                        if (line.trim()) {
                            tooltipContent.append($("<li></li>").text(line)).append("<br>");
                        }
                    });


                    if (!tooltipNode) {
        // 创建或更新弹窗
                        tooltipNode = document.createElement("div");
        if (!tooltipData.tooltipNode) {
                        tooltipNode.className = "unihantooltip";
            tooltipData.tooltipNode = createTooltipNode(tooltipData.originalTitle);
                        $(tooltipNode).append(tooltipContent);
        } else {
                       
            const $contentContainer = $(tooltipData.tooltipNode).find("li:first-child > div");
                        if (!isTouchscreen) {
            $contentContainer.replaceWith(createContentElement(tooltipData.originalTitle));
                            $(tooltipNode).on("mouseenter", show)
        }
                                          .on("mouseleave", hide);
                        }
                    }


                    show();
        // 定位并显示
                   
        positionTooltip(element, tooltipData.tooltipNode);
                    const position = $(element).position();
        showTooltip(tooltipData, tooltipData.tooltipNode);
                    const viewport = mw.util.getViewportDimensions();
                    let top = position.top - tooltipNode.offsetHeight;
                    let left = position.left - 7;


                    // 边界检测
        // 触摸设备绑定外部点击关闭
                    if (top < 0) {
        if (isTouchscreen) {
                        $(tooltipNode).addClass("UHflipped");
            setupClickOutsideHandler(tooltipData, tooltipData.tooltipNode, element);
                        top = position.top + $(element).outerHeight();
        }
                    } else {
    }
                        $(tooltipNode).removeClass("UHflipped");
                    }


                    if (left + tooltipNode.offsetWidth > viewport.width) {
    /*===== 事件委托:支持动态元素 =====*/
                        left = viewport.width - tooltipNode.offsetWidth - 10;
    if (isTouchscreen) {
                    }
        // 触摸设备:点击触发
        $bodyContent.on("click", ".inline-unihan", function(e) {
            e.preventDefault();
            const element = this;
            const tooltipData = initElementData(element);
            // 切换显示/隐藏状态
            if (tooltipData.tooltipNode && bodyContent.contains(tooltipData.tooltipNode)) {
                hideTooltip(tooltipData, tooltipData.tooltipNode);
            } else {
                triggerTooltip(element, tooltipData);
            }
        });
    } else {
        // 非触摸设备:鼠标悬停触发
        $bodyContent.on("mouseenter", ".inline-unihan", function() {
            const element = this;
            const tooltipData = initElementData(element);
            tooltipData.showTimer = setTimeout(() => {
                triggerTooltip(element, tooltipData);
            }, hoverDelay);
        }).on("mouseleave", ".inline-unihan", function() {
            const element = this;
            const tooltipData = initElementData(element);
            if (tooltipData.tooltipNode) {
                hideTooltip(tooltipData, tooltipData.tooltipNode);
            }
        });
    }


                    $(tooltipNode).css({
    /*===== 窗口滚动时重新定位弹窗 =====*/
                        top: Math.max(0, top),
    $(window).on("scroll", function() {
                        left: Math.max(0, left)
        $(".inline-unihan").each(function() {
                    });
            const tooltipData = $(this).data("tooltipData");
                 }, timerLength);
            if (tooltipData && tooltipData.tooltipNode && bodyContent.contains(tooltipData.tooltipNode)) {
             });
                 positionTooltip(this, tooltipData.tooltipNode);
             }
         });
         });
     })();
     });
})();
 
})(jQuery, mediaWiki);

2025年11月11日 (二) 12:45的最新版本

/*
  本小工具可以將[[Template:僻字]]的提示由原來的title提示改為元素式彈出提示,使觸控式裝置可以觀看有關提示
  适配说明:兼容MediaWiki 1.43+,修复模块依赖错误,移除jquery.ui.animate依赖,优化动画逻辑
*/
// 显式传入jQuery和mediaWiki,避免$冲突
(function($, mw) {
    "use strict";

    /*===== 初始化检查:控制工具是否加载 =====*/
    // 获取URL参数"UTdontload"(0=清除不加载状态,数字=设置不加载状态)
    const dontLoadParam = mw.util.getParamValue("UTdontload");
    if (dontLoadParam !== null) {
        const paramVal = parseInt(dontLoadParam, 10);
        if (!isNaN(paramVal)) {
            paramVal === 0 ? mw.storage.remove("UTdontload") : mw.storage.set("UTdontload", "1");
        }
    }
    // 若本地存储存在"不加载"状态,直接终止
    if (mw.storage.get("UTdontload") === "1") {
        return;
    }

    /*===== 主初始化:命名空间与容器 =====*/
    const canonicalNamespace = mw.config.get("wgCanonicalNamespace");
    const allowedNamespaces = ["", "Project", "Help"]; // 主/项目/帮助命名空间
    if (!allowedNamespaces.includes(canonicalNamespace)) {
        return;
    }

    // 获取内容容器(优先mw-content-text,兼容旧版本)
    const $bodyContent = $("#mw-content-text").length ? $("#mw-content-text") : $("body");
    const bodyContent = $bodyContent[0];

    /*===== 设备类型检测 =====*/
    const isTouchscreen = window.matchMedia("(hover: none), (pointer: coarse)").matches ||
                         mw.config.get("wgDisplayResolution") === "mobile" ||
                         "ontouchstart" in document.documentElement;
    const hoverDelay = isTouchscreen ? 0 : 200; // 触摸设备无延迟

    /*===== 工具函数:弹窗核心逻辑 =====*/
    /**
     * 初始化元素数据(存储到元素data中)
     */
    function initElementData(element) {
        let tooltipData = $(element).data("tooltipData");
        if (!tooltipData) {
            const originalTitle = element.title || "";
            element.title = ""; // 清空原生title提示
            $(element).addClass("UHTarget"); // 添加高亮类
            tooltipData = {
                originalTitle: originalTitle,
                tooltipNode: null,
                hideTimer: null,
                showTimer: null
            };
            $(element).data("tooltipData", tooltipData);
        }
        return tooltipData;
    }

    /**
     * 创建弹窗内容(支持多行文本分割)
     */
    function createContentElement(originalTitle) {
        const $contentContainer = $("<div>");
        originalTitle.split("\n").forEach(line => {
            const trimmedLine = line.trim();
            if (trimmedLine) {
                $contentContainer.append($("<p>").text(trimmedLine));
            }
        });
        return $contentContainer[0];
    }

    /**
     * 创建弹窗DOM结构(包含内容区和箭头)
     */
    function createTooltipNode(originalTitle) {
        const tooltipNode = document.createElement("ul");
        tooltipNode.className = "unihantooltip";

        // 内容区(包含提示文本和设置图标)
        const $contentLi = $("<li>");
        const contentElement = createContentElement(originalTitle);
        const $settingsIcon = $("<div>")
            .addClass("UHsettings")
            .attr("title", "设置")
            .on("click", (e) => {
                e.stopPropagation();
                // 示例:设置功能入口
                alert("僻字提示设置(可扩展)");
            });

        // 组装内容区和箭头
        $contentLi.append(contentElement).append($settingsIcon);
        const $arrowLi = $("<li>");
        $(tooltipNode).append($contentLi).append($arrowLi);

        // 非触摸设备:绑定弹窗悬停事件
        if (!isTouchscreen) {
            const tooltipData = { hideTimer: null, showTimer: null };
            $(tooltipNode).data("tooltipData", tooltipData)
                .on("mouseenter", function() {
                    showTooltip($(this).data("tooltipData"), this);
                })
                .on("mouseleave", function() {
                    hideTooltip($(this).data("tooltipData"), this);
                });
        }

        return tooltipNode;
    }

    /**
     * 弹窗定位(避免超出视口,精准对齐)
     */
    function positionTooltip(element, tooltipNode) {
        const $element = $(element);
        const $tooltip = $(tooltipNode);

        // 离线计算尺寸(避免布局抖动)
        $tooltip.css({ visibility: "hidden", display: "block" }).appendTo(bodyContent);

        // 元素与视口信息
        const elementOffset = $element.offset();
        const elementWidth = $element.outerWidth();
        const elementHeight = $element.outerHeight();
        const tooltipWidth = $tooltip.outerWidth();
        const contentHeight = $tooltip.find("li:first-child").outerHeight();
        const arrowHeight = 12; // 与CSS箭头高度一致
        const viewport = {
            top: $(window).scrollTop(),
            bottom: $(window).scrollTop() + $(window).height(),
            left: 0,
            right: $(window).width()
        };

        // 垂直定位(优先上方,不足则翻转)
        let top = elementOffset.top - contentHeight - arrowHeight;
        const needFlip = top < viewport.top || (elementOffset.top + contentHeight + arrowHeight) > viewport.bottom;
        $tooltip.toggleClass("UHflipped", needFlip);
        if (needFlip) {
            top = elementOffset.top + elementHeight + arrowHeight - 2; // 适配CSS翻转间距
        }
        top = Math.max(viewport.top, Math.min(top, viewport.bottom - contentHeight));

        // 水平定位(居中对齐,留边距)
        let left = elementOffset.left + (elementWidth - tooltipWidth) / 2;
        left = Math.max(viewport.left + 10, Math.min(left, viewport.right - tooltipWidth - 10));

        // 箭头定位(指向元素中心)
        const arrowOffset = elementOffset.left - left + elementWidth / 2 - 7;
        $tooltip.find("li:last-child").css("margin-inline-start", `${arrowOffset}px`);

        // 应用定位并显示
        $tooltip.css({ top: `${top}px`, left: `${left}px`, visibility: "visible", display: "" });
    }

    /**
     * 显示弹窗(依赖CSS过渡,无JS动画依赖)
     */
    function showTooltip(tooltipData, tooltipNode) {
        const $tooltip = $(tooltipNode);
        // 确保弹窗已插入DOM
        if (!tooltipNode.parentNode || tooltipNode.parentNode.nodeType === 11) {
            bodyContent.appendChild(tooltipNode);
        }
        // 添加可见类(触发CSS渲染)
        $tooltip.addClass("is-visible");
        // 直接设置透明度(CSS过渡自动处理动画)
        $tooltip.stop().css("opacity", 1);
        // 清除隐藏定时器
        if (tooltipData.hideTimer) {
            clearTimeout(tooltipData.hideTimer);
            tooltipData.hideTimer = null;
        }
    }

    /**
     * 隐藏弹窗(依赖CSS过渡,无JS动画依赖)
     */
    function hideTooltip(tooltipData, tooltipNode) {
        const $tooltip = $(tooltipNode);
        // 清除显示定时器
        if (tooltipData.showTimer) {
            clearTimeout(tooltipData.showTimer);
            tooltipData.showTimer = null;
        }
        // 延迟隐藏(防误触)
        tooltipData.hideTimer = setTimeout(() => {
            // 触发淡出(CSS过渡处理)
            $tooltip.stop().css("opacity", 0);
            // 过渡结束后清理
            setTimeout(() => {
                $tooltip.removeClass("is-visible");
                if (bodyContent.contains(tooltipNode)) {
                    bodyContent.removeChild(tooltipNode);
                }
            }, 150); // 与CSS transition时长一致
        }, isTouchscreen ? 16 : 100);
    }

    /**
     * 触摸设备:点击外部关闭弹窗
     */
    function setupClickOutsideHandler(tooltipData, tooltipNode, element) {
        const handler = function(e) {
            if (!tooltipNode.contains(e.target) && e.target !== element) {
                hideTooltip(tooltipData, tooltipNode);
                $(document).off("click touchstart", handler); // 移除事件避免泄漏
                if (e.type === "touchstart") e.preventDefault(); // 防点击穿透
            }
        };
        $(document).on("click touchstart", handler);
    }

    /**
     * 触发弹窗显示
     */
    function triggerTooltip(element, tooltipData) {
        if (!tooltipData.originalTitle.trim()) return;

        // 创建或更新弹窗
        if (!tooltipData.tooltipNode) {
            tooltipData.tooltipNode = createTooltipNode(tooltipData.originalTitle);
        } else {
            const $contentContainer = $(tooltipData.tooltipNode).find("li:first-child > div");
            $contentContainer.replaceWith(createContentElement(tooltipData.originalTitle));
        }

        // 定位并显示
        positionTooltip(element, tooltipData.tooltipNode);
        showTooltip(tooltipData, tooltipData.tooltipNode);

        // 触摸设备绑定外部点击关闭
        if (isTouchscreen) {
            setupClickOutsideHandler(tooltipData, tooltipData.tooltipNode, element);
        }
    }

    /*===== 事件委托:支持动态元素 =====*/
    if (isTouchscreen) {
        // 触摸设备:点击触发
        $bodyContent.on("click", ".inline-unihan", function(e) {
            e.preventDefault();
            const element = this;
            const tooltipData = initElementData(element);
            // 切换显示/隐藏状态
            if (tooltipData.tooltipNode && bodyContent.contains(tooltipData.tooltipNode)) {
                hideTooltip(tooltipData, tooltipData.tooltipNode);
            } else {
                triggerTooltip(element, tooltipData);
            }
        });
    } else {
        // 非触摸设备:鼠标悬停触发
        $bodyContent.on("mouseenter", ".inline-unihan", function() {
            const element = this;
            const tooltipData = initElementData(element);
            tooltipData.showTimer = setTimeout(() => {
                triggerTooltip(element, tooltipData);
            }, hoverDelay);
        }).on("mouseleave", ".inline-unihan", function() {
            const element = this;
            const tooltipData = initElementData(element);
            if (tooltipData.tooltipNode) {
                hideTooltip(tooltipData, tooltipData.tooltipNode);
            }
        });
    }

    /*===== 窗口滚动时重新定位弹窗 =====*/
    $(window).on("scroll", function() {
        $(".inline-unihan").each(function() {
            const tooltipData = $(this).data("tooltipData");
            if (tooltipData && tooltipData.tooltipNode && bodyContent.contains(tooltipData.tooltipNode)) {
                positionTooltip(this, tooltipData.tooltipNode);
            }
        });
    });

})(jQuery, mediaWiki);