有两个开放的 PR(Pull Request)引入了在帖子原始内容中编辑 BBCode 属性的不同方法:
-
#43187 — 添加周期性事件后继主题模式 引入了
BbcodeRewriter,它通过识别已渲染的事件,并在自动滚动时安全地重写其对应的 BBCode 开始标签。 -
#42932 — 将事件操作添加到工作流 引入了
PrettyText.update_bbcode_attributes,它定位解析后的 BBCode 块,并在工作流打开或关闭事件时更新其属性。
在对 #43187 进行审查期间,Martin 建议从特定于事件的服务中提取通用的 BBCode 操作功能。
我最初包含了一些来自另一个 PR 的相关基于令牌的功能,但这在 #43187 中引入了两种重写机制。由于该分支中的基于令牌的 API 没有生产环境调用者,我在提交 6d13371 中将其移除。
基于令牌的 API 对于 #42932 仍然具有独立的价值。
我在思考这两种方法最终是否应该收敛为一个可复用的核心 API,还是它们不同的需求证明保留两者是合理的。
当前实现的比较
下表描述了代码当前实现的内容,而不是暗示任何一种方法本质上都是错误的。
| 能力 | BbcodeRewriter (#43187) |
基于令牌的 API (#42932) | 含义与重要性 |
|---|---|---|---|
| 1. 使用 Discourse 的 BBCode 解析器 | 使用 parseBBCodeTag 扫描候选开始标签,这是 Markdown 引擎使用的相同开始标签语法。 |
使用 DiscourseMarkdownIt.parse 获取 Markdown 令牌并找到匹配的 BBCode 块。 |
两种方法都理解 BBCode 语法,而不是仅依赖独立的正则表达式。它们在如何识别要编辑的标签方面有所不同。 |
| 2. 选择目标 BBCode 块 | 临时标记候选开始标签,渲染帖子,并识别对应于选定 HTML 元素(如 div.discourse-post-event)的候选项。 |
选择第一个其 meta.bbcode 与请求的标签名称匹配的解析后 BBCode 令牌。 |
目标身份很重要。 #43187 需要将原始 BBCode 与 Discourse 实际渲染的事件关联起来。#42932 的工作流使用第一个匹配的解析后事件块。 |
| 3. 处理缺失或模糊的目标 | 当无法可靠地识别目标时,引发 TargetNotFound、AmbiguousTarget 或 UnsupportedRenderer。 |
如果不存在匹配的解析块则返回 null;否则选择第一个匹配项。 |
失败行为不同。 #43187 故意拒绝模糊的自动编辑。当调用者预期只有一个相关块时,选择第一个解析块可能已经足够。 |
| 4. 验证原始到渲染的映射 | 渲染标记内容,检查预期 HTML 元素上的标记,并确认插入和移除标记不会改变渲染后的 HTML。 | 使用 Markdown 解析器的令牌元数据来定位开始标签,无需额外的渲染 HTML 身份检查。 | 这防止编辑错误的渲染目标。 当后台作业自动归档现有事件时,这一点特别相关,但涉及额外的渲染和复杂性。 |
| 5. 检查选定的开始标签 | 在重写之前,重新扫描提供的原始内容,并检查选定的候选项是否仍与其预期的开始标签文本匹配。 | 在更新调用期间定位匹配的开始标签,而没有单独的预期开始比较。 | 这是一个内部一致性检查。 它有助于避免重写不再符合早期选择的候选项。它本身并不是针对并发数据库编辑的保护。 |
| 6. 处理属性名称和现有值 | 使用解析器的属性边界重写选定属性。它还规范化等效的渲染属性名称,允许一致地删除诸如 Recurrence 和 recurrence 之类的变体。 |
使用属性令牌匹配来替换或删除请求的名称,并使用共享的 BBCode 属性序列化器来插入值。 | 两者都旨在保留无关属性。 在归档事件时,规范化等效名称尤为重要,因为遗留一个循环别名可能会导致已完成的事件保持循环状态。 |
| 7. 验证重写后的标签 | 再次解析重写后的开始标签,并检查其属性是否与预期值匹配。无效的往返过程会引发错误。 | 直接生成重写后的开始标签,没有等效的最终解析和比较步骤。 | 往返验证可以捕获序列化问题,例如属性值变化或格式错误的边界。基于令牌的方法更简单,但它依赖于其重写逻辑和测试,而不是这种额外的运行时检查。 |
| 8. 保留无关的帖子内容 | 更改选定的开始标签,同时保留周围的原始内容。它还检查无关的属性值是否在重写后幸存。 | 替换选定的开始标签子字符串,同时保留周围的原始内容和未请求修改的属性。 | 两者都需要避免意外的帖子更改。 与事件相关的编辑不应悄悄修改周围的讨论或无关的 BBCode 属性。 |
| 9. 实现成本 | 需要候选扫描、临时标记、帖子渲染、HTML 检查、属性重写和验证。 | 解析 Markdown 令牌,选择第一个匹配的开始标签并直接重写它。 | 更强的运行时检查是有成本的。 问题是这些额外的安全措施应该对所有调用者可用、可选,还是仅在应用程序要求的地方实现。 |
为什么需求不同
对于 #43187,当一次发生结束时,后继滚动会自动运行。原始事件被归档为非循环发生,并为下一次发生创建一个新的主题。
如果修改了错误的 [event] 块,或者重写后的属性不再代表预期的事件,错误可能会在自动滚动尝试中持续存在。
对于 #42932,工作流操作显式请求打开或关闭事件。其当前的基于令牌的实现修改第一个解析的匹配事件块。
因此,基于令牌的方法对于该用例不一定不合适。它只是提供了不同的选择和验证保证。
两种方法已经共享一些底层基础设施,包括 parseBBCodeTag 和 serializeBBCodeAttr。
共享抽象的可能方向
一种可能性可能是分离三个职责:
-
查找 BBCode 开始标签 — 使用解析器令牌、显式候选选择或渲染元素映射。
-
重写其属性 — 使用一个共享实现来处理属性边界、序列化以及保留无关值。
-
验证结果 — 根据调用者的情况,可选地强制目标身份、歧义检测、渲染内容等价性或属性往返检查。
这可能允许更简单的工作流使用直接的令牌选择,而更敏感的自动编辑操作请求更强的安全措施。
然而,与维持两个较小的 API 相比,这也可能引入不必要的复杂性。
给维护者的问题
鉴于它们不同的目标选择需求,是否最好让这两个 PR 保留各自独立的重写 API?
或者,一个具有可配置目标选择和验证的共享低级属性重写器是否是更好的长期方向?
我特别感兴趣的是,BbcodeRewriter 当前实现的额外安全检查是否被视为通用的有用核心功能,还是应该保留为需要更强保证的用例所特有。
我并不是建议任何一个 PR 需要阻止另一个;目的是随着这两个功能的开发,避免在 Discourse 的核心 BBCode 基础设施中产生不必要的重复。