Filament MarkdownEditor 深度解析:Markdown 编辑、图片上传与 XSS 防护的完整实践
【免费下载链接】filamentA powerful open-source UI framework for Laravel • Build and ship apps & admin panels fast with Livewire项目地址: https://gitcode.com/GitHub_Trending/fi/filament
本文以 Filament Forms 包的 Markdown editor 组件为核心,完整讲解MarkdownEditor的使用方法:默认工具栏配置、按钮自定义、编辑器高度控制、图片拖拽上传的存储策略,以及最关键的安全防护(sanitizeHtml()防 XSS)。读完本文,你不仅能直接复制可运行的字段配置代码,还能从源码层面理解 Filament 是如何强制图片公开存储、校验上传文件并渲染编辑器的。
一、基础用法:编辑与预览 Markdown
Markdown editor 允许用户编辑并预览 Markdown 内容,同时支持通过拖拽上传图片。它是 Filament 表单字段体系中的一个标准字段组件,实现位于 MarkdownEditor.php:
use Filament\Forms\Components\MarkdownEditor; MarkdownEditor::make('content')从源码结构看,MarkdownEditor继承自Field,实现了Contracts\CanBeLengthConstrained与HasEmbeddedView接口,并组合了多个 Concern(MarkdownEditor.php#L14-L28):
CanConfigureCommonMark:配置 Markdown 解析器(CommonMark)的选项与扩展;Concerns\HasFileAttachments:处理图片附件的存储、校验与 URL 生成;Concerns\HasMinHeight/Concerns\HasMaxHeight:控制编辑器高度;Concerns\InteractsWithToolbarButtons:管理工具栏按钮;Concerns\HasPlaceholder:占位文本。
其渲染方式值得注意:toEmbeddedHtml()输出的不是一个普通的<textarea>,而是一个挂载了 Alpine 组件markdownEditorFormComponent的容器(MarkdownEditor.php#L118-L169),内部的<textarea x-ref="editor">会被 CodeMirror 接管成为富文本编辑区。浏览器端测试(MarkdownEditorTest.php#L684-L738)中可以直接访问document.querySelector('.CodeMirror'),印证了底层编辑器为 CodeMirror。
组件还会把一系列状态注入 Alpine,包括minHeight、maxHeight、placeholder、toolbarButtons、liveDebounce等,并通过canAttachFiles开关决定前端是否启用图片附加功能。
二、安全:输出 HTML 必须经过 sanitizeHtml 处理
这是官方文档着重强调的一点:编辑器默认输出原始 Markdown 和 HTML,并直接发送到后端。攻击者可以拦截组件值,向后端发送任意的原始 HTML 字符串。因此,当你从 Markdown editor 中取出 HTML 并输出到页面时,必须做净化(sanitization),否则站点可能暴露于跨站脚本攻击(XSS)风险之下。
Filament 自身在TextColumn、TextEntry等组件中从数据库输出原始 HTML 时会自动净化,以移除危险的 JavaScript。但如果你在自己的 Blade 视图中输出 Markdown editor 的内容,这一责任在你。推荐使用 Filament 提供的sanitizeHtml()辅助方法——它与上述组件使用的是同一套净化工具:
{!! str($record->content)->markdown()->sanitizeHtml() !!}编辑器自身的只读(disabled)渲染路径也遵循同样的原则:在 MarkdownEditor.php#L102 中,禁用态内容通过str($this->getState())->markdown(...)->sanitizeHtml()输出,源码注释(MarkdownEditor.php#L16-L19)也明确提醒:渲染 Blade 视图时务必同时使用sanitizeHtml()与markdown(),绝不能对未净化内容使用{!! !!}。
需要注意的默认配置限制:Filament 内置的 HTML 净化器为了支持富文本排版特性(字体颜色、文字高亮、图片尺寸等),允许保留内联style属性。这意味着background: url(...)、position: fixed之类的 CSS 属性不会被剥离。如果内容来自不可信用户,应考虑收紧默认配置,具体方法见 安全文档 中关于自定义净化器(customizing the sanitizer)的章节。
三、自定义工具栏按钮
通过toolbarButtons()方法可以设置编辑器工具栏按钮,文档给出的默认值如下:
use Filament\Forms\Components\MarkdownEditor; MarkdownEditor::make('content') ->toolbarButtons([ ['bold', 'italic', 'strike', 'link'], ['heading'], ['blockquote', 'codeBlock', 'bulletList', 'orderedList'], ['table', 'attachFiles'], ['undo', 'redo'], ])主数组中的每个嵌套数组代表工具栏中的一组按钮。除静态数组外,toolbarButtons()还接受闭包以动态计算按钮,闭包参数可注入各类工具方法(UtilityInjection)。
3.1 默认按钮与attachFiles的特殊联动
默认的按钮定义在 MarkdownEditor.php#L35-L47 的getDefaultToolbarButtons()中。注意第四组有个细节:attachFiles按钮是否出现取决于hasFileAttachments()的结果——也就是说,如果你通过fileAttachments(false)关闭了图片附加功能,默认工具栏中的attachFiles会自动消失。hasFileAttachmentsByDefault()的实现(MarkdownEditor.php#L72-L75)就是检查工具栏中是否存在attachFiles按钮,二者互为因果。
3.2 增删按钮:disableToolbarButtons()、enableToolbarButtons()等
工具栏按钮的管理逻辑集中在 InteractsWithToolbarButtons.php。除了直接整体替换的toolbarButtons(),还支持渐进式修改:
MarkdownEditor::make('content') ->disableToolbarButtons(['bold', 'italic', 'attachFiles']) // 从默认按钮中移除 ->enableToolbarButtons(['underline', 'subscript']) // 追加额外按钮 ->disableAllToolbarButtons() // 清空全部按钮 ->disableAllToolbarButtons(false) // 条件式禁用getToolbarButtons()(InteractsWithToolbarButtons.php#L78-L125)的装配流程是:先求值toolbarButtons(闭包或静态值),没有则取getDefaultToolbarButtons();然后依次应用插件层的附加修改(getExtraToolbarButtonsModifications())和实例层修改(disable/enable/disableAll),用户层修改总是优先;最后做分组归一化——连续的字符串项会被合并成一组,数组自成一组,空白组被过滤掉。
从测试用例(MarkdownEditorTest.php#L41-L257)可以确认几个行为边界:
toolbarButtons([['bold', 'italic'], 'strike', 'link'])这样的混合格式会被自动整理为[['bold', 'italic'], ['strike', 'link']];- 空数组组(
[])会被自动过滤; - 当
toolbarButtons()使用了闭包时,再调用disableToolbarButtons()或enableToolbarButtons()会抛出LogicException,提示应直接在闭包中返回想要的按钮; hasToolbarButton()支持传数组做“至少存在一个”的查询。
这些测试与源码实现一一对应,可以作为你在自己项目中集成该组件时的行为基线。
四、设置编辑器高度
通过minHeight()和maxHeight()控制编辑器高度,二者接受任意 CSS 长度值:
use Filament\Forms\Components\MarkdownEditor; MarkdownEditor::make('content') ->minHeight('12rem') ->maxHeight('24rem')关键行为规则(均与文档一致,并由 MarkdownEditorTest.php#L420-L505 的“height constraints”测试组验证):
- 默认最小高度为
10rem(getMinHeight()缺省返回'10rem'); - 内容超过
maxHeight()后,编辑器停止增长并变为可滚动; - 两个方法可单独使用:
minHeight()设置起始高度但允许编辑器继续增长,maxHeight()限制最大高度; - 向
minHeight()传null时,交互态编辑器的最小高度降为3rem;向maxHeight()传null表示取消上限(getMaxHeight()默认就是null); - 禁用(disabled)内容在
minHeight()为null时使用自然高度;这两个约束对禁用态同样生效——渲染时以 CSS 变量--min-height/--max-height注入(MarkdownEditor.php#L93-L96),且设置了maxHeight时容器会获得tabindex="0"以便键盘滚动; - 两个方法同样接受闭包动态计算。
浏览器端断言(MarkdownEditorTest.php#L694-L729)进一步验证了像素级表现:默认编辑器clientHeight为 160px(10rem),minHeight(null)时为 48px(3rem),minHeight(null)+maxHeight('12rem')时最大滚动容器高度为 192px,内容超出后scrollHeight > clientHeight即进入滚动模式。
五、图片上传:强制公开存储与校验
5.1 为什么只能公开访问
图片可以直接拖拽进编辑器上传。文档明确指出:这些图片始终上传到具有公开存储权限的公开 URL,因为静态内容(Markdown 正文)中不支持生成临时文件上传 URL。源码对此做了硬性约束(MarkdownEditor.php#L62-L70):
public function fileAttachmentsVisibility(string | Closure | null $visibility): static { throw new LogicException('The visibility of file attachments for markdown content is always `public`, since generating temporary file upload URLs is not supported in static content.'); } public function getFileAttachmentsVisibility(): string { return 'public'; }也就是说,试图调用fileAttachmentsVisibility('private')会直接抛出LogicException(对应 MarkdownEditorTest.php#L406-L418 的测试)。
5.2 自定义存储磁盘与目录
use Filament\Forms\Components\MarkdownEditor; MarkdownEditor::make('content') ->fileAttachmentsDisk('s3') ->fileAttachmentsDirectory('attachments')两个方法同样支持闭包动态取值。磁盘名的解析逻辑在 MarkdownEditor.php#L49-L60:显式设置的磁盘名优先;否则读取filament.default_filesystem_disk配置,且当该配置为local时会自动切换为public磁盘——这正是“必须公开”这一约束在磁盘选择上的落地。对应测试验证了三种情形:配置为local时返回public、配置为s3时返回s3、显式指定custom-disk时返回custom-disk(MarkdownEditorTest.php#L379-L404)。
5.3 上传文件类型的验证
使用fileAttachmentsAcceptedFileTypes()控制允许的 MIME 类型,默认接受image/png、image/jpeg、image/gif、image/webp(默认值定义在 HasFileAttachments.php#L35):
use Filament\Forms\Components\MarkdownEditor; MarkdownEditor::make('content') ->fileAttachmentsAcceptedFileTypes(['image/png', 'image/jpeg'])5.4 上传文件大小上限
使用fileAttachmentsMaxSize()控制图片最大体积,单位为 KB,默认上限为 12288 KB(12 MB)(默认值见 HasFileAttachments.php#L37):
use Filament\Forms\Components\MarkdownEditor; MarkdownEditor::make('content') ->fileAttachmentsMaxSize(5120) // 5 MB5.5 上传链路源码走读
HasFileAttachments(HasFileAttachments.php)揭示了完整的上传链路,前后端各有一道校验:
- 前端预检:Alpine 的
uploadFileAttachmentUsing(MarkdownEditor.php#L136-L163)在调用$wire.upload(...)前,先用注入的acceptedTypes与maxSize(file.size > maxSize * 1024)做客户端拦截,不通过直接回调onError并展示对应语言包消息; - Livewire 上传:文件经
$wire.upload("componentFileAttachments.{statePath}", file, ...)暂存为TemporaryUploadedFile; - 服务端再校验:
getUploadedFileAttachment()(HasFileAttachments.php#L60-L89)用 Laravel Validator 再次执行file、max:{maxSize}、mimetypes:{...}规则,任何一道不过即返回null; - 落盘并取 URL:
saveUploadedFileAttachmentAndGetUrl()(HasFileAttachments.php#L121-L134)保存文件后返回其 URL,前端拿到 URL 后由编辑器插入图片语法。落盘时若可见性为public,还会尝试setVisibility($path, 'public')(HasFileAttachments.php#L103-L111)。
此外,fileAttachments(bool | Closure | null)可用于整体开关图片附加功能(HasFileAttachments.php#L301-L306)。测试还确认了一个细节:fileAttachments(true)不会强制把attachFiles按钮加回被disableToolbarButtons()移除的工具栏,但拖拽上传能力本身依然可用(MarkdownEditorTest.php#L362-L376)。
六、验证与其他约束能力
MarkdownEditor还实现了CanBeLengthConstrained,因此支持内容长度约束(测试见 MarkdownEditorTest.php#L507-L583):
MarkdownEditor::make('content') ->minLength(10) // 生成 min:10 验证规则 ->maxLength(1000) // 生成 max:1000 验证规则 // 或 ->length(100) // 固定长度,生成 size:100 ->required() // 常规 required 验证同样生效以及placeholder()设置占位文本(支持闭包动态取值)。
七、小结与参考路径
MarkdownEditor的设计可以概括为三条主线:
- 编辑体验:CodeMirror 驱动的编辑/预览一体区域,工具栏按钮可整体替换、渐进增删或清空,高度可用
minHeight/maxHeight精确控制; - 图片上传:公开存储是唯一选项(
local磁盘自动升级为public磁盘),前后端双重校验 MIME 类型与大小(默认 4 种图片格式、12288 KB 上限); - 安全边界:原始 HTML 入库后,输出侧必须配合
markdown()与sanitizeHtml()使用,且需知悉默认净化器保留内联style属性的取舍。
延伸阅读(均为当前仓库内相对路径):
- 组件实现:packages/forms/src/Components/MarkdownEditor.php
- 工具栏按钮 Trait:packages/forms/src/Components/Concerns/InteractsWithToolbarButtons.php
- 文件附件 Trait:packages/forms/src/Components/Concerns/HasFileAttachments.php
- 行为测试基线:tests/src/Forms/Components/MarkdownEditorTest.php
- 净化器自定义与安全文档:docs/09-advanced/06-security.md
- 官方字段文档:packages/forms/docs/11-markdown-editor.md
【免费下载链接】filamentA powerful open-source UI framework for Laravel • Build and ship apps & admin panels fast with Livewire项目地址: https://gitcode.com/GitHub_Trending/fi/filament
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考