古早的gmail有个lab功能,
可以在邮件正文中选中一段文字,
然后按下回复的快捷键r
,
这段文字就会自动加入到回复文本框的最上面, 作为引用文字.
然后下面可以输入自己的回复.
我一直觉得这样的引用非常有针对性.
比嵌套性的引用所有以前邮件的内容要清晰明了的多.
但不出意料的是,
这样的功能被google取消了.
我尝试用油猴脚本实现类似的功能,
但是自己水平有限,
手搓到下面这些后,
我就进行不下去了.
请问有没有熟悉js的朋友可以帮我实现这个功能?
万分感谢.
我期望的回复样式是这样的:
- 引用内容的时候同时会附加原邮件的时间, 发信人和邮箱地址
- 下面使用引用格式, 后面自动附加我高亮的文字.
- 可以在一个主题邮件的多个邮件中重复: 高亮 > r 的动作. 它们会自动的叠起来. (比如下图中就有两个叠加在了一起.) 这样便于我处理同个主题下多封邮件的回复.
我的手搓到8楼和9楼: 油猴脚本: 在gmail中回复邮件时, 可以自定义引用文本 - #8,来自 sav3uluan
回复框好像可以用 ‘div[role=“textbox”]’ 选择器获取,目测只是个可以编辑的 div,也没有数据绑定。。。
默认仍然会将原始内容追加到后边发送,有一个省略号按钮,点击后才显示完整内容 - -
补充: TypeError: Failed to set the ‘innerHTML’ property on ‘Element’: This document requires ‘TrustedHTML’ assignment.
好像不让用 innerHTML 赋值。。emmm
还有一个方案是写入剪切板自己粘贴覆盖一次。。
// ==UserScript==
// @name Gmail Reply Enhancer
// @namespace https://www.wdssmq.com/
// @version 1.0.0
// @author 沉冰浮水
// @description Enhance Gmail reply functionality
// @license MIT
// @null ----------------------------
// @contributionURL https://github.com/wdssmq#%E4%BA%8C%E7%BB%B4%E7%A0%81
// @contributionAmount 5.93
// @null ----------------------------
// @link https://github.com/wdssmq/userscript
// @link https://afdian.com/@wdssmq
// @link https://greasyfork.org/zh-CN/users/6865-wdssmq
// @null ----------------------------
// @noframes
// @run-at document-end
// @match https://mail.google.com/*
// @grant none
// ==/UserScript==
/* eslint-disable */
/* jshint esversion: 6 */
(function () {
'use strict';
const _sleep = ms => new Promise(resolve => setTimeout(resolve, ms));
// -------------------------------------
// const $ = window.$ || unsafeWindow.$;
function $n(e) {
return document.querySelector(e);
}
function $na(e) {
return document.querySelectorAll(e);
}
function genReplayCon() {
// 获取选中的文字
const selectedText = window.getSelection()?.toString()?.trim() || "";
console.log('Selected text:', selectedText);
// 获取发件人姓名
let senderName = '';
const senderNameElement = $n('span[email]');
if (senderNameElement) {
senderName = senderNameElement.getAttribute('name') || '';
}
console.log('Sender name:', senderName);
// 获取发件人地址
let senderAddress = '';
const senderAddressElement = $n('span[email]');
if (senderAddressElement) {
senderAddress = senderAddressElement.getAttribute('email') || '';
}
console.log('Sender address:', senderAddress);
// 获取邮件接收时间,这里选择器还是不对
const receivedTime = '';
const receivedTimeElement = $n('div[aria-expanded="false"] > span.g3');
if (receivedTimeElement) {
receivedTime = receivedTimeElement.getAttribute('title') || '';
}
console.log('Received time:', receivedTime);
// 构建回复内容
let replyContent = [
senderName ? `${senderName} ` : '',
senderAddress ? `<${senderAddress}> ` : '',
receivedTime ? `${receivedTime}<br>` : '',
selectedText ? `<br>${selectedText}<br><br>` : '<br><br>'
].join('');
console.log('Reply content:', replyContent);
return replyContent;
}
// 创建新回复
async function createNewReply(con) {
// 获取回复按钮
const $$el = $na('[role="link"]');
// 遍历找到内容为回复的按钮
let replyButton = null;
for (let i = 0; i < $$el.length; i++) {
if ($$el[i].textContent.trim() === '回复') {
replyButton = $$el[i];
break;
}
}
console.log(replyButton);
if (replyButton) {
replyButton.click(); // 触发点击事件
// 这里有很高几率实际并没有触发点击,不知道为什么
console.log('reply button toggled');
}
// 等待回复框加载完成
await _sleep(2000);
// 获取回复框
const replyBox = $n('div[role="textbox"]');
console.log(replyBox);
if (!replyBox) {
return;
}
// 显示隐藏内容
const btnMore = $n("[aria-label='显示删减的内容']");
console.log(btnMore);
if (btnMore) {
btnMore.click();
}
await _sleep(1000);
// 写入回复内容
// replyBox.innerHTML = con; // ← 好像不让用这个 -_-!
replyBox.textContent = con;
}
document.addEventListener('keydown', async function(event) {
// 检查是否按下 'r' 键(键码为 82)
if (event.key === 'r' && !event.shiftKey && !event.ctrlKey && !event.metaKey && !event.altKey) {
// 阻止默认事件,避免 Gmail 的默认行为
event.preventDefault();
alert('按下了 r 键');
const replyContent = genReplayCon();
await createNewReply(replyContent);
}
});
})();
感谢您的回复, 但是似乎有两个问题:
关键的一个问题是: 回复框并未打开. 我的js水平只能照猫画虎, 尝试的改前改后, 也没有办法让回复框打开.
另外, Received time:没有获取到数据, 在log里面是空的.
还有 replyBox和btnMore, 在log中都是null
不知道有什么办法? 非常感谢你拨冗为我写这个小脚本.
我尝试将128行的 event.preventDefault();
这句 注释后,
回复框是能打开, 等一秒内容也加上了, 但是 1) 加入的是个html码, 2) 而且gmail自动引用的部分还是保留的.
供你参考.
我注释里写了,有很高的几率触发不了回复按钮, 代码执行了,但是没产生预期效果,导致后边代码也无从执行。。
一般都是主要逻辑跑通后再考虑细节,问题是主要逻辑跑不通 (╯﹏╰)
我尝试拼凑了一些代码, 但在下面这句出现了问题:
document.execCommand('insertHTML', false, '<div class="gmail_quote">' +
`${date} ${name == email ? '' : name + ' '}<<a href="${email}" target="_blank">${email}</a>><br>` +
`<blockquote class="gmail_quote" style="margin:0 0 0 .8ex;border-left:1px #ccc solid;padding-left:1ex">${selText}</blockquote>` +
'</div><br><br><br>');
错误的提示是:
Failed to execute 'execCommand' on 'Document': This document requires 'TrustedHTML' assignment.
我查了一下, 似乎是新的chrome加紧了安全, 不允许使用 execCommand 更改内容? via
我水品有限, 不知道如果使用别的方法, 该如何实现这句代码的功能呢?
希望大家给我个提点. 感谢.
以下是完整的代码, 出问题的在80行.
// ==UserScript==
// @name gmail 'r' key quotes selection - 80 issue
// @description Pressing 'r' will quote selection (only when selection is within an email)
// @version 0.1
// @author anyone
// @namespace anywhere
// @match https://mail.google.com/*
// @license MIT License
// ==/UserScript==
// 监听键盘按下事件
window.addEventListener('keydown', e => {
console.log('Key pressed:', e.key); // 添加调试语句,输出按下的键
// 如果按下的键同时按下了 Ctrl、Alt、Meta 键,或者按下的键不是 'r',则直接返回
if (e.ctrlKey || e.altKey || e.metaKey || e.key != 'r')
return;
// 获取当前选中的文本
const sel = getSelection();
const selText = sel.toString().trim().replace(/[\r\n]/g, '<br>');
// 如果没有选中文本,则直接返回
if (!selText)
return;
// 获取包含当前选中文本的邮件项
const item = sel.anchorNode.parentElement.closest('[role="listitem"]');
// 如果找不到邮件项,则直接返回
if (!item)
return;
// 获取发件人邮箱和姓名以及邮件发送日期
const emailNode = item.querySelector('[email]');
const email = emailNode && emailNode.getAttribute('email');
const name = emailNode && emailNode.getAttribute('name');
const date = (item.querySelector('span[title]') || {}).title || '';
// 阻止默认行为、冒泡和立即停止事件传播
e.preventDefault();
e.stopPropagation();
e.stopImmediatePropagation();
// 输出调试信息:选中文本、发件人邮箱、发件人姓名和邮件发送日期
console.log('Selected text:', selText); // 添加调试语句,输出选中的文本
console.log('Email:', email); // 添加调试语句,输出邮箱地址
console.log('Name:', name); // 添加调试语句,输出发件人姓名
console.log('Date:', date); // 添加调试语句,输出邮件发送日期
// 获取邮件编辑器,如果没有找到,则创建一个新的
const getEditor = () => item.querySelector('.editable') || item.parentElement.querySelector('.editable');
Promise.resolve(getEditor() || new Promise(resolve => {
// 获取回复按钮
const replyMenu = item.parentElement.querySelector('[role="button"] + [aria-haspopup="true"]');
// 如果找不到回复按钮,则返回
if (!replyMenu)
return;
// 模拟点击回复按钮
replyMenu.previousElementSibling.dispatchEvent(new MouseEvent('click'));
const t0 = performance.now();
// 每100毫秒检查一次是否找到编辑器或超时1秒
const interval = setInterval(() => {
const editor = getEditor();
if (editor || performance.now() - t0 > 1000) {
// 如果找到编辑器,则模拟点击发送按钮,并清空编辑器内容
if (editor) {
editor.closest('table').parentNode.closest('table').querySelector('[role="button"]').click();
editor.textContent = '';
}
clearInterval(interval);
resolve(editor);
}
}, 100);
})).then(editor => {
// 如果没有编辑器,则返回
if (!editor)
return;
// 让编辑器获得焦点
editor.focus();
// 在编辑器中插入引用邮件内容和选中文本
// 下面的语句出现了 Failed to execute 'execCommand' on 'Document': This document requires 'TrustedHTML' assignment.
document.execCommand('insertHTML', false, '<div class="gmail_quote">' +
`${date} ${name == email ? '' : name + ' '}<<a href="${email}" target="_blank">${email}</a>><br>` +
`<blockquote class="gmail_quote" style="margin:0 0 0 .8ex;border-left:1px #ccc solid;padding-left:1ex">${selText}</blockquote>` +
'</div><br><br><br>');
let isLastEmpty = true, lastNode = getSelection().focusNode.parentElement;
// 循环检查是否为最后一个空节点
while ((lastNode = lastNode.nextElementSibling) && isLastEmpty)
isLastEmpty = !lastNode.textContent.trim();
// 向后调整选区位置,以便在引用邮件后面继续输入文本
getSelection().modify('move', 'backward', 'character');
if (!isLastEmpty)
getSelection().modify('move', 'backward', 'character');
});
}, true);
Qingwa
10
根据错误,问题出在’document.execCommand’的调用上。在Chrome 68+版本,现有的执行插入操作将被禁止由非信任源(TrustedHTML)插入,此举是为了防止跨站脚本注入(XSS)攻击。
你可以尝试把document.execCommand(‘insertHTML’, false, ‘HTML内容’) 这段代码替换掉,改用创建元素和文本节点的方式来插入HTML。这样也可以追踪元素的创建,可以尽量避免XSS攻击。
// 在编辑器中插入引用邮件内容和选中文本
const blockQuoteEmail = document.createElement('div');
blockQuoteEmail.classList.add('gmail_quote');
const emailAnchor = document.createElement('a');
emailAnchor.href = email;
emailAnchor.target = '_blank';
emailAnchor.innerText = email;
blockQuoteEmail.innerHTML = `${date} ${name == email ? '' : name + ' '}<`;
blockQuoteEmail.appendChild(emailAnchor);
blockQuoteEmail.innerHTML += '><br>';
const blockquote = document.createElement('blockquote');
blockquote.classList.add('gmail_quote');
blockquote.style = "margin:0 0 0 .8ex;border-left:1px #ccc solid;padding-left:1ex";
blockquote.innerHTML = selText;
blockQuoteEmail.appendChild(blockquote);
editor.appendChild(blockQuoteEmail);
editor.innerHTML += '<br><br><br>';
这样,插入HTML的问题应该可以解决。再看一下代码中其他可能导致问题的地方,如果还有问题请再向我询问。
感谢你的回复. 我尝试将你给我的这段代码替换了之前的 document.execCommand
, 在gmail中运行出现错误, 反馈如下:
错误提示:
Uncaught (in promise) TypeError: Failed to set the 'innerHTML' property on 'Element': This document requires 'TrustedHTML' assignment.
提示错误的行:
blockQuoteEmail.innerHTML = `${date} ${name == email ? '' : name + ' '}<`;
不知该怎么解决?
我的猜想: 是不是所有的.innerhtml的属性都有安全问题?
否则, 只需要建立好第一个blockQuoteEmail元素后, 直接在其上用.innerHTML属性加入html代码即可? 不知道我理解的对不对?
问了gpt, 他让我将
blockQuoteEmail.innerHTML = `${date} ${name == email ? '' : name + ' '}<`;
改为
blockQuoteEmail.textContent = `${date} ${name == email ? '' : name + ' '}<`;
我理解可能是将innerHTML, 变为textContent.
将 @Qingwa 的代码中相应的属性全部修改后, 确实可以输出了. 但是完全没有格式:
好像就差一点点了, 求助.
摸索了一下, 将
blockQuoteEmail.innerHTML = `${date} ${name == email ? '' : name + ' '}<`;
改为:
const escapeHTMLPolicy = trustedTypes.createPolicy("myEscapePolicy", {
createHTML: (string) => string,
});
const escaped = escapeHTMLPolicy.createHTML('<div class="gmail_quote">' +
`${date} ${name == email ? '' : name + ' '}<<a href="${email}" target="_blank">${email}</a>><br>` +
`<blockquote class="gmail_quote" style="margin:0 0 0 .8ex;border-left:1px #ccc solid;padding-left:1ex">${selText}</blockquote>` +
'</div><br><br><br>');
就可以了.
在紧接着加入一个
editor.insertAdjacentHTML('beforeend', escaped);
让多次引用可以堆叠起来. 而不是替换.
这样就基本满足我的需要啦.
感谢大家的帮助.
dust2k
14
dust2k
15
算了,楼主的脚本还是差很多东西,我还是自己修改我目前的脚本吧。。
现在console里的错误提示提示的问题其实是我引用了Jquery的问题,已经去掉了JQuery的引用。
抱歉, 回复晚了. 我完全的小白,
只会自用, 不知道如何搞repo. 哈哈
你要有了repo, 可否分享下. 感谢.