You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何编写用户脚本在网页添加按钮触发GM_registerMenuCommand功能

给Gmail LaTeX脚本添加网页内触发按钮

问题

现有Gmail LaTeX渲染脚本,仅能通过ViolentMonkey插件菜单的「Render LaTeX」按钮触发传统LaTeX分隔符的渲染逻辑。需要在Gmail页面内添加一个按钮,点击后实现相同功能,同时适配Gmail的样式。

解决步骤

  1. 提取核心逻辑为独立函数
    将GM_registerMenuCommand内的匿名函数内容抽成单独的renderTraditionalLatex函数,让菜单和按钮共用同一逻辑,避免代码重复。

  2. 适配Gmail样式创建按钮
    使用Gmail原生按钮的CSS类名,让新按钮样式与页面原生按钮一致;将按钮插入到邮件列表上方的工具栏区域,符合用户操作习惯。

  3. 确保页面元素加载完成后插入按钮
    利用原脚本的waitForElement函数,等待Gmail工具栏加载完成后再插入按钮,避免因元素未加载导致的插入失败。

修改后的完整脚本

// ==UserScript==
// @name        LaTeX for Gmail v2.2
// @namespace   Violentmonkey Scripts
// @match       https://mail.google.com/mail/*
// @grant       GM_registerMenuCommand
// @grant       GM_addElement
// @require     https://cdn.jsdelivr.net/npm/katex@0.16.11/dist/katex.min.js
// @version     2.2
// @author      /u/MistralMireille & /u/LoganJFisher
// @description Adds support for TeXTheWorld delimiters to Gmail, plus an on-page button and menu option to render traditional LaTeX delimiters
// ==/UserScript==

GM_addElement('link', {
  rel: "stylesheet",
  href: "https://cdn.jsdelivr.net/npm/katex@0.16.11/dist/katex.css"
});

function renderLatex() {
  document.querySelectorAll("#\:1 > .nH .aHU.hx > [role='list'] > [role='listitem'][aria-expanded='true']").forEach(message => {
    let subportion = message.querySelector("[data-message-id]");
    if(subportion) {
      message = subportion;
    }
    message.innerHTML = message.innerHTML.replace(/\[;(.+?);\]/g, (match, p1) => {
      return katex.renderToString(p1, { throwOnError: false, output: "mathml", displayMode: false });
    });
    message.innerHTML = message.innerHTML.replace(/\[\(;(.+?);\)\]/g, (match, p1) => {
      return katex.renderToString(p1, { throwOnError: false, output: "mathml", displayMode: true });
    });
  });
}

// 提取菜单中的核心渲染逻辑为独立函数
function renderTraditionalLatex() {
  document.querySelectorAll("#\:1 > .nH .aHU.hx > [role='list'] > [role='listitem'][aria-expanded='true']").forEach(message => {
    let subportion = message.querySelector("[data-message-id]");
    if(subportion) {
      message = subportion;
    }
    message.innerHTML = message.innerHTML.replace(/\\\[(.+?)\\\]/g, (match, p1) => {
      return katex.renderToString(p1, { throwOnError: false, output: "mathml", displayMode: true });
    });
    message.innerHTML = message.innerHTML.replace(/\$\$(.+?)\$\$/g, (match, p1) => {
      return katex.renderToString(p1, { throwOnError: false, output: "mathml", displayMode: true });
    });
    message.innerHTML = message.innerHTML.replace(/\\\((.+?)\\\)/g, (match, p1) => {
      return katex.renderToString(p1, { throwOnError: false, output: "mathml", displayMode: false });
    });
    message.innerHTML = message.innerHTML.replace(/\$(.+?)\$/g, (match, p1) => {
      return katex.renderToString(p1, { throwOnError: false, output: "mathml", displayMode: false });
    });
    message.innerHTML = message.innerHTML.replace(/\\begin\{math}(.+?)\\end\{math}/g, (match, p1) => {
      return katex.renderToString(p1, { throwOnError: false, output: "mathml", displayMode: false });
    });
  });
}

GM_registerMenuCommand('Render LaTeX', renderTraditionalLatex);

function waitForElement(queryString) {
  let count = 0;
  return new Promise((resolve, reject) => {
    let findInterval = setInterval(() => {
      let waitElement = document.querySelector(queryString);
      if(waitElement) {
        clearInterval(findInterval);
        resolve(waitElement);
      } else if(count > 20) {
        clearInterval(findInterval);
        reject(`Couldn't find waitElement: ${queryString}.`);
      } else {
        count += 1;
      }
    }, 100);
  });
}

// 创建并插入适配Gmail样式的按钮
function addRenderButton() {
  const button = document.createElement('button');
  // 使用Gmail原生按钮类名,自动适配页面样式
  button.className = 'T-I J-J5-Ji L3 T-I-ax7 T-I-Js-Gs mA';
  button.textContent = 'Render LaTeX';
  button.style.marginLeft = '8px';
  
  // 绑定渲染逻辑
  button.addEventListener('click', renderTraditionalLatex);
  
  // 插入到邮件列表上方的工具栏区域
  waitForElement('.T-I.J-J5-Ji.T-I-KE.L3').then(toolbar => {
    toolbar.parentNode.insertBefore(button, toolbar.nextSibling);
  });
}

window.addEventListener('load', () => {
  waitForElement("#\:1 > .nH").then(messagesDiv => {
    (new MutationObserver((mutationRecords, observerElement) => {
      mutationRecords.forEach(mutationRecord => {
        switch(mutationRecord.type) {
          case "childList":
            mutationRecord.addedNodes.forEach(addedNode => {
              if(addedNode.tagName === "DIV" && addedNode.getAttribute("role") === "listitem") {
                renderLatex();
              }
            });
            break;
          case "attributes":
            if(mutationRecord.target.tagName === "DIV" && mutationRecord.target.getAttribute("role") === "listitem" && mutationRecord.attributeName === "aria-expanded") {
              renderLatex();
            }
        }
      });
    })).observe(messagesDiv, { childList: true, subtree: true, attributes: true, attributeOldValue: true});
    
    // 页面加载完成后添加按钮
    addRenderButton();
  });
});

关键说明

  • 按钮复用Gmail原生CSS类,无需手动编写大量样式即可适配页面风格
  • 按钮插入位置为邮件列表上方的工具栏,符合用户日常操作路径
  • 菜单命令与页面按钮共用同一渲染函数,确保逻辑一致性
  • 修复了原脚本中GM_addElement加载样式表的属性错误,将src改为href以正确加载KaTeX样式

内容的提问来源于stack exchange,提问作者Logan J. Fisher

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.18 05:23:12