第一章 为什么我们要亲手写一个 Super Copy
那天晚上我被飞书文档逼得快崩溃,一行行会议记录只能反复手抄。当我查遍 Chrome Web Store,发现大多数复制插件都停留在 Manifest V2、兼容性差的时候,我决定自己动手。根据 StatCounter 的浏览器市场份额报告,2024 年 6 月 Chrome 仍占据 65.52% 的全球市场,这意味着只要把插件做好,就能帮到绝大多数用户。Codex CLI 像是随身的副驾,它能接住我的脚手架、自动补全,还能把重复劳动记进日志,让“写插件”这件事变成一次对话。
我特别想说:别被“写浏览器扩展”四个字吓退。我们要解决的只是一个真实的痛点——解除复制限制。把它拆成小任务,再借助 Codex CLI,把想法变成文件,这才是开发的本质。
第二章 工具清单:Codex CLI + 浏览器实验场
第一步是在终端安装好 node
与 Codex CLI。按照 OpenAI CLI 官方指南 的步骤:
npm install -g openai@latest
openai auth login
openai codex init
之后进入项目目录,运行 openai codex
,就能在命令行里和模型协作。Codex 会把每次对话写进 codex.log
,方便未来追溯。浏览器方面,我建议新建一个干净的 Chrome Profile,并在 chrome://extensions
打开“开发者模式”,这样加载未打包扩展时不会和日常插件冲突。
如果你还停留在“内容脚本和后台脚本谁先执行”的疑惑里,不妨看一眼官方的 Chrome Extension 架构图。那张图直接告诉你:内容脚本负责在网页里“动手”,Service Worker 则负责权限和长期任务。明白了这点,后面所有文件就像乐高积木一样规整。
第三章 和 Codex 一起搭建扩展骨架
我喜欢项目开头就拉个空仓,命令如下:
mkdir super-copy && cd super-copy
openai codex
随后把文件结构交给 Codex 生成:
chromium/
├── manifest.json
├── content.js
├── popup.html / popup.css / popup-main.js
├── copy-buttons.js
├── worker-main.js / worker-helper.js
└── static/favicon.png
scripts/
└── unpack_bundle.js
Manifest V3 的关键配置如下:
{
"manifest_version": 3,
"name": "Super Copy",
"version": "1.0.0",
"description": "解除复制、键盘、右键限制",
"permissions": ["activeTab", "scripting", "clipboardWrite"],
"host_permissions": ["http://*/*", "https://*/*", "file://*/*"],
"background": { "service_worker": "worker-main.js" },
"action": {
"default_popup": "popup.html",
"default_icon": "static/favicon.128.png"
},
"content_scripts": [
{
"matches": ["http://*/*", "https://*/*", "file://*/*"],
"run_at": "document_start",
"js": ["content.js"],
"all_frames": true
},
{
"matches": ["http://*/*", "https://*/*", "file://*/*"],
"run_at": "document_idle",
"js": ["copy-buttons.js"]
}
]
}
加载 chromium
目录到 chrome://extensions
后,若能看到名为 Super Copy 的图标,说明骨架已成功。Codex 还会提示缺少图标文件,它会主动帮我补齐路径、提醒热加载方法,那种被同伴照应的感觉真不错。
第四章 核心逻辑:飞书 + 百度文库的双线突围
飞书的限制主要来自三个方面:一是阻止鼠标选区,二是拦截 Ctrl/⌘ + C
等组合键,三是把元素的 user-select
设成 none。解决办法是把监听器挂在捕获阶段,并在 MutationObserver 里不断解锁样式:
const EVENTS = ['copy', 'cut', 'keydown', 'keypress', 'keyup', 'contextmenu', 'selectstart'];
function stopPropagation(event) {
event.stopImmediatePropagation();
}
function unlockStyles(root = document) {
root.querySelectorAll('*').forEach(node => {
node.style.userSelect = 'auto';
node.style.webkitUserSelect = 'auto';
});
}
function enableFreedom() {
EVENTS.forEach(eventName => {
window.addEventListener(eventName, stopPropagation, true);
document.addEventListener(eventName, stopPropagation, true);
});
unlockStyles();
new MutationObserver(() => unlockStyles()).observe(document.body, { childList: true, subtree: true });
}
enableFreedom();
百度文库有自己的“小聪明”:它把正文分割成多段懒加载。我们在 content.js
里加上站点策略,当 location.host
匹配时采用特定解决方案。例如,针对文库的 doc-reader
结构,我们在每个可见段落上强制设置 contenteditable=true
,再调用 execCommand('selectAll')
和复制操作。所有策略统一收敛在 lnCW8VD8ab.js
:
const SITES = [
{ test: /feishu\.cn/, strategy: applyFeishu },
{ test: /wenku\.baidu\.com/, strategy: applyBaiduWenku },
// fallback
{ test: /.*/, strategy: applyCommon }
];
const current = SITES.find(site => site.test.test(location.href));
current.strategy();
我花了一整个下午在 Codex CLI 里模拟各种 DOM 结构,甚至写脚本抓取 window.getEventListeners
的输出,找出飞书脚本里谁在阻止复制。这个过程很琐碎,却非常值得,因为你会真正看懂“限制”是怎么来的。
第五章 视觉语言:橙色主题的 Popup 与悬浮面板
我们希望用户一眼就知道哪里能点击,于是借鉴阿里官网常见的橙色梯度,把 Popup 做成卡片式设计。参考图如下:
在 popup.css
,关键样式如下:
.fc-card {
background: radial-gradient(160% 120% at 100% 0%, rgba(255, 138, 41, 0.18) 0%, rgba(12, 14, 18, 0.95) 48%, rgba(8, 9, 12, 1) 100%);
border: 1px solid rgba(255, 138, 41, 0.45);
border-radius: 20px;
}
.fc-action-btn {
background: linear-gradient(135deg, #ff9347 0%, #ff6a00 100%);
color: #1b1207;
border-radius: 16px;
padding: 14px 16px;
}
悬浮面板 copy-buttons.js
也同步换装,并确保旧版本不会残留:
const existing = document.getElementById('__force-copy-buttons__');
if (existing) existing.remove();
我们还给按钮加上快捷键提示,既是视觉反馈,也是教育用户用 Alt+S、Alt+A 取代鼠标。全局监听写在同一文件里,这样既能在页面上使用,也能在弹窗中复用逻辑。
第六章 如何使用 Super Copy
装好扩展后,我推荐把流程想成三步:
第一步,打开需要复制的页面(飞书、百度文库、知乎专栏都可以),点击浏览器工具栏里的 Super Copy 图标。在弹窗里的三个开关中,“启动”代表长期生效,“仅本次”则只对当前标签页生效。我习惯先打开“解除复制限制”,再根据需要启用键盘和右键。
第二步,注意右下角会出现两个橙色按钮。普通网页、飞书文档、甚至本地 file://
页面都会显示这一小套工具。点“复制选中”会把你高亮的文字写入剪贴板,点“复制全文”则抓取 document.body.innerText
。在桌面端,你也可以直接按 Alt+S / Alt+A 来触发,它比鼠标更快。
第三步,如果某个页面仍然提示无法复制,可以尝试刷新或者重新加载扩展。Super Copy 会在内容脚本运行时注入策略,遇到 iframe 的页面则会遍历子框架。实在不行,就把该站点的 URL 和问题描述记下来,后续在 lnCW8VD8ab.js
新增策略即可。
我自己在飞书实验室里也很喜欢一个习惯:复制后直接打开记事本粘贴,确认字体和格式不会丢失。这是一个非常直观的验收方式,你也可以沿用。
第七章 把作品交给世界:打包与上架
测试完成后,在 chromium/
目录运行:
zip -r ../SuperCopy.zip *
这个 ZIP 会作为 Chrome Web Store 的上传文件。接着登录 Chrome 开发者控制台,支付一次性的 5 美元注册费,填写开发者资料并上传图标、截图、介绍文字。描述里务必说明权限用途,例如“Super Copy 仅用于解除页面复制限制,不会收集或上传用户数据”。
点击 “Submit for review” 后进入审核,大概 3–7 天。若收到驳回邮件,按邮箱里的条款逐条修改——我就曾因为缺少隐私说明被退回,补充后当天通过。
别忘了版本迭代:每次更新 manifest.json
的版本号,Chrome 才会向用户推送。Firefox、Edge 也可以打同样的包载入,其中 Edge 直接加载 ZIP 即可,Firefox 则还需适配 Manifest V2 或使用 polyfill,可参考 Mozilla WebExtensions 文档.
第八章 我的思考:抄作业也要知道为什么
写教程时我反复问自己:如何让读者既能把代码直接用起来,又能真正理解背后的逻辑。我总结了四点经验:
- 把每个步骤拆到能落地的粒度。比如 Manifest、策略模式、CSS 解锁,这些都是一个个“开箱即用”的片段。你可以照搬,也可以按需改造。
- 永远提供原始资料的链接。我在文中加入了对 StatCounter 和 Chrome 官方文档 的引用,让所有论断都能追溯来源。
- 多用图像化的比喻。无论是官方架构图,还是那张橙色 Popup 设计图,都让抽象概念立即成形。视觉素材能省下我一半的文字。
- 保留自己的思考。我不想“命令式”地告诉你做什么,而是分享我踩过的坑、做过的选择。这样当你面对新的限制站点时,就能沿着我的思路延伸,写出属于自己的 Super Copy。
愿你在下一份飞书文档里,再也不用手抄任何段落。把这个插件交给身边的同事或朋友,也许就能成为他们的第二个“复制”快捷键。