为什么做这个插件
Bilibili 专栏编辑器本身更偏富文本输入。如果平时习惯用 Markdown 写作,每次发布文章时都要手动处理标题、列表、代码块、链接和图片,流程会被打断。
Easy Bilibili Markdown 的目标很直接:复制一段 Markdown,打开 Bilibili 专栏编辑器,点一下页面里的 Markdown 按钮,然后把内容转换成编辑器能识别的富文本。
这个插件不是重新做一个编辑器,而是尽量贴着 Bilibili 原有编辑器工作。这样使用成本最低,也不需要额外维护一套发布流程。
核心流程
整个流程可以拆成四步:
- Content Script 进入 Bilibili 专栏编辑页面。
- 向页面注入真正执行逻辑的脚本。
- 点击 Markdown 按钮后读取剪贴板内容。
- 把 Markdown 解析成 AST,再转换成当前编辑器能接受的格式。
这里选择 WXT 做扩展框架,主要是因为它把 Manifest、入口文件、开发构建这些浏览器扩展的重复工作处理得比较干净。业务代码可以集中在 entrypoints 和 lib 里,不用把精力浪费在扩展工程配置上。
按钮注入要足够耐心
页面注入看起来简单,实际很容易失效。Bilibili 创作后台不是一次性静态渲染完的页面,导航栏和编辑区都有可能延迟出现,路由切换时 DOM 也会变化。
所以按钮不能只在页面加载时插入一次,而是用 MutationObserver 监听页面变化,发现目标区域出现后再把按钮插到主站导航附近。这个做法的重点不是复杂,而是要接受一个现实:内容脚本面对的是别人的页面,DOM 结构随时可能比自己预期得更晚出现。
按钮样式也尽量贴近 Bilibili 的视觉,不做太强的存在感。插件入口应该容易找到,但不能干扰原来的写作区域。
Markdown 不应该直接靠字符串替换
Markdown 到富文本的转换,最容易走偏的方式是手写一堆正则。短期能跑,遇到嵌套列表、表格、链接、代码块之后就会很快失控。
这个插件用了 mdast-util-from-markdown 和 GFM 扩展先把 Markdown 解析成 AST,再根据编辑器类型做转换。这样有两个好处:
- Markdown 结构是明确的,不需要猜一段文本到底是标题还是普通段落。
- 后续要支持表格、分割线、图片等语法时,可以围绕节点类型扩展。
插件还会提取第一个一级标题作为文章标题,然后从正文里移除这个标题。这个体验细节很重要,因为很多 Markdown 文章本身第一行就是标题,如果直接导入正文,会导致标题重复。
编辑器兼容是主要难点
Bilibili 页面里可能遇到 Quill,也可能遇到 ProseMirror 或类似的 contenteditable 编辑器。两类编辑器的写入方式完全不同。
Quill 更适合写入 Delta,所以插件会尝试找到页面里的 Quill 实例,再用 markdown-to-quill-delta 把 Markdown 转成 Delta 插入。这样比直接改 DOM 稳定,编辑器内部状态也更容易同步。
ProseMirror 这一类编辑器则更适合插入 HTML。插件会把 Markdown AST 渲染成 HTML,再写入当前编辑区域,并主动派发输入事件,尽量让页面自己的保存、校验和字数统计逻辑感知到内容变化。
这里的经验是:不要只追求“看起来插进去了”。富文本编辑器通常有自己的内部状态,如果只是改 DOM,页面后续保存时可能拿不到正确内容。要尽量走编辑器能理解的写入方式,至少也要补齐输入事件。
Content Script 和页面上下文的边界
浏览器扩展的 Content Script 和网页本身不是同一个 JavaScript 上下文。很多时候,页面上的编辑器实例挂在网页上下文里,Content Script 不能像普通网页脚本那样直接访问。
因此这个项目把职责分开:Content Script 负责匹配页面和加载脚本,真正操作页面编辑器的逻辑放在注入脚本里执行。这个结构比把所有逻辑塞进 Content Script 更清晰,也更符合扩展的运行模型。
做完后的经验
这个插件给我的一个明显感受是:浏览器扩展开发不只是写功能,还要和目标网站的页面生命周期相处。
编辑器什么时候出现、按钮插在哪里、路由切换后是否还存在、剪贴板权限是否被允许、写入后页面是否真的识别内容变化,这些都比“Markdown 怎么转 HTML”更容易影响真实可用性。
如果以后继续扩展,我会优先做三件事:
- 给转换结果增加更系统的测试样例。
- 把 Quill 和 ProseMirror 的写入适配拆得更清楚。
- 增加失败时的提示,让用户知道是剪贴板权限、页面结构变化,还是编辑器未识别导致的问题。
它解决的是一个很小但高频的痛点:让写作从 Markdown 到 Bilibili 发布少一步手工整理。