[插件] Cherry 编辑器(cherry)插件发布,可以自行安装测试。

👑Lv.11 元老 🌞 骨干会员
2026-10-20 15:40:21

用 Cherry Markdown(腾讯开源的 Markdown 编辑器)替换 FlintHub 核心富文本编辑器。
纯插件实现,零核心改动(本版唯一的核心改动是给正文净化白名单补了 mark 标签,见「四」)。

  • 版本:1.0.0
  • 依赖:Cherry 内核 0.11.10(构建产物已随包,运行期不请求任何外网)
  • 最低核心版本:1.2.1

一、功能简介

能力 说明
编辑 / 预览双栏 三档初始模式:纯编辑 / 双栏 / 纯预览,用户可在工具栏随时切换
工具栏 加粗、斜体、删除线、上下标、颜色 + 背景色、字号、标题(h2~h4)、列表、有序列表、复选、引用、对齐、插入(图片/链接/表格/分割线/换行/代码块/目录)、撤销重做、全屏、字数、模式、搜索
颜色与背景色 产出 <span style="color:#…"> / <span style="background-color:#…">,能完整过核心白名单(背景色即事实上的「高亮」)
字号 / 对齐 font-size / text-align,同样在白名单内
代码高亮 Prism 内置,前台由 cherry-content.min.css 提供配色
图片上传 走核心 POST /api/upload?ajax=1,自动带 CSRF 与 category_id
草稿 / 引用 提供 window.ACTIVE_EDITORS 兼容层,核心 draft_saver 与引用按钮照常工作
格式损失提示 编辑旧帖时,Markdown 保留不了的格式会提示,并给「源码模式」出口(逐字节原样提交)
前台正文样式 只在正文展示页额外注入 9.2 KB 的 cherry-content.min.css(只补 Prism 配色 / 图标字体 / 表格滚动,不动核心排版)

