extension for axure

axure 原型页面辅助

您需要先安裝使用者腳本管理器擴展,如 TampermonkeyGreasemonkeyViolentmonkey 之後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyViolentmonkey 後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyViolentmonkey 後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyUserscripts 後才能安裝該腳本。

你需要先安裝一款使用者腳本管理器擴展,比如 Tampermonkey,才能安裝此腳本

您需要先安裝使用者腳本管理器擴充功能後才能安裝該腳本。

(我已經安裝了使用者腳本管理器,讓我安裝!)

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

(我已經安裝了使用者樣式管理器,讓我安裝!)

// ==UserScript==
// @name         extension for axure
// @namespace    http://tampermonkey.net/
// @version      1.2.2
// @description  axure 原型页面辅助
// @author       gkeeno
// @grant        none
// @run-at       document-idle
// @match        http://192.168.1.5:30032/*
// @noframes
// ==/UserScript==

// axure增强
setTimeout(() => {
    if (self != top) return; // 不是顶层页面

    if (RP_VERSION && RP_VERSION <= 9) {
        loadFoldBtn(); // 折叠功能
        loadFolderTextClick(); // 给文件夹的整个元素加上点击即toggle的功能
    } else {
        console.warn('[extension for axure] 非axure页面或axure版本过高')
    }
}, 1000)

// TFS增强
setTimeout(() => {
    if (self != top) return; // 不是顶层页面

    if (TFS) {
        loadTFSImageImprove();
    } else {
        console.warn('[extension for axure] 非TFS页面')
    }
}, 1000)



// ******************************************************************************************* axure功能 BEGIN
function loadFolderTextClick() {
    // 所有文件夹元素
    const sitemapPageLinkContainers = document.querySelectorAll("div.sitemapPageLinkContainer");
    for (const div of sitemapPageLinkContainers) {
        const toggleBtn = div.querySelector(".sitemapPlusMinusLink")
        if (toggleBtn) {

            // 判断是可折叠的页面,可以不用处理(原本点击页面三角仅触发加载页面和折叠)
            //const toggleSibling = toggleBtn.nextElementSibling
            //if(toggleSibling && toggleSibling.querySelector('.sitemapPageIcon')) continue;

            // 给整个文件夹元素添加点击事件,点击时触发左边三角形按钮的点击
            div.addEventListener("click", function (e) {
                console.log("clicked")
                // 如果点击的是三角按钮本身,不需要处理,避免触发两次
                if (e.target === toggleBtn || toggleBtn.contains(e.target)) {
                    return;
                }
                e.stopPropagation();
                // 模拟点击三角形按钮
                toggleBtn.click();
            });

            // 设置鼠标样式为指针,提示用户可以点击
            div.style.cursor = "pointer";
        }
    }

    console.info('[extension for axure] 文件单击折叠已添加')
}
function loadFoldBtn() {

    const headerBtnMenu = document.querySelector("#sitemapToolbar"); // tips: 等1s后再添加较为安全(依据菜单数量)

    const foldMenuBtn = document.createElement("div");
    foldMenuBtn.title = "折叠菜单";
    foldMenuBtn.classList.add('sitemapToolbarButton');
    foldMenuBtn.textContent = '🧺';
    foldMenuBtn.onclick = function () {
        const userRes = prompt("从几级开始折叠?(最小为1级)", 2);
        if (!userRes) return;

        const level = Math.floor(Number(userRes));
        if (isNaN(level) || level < 0) return alert("必须输入正整数");
        foldLeftMenu(level);
    };

    if (headerBtnMenu) {
        headerBtnMenu.insertBefore(foldMenuBtn, headerBtnMenu.firstChild);
    }

    console.info('[extension for axure] 折叠按钮已添加')
}

function foldLeftMenu(level) {
    const leftMenuTree = document.querySelector("#sitemapTreeContainer > ul.sitemapTree");
    const levelFlag = {
        lvMax: 10,
        lvMin: level || 1,
        lvCur: 1,
        isExceed: function () {
            return this.lvCur > this.lvMax;
        },
        completeFold: function () {
            this.lvCur++;
        }
    };

    const nodeList = Array.from(leftMenuTree.querySelectorAll(":scope > .sitemapNode"));
    foldAllNodeByNodeList(nodeList, levelFlag, []);
}

