html-text-editor-lite

Other

Inject a lightweight inline text editor into static HTML for non-technical users to click-to-edit text, save edits locally, and export clean HTML without sty...

Install

openclaw skills install @ytisvibecoding/html-text-editor-lite

html-text-editor-lite

把任意静态 HTML 升级成「可点击改文字」的版本:注入一个右下角浮动工具条(开始编辑 / 保存到浏览器 / 导出干净版),纯原生 JS、零依赖、约 6KB,不破坏页面。

When to use

  • 用户说「我想自己改文字」「做个可编辑版」「可编辑」「让我直接改 HTML 文字」「不用每次截图给你改」。
  • 已经产出了 HTML 报告 / 看板 / 长图,用户只想微调文案措辞(不是调颜色/字号/布局)。
  • 需要给非技术用户一个浏览器内所见即所得的改字入口。

不适用:用户想调色、改字号、换布局、重塑视觉风格 → 那是 html-visual-editor(html-editor)的活。本 skill 只管"改文字"。

Steps

  1. 确认目标 HTML 是完整文档(含 </body>)。
  2. 运行注入脚本(默认生成 <input>-editable.html,不动原文件):
    python <skill-dir>/scripts/inject.py <input.html>
    
    常用参数:
    • -o <out.html>:指定输出路径
    • --force:覆盖已存在的输出
    • --inplace:直接写回原文件(谨慎)
    • --exclude ".side-nav,nav,.legend":排除不希望被编辑的区域(默认已排除 .side-nav,nav,避免误触导航目录)
  3. present_files 展示生成的 *-editable.html 给用户。
  4. 告诉用户三步用法:① 开始编辑 点文字改 → ② 保存到浏览器(localStorage,刷新不丢)→ ③ 导出干净版 下载无编辑器的 index.html
  5. 若该报告同时部署在 pages.woa / EdgeOne 等:约定「用户改完导出 index.html 发回 → 我用对应部署接口 PUT 同步上线」。原始 index.html 始终是唯一真源。

How it works

  • 混合 DOM 遍历(v2,关键):从 body 递归下行,对每个元素判断"孩子是否全是 inline":
    • 孩子全是 inline(span/a/b/strong/em/img/br…)→ 整个元素是一个「文本块」,打 data-hve不再下钻(inline 子元素随父一起可编辑)。
    • 含块级孩子(div/section/ul/table…)→ 这是「容器」,就地把它的直接文本节点用 <span class="hve-t"> 包起来(处理 flex/grid 卡片里直接躺着的"孤儿文字"),再递归进入每个元素孩子。
    • 跳过 SCRIPT/STYLE/SVG/CANVAS/INPUT/BUTTON 等,及 --exclude 命中的子树。
  • 这样能保证保证不嵌套(父子不会都可编辑、互相打架),且孤儿文字也能改
  • 编辑块用 HVE-LITE START/END 注释包裹 → 幂等:重复运行先剥离旧块再注入,不会叠加。
  • localStorage key 由输出文件名 md5 派生 → 同源多份报告互不覆盖。
  • 「导出干净版」克隆 DOM、按 marker 正则移除整个编辑器块、还原 data-hve/contenteditable、unwrap 掉 span.hve-t 辅助标签,Blob 下载 index.html

为什么 v1 会「只有正文加粗能改、大量文字改不了」(务必记住)

v1 用「白名单标签 + querySelectorAll」:只扫 h1-h4/p/li/span/td/th/b/i/strong/em/a,且要求"不含块级子元素"。两个致命漏洞:

  1. 白名单漏标签:页面里 <div class="quote"><div class="cs-line"><div class="onum"><div class="sn-title"> 这类用 div 直接装文字的元素不在窄白名单里 → 整片改不了。
  2. 孤儿文字无人认领:flex/grid 卡片常见结构是 <div class="card">文字A<div>子块</div>文字B</div>,文字 A/B 是 div 的直接文本节点,但 div 因为"有块级子元素"被判为非叶子而整体跳过 → 文字 A/B 永远标不到。 结果就是:只有恰好是 <b>/<strong> 且独立的加粗短语命中了白名单,其余大量 div 文字全漏。实测某真实简报:v1 仅覆盖 318/513 文本节点,v2 覆盖 496/513(剩余即 side-nav 导览,故意排除)。

Pitfalls

  • 导航/目录会被误编辑:默认 --exclude ".side-nav,nav" 已处理;若页面用别的类名做导航/图例/工具条,手动加进 --exclude
  • 必须有 </body>:脚本在 </body> 前注入;残缺片段会报错退出。
  • localStorage 是按浏览器/域名存的:换浏览器或清缓存会丢;正式留存必须「导出干净版」。
  • 不要在已注入的文件上再跑 html-visual-editor:两者标记不同,先用导出的干净版再做其它处理。
  • 中文字符在 Python 源码模板里用 \uXXXX 转义(脚本已这么做),避免某些环境 f-string/编码问题。

Verification

  • 注入后 grep -c '^<!-- ==== HVE-LITE START' out.html 应为 1(精确注释标记,不是 JS 里的字符串)。
  • grep -c 'id="hve-bar"' out.html 应为 1
  • 覆盖率自检(强烈建议,尤其是带 div 卡片/孤儿文字的页面):用 jsdom 加载注入后的文件,runScripts:'dangerously' 执行内嵌脚本,统计 document.querySelectorAll('[data-hve]').length——应远大于个位数(真实简报实测 262)。若只有几个,说明回退到了 v1 行为,需排查。
  • 浏览器打开:点「开始编辑」→ 页面绝大多数文字(含 div 装的文字、卡片里的孤儿文字)可改、导航目录不可改;「导出干净版」下载的 index.html 里 grep hve-bar 应为 0、且无残留 class="hve-t"