npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

hy-editor

v0.4.0

Published

好运编辑器(hy-editor)—— Quill 2 内核的轻量块级富文本编辑器:表格、代码块、公式、Mermaid 图表、高亮块、步骤条、时间轴、批注开箱即用,落库语义 HTML,前台一行代码还原

Readme

hy-editor

轻量富文本编辑器(Quill 2 内核,仿 XNote)。专注「写文章 → 存 HTML → 前台还原」这条链路, 自带表格、代码块、公式、图表、高亮块、步骤条、时间轴、视频、批注等块,以及一套插件注册表, 二次开发加自定义块不用改库源码。

  • 包:ESM(dist/hy-editor.mjs)+ UMD(dist/hy-editor.js)+ 样式(dist/hy-editor.css)+ 类型(dist/hy-editor.d.ts)
  • 官网:web/(首页 / 使用文档 / 开发文档,纯静态,含在线试写),本地 npm run web:serve
  • 依赖:只依赖 quill 与 katex(打包进产物);mermaid / highlight.js 走 CDN 懒加载,可自托管

1. 安装与引入

npm / 打包器

npm i hy-editor
import { createEditor } from 'hy-editor'
import 'hy-editor/dist/hy-editor.css'

const ed = createEditor({ container: '#editor', theme: 'light' })

无打包器(PHP / 传统后台页面直引)

<link rel="stylesheet" href="/vendor/hy-editor/hy-editor.css">
<div id="editor"></div>
<script src="/vendor/hy-editor/hy-editor.js"></script>
<script>
  var ed = window.HYEditor.createEditor({ container: '#editor' })
</script>

UMD 直引时插件要用的 Quill 基类从 window.HYEditor.Quill 取:

var BlockEmbed = window.HYEditor.Quill.import('blots/block/embed')

完整可运行示例见 examples/umd-php.html(运行时注册一个「评分卡」插件)。


2. 快速上手

const ed = createEditor({
  container: '#editor',
  theme: 'light',
  content: '<h2>标题</h2><p>正文</p>',   // 存储格式 HTML
  toolbar: 'full',                        // 'full' | 'simple' | { items: [...] } | Quill 原生数组
  upload: async (file) => {                // 图片上传必须自己接
    const fd = new FormData(); fd.append('file', file)
    const r = await fetch('/admin/upload', { method: 'POST', body: fd }).then(r => r.json())
    return r.url                           // 返回可访问的图片地址(相对路径也可以)
  },
  // 副工具栏:业务动作(主工具栏下的第二行)
  subToolbar: [
    { label: '封面图', onClick: (q, ed) => console.log(ed.getWordCount()) },
    '|',
    { builtin: 'toc', align: 'right' },                       // 内建:目录面板
    { builtin: 'preview', title: '预览', align: 'right' }      // 内建:预览浮层
  ],
  statusBar: { wordCount: true, actions: [{ label: '发布', variant: 'primary', onClick: () => save(ed.getHTML()) }] }
})

toolbarSlot / subToolbarSlot / statusBarSlot 可把这三行渲染到页面级容器(页面顶栏、底部操作条)。


3. 编辑器 API

| 方法 | 说明 | |---|---| | getHTML() / setHTML(html) | 取 / 回填存储格式 HTML(自定义块是 <hy-xxx> 语义标签) | | getQuill() | 底层 Quill 实例(逃生口,原生能力全都能用) | | setTheme(t) / getTheme() | 亮暗切换(联动 mermaid 与外挂容器) | | getWordCount() | 字数 | | getTOC() | 目录 JSON(H1 = 标题,H2 = 一级节点) | | showOutline() / hideOutline() / toggleOutline() / isOutlineOpen() / onTOCChange(fn) | 目录面板 | | openPreview(opts) / getPreviewHTML(opts) / mountPreview(el, opts) | 预览:全屏浮层 / 静态字符串 / 渲染进任意容器 | | getComments() / ed.comments.* | 批注:列表 / 改派处理人 / 标记完成 / 删除 | | getBranding() / setBranding(v) | 官方署名:读当前配置 / 运行时隐藏、换文案(见 §8.4) | | on(event, fn) / off(event, fn) | 事件订阅(见下) | | destroy() / isDestroyed() | 销毁:解绑全局监听、清浮层、清容器、释放实例级预设 / 语言作用域 | | closeSlash() / isSlashOn() | 收起斜杠菜单 / 是否开启了斜杠菜单 | | debug() | 调试:这一例回收了多少全局监听、挂了哪些插件 |