function foldAllNodeByNodeList(nodeList, flag, foldCallBacks) {
    if (flag.isExceed()) {
        executedAllCallBacks(foldCallBacks);
        return;
    }

    const nextFoldNodeList = [];
    const needSkipLevel = isSkipLevel(flag);

    nodeList.forEach(node => {
        const subNodes = Array.from(node.querySelectorAll(":scope > ul > .sitemapNode"));
        nextFoldNodeList.push(...subNodes);

        if (!needSkipLevel) {
            foldCallBacks.push(() => foldNode(node));
        }
    });

    flag.completeFold();

    if (nextFoldNodeList.length === 0) {
        executedAllCallBacks(foldCallBacks);
        return;
    }

    foldAllNodeByNodeList(nextFoldNodeList, flag, foldCallBacks);
}

function foldNode(node) {
    const foldBtn = node.querySelector(":scope > div > div.sitemapPageLinkContainer .sitemapPlusMinusLink");
    if (!foldBtn) return;

    const isFolded = foldBtn.querySelector(".sitemapPlus");
    if (isFolded) return;

    foldBtn.click();
}

function executedAllCallBacks(arrCallBacks) {
    arrCallBacks.reverse().forEach(cb => cb());
}

function isSkipLevel(flag) {
    return flag.lvCur < flag.lvMin;
}
// ******************************************************************************************* axure功能 END

// ******************************************************************************************* TFS功能 BEGIN
function loadTFSImageImprove() {
    const iframes = document.querySelectorAll('iframe');

    // 遍历所有iframe
    iframes.forEach(iframe => {
        try {
            // 确保iframe已加载并且可以访问
            if (iframe.contentDocument) {
                // 给iframe中的所有图片添加点击事件
                addClickHooksToImages(iframe.contentDocument);

                // 给iframe添加load事件,以防iframe内容在后期加载
                iframe.addEventListener('load', function () {
                    try {
                        addClickHooksToImages(this.contentDocument);
                    } catch (e) {
                        console.warn('Error adding hooks to iframe on load:', e);
                    }
                });

                // 监视iframe内部DOM变化
                observeIframeChanges(iframe);
            }
        } catch (e) {
            // 处理跨域iframe的访问错误
            console.warn('[TFS Image Hook] Cannot access iframe content due to same-origin policy:', e);
        }
    });

    // 监听页面上动态添加的iframe
    observeDocumentForNewIframes();

    // 监听页面上的图片
    observePageImagesChanges();
    console.info('[extension for axure] TFS 富文本图片单击浏览已添加')
}

