V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
推荐关注
Meteor
JSLint - a JavaScript code quality tool
jsFiddle
D3.js
WebStorm
推荐书目
JavaScript 权威指南第 5 版
Closure: The Definitive Guide
Chipmunker
V2EX  ›  JavaScript

Tampermonkey 脚本开发的问题求助

  •  
  •   Chipmunker · 62 天前 · 1318 次点击
    这是一个创建于 62 天前的主题,其中的信息可能已经有所发展或是发生改变。

    1. 具体问题简述

    我开发了一个 Tampermonkey 脚本,但是往往需要手动刷新页面才能触发。请教该如何优化。(代码附在最后面)

    2. 脚本功能描述

    inSpirehep.net这个网站可以复制文献的BibTeX引文信息(通过点击文献左下角的cite实现),现在我想在BibTeX内容中加入一个inSpirehep.net网站的编号。为此我使用 Tampermonkey 脚本在文献的右下角添加了一个Copy BibTeX to Clipboard的按钮,点击按钮可以将加入编号的BibTeX内容复制到系统剪贴板。

    现在的问题是,这个脚本往往需要重新刷新页面才能触发,在第一次展示文献列表的时候不会出现。不知道该如何修复。PS ,这份代码也是在 ChatGPT 协助下完成的,本人完全没有 JS 开发经验。希望能有高手指点一二。

    3. 我的原始代码

    // ==UserScript==
    // @name         Copy BibTeX to Clipboard-v2
    // @version      2.0
    // @description  Adds a button to fetch data and copy it to clipboard
    // @author       Chipmunker
    // @match        https://inspirehep.net/literature*
    // @run-at       document-end
    // @grant        GM_xmlhttpRequest
    // @grant        GM_setClipboard
    // @grant        GM_notification
    // ==/UserScript==
    
    (function () {
        'use strict';
    
        var maxAttempts = 200; // 最大尝试次数
        var attempts = 0;     // 当前尝试次数
        var copyBtnText = "Copy BibTeX to Clipboard";
        var copyBtnColor = "#000008";
        var copiedBtnText = "Data Copied to Clipboard";
        var copiedBtnColor = "#f44336";
        var showDuration = 5000; // ms
        var regexPattern = /^https:\/\/inspirehep.net\/literature\/(\d+)$/;
    
        // 按钮的点击事件处理函数
        function CopyToClipOnClick(button, inspireID) {
            // 发送 GET 请求
            GM_xmlhttpRequest({
                method: 'GET',
                url: 'https://inspirehep.net/api/literature/' + inspireID,  // 替换为你要请求的 API URL
                headers: {
                    'authority': 'inspirehep.net',
                    'accept': 'application/x-bibtex'
                },
                onload: function (response) {
                    // 获取响应数据
                    var data = response.responseText;
    
                    // console.log("|" + data + "|");
    
                    // 为 BibTeX 添加 inspireID
                    let BibInspireID = data.replace(/\n\}\n$/, ",\n    inspirehepID = \"" + inspireID + "\"\n}\n");
    
                    // 将数据写入系统剪贴板
                    GM_setClipboard(BibInspireID);
    
                    // 修改复制按钮并在一秒后修复
                    var textSpan = button.querySelector('.v-top, #span-' + inspireID);
                    textSpan.innerText = copiedBtnText;
                    textSpan.style.color = copiedBtnColor;
                    setTimeout(function () {
                        textSpan.innerText = copyBtnText;
                        textSpan.style.color = copyBtnColor;
                    }, showDuration);
    
                    // 提示用户数据已复制
                    // alert('Data copied to clipboard: \n' + BibInspireID);
                    GM_notification({
                        text: BibInspireID,
                        title: copiedBtnText,
                        // url: 'https:/example.com/',
                        onclick: (event) => {
                            // The userscript is still running, so don't open example.com
                            // event.preventDefault();
                            // Display an alert message instead
                            // alert('I was clicked!')
                            console.log('NotificationClick')
                        }
                    });
                }
            });
        }
    
        function findRefSearchBtnAll() {
            var intervalId = setInterval(function () {
                // 查找 ref Search button
                var refSearchBtnAll = document.querySelectorAll('[data-test-id="reference-search-button"]');
                if (refSearchBtnAll.length > 0) {
                    // 找到了 ref Search button 元素,可以执行相应的操作
                    clearInterval(intervalId);  // 停止轮询
                    console.log('找到了 ref Search button 元素');
    
                    var Btn = document.querySelector('.CopyBtn');
                    if (Btn) {
                        return;
                    }
    
                    for (var refSearchBtn of refSearchBtnAll) {
                        var button = createCopyBtn(refSearchBtn, copyBtnText);
    
                        // 按钮点击事件处理函数
                        button.addEventListener('click', function () {
                            var inspireID = this.id;
                            CopyToClipOnClick(this, inspireID);
                        });
                    }
                } else {
                    attempts++;   // 增加尝试次数
                    // console.log('未找到 ref Search button 元素');
                    if (attempts >= maxAttempts) {
                        clearInterval(intervalId); // 达到最大尝试次数,停止轮询
                        console.log('未找到 ref Search button 元素, 达到最大尝试次数');
                    }
                }
            }, 100); // 每隔 100 毫秒钟检查一次
        }
    
        const observer = new MutationObserver(function (mutationsList, observer) {
            // 在 div 变化时重新运行脚本的代码
            // 例如,重新加载页面
            // location.reload();
            findRefSearchBtnAll();
        });
    
        var currentURL = window.location.href;
        if (regexPattern.test(currentURL)) {
            findRefSearchBtnAll();
        } else {
    
            findRefSearchBtnAll();
    
    
            // 监听目标 div 的变化
            const targetDiv = document.querySelector('[class="ant-col ant-col-xs-24 ant-col-lg-17"]');//'[data-test-id="search-results"]');
            if (targetDiv) {
                observer.observe(targetDiv, { attributes: false, childList: true, subtree: true });
            }
        }
    })();
    
    function createCopyBtn(refSearchBtn, copyBtnText) {
        var refSearchSpan = refSearchBtn.parentNode;
        // 构造 copy icon
        var copyImgSpan = document.createElement('span');
        copyImgSpan.role = "img";
        copyImgSpan.innerHTML = '<svg viewBox="64 64 896 896" focusable="false" data-icon="copy" width="1em" height="1em" fill="currentColor" aria-hidden="true"><path d="M832 64H296c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8h496v688c0 4.4 3.6 8 8 8h56c4.4 0 8-3.6 8-8V96c0-17.7-14.3-32-32-32zM704 192H192c-17.7 0-32 14.3-32 32v530.7c0 8.5 3.4 16.6 9.4 22.6l173.3 173.3c2.2 2.2 4.7 4 7.4 5.5v1.9h4.2c3.5 1.3 7.2 2 11 2H704c17.7 0 32-14.3 32-32V224c0-17.7-14.3-32-32-32zM350 856.2L263.9 770H350v86.2zM664 888H414V746c0-22.1-17.9-40-40-40H232V264h432v624z"></path></svg>';
    
        // 构造 icon span
        var iconSpan = document.createElement('span');
        iconSpan.className = "icon";
        iconSpan.appendChild(copyImgSpan);
    
        // 获取文献 ID
        var inspireMatch = refSearchBtn.href.match(/citedby:recid:(\d+)$/);
        var inspireID = inspireMatch[1];
    
        // 构造 __IconText__
        var IconTextSpan = document.createElement("span");
        IconTextSpan.className = "__IconText__";
        var textSpan = document.createElement("span");
        textSpan.className = "v-top";
        textSpan.innerText = copyBtnText; // "Copy BibTeX to Clipboard";
        textSpan.id = "span-" + inspireID;
        IconTextSpan.appendChild(iconSpan);
        IconTextSpan.appendChild(textSpan);
    
        // 构造 Button
        var button = document.createElement('button');
        button.type = "button";
        button.id = inspireID;
        button.className = "CopyBtn";
        button.appendChild(IconTextSpan);
    
        var UserActionSpan = document.createElement("span");
        UserActionSpan.className = "__UserAction__";
        UserActionSpan.appendChild(button);
        refSearchSpan.parentElement.insertBefore(UserActionSpan, refSearchSpan);
    
        return button;
    }
    
    3 条回复    2024-10-11 11:17:19 +08:00
    Huelse
        1
    Huelse  
       62 天前   ❤️ 1
    关键词:DOMNodeInserted, MutationObserver
    UIXX
        2
    UIXX  
       62 天前   ❤️ 1
    我不是专业的前端,这个问题大概是这样的:
    首次刷新时(从主页进入文章列表页),你的监听目标未能第一时间创建完成,所以无法完成绑定监听的工作,从而无法进行后续的按钮创建处理。

    [解决方案]
    从首页开始执行脚本,并监听第一时间被创建出来的主框架。

    [具体实施]
    改这两行:
    // @match https://inspirehep.net/*
    const targetDiv = document.querySelector('[class="ant-layout-content content"]');
    Chipmunker
        3
    Chipmunker  
    OP
       62 天前   ❤️ 1
    @Huelse @UIXX 非常感谢两位的热情帮助。参考两位的回答,改了一下代码,简单测试了一下,好像没有问题了。

    主要改动了两处,第一处就是 @UIXX 指出的要匹配主页。第二处是使用 MutationObserver 来监听页面变化,参考了[StackOverflow]( https://stackoverflow.com/a/16618904/8097964)的这个回答。
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   2867 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 21ms · UTC 13:12 · PVG 21:12 · LAX 05:12 · JFK 08:12
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.