事件:change(内容变更)、selection、upload:start / upload:done / upload:error、 destroy,其余名字透传给 Quill。on() 返回取消订阅函数。

const off = ed.on('upload:done', ({ url }) => console.log('已插入', url))
ed.on('change', () => autosave(ed.getHTML()))

以前换 toolbar / sizeMode 这类「构造期才生效」的选项只能整容器重建,现在 destroy() 后重新 createEditor() 即可。


4. 前台(详情页)怎么用

文章页不需要编辑器实例,两个静态函数就够:

import { toPreviewHTML, getTOCFromHTML } from 'hy-editor'

document.querySelector('#article').innerHTML = toPreviewHTML(htmlFromDB, { theme: 'light' })
const toc = getTOCFromHTML(htmlFromDB)      // 侧边目录(id 与正文锚点一致)

两种落地方式:

  • 后端渲染:保存时额外存一份 getPreviewHTML() 的结果,前台直接输出,零 JS(公式 / 图表需要 mermaid、katex 的 JS,见 §7)。
  • 前端渲染:前台 mountPreview(el, html),公式 / 图表会在挂载后异步补渲染。

CDN 依赖(都是懒加载 + 三级降级 jsDelivr → npmmirror → unpkg):mermaid 图表、KaTeX 公式、highlight.js 代码高亮。 内网 / 离线部署请自托管并指定:createEditor({ mermaidUrl: '/vendor/mermaid.min.js', katexUrl: '/vendor/katex.min.js' })。


5. 存储格式

落库 HTML 里,自定义块是干净语义标签(不含渲染产物):

| 标签 | 内容 | |---|---| | <hy-mermaid data-src> | 图表源码 | | <hy-formula data-src data-display data-color> | 公式(LaTeX) | | <hy-highlight data-type data-title data-icon data-color>…</hy-highlight> | 高亮块(容器,内部可嵌套) | | <hy-quote data-style data-cite>…</hy-quote> | 增强引用块 | | <hy-steps data-dir data-color> / <hy-timeline data-color> | 步骤条 / 时间轴 | | <hy-video data-src data-type data-ratio data-autoplay> | 视频 | | <hy-toc> | 目录占位(前台在此处渲染目录) | | <hy-icon data-name data-color data-size> | 内联图标 | | <pre data-language data-ln data-wrap data-em> | 代码块 | | <span class="hy-commented" data-cid data-ctext data-ctime data-cowner data-cdone> | 批注 | | <hy-vote …> 等 | 宿主自定义块(见 §6) |

净化注意:PHP 侧如果用 HTMLPurifier 之类的白名单净化器,必须放行上表标签与该标签上的 data-* 属性 —— 默认配置会把自定义标签和 data 属性全部删掉,表现是「前台块全没了 / 颜色丢了」。 改白名单后记得 bump HTML.DefinitionRev(它有缓存)。详见 §7。


6. 二次开发:加一个自定义块

以前要改五处源码(blot 注册、存储往返、预览渲染、插入菜单、动作表),现在一次 register() 全接通:

import { register, createEditor } from 'hy-editor'

register({
  name: 'vote-card',

  // ① Quill blot:编辑态长什么样、value 怎么存
  formats: [VoteBlock],

  // ② 存储标签 ↔ 编辑器内 class(getHTML / setHTML / 粘贴 往返)
  embed: { tag: 'hy-vote', cls: 'hy-vote-block' },

  // ③ 前台渲染(toPreviewHTML / mountPreview / openPreview)
  preview: { tag: 'hy-vote', render: (el) => buildVoteCard(el) },

  // ④ 动作(进 ACTIONS:工具栏 / 插入菜单 / 块手柄 / 气泡都查这张表)
  actions: { vote: (quill) => insertVote(quill) },

  // ⑤ 插入菜单条目(key 对应 ④;追加在内建条目之后 —— 同时进斜杠菜单)
  menu: { key: 'vote', label: '投票卡', icon: '<svg …/>' },

  // ⑥ 工具栏按钮(自定义 token;宿主写进 toolbar.items 即可,见 §8.3)
  toolbar: { vote: { title: '投票卡', render: (opts) => buildVoteButton(opts) } },

  // ⑦ 插件自己的交互(双击编辑等),每个编辑器实例创建后调用一次
  setup: (quill, { editor, emit }) => { /* … */ },

  // ⑧ 插件样式(自动注入 <style>,unregister 时移除)
  css: '.hy-vote-block{…}'
})

