🪄 主要功能
- CSS 修复
- 悬浮栏动态透明度
- 部分不可点击图标上光标不显示为 pointer
- 一键隐藏
- 为旧版代码块添加“隐藏代码”的功能
- 一键隐藏所有置顶帖
- 隐藏/显示任意回复(帖子左下角)
- 自动隐藏被屏蔽的回复
- 回到顶部
- 双击顶栏回到顶部
- 回到顶部按钮行为修改:使用原生解决方案
- 使用原生提示框
- 修复 Emoji:回复/评分内修复 Emoji 显示
- 隐藏签名档
- 允许小签名:允许小型签名档 (
clientHeight
<= 41)
- 隐藏提醒:隐藏帖子回复上下侧时不时出现的提醒
- 隐藏头像详情:隐藏头像下的详情 (统计信息、各类奖章、收听按钮)
- 隐藏评分
- 隐藏点评
- 隐藏主页帖子列表的序号
- 隐藏背景图
- 限制图片最大高度为 70vh
- 后台自动签到
- 快捷键
- 无限滚动:滚动到末尾时自动加载下一页
⚙️ 配置
在论坛界面点击拓展 Tampermonkey 打开菜单,然后点击 "Show configuration" 即可开始修改配置
脚本链接
Greasy Fork
🤔 原理
其他功能原理过于简单,此处不再赘述。下面简单介绍一下修复 Emoji 的原理。
问题
为了避免 HTML 实体编码问题,特在所有必要的 &
后添加空格,测试时请注意删去。
我们知道,点赞 (👍) 这个 Emoji 用 HTML 实体编码是这么表示的: & #128077;
(或者 16 进制 & #x1F44D;
)。具体可以访问 这个在线编码解码网站。例如:
let test = document.createElement("span");
test.innerHTML = "& #128077;";
test.textContent // -> '👍'
为了避免潜在的危险,发表回复后论坛会将其中的特殊字符用 HTML 实体编码,于是 &
变为了 & amp;
,而后者就是字符 &
在 HTML 实体中的表示。于是原来的代码 (innerHTTML
) & #128077;
变为了 & amp;#128077;
,而浏览器渲染后则显示 (textContent
) 为 & #128077;
。于是你发布一个带 "👍" 的回复,最后会显示为 "& #128077;"。
特定条件下,多次编码还有可能出现 & amp;amp;#129392;
的情况。这种情况下我们只需稍微调整一下正则表达式即可。
思路 1
把给定节点的 HTML 代码进行查找替换。通过正则表达式,把形如 &(amp;)+#(\d+);
的代码替换为 ${p2};
。优点是简单方便,缺点是目标节点的后代会丢失所有的 Event Listener,也就是说它们无法响应原先绑定的事件。
思路 2
遍历给定节点的所有纯文本节点 (text node),将其形如 &(amp;)*#(\d+);
的内容改为对应 Emoji。优点是不会丢失 Event Listener,问题是如何遍历纯文本节点以及如何修改为对应 Emoji。
遍历纯文本节点
纯文本节点 (text node) 是一种很神奇的存在,你没法通过纯 CSS 选择器来选中所有的纯文本节点,因为它在选择器中没有对应的 tagName。最后还是 copilot 给出了优雅的解决方案:
function fix(node) {
// Select text nodes
let walker = document.createTreeWalker(node, NodeFilter.SHOW_TEXT, null, false);
let textNode;
while (textNode = walker.nextNode()) {
textNode.nodeValue = fixEmoji(textNode.nodeValue);
}
}
修改为 Emoji
纯文本节点没有 innerHTML
的属性,所以不能通过类似方法 1 的操作来修复 Emoji。当然,我们可以做个查找表来解决这个问题,但是这样子就不够优雅与高效。经过调研,我最终决定通过其他节点作为跳转的方法解决。首先创建一个正常的节点,设置它的 innerHTML
为被编码的代码,然后获取它的 textContent
就可以获得编码前的原始代码了。
let temp = document.createElement("span");
function fixEmoji(html) { // Replace patterns like `& #128077;` with represented emoji
return html.replace(/&(amp;)*#(\d+);/g, (match, p1, p2) => {
temp.innerHTML = `${p2};`;
// console.log(`${match} -> ${temp.textContent}`); // DEBUG
return temp.textContent;
});
}