十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

EmDash 插件开发实战:深入 Block Kit 声明式 UI 体系

EmDash 插件开发实战:深入 Block Kit 声明式 UI 体系 CMS后端前端插件系统【免费下载链接】emdashEmDash is a full-stack TypeScript CMS based on Astro; the spiritual successor to WordPress项目地址https://gitcode.com/gh_mirrors/emdas/emdash点击查看免费下载Block Kit 是 EmDash CMS基于 Astro 的全栈 TypeScript CMS为沙箱化插件提供的一套声明式 JSON UI 方案插件通过普通 JSON 描述管理后台页面由宿主的BlockRenderer负责渲染插件 JavaScript 完全不进入浏览器。本文以官方参考文档 block-kit.md 为主线结合emdash-cms/blocks包的源码与测试完整讲解块类型、元素类型、交互协议、校验边界与实战写法读完即可用纯 JSON 或 TypeScript 构建出带表单、表格、图表、条件字段和通知的管理界面。什么是 Block KitBlock Kit 是 EmDash 为运行时安装的沙箱插件提供的管理后台 UI 语言。它的核心理念是声明式插件 admin 路由返回一个BlockResponse包含blocks数组与可选的toast宿主负责渲染。零浏览器 JavaScript插件代码在沙箱中运行宿主浏览器端只渲染 JSON 描述出的块与元素。借鉴 Slack Block Kit 但不等同概念和命名相似但块/元素类型与能力不同。需要区分两种插件形态受信任插件在astro.config.ts中声明可以携带自定义 React 组件Block Kit 面向运行时安装的沙箱插件。此外原生插件也会使用 Block Kit 元素实现 Portable Text 块级编辑字段而 Plugin CLI 与 registry 包不能注册 Portable Text 块类型。从仓库源码看emdash-cms/blockspackage.jsonv0.38.0是独立的包同时提供客户端与服务端两个入口index.ts默认入口导出BlockRenderer、renderElement、blocks/elements构建器、validateBlocks与全部类型server.tsemdash-cms/blocks/server服务端安全入口只导出构建器、校验函数与类型不引入任何 React 组件插件路由处理器应优先从这里导入。// 服务端入口无 React 依赖 import { blocks, elements, validateBlockResponse } from emdash-cms/blocks/server;工作原理一次完整的交互闭环Block Kit 的交互模型是无状态往返用户导航到插件 admin 页面Admin 向插件 admin 路由发送page_loadinteraction插件返回带blocks数组的BlockResponseAdmin 使用BlockRenderer渲染这些块用户交互按钮点击、表单提交→ interaction 被发回插件插件返回新的块Admin 重新渲染。注意一个关键细节EmDash 只会解析一次请求体并把它暴露为ctx.input。在路由处理器中应直接读取ctx.input而不是调用ctx.request.json()——请求体已被消费。BlockInteraction是page_load、block_action、form_submit三类载荷的判别联合见 types.ts。import type { BlockInteraction } from emdash-cms/blocks; routes: { admin: { handler: async (ctx) { // EmDash parses the request body once and exposes it as ctx.input; // read it directly rather than ctx.request.json() (the body is consumed). // BlockInteraction is the discriminated union of page_load, // block_action, and form_submit payloads. const interaction ctx.input as BlockInteraction; if (interaction.type page_load) { return { blocks: [ { type: header, text: My Plugin Settings }, { type: form, block_id: settings, fields: [ { type: text_input, action_id: api_url, label: API URL }, { type: toggle, action_id: enabled, label: Enabled, initial_value: true }, ], submit: { label: Save, action_id: save }, }, ], }; } if (interaction.type form_submit interaction.action_id save) { await ctx.kv.set(settings, interaction.values); return { blocks: [/* updated blocks */], toast: { message: Settings saved, type: success }, }; } }, }, }BlockInteraction的三个成员在 types.ts 中定义类型载荷字段触发时机PageLoadtype: page_load、page首次进入 admin 页面/面板BlockActiontype: block_action、action_id、block_id?、value?、page?点击按钮、排序/翻页等FormSubmittype: form_submit、action_id、block_id?、values、page?提交表单values为各字段值对象BlockResponse的结构是{ blocks: Block[]; toast?: { message; type } }types.ts。块类型总览Block TypesBlock Kit 内置 17 类可用的块另有tab类型存在但生产校验器不允许见下文类型描述header大号加粗标题section文本可带可选附件元素divider水平分隔线fields两列标签/值网格table数据表格支持格式化、排序、分页actions水平排布的按钮与控件行stats仪表盘指标卡片带趋势指示form输入字段支持条件显隐与提交image块级图片带 alt 文本与可选标题context小号弱化帮助文本columns2-3 列布局内部可嵌套块chart图表时间序列折线/柱状、饼图、自定义 EChartscode语法高亮代码块meter进度/配额仪表条banner信息、警告或错误内联消息empty空状态带可选命令与操作accordion可折叠区块内部嵌套块这些类型的 TypeScript 定义集中在 types.ts 的Block联合类型中每一类都有明确的必填/可选字段。元素类型总览Element Types块内部的交互与输入单元称为元素类型描述button操作按钮可带确认对话框link宿主解析的导航不派发 actiontext_input单行或多行文本输入number_input数字输入支持 min/maxselect下拉选择toggle开关secret_input掩码输入API Key、Tokencheckbox多选复选框radio单选按钮date_input日期选择器combobox可搜索的下拉选择repeater对象数组含标量子字段media_picker媒体库选择器存储资产 URL元素类型的定义见 types.ts其中值得注意的细节select支持optionsRoute指向一个返回{ items: Array{ id, name } }的插件路由用于动态填充选项secret_input的has_value用于表示已保存过值避免把密钥回显repeater的子字段被限制为text_input、number_input、select、toggle四种标量元素media_picker存储的是所选资产的URL 字符串因此与普通text_input值兼容——替换控件后旧内容仍可正常工作源码注释明确说明这一点。块语法详解以下所有 JSON 示例均来自官方参考文档并经过validateBlocks校验测试覆盖见下文文档示例被自动测试一节。Header{ type: header, text: Settings }Section{ type: section, text: Configure your plugin settings below., accessory: { type: button, label: Refresh, action_id: refresh } }accessory可挂载任意操作元素按钮、链接等section必须有text。Divider{ type: divider }Fields{ type: fields, fields: [ { label: Status, value: Active }, { label: Last Sync, value: 2 hours ago } ] }Stats{ type: stats, items: [ { label: Total, value: 1,234, trend: up, description: 12% vs last week }, { label: Active, value: 567 } ] }三个易错点items— 数组键名是items不是statstrend— 取值为up、down或neutral在数值旁渲染方向箭头description— 数值下方的次级说明行用于 12% vs last week 这类上下文。校验器validation.ts要求每项label为字符串、value为字符串或数字、trend必须命中TREND_VALUES。Table{ type: table, columns: [ { key: name, label: Name }, { key: status, label: Status }, { key: date, label: Date } ], rows: [{ name: Item 1, status: Active, date: 2025-01-01 }], page_action_id: browse_items, empty_text: No items yet. }page_action_id—必填。用户排序或翻页时Admin 会把该 id 作为block_action的action_id发回插件empty_text— 当rows为空时展示的占位文本next_cursor— 设置后渲染加载更多控件用于游标分页。列定义还支持formattext | badge | relative_time | number | code与sortable布尔值见 types.ts 的TableColumn。校验器要求columns、rows、page_action_id均必填validation.ts。Actions{ type: actions, elements: [ { type: button, label: Save, action_id: save, style: primary }, { type: button, label: Cancel, action_id: cancel } ] }按钮style可取值primary | danger | secondary。Form{ type: form, block_id: settings, fields: [ { type: text_input, action_id: name, label: Name }, { type: number_input, action_id: count, label: Count, min: 0, max: 100 }, { type: select, action_id: theme, label: Theme, options: [ { label: Light, value: light }, { label: Dark, value: dark } ] }, { type: toggle, action_id: enabled, label: Enabled, initial_value: true }, { type: secret_input, action_id: api_key, label: API Key } ], submit: { label: Save, action_id: save_settings } }校验器对表单字段有额外约束link不能作为表单字段condition必须是{ field, eq }或{ field, neq }之一validation.ts。submit对象必填label与action_id。Columns{ type: columns, columns: [ [ { type: header, text: Usage }, { type: meter, label: Storage used, value: 65 } ], [ { type: header, text: Activity }, { type: context, text: Last sync 2 hours ago } ] ] }columns— 恰好2 或 3 列每列是一个块数组。校验器会拒绝少于 2 或多于 3 列的响应validation.ts。Chart时间序列{ type: chart, config: { chart_type: timeseries, series: [ { name: Requests, data: [ [1709596800000, 42], [1709600400000, 67], [1709604000000, 53] ], color: #086FFF }, { name: Errors, data: [ [1709596800000, 2], [1709600400000, 5], [1709604000000, 1] ] } ], x_axis_name: Time, y_axis_name: Count, style: line, gradient: true, height: 300 } }参数说明series[].data—[timestamp_ms, value]元组数组按时间排序series[].color— 十六进制颜色可选缺省时按系列索引从 Kumo 调色板自动分配style—line默认或bargradient— 折线下方填充渐变默认 falseheight— 图表高度像素默认 350。校验器要求每个数据点必须是[number, number]二元组series不能为空validation.ts。Chart自定义 ECharts饼图、仪表盘或任意 ECharts 可视化{ type: chart, config: { chart_type: custom, options: { series: [ { type: pie, data: [ { value: 335, name: Published }, { value: 234, name: Draft }, { value: 120, name: Scheduled } ] } ] }, height: 300 } }options— 原始 ECharts option 对象会原样传给chart.setOption()。一个安全细节自定义图表的options会被递归扫描其中的image://资源与image键会被当作图片 URL 校验必须 root 相对或 HTTPS 且命中允许主机列表见 validation.ts 的validateChartResources。Code{ type: code, code: const greeting \Hello!\;\nconsole.log(greeting);, language: ts }language—ts、tsx、jsonc、bash或css缺省ts。Meter{ type: meter, label: Storage used, value: 65, custom_value: 6.5 GB / 10 GB }value— 数值默认范围 0-100max/min— 自定义范围默认 0-100校验器要求min maxcustom_value— 用自定义字符串替代百分比显示如 750 / 1,000。Banner{ type: banner, title: API key invalid, description: Please check your API key in settings., variant: error }variant—default信息默认、alert警告或errortitle与description至少提供其一校验器强制。Empty{ type: empty, title: No submissions, description: New submissions appear here., command_line: pnpm run seed, size: base, actions: [{ type: button, action_id: refresh, label: Refresh }] }size可取值sm | base | lgcommand_line显示一条建议的命令actions是操作元素数组。不要使用tab块包导出了TabBlock类型与blocks.tab()构建器builders.tsReact 渲染器也有 tab 组件tab.tsx但生产环境validateBlocks()的允许列表在渲染管线中不包含tab——包含它的 admin 响应会被拒绝。在校验器接受它之前请不要产出tab块。Accordion{ type: accordion, label: Advanced settings, default_open: false, blocks: [{ type: context, text: Settings visible when expanded }] }default_open控制默认展开状态blocks内可嵌套任意合法块。Repeater 与 media_picker管理端创作元素repeater与media_picker是管理端创作元素admin-authoring elements不是沙箱 admin 页面form中的普通字段。repeater捕获对象数组嵌套字段仅限text_input、number_input、select、toggle四种标量类型{ type: repeater, action_id: items, label: Questions, item_label: Question, fields: [ { type: text_input, action_id: question, label: Question }, { type: text_input, action_id: answer, label: Answer, multiline: true } ] }item_label用于 UI 中的单数标签如 FAQ → Add FAQ。源码类型 RepeaterElement 还支持min_items/max_items/initial_value并注释了两个重要行为管理端组件从子字段类型播种新行空字符串/false不会使用initial_value预填行运行时块渲染器renderElement对repeater故意返回null——repeater 值持久化在父块上由插件自己的运行时组件消费。media_picker打开媒体库并把所选资产的URL 字符串作为值存储{ type: media_picker, action_id: hero, label: Hero image, mime_type_filter: image/ }mime_type_filter是 RFC 6838 风格的图片 MIME 前缀或精确类型如image/、image/png、image/svgxml。校验器使用正则MEDIA_PICKER_MIME_FILTER_REvalidation.ts拒绝image/*通配符以及video/、application/pdf等非图片类型缺省为image/。声明式字段组件Declarative field widgetsadmin 字段编辑器可以用 Block Kit 元素渲染插件字段组件。schema 字段通过pluginId:widgetName引用配置声明的标准描述符提供name、label、兼容的fieldTypes与elements。字段组件渲染器当前仅支持五种元素text_inputnumber_inputtoggleselectmedia_picker它按每个元素的action_id存储一个对象因此应使用json字段承载组合值其他字段类型虽被 manifest schema 接受但没有端到端保存测试覆盖。不支持的其它元素类型会渲染unsupported element提示消息。emdash-plugin.jsonc接受这种字段组件定义plugin CLI 会把它保留在 registry manifest 与生成的描述符中。注意现状构件传输链路是有测试的但仓库的浏览器 E2E 测试仍覆盖原生 React 字段组件而非 registry 声明式组件——对选中的元素请自行验证渲染出的编辑器与保存值。条件字段Conditional Fields根据其他字段的值显示/隐藏字段。在客户端求值无网络往返{ type: toggle, action_id: auth_enabled, label: Enable Authentication }{ type: secret_input, action_id: api_key, label: API Key, condition: { field: auth_enabled, eq: true } }condition的两种合法形态types.ts{ field: string; eq?: unknown }{ field: string; neq?: unknown }表单条件的渲染行为有专门测试 form-conditions.test.tsx 覆盖。Builder 辅助函数用 TypeScript 生成块手写 JSON 容易出错emdash-cms/blocks提供类型安全的构建器。blocks与elements两组函数的完整实现见 builders.ts全部参数都有类型约束与缺省处理import { blocks, elements } from emdash-cms/blocks; const { header, form, section, stats, timeseriesChart, customChart, banner: bannerBlock, empty, accordion, } blocks; const { textInput, toggle, select, button, repeater, mediaPicker } elements; return { blocks: [ header(Settings), form({ blockId: settings, fields: [ textInput(site_title, Site Title, { initialValue: My Site }), toggle(generate_sitemap, Generate Sitemap, { initialValue: true }), select(robots, Default Robots, [ { label: Index, Follow, value: index,follow }, { label: No Index, value: noindex,follow }, ]), ], submit: { label: Save, actionId: save }, }), // Timeseries chart timeseriesChart({ series: [ { name: Page Views, data: [ [Date.now() - 3600000, 100], [Date.now(), 150], ], }, ], yAxisName: Views, gradient: true, }), // Pie chart via custom ECharts options customChart({ options: { series: [ { type: pie, data: [ { value: 335, name: Published }, { value: 234, name: Draft }, ], }, ], }, }), ], };构建器命名采用 camelCase 参数如blockId、actionId、initialValue内部转换为 JSON 的 snake_case 字段block_id、action_id、initial_value并只在传入时才写入对应键。按钮确认对话框Button Confirmations危险操作前弹确认框{ type: button, label: Delete All, action_id: delete_all, style: danger, confirm: { title: Are you sure?, text: This cannot be undone., confirm: Delete, deny: Cancel } }ConfirmDialog结构见 types.tstitle、text、confirm、deny全部必填style若提供则只能是danger。已保存条目的面板与操作Saved-entry panels and actionsadmin.editorPanels与admin.editorActions指向私有插件路由面板路由editorPanels返回 Block Kit接收panel_load、block_action或form_submit操作路由editorActions接收editor_action且只能返回三个受限字段{ toast?: { message: string; type: success | error | info }; refresh?: true; navigate?: LinkTarget; }约束与校验器validateContentEditorActionResponse完全一致见 validation.tsrefresh与navigate二选一不可同时使用导航复用与link元素相同的结构化 target 校验器危险操作danger action声明必须包含确认对话框返回未知字段如blocks会被拒绝。两个表面都通过routeCtx.ui.entry获得宿主重载后的集合信息collection、已保存条目 ID、内容 locale 与版本号routeCtx.ui.extensionId标识 manifest 声明。已保存的字段值和未保存的编辑器状态不会包含在内。PluginUiContext的完整形状admin-page/dashboard-widget与content-editor-panel/content-editor-action两种变体见 types.ts。链接与管理员 locale不要直接返回 admin URL使用结构化链接目标{ type: link, label: Edit article, target: { kind: content, collection: posts, id: post-1, locale: ar }, appearance: primary }LinkTarget的四种形态types.tskind说明content指向已保存内容含collection、id、可选localeplugin-page指向同一插件声明的页面path必须安全plugin-settings插件生成的设置页external绝对外部 URL仅允许http:、https:、mailto:协议行为要点外部链接在新标签页打开带noopener noreferrerlink没有action_id——需要回调插件时请使用button校验器会拒绝带action_id的 link 元素plugin-page的 path 必须通过isSafePluginPagePath禁止./..段且提供策略时必须是该插件已声明的页面validation.ts。Admin 路由通过routeCtx.ui获得宿主认证的 UI 上下文与 interaction 分开传递const { locale, direction, surface } routeCtx.ui ?? { locale: en, direction: ltr, surface: admin-page, };UI locale 是管理员当前激活的 locale与ctx.site.locale站点默认内容 locale相互独立。运行时页面文案可以用它选择本地化的 Block Kit 文本manifest 导航标签则是静态的。Toast 响应在 blocks 旁返回toast展示通知return { blocks: [/* ... */], toast: { message: Settings saved, type: success }, // success | error | info };响应校验安全边界与配额源码深入所有页面与组件响应在渲染前都会经过校验。validateBlockResponsevalidation.ts由两部分组成先做边界检查再做结构与策略校验。BLOCK_RESPONSE_LIMITSvalidation.ts定义了硬性配额限制项数值说明maxBytes256 KiB序列化后总字节数maxDepth20嵌套深度maxNodes2,000节点总数maxArrayItems1,000每个数组的长度上限maxStringBytes64 KiB单个字符串字节数maxErrors50单次校验最多报告的错误数边界检查实现validation.ts是一个显式栈遍历器逐节点累计计数并实时以TextEncoder计算 UTF-8 字节同时拒绝bigint等不可 JSON 序列化的值。图片与外部资源策略BlockValidationPolicyroot 相对图片 URL以/开头、非//、不含\直接接受外部图片必须 HTTPS且主机名必须命中策略中的allowedImageHosts支持*与*.example.com通配在插件层面这与 manifest 的network:requestallowedHosts或network:request:unrestricted权限声明配合生效。插件路由应在返回响应前调用服务端的validateBlockResponse或validateBlocks获得早期错误反馈服务端入口 server.ts 同时导出面板/操作交互的专用校验器validateContentEditorPanelInteraction与validateContentEditorActionResponse。文档示例被自动测试一个值得注意的工程实践emdash-cms/blocks的测试 skill-examples.test.ts 会直接解析本参考文档中的 JSON 代码块按章节包装成对应块Block Syntax→原样、Conditional Fields→包进 form、Button Confirmations/Links and admin locale→包进 actions逐条断言validateBlocks(...)返回{ valid: true, errors: [] }。这意味着本文中的示例都是可校验、可运行的你在插件中照抄这些 JSON 结构即可通过宿主校验。小结Block Kit 把插件管理界面抽象为一次次的请求-响应交互循环插件只负责返回声明式 JSON宿主负责渲染、事件回传、校验与安全边界。掌握它就能在不接触浏览器 JS 的前提下为 EmDash 构建完整的设置表单、数据表格、仪表盘图表、条件字段和内容编辑器面板。进一步阅读可参考同目录下的 admin-ui.mdadmin 页面与路由声明、portable-text-blocks.mdPortable Text 块级编辑字段与 sandbox-boundaries.md沙箱权限边界以及emdash-cms/blocks包的 types.ts、validation.ts、builders.ts 三份核心源码。赞分享CMS后端前端插件系统【免费下载链接】emdashEmDash is a full-stack TypeScript CMS based on Astro; the spiritual successor to WordPress项目地址https://gitcode.com/gh_mirrors/emdas/emdash点击查看免费下载相关推荐EmDash CMS Block Kit 深入指南用 emdash-cms/blocks 构建声明式插件 UIEmDash CMS Block Kit 深入指南用 emdash cms/blocks 构建声明式插件 UI 本文围绕 EmDash CMS 插件体系的CMS后端前端插件系统EmDash Block Kit 开发指南用 JSON 声明式 UI 构建沙箱插件管理后台EmDash Block Kit 开发指南用 JSON 声明式 UI 构建沙箱插件管理后台 导读 Block Kit 是 EmDash 为 沙箱插件 提供的一CMS后端前端插件系统Stable Diffusion Forge 本地部署3 个决策点让 AI 绘图数据不出这台机器Stable Diffusion Forge 本地部署3 个决策点让 AI 绘图数据不出这台机器 Stable Diffusion Forge 是一个开源的CMS后端前端插件系统创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表