const ed = createEditor({ container: '#editor' })   // register 前后调用都行

字段说明与可选形态:

| 字段 | 用途 | 备注 | |---|---|---| | formats | 注册 blot | 数组;容器型块也放这里(会自动进表格单元格白名单) | | embed | 原子块的标签映射 | { tag, cls, attrs?, drop? };属性默认同名透传 | | convert | 容器块的自定义往返 | { tag, cls, toTag?(el), fromTag?(el) },内部还有可编辑内容时用 | | preview | 前台渲染 | { tag, render(el, ctx) } 或数组;返回 Element 则替换,返回空表示已就地改完 | | actions | 动作表 | key 与 menu.key 对应;覆盖内建同名动作会打 warn | | menu | 插入菜单条目 | 单个或数组;同时进斜杠菜单 | | toolbar | 工具栏按钮 | { token: { title, render(opts) } },unregister 时一并移除 | | setup | 实例级交互 | (quill, { editor, emit }) => void | | css | 样式 | 字符串,按插件名注入 | | dispose | 卸载回调 | unregister(name) 时调用 |

其他入口:

  • registerAll([p1, p2])、unregister(name)、listPlugins()
  • 只想给某一个实例加:createEditor({ extensions: [plugin] })(注意 blot 注册是全局的,Quill 格式表不分实例)
  • 参考实现:src/plugins/vote-card.js(投票卡,含弹窗编辑、双击改、预览渲染、样式)

7. PHP 后台接入

7.1 页面

后台编辑页直引 UMD(见 §1),把配置(上传地址、初始内容、主题)由模板变量注入即可:

<script>
  window.__ed = HYEditor.createEditor({
    container: '#editor',
    theme: <?= $theme === 'dark' ? "'dark'" : "'light'" ?>,
    content: <?= json_encode($articleHtml, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES) ?>,
    upload: async (file) => {
      const fd = new FormData(); fd.append('file', file)
      const r = await fetch('/admin/upload', { method: 'POST', body: fd, headers: { 'X-CSRF': '<?= $csrf ?>' } }).then(r => r.json())
      if (!r.url) throw new Error(r.msg || '上传失败')
      return r.url
    },
    subToolbar: [
      { label: '封面图', onClick: (q, ed) => pickCover(ed) },
      '|',
      { builtin: 'toc', align: 'right' },
      { builtin: 'preview', align: 'right' },
      { label: '保存', variant: 'primary', align: 'right', onClick: () => save(window.__ed.getHTML()) }
    ]
  })
</script>

外部依赖自托管(内网必做):把 mermaid / katex / highlight 放到自己的静态目录, createEditor({ mermaidUrl: '/vendor/mermaid.min.js', katexUrl: '/vendor/katex.min.js' })。

7.2 上传接口(示例)

// POST /admin/upload —— 返回 { url };编辑器拿到 url 后 insertEmbed
$file = $_FILES['file'] ?? null;
if (!$file || $file['error'] !== UPLOAD_ERR_OK) { http_response_code(400); exit(json_encode(['msg' => '上传失败'])); }

$finfo = new finfo(FILEINFO_MIME_TYPE);
$mime  = $finfo->file($file['tmp_name']);
$allow = ['image/jpeg' => 'jpg', 'image/png' => 'png', 'image/gif' => 'gif', 'image/webp' => 'webp'];
if (!isset($allow[$mime])) { http_response_code(415); exit(json_encode(['msg' => '不支持的图片类型'])); }

$name  = date('Ymd') . '/' . bin2hex(random_bytes(8)) . '.' . $allow[$mime];
$dir   = __DIR__ . '/../public/uploads/';
@mkdir($dir . dirname($name), 0755, true);
move_uploaded_file($file['tmp_name'], $dir . $name);