二、入口与交互

  1. 后台 插件 → Cherry 编辑器 → 打开「总开关」并保存,前台立即生效。
  2. 支持按页面分别开关:发帖、回复、博客、私信、后台编辑、插件页面。
  3. 编辑器上方出现工具条;数据岛(#cherryCfg)+ boot.js 在 body 末尾同步加载,无二次请求。

三、目录结构

plugins/cherry/
├── plugin.json                    # 4 个钩子 + route:admin / system:settings 权限
├── Plugin.php                     # 主类:配置 / 生效判定 / defaults 补丁 / 生命周期
├── ConfigStore.php                # 配置读写收敛层(校验 → 归一化 → 整体落库)
├── AdminController.php            # 后台设置页 + 保存(继承 Admin\BaseController)
├── hook/
│   ├── admin_route_register.php   # /admin/cherry、/admin/cherry/save
│   ├── controller_view_before.php # 关核心编辑器 + 标记接管 / 标记正文页
│   ├── layout_head_end.php        # 注入 CSS / JS(编辑器页与正文页分开判断)
│   └── layout_body_end.php        # 数据岛 + boot.js
├── views/admin.php                # 设置页(自包 admin-container + _sidebar + admin-content)
├── assets/
│   ├── boot.js                    # 前端挂载(本插件最核心的文件)
│   ├── style.css                  # 宿主容器 / 提示条 / 源码模式 / 工具栏裁剪 / 后台页
│   └── lib/cherry/                # 构建产物(勿手改)
│       ├── cherry-markdown.js        2.34 MB  UMD(terser 压缩)
│       ├── cherry-markdown.css       241 KB   编辑器皮肤
│       ├── cherry-content.min.css    9.2 KB   前台正文专用
│       └── fonts/ch-icon.woff2|woff  47 KB    图标字体
└── lang/{zh,zh_tw,en}.php         # 79 键 × 3 语,键集合与键序完全一致

四、核心改动(全仓仅一处)

app/Helpers/Content.php 的 $allowedTags 增加 mark。

原因:Cherry 的 ==文字== 语法与 Vditor 的高亮按钮都产出 <mark>;不放行会被净化层
unwrap 成纯文字(写了高亮、发出来没有)。mark 无属性、无脚本能力、浏览器默认样式即可,
放行不扩大攻击面。

  • 属性白名单未放宽:<mark onclick=… style="background:url(javascript:…)"> 里的危险属性仍被清空。
  • 三轮净化逐字节幂等。
  • 回归:test_p5_content_img.php 65/0、test_p8d_img_group.php 66/0、cherry_mark_verify.php 15/0。

五、六条硬约束(改 boot.js / Plugin.php 前必读)

1. ⚠️⚠️ 所有子配置只能改 Cherry.config.defaults,不能作为构造参数传

mergeWith.js:51 只在「两边都是 plain object」时下钻合并,而实测
defaults.engine / .toolbars / .editor / .callback / .previewer 全部不是 plain object,
传任何局部配置都会整块替换掉默认值,导致编辑器根本起不来:

{engine:{syntax:{…}}}  → TypeError: Cannot read properties of undefined (reading 'urlProcessor')
{toolbars:{…}}         → TypeError: Cannot read properties of undefined (reading 'isReplace')

唯一正确姿势(boot.js 的 applyPatches()):改 defaults → new Cherry() → 立即还原。
构造时会 cloneDeep,还原不影响已建实例;不还原会污染同页其它实例。

2. ⚠️⚠️ locale 必须白名单化

src/locales/ 只有 zh_CN / en_US / ru_RU。传 zh_tw 或任意未知值会抛
TypeError: Cannot read properties of undefined (reading 'colorPickerText') —— 不是降级,是起不来。
所以繁体强制映射到简体(Plugin::cherryLocale() + boot.js 双重兜底)。

3. ⚠️⚠️ 内部 textarea 必须彻底干掉 name

Cherry 自己造 <textarea id="code" name="code"> 再 display:none,在表单里会多提交一个 code 字段。
两道保险:editor.name = ''(空串会被尊重)+ 挂载后 removeAttribute('name')(空 name 仍会提交空字段)。

4. ⚠️ 提交取 HTML 用 engine.makeHtml(getValue()),不要依赖 getHtml()

getHtml() 在纯编辑模式下读的是 previewerCache.html,新鲜度依赖预览器是否更新过 DOM;
engine.makeHtml(md) 是同步纯函数,恒定正确。getHtml(false) 只作兜底。

5. ⚠️ ACTIVE_EDITORS[x].editor 不能是真实 DOM

draft.js 会 ed.editor.innerHTML = … 直接赋值,绕过 Cherry 内部模型 → 表面有字、getValue() 还是旧的。
故给一个 detached 假节点,用 Object.defineProperty 接管 innerHTML。

6. ⚠️ 代码块行号必须关

engine.syntax.codeBlock.lineNumber 默认 true,会把 <span class="code-line"> 写进存库 HTML。
已随补丁关闭(同时关 copyCode / editCode / changeLang / expandCode,减少 data-* 噪音)。


六、设置项(13 个,前缀 cherry_)

键 默认 说明
enabled 0 总开关。关闭时前台完全无变化(退回核心编辑器)
on_thread 1 发帖 / 编辑主题
on_reply 1 回帖框
on_blog 0 博客编辑
on_message 0 私信编辑
on_admin 0 后台主题编辑
on_plugin_pages 0 其它插件自带的编辑页(问答 / 网盘求助 / 单页 / 市场投稿 / 公告)
default_mode editOnly editOnly / edit&preview / previewOnly
height_thread 520 发帖框高度(160~2000)
height_reply 300 回帖框高度(160~2000)
theme auto auto / light / dark;auto 跟随站点皮肤
lossy_threshold 15 格式损失提示阈值(%),0 = 关闭提示
keep_h1 0 保留 h1(默认降级为 h2,因核心白名单只有 h2~h4)

七、路由

方法 路径 动作
GET /admin/cherry 设置页
POST /admin/cherry/save 保存全部字段(PRG 302 + 会话 flash)

必须继承 app\Controllers\Admin\BaseController(自动获得管理员校验 + 二次验证门禁)。


八、技术要点

  • Fail-Safe 而非 Fail-Closed:产物缺失 / 总开关关闭 / 本页未启用 → 一律不动 needsEditor,
    核心编辑器照常工作。boot.js 里 new Cherry() 失败会完整回滚(移除宿主、恢复 textarea 的 name),
    保证用户至少能发帖。
  • 零外链:产物 CSS 里 0 个 @import、0 个外域 url();只有本地字体。
    不含 blob: / wasm-unsafe-eval 依赖,现有 CSP 直接可用。
  • 零内联:参数走 <div hidden data-cfg="…">(JSON_HEX_* + ENT_QUOTES 双保险),
    视图/钩子里没有任何 JS 字面量或 style=。
  • 破缓存:所有资源 URL 带 ?v=filemtime。
  • 请求级状态用类静态属性(Plugin::runtime())—— 钩子在闭包作用域执行,变量传不出去。
  • 后台设置页与 vditor 插件共用同一套视觉规范:盒子全部交给核心
    (.admin-content 本身是白卡、.admin-section 只给间距),标题/描述用核心
    tab-pane-head / tab-pane-desc,语义色用 --mn-alert-success-* / --mn-alert-error-* /
    --mn-comp-alert-warning-*。
    ⚠️ 改这两个插件的设置页样式时要同步改,否则两个编辑器插件看起来像两套产品。
    核心后台布局类在 assets/css/admin.css(不在 modern.css),且用的是 --c-* 变量。
  • 正文样式判据是 activated 而非 enabled:关掉编辑器后,用 Cherry 写过的老帖子仍要能正常显示。

九、已知取舍(有意为之,不是缺陷)

  1. 一级标题在提交时降为二级 —— 核心白名单只有 h2~h4,不降级会被 unwrap 成段落。
  2. 没有下划线按钮 —— 下划线靠 text-decoration,核心样式白名单不放行,加了也看不见。
  3. 高亮用「背景色」实现 —— 输出 span + background-color,能完整保留;==x== 语法已关闭。
  4. 公式 / 图表 / 音视频 / PDF-Word 导入已移除 —— 会请求外网,或产出白名单外的标签。
  5. 编辑旧帖时格式可能损失 —— 提示 + 「源码模式」出口(源码模式逐字节原样提交,不降级 h1)。
  6. 前台正文样式只补三样 —— Prism 配色 / 图标字体 / 表格滚动;核心 .post-text-content 的排版一概不动。

十、安装与启用

  1. 把 plugins/cherry/ 整个目录放到站点 plugins/ 下(产物已随包,无需构建)。
  2. 后台「插件」列表找到 Cherry 编辑器 → 启用。
  3. 进设置页 → 打开总开关 → 保存。
  4. 打开 /post/new 验证:工具栏出现、内容能发出去。

产物缺失时插件完全不生效(后台设置页会红字列出缺失文件),前台退回核心编辑器,不会白屏。

轻量级、高性能、零 MySQL 依赖的PHP社区系统。
| Views 13 | Replies 1

All Replies (1)

🌲Lv.3 初级 🌛 见习会员
2026-10-20 17:52:55
「纯插件实现、零核心改动」这点挺加分,尤其补白名单 mark 标签这种收口做得很克制,比直接改核心模板的插件好维护太多。几个想问的:一是编辑旧帖时的格式损失提示,判定粒度是到块还是到字符,会不会出现 Markdown 能表示、渲染出来却和原文对不上的情况;二是颜色和字号走的是 span style 白名单,那前台主题如果自带 CSS 覆盖同名样式会不会打架;三是包上写最低核心版本 1.2.1,可现在站上还是 1.2.0,是得等核心先升,还是向下兼容能直接跑?
#1 floor

Please Log in