// 给指定文档中的所有图片添加点击事件
function addClickHooksToImages(doc) {
    if (!doc) return;
    const images = doc.querySelectorAll('img');


    images.forEach(img => {

        img.setAttribute('data-hook-added', 'true');

        // 添加鼠标悬停样式,表明图片可点击
        img.style.cursor = 'pointer';

        // 添加点击事件
        img.addEventListener('click', function (e) {
            e.preventDefault();
            e.stopPropagation();

            // 在这里实现您想要的点击功能
            // 例如:显示大图、复制图片链接等
            handleImageClick(this);
        });
    });
}
// 处理图片点击事件
function handleImageClick(img) {
    // 示例功能:在新窗口中打开大图
    const imgSrc = img.src;
    const imgAlt = img.alt || 'Image';

    // 创建一个模态框来显示大图
    const modal = document.createElement('div');
    modal.style.position = 'fixed';
    modal.style.top = '0';
    modal.style.left = '0';
    modal.style.width = '100%';
    modal.style.height = '100%';
    modal.style.backgroundColor = 'rgba(0,0,0,0.8)';
    modal.style.display = 'flex';
    modal.style.justifyContent = 'center';
    modal.style.alignItems = 'center';
    modal.style.zIndex = '10000';

    // 创建大图
    const largeImg = document.createElement('img');
    largeImg.src = imgSrc;
    largeImg.style.maxWidth = '90%';
    largeImg.style.maxHeight = '90%';
    largeImg.style.objectFit = 'contain';
    largeImg.alt = imgAlt;

    largeImg.dataset.hookAdded = 'true';

    // 创建关闭按钮
    const closeBtn = document.createElement('div');
    closeBtn.textContent = '×';
    closeBtn.style.position = 'absolute';
    closeBtn.style.top = '20px';
    closeBtn.style.right = '20px';
    closeBtn.style.color = 'white';
    closeBtn.style.fontSize = '30px';
    closeBtn.style.cursor = 'pointer';

    // 点击模态框背景或关闭按钮时关闭
    modal.addEventListener('click', function () {
        document.body.removeChild(modal);
        document.removeEventListener('keydown', escKeyHandler);
    });

    // 阻止点击图片时关闭模态框
    largeImg.addEventListener('click', function (e) {
        e.stopPropagation();
    });

    // 添加ESC键监听器退出大图
    const escKeyHandler = function (e) {
        if (e.key === 'Escape' || e.keyCode === 27) {
            document.body.removeChild(modal);
            document.removeEventListener('keydown', escKeyHandler);
        }
    };
    document.addEventListener('keydown', escKeyHandler);

    // 添加元素到模态框
    modal.appendChild(largeImg);
    modal.appendChild(closeBtn);

    // 添加模态框到页面
    document.body.appendChild(modal);
}


// 监视iframe内部DOM变化
function observeIframeChanges(iframe) {
    try {
        if (iframe.contentDocument) {
            const observer = new MutationObserver(function (mutations) {

                // DOM变化时,重新给所有图片添加点击事件
                addClickHooksToImages(iframe.contentDocument);
            });

            observer.observe(iframe.contentDocument.body, {
                childList: true,
                subtree: true
            });
        }
    } catch (e) {
        console.log('Cannot observe iframe content due to same-origin policy:', e);
    }
}
// 监视页面上动态添加的iframe
function observeDocumentForNewIframes() {
    const observer = new MutationObserver(function (mutations) {

        mutations.forEach(function (mutation) {
            if (!mutation.addedNodes) return;

            mutation.addedNodes.forEach(function (node) {
                // 如果添加的是iframe
                if (node.nodeName !== 'IFRAME') return;

                // 给新iframe添加load事件
                node.addEventListener('load', function () {
                    try {
                        addClickHooksToImages(this.contentDocument);
                        observeIframeChanges(this);
                    } catch (e) {
                        console.log('Error processing dynamically added iframe:', e);
                    }
                });
            });

        });
    });

    observer.observe(document.body, {
        childList: true,
        subtree: true
    });
}


function processImageAll() {

    // 找到评论框中的所有图片
    const images = document.querySelectorAll('img');

    // 为每个图片添加点击事件
    images.forEach(img => {
        if (!img.dataset.hookAdded) {
            img.dataset.hookAdded = 'true';
            img.style.cursor = 'pointer';

            img.addEventListener('click', function (e) {
                e.preventDefault();
                e.stopPropagation();
                console.log("Comment box image clicked:", this.src);
                handleImageClick(this);
            });
        }
    });
}

function observePageImagesChanges() {
    const imagesContainer = document.body;

    // 如果body尚未加载,则稍后重试
    if (!imagesContainer) {
        setTimeout(observePageImagesChanges, 200);
        return;
    }
    // 创建一个观察器实例
    const observer = new MutationObserver(function (mutations) {
        // 当评论框内容变化时,重新处理图片
        processImageAll();
    });

    // 配置观察选项
    const config = {
        childList: true,
        subtree: true,
        attributes: false,
        characterData: false
    };

    // 开始观察
    observer.observe(imagesContainer, config);

    // 立即处理一次当前的评论框图片
    processImageAll();
}
// ******************************************************************************************* TFS功能 END