header('Content-Type: application/json');
echo json_encode(['url' => '/uploads/' . $name]);   // 相对路径也行:库已放宽图片 URL 白名单

要点:

  • 校验放服务端(类型、大小、频次),前端 upload 只负责传;
  • 返回相对路径没问题(Quill 默认只放行 http/https/data,这里已放宽到相对路径与 blob);
  • 上传失败请 throw / reject,编辑器会走 upload:error 事件,可以拿它做进度条与失败提示。

7.3 落库与输出

  • 字段:MEDIUMTEXT、utf8mb4(emoji 与特殊符号);目录可以另存一份 getTOC() 的 JSON,前台省一次计算。
  • XSS:作者 HTML 直接输出到前台等于 XSS 入口。两种策略:
    • 严格白名单(HTMLPurifier 等)——必须放行 §5 的标签与其 data-* 属性,否则块和颜色会被静默清掉; 改配置后 bump HTML.DefinitionRev / Cache.DefinitionImpl(定义有缓存)。示意:

      $config = HTMLPurifier_Config::createDefault();
      $config->set('HTML.DefinitionID', 'hy-editor');
      $config->set('HTML.DefinitionRev', 3);              // 改白名单时 +1
      $config->set('URI.AllowedSchemes', ['http' => true, 'https' => true, 'mailto' => true]);
      if ($def = $config->maybeGetRawHTMLDefinition()) {
          $def->addElement('hy-mermaid', 'Block', 'Empty', 'Common', ['data-src' => 'Text']);
          $def->addElement('hy-formula', 'Block', 'Empty', 'Common', ['data-src' => 'Text', 'data-display' => 'Text', 'data-color' => 'Text']);
          $def->addElement('hy-highlight', 'Block', 'Flow', 'Common', ['data-type' => 'Text', 'data-title' => 'Text', 'data-icon' => 'Text', 'data-color' => 'Text']);
          // … 其余 hy-* 与 <span class="hy-commented"> 的 data-* 同理
      }
      $clean = (new HTMLPurifier($config))->purify($html);
    • 或信任后台作者(多数 CMS 的做法),只做「去掉 <script> / on* 事件属性 / javascript: 协议」的窄过滤 —— 风险是内网多作者场景下的横向越权,取舍自己定。

  • 版本升级:编辑器升级后旧 HTML 由 deserializeHTML 兼容解析(含 XNote 旧标签),但净化白名单要跟着版本同步,否则新块在前台被净化器吃掉。

7.4 扩展后台工具

  • 加按钮 / 业务动作:副工具栏 / 状态栏用 subToolbar / statusBar 配置(封面图、AI 助手、一键排版、保存草稿、发布…),onClick(quill, ed) 里能拿到实例;要放进主工具栏就用 registerToolbarItem()(§8.3)。
  • 加内容块:register() 插件(§6),UMD 场景用 HYEditor.Quill 取基类;插件可以随自己的业务仓库维护,不必 fork 编辑器。
  • 改预设数据(字号档位、色板、插入菜单条目、图标库):用 presets(§8),不必 fork。
  • 改文案 / 做英文版:用 i18n(§8),不必全局搜字符串。

8. 预设数据与文案(presets / i18n)

8.1 presets —— 色板、字号档位、字体、图标库、菜单条目

同一套 35 色以前在三个文件里各写了一份,字号档位与字体列表是写死的常量。现在统一在 presets:

import HYEditor from 'hy-editor'

// ① 全局(影响之后创建的所有实例)
HYEditor.presets.set('colors', ['#000000', '#e60000', '#0066cc'])   // 替换
HYEditor.presets.extend('pxSizes', ['144px', '192px'])              // 追加档位
HYEditor.presets.addIcons({                                          // 追加图标
  'my-logo': { label: '我的图标', cat: '品牌', body: '<circle cx="12" cy="12" r="9"/>' }
})

// ② 实例级(只影响这一个编辑器)
createEditor({ container, presets: { colors: [...], fonts: [...], insertMenu: [{ key: 'x', label: 'X' }] } })

可覆盖的键(HYEditor.presets.PRESET_KEYS):

| 键 | 默认 | 覆盖方式 | |---|---|---| | colors | Quill 35 色 | 替换 | | iconColors | 11 色(首项空 = 跟随文字色) | 替换 | | pxSizes | 8px–120px 共 20 档 | 追加 / 替换 | | fonts | 宋体 / 黑体 / 楷体 / 仿宋 / 雅黑 / 等宽 | 替换 | | headers | 正文 + H1-H6 | 替换 | | icons hlIcons | 内建图标库 | 追加(merge) | | emojiGroups | 7 类 205 个 | 追加 | | insertMenu | 内建 12 条 | 追加 | | slashMenu | 与插入菜单同源 | 追加 | | toolbarPresets | full / simple 两档 | 追加 / 改档(merge) | | branding | 官方署名(好运编辑器) | 改字段(merge)/ 传 false 全局关闭 | | mermaidThemes | 5 套 | 替换 |

实例级覆盖(createEditor({ presets }))分两层生效:

  • 构建期(同步作用域):工具栏、菜单、色板的同步构建 —— 一直如此;
  • 存活期(持久作用域):运行时才点开的浮层(颜色选择器、图标面板…)也读本实例的值, 编辑器被交互时成为「当前实例」,多实例互不干扰,destroy() 时释放。

不经过编辑器的调用(比如你自己页面里的浮层)读的是全局值 —— 那种场景用 HYEditor.presets.set()。

8.2 i18n —— 多语言与改措辞

HYEditor.i18n.setLocale('en')                              // 切英文(内置 en 包)
HYEditor.i18n.addMessages('zh-CN', { 'cmt.title': '评论' }) // 只改中文措辞
createEditor({ container, locale: 'en', messages: { 'menu.table': 'Table' } })

设计上是渐进式的:t(key, fallback) 在文案表里查不到就用调用点写的中文兜底, 所以没接 i18n 的旧文案照常显示,不会因为漏配就变成空白或 key。 内置 en 覆盖约 90 条核心 UI 文案(菜单、工具栏按钮、对齐缩进、斜杠菜单、批注、 预览、表格工具、图标选择器);其余未接的文案仍是中文,想翻就 addMessages('en', {...}) 补。

8.3 工具栏:加按钮 / 换预设 / 斜杠菜单

工具栏以前是一张写死的清单(想加按钮只能 fork)。现在三个口子都是开放的:

import HYEditor, { registerToolbarItem } from 'hy-editor'

// ① 自定义按钮:注册一个 token,之后与内建 token 完全等价
registerToolbarItem('ai-polish', {
  title: 'AI 润色',
  render(opts) {
    const b = document.createElement('button')
    b.type = 'button'
    b.className = 'hy-tb-btn'
    b.innerHTML = '<svg viewBox="0 0 16 16">…</svg>'
    b.addEventListener('mousedown', (e) => e.preventDefault())  // 保住编辑器选区
    b.addEventListener('click', () => opts.actions.aiPolish?.())
    return b
  }
})

// ② 自定义一档预设(不用改源码)
HYEditor.presets.set('toolbarPresets', { minimal: ['bold', 'italic', '|', 'ai-polish'] })

createEditor({ container, toolbar: 'minimal' })
// 或者直接给数组:createEditor({ toolbar: { items: ['undo','redo','|','ai-polish'] } })

插件也能声明按钮(register({ toolbar: { token: { title, render } } })), 卸载插件时 token 一并移除 —— 见 §6。

斜杠菜单:行首输入 / 唤出插入菜单(↑↓ 选择、Enter 执行、Esc 关闭), 条目与工具栏「插入」下拉同源(内建 + presets.slashMenu 追加 + 插件 menu 条目), 所以加一个块,四个入口同时出现。不要就 createEditor({ slash: false })。

HYEditor.presets.extend('slashMenu', [
  { key: 'slogan', label: '标语', run: (quill) => insertSlogan(quill) } // 自带 run 时优先
])

8.4 官方署名(branding)

编辑器默认在底部带一行官方署名:「好运编辑器 · hyeditor.haoyundada.com」+ 官方 logo,点击新窗口打开官网。 它是编辑器外壳:不进 getHTML() 的存储内容,也不随内容回填。

// ① 默认:什么都不用写(配了 statusBar 就并进状态栏右侧,没配则独立一条)
createEditor({ container })

// ② 关掉这一个实例
createEditor({ container, branding: false })

// ③ 换文案 / 换 logo / 换链接(只写要改的字段,其余沿用默认)
createEditor({
  container,
  branding: { name: 'XX 编辑器', url: 'https://writer.example.com', logo: '/static/logo.png' }
})

// ④ 白标:全局关掉(建实例之前调用;实例传对象也翻不出来)
HYEditor.presets.set('branding', false)

运行时也能改:

ed.setBranding(false)                     // 隐藏
ed.setBranding({ name: '集团编辑器' })     // 换文案(与当前配置合并)
ed.getBranding()                          // 当前生效配置,null = 未显示

| 字段 | 默认 | 说明 | |---|---|---| | name | 好运编辑器 | 产品名 | | vendor | 好运哒哒 | 出品方(进 hover 提示) | | url | https://hyeditor.haoyundada.com | 官网,决定 href 与显示域名 | | logo | 官方 logo(pay.haoyundada.com) | 传空字符串即不显示图;加载失败会自动撤掉不留破图 | | text | — | 整条文案覆盖(给了就不再拼 name + 域名) | | position | auto | auto / statusbar / bar —— 强制放状态栏或独立成条 |


9. 已知边界与后续

| 项 | 现状 | 建议 | |---|---|---| | 块工具栏(块手柄菜单)的行为定义 | 仍是模块常量 | 想改条目顺序 / 增删动作,目前要改 block-toolbar.js | | i18n 覆盖率 | 核心 UI 约 90 条 key,未全覆盖 | 按模块继续接(未接的仍是中文) | | 全局副作用 | 字体白名单、图片 sanitize 补丁、sizeMode 打在 Quill 全局 | 同页多实例请统一 sizeMode | | 测试 | 纯逻辑层(预设 / 文案 / 注册表 / 目录) | 编辑器交互仍靠 demo 页浏览器冒烟,可补 E2E | | 编辑器与预览的功能差 | 预览是只读(批注可查看、不可回批) | 需要「读者回批 / 批注侧栏」再开 |


10. 开发

npm i
npm run dev          # 开发(demo 页)
npm run build        # 库产物:dist/(ESM + UMD + CSS + d.ts + sourcemap)
npm run build:demo   # demo 产物:demo/(可直接静态托管)
npm test             # 单测(vitest + jsdom):预设 / 文案 / 插件注册表 / 目录
npm run web:sync     # 把 dist 产物同步进官网 web/vendor/(在线试写用)
npm run web:serve    # 本地预览官网:http://localhost:5200

目录:src/hy-editor.js 主入口与装配,src/modules/* 各功能块,src/bridge/html-bridge.js 存储往返, src/modules/preview.js 前台渲染,src/toolbar/* 工具栏,src/extensions.js 插件注册表, src/plugins/* 示例插件,src/styles/* 样式。

11. 官网(web/)

web/ 是一个纯静态站点,可直接托管(也是库的自述页):

| 文件 | 内容 | |---|---| | web/index.html | 首页:能力、真机截图、在线试写(引 web/vendor/ 里的 UMD 产物起真实例)、存储格式、快速开始、打赏赞助(赞赏码可点击放大)、已知边界 | | web/docs.html | 使用文档:安装引入 / 配置全表 / API 与事件 / 内容块 / 上传 / 存储 / 前台渲染 / 主题 / 预设 / i18n / 官方署名 / PHP 接入 / FAQ | | web/dev.html | 开发文档:架构、源码结构、往返链路、插件开发、工具栏 token、presets / i18n 机制、构建测试、版本约定、Quill 踩坑记录 |

三页共用同一套 header / footer(手写重复,不靠 JS 注入,保证无 JS 也能读),页脚带出品方 (好运哒哒)与备案号。web/sponsor/wxpay.png 是赞赏码、web/assets/logo.png 是官方 logo (同时用作 favicon),换图直接替换同名文件即可。

改完 src/ 记得 npm run build && npm run web:sync(官网在线试写读的是 web/vendor/ 的副本); 改 web/assets/*.css|js 后要把三个 HTML 里的 ?v= 缓存串加一档,否则浏览器会用旧缓存。