EmDash 插件自定义 Portable Text 块类型从声明、编辑到站点渲染的完整实战【免费下载链接】emdashEmDash is a full-stack TypeScript CMS based on Astro; the spiritual successor to WordPress项目地址: https://gitcode.com/gh_mirrors/emdas/emdash导读本文以 EmDash 的 Portable Text 富文本块系统为核心系统讲解如何在原生插件native plugin中通过definePlugin()声明自定义块类型使其出现在编辑器斜杠命令菜单中并可插入任意portableText字段同时深入剖析站点侧的 Astro 渲染组件如何通过componentsEntry自动装配进PortableText最终实现编辑器插入 → 数据存储 → 前端渲染的完整闭环。读完本文你将掌握块配置字段的完整语义、Block Kit 表单字段的用法、blockComponents导出约定、组件合并优先级以及沙箱插件与原生插件在此功能上的边界。适用范围与能力边界在动手之前必须先明确一条硬性约束Plugin CLI 与 registry 包无法定义 Portable Text 块类型。emdash-plugin build会对此发出警告——portableTextBlocks需要受信任模式trusted mode因此在沙箱化清单中会被直接忽略。该警告逻辑位于 packages/plugin-cli/src/bundle/api.ts当解析出的插件admin.portableTextBlocks非空时CLI 提示这些块需要 trusted mode、在沙箱插件中会被忽略。从架构上看站点侧渲染还需要一个 AstrocomponentsEntry它会在站点构建时被加载。Core 虽然可以从配置声明的标准插件描述符中转发声明式的块元数据但这条路不会让定义经由 registry 变得可移植也不会提供站点渲染器。因此实践上的准则是将自定义 Portable Text 块视作原生插件native plugin特性除非站点同时拥有描述符与渲染组件、且已验证从编辑器到渲染的完整链路否则不要走 registry 分发路线。这与 skills/creating-plugins/SKILL.md 中对何时选择原生插件的指导一致需要 Astro 渲染组件、自定义 Portable Text 块定义等功能时应使用原生插件——原生插件以站点权限运行不能从 registry 安装。声明块类型在definePlugin()中通过admin.portableTextBlocks声明块admin: { portableTextBlocks: [ { type: youtube, label: YouTube Video, icon: video, placeholder: Paste YouTube URL..., fields: [ { type: text_input, action_id: id, label: YouTube URL }, { type: text_input, action_id: title, label: Title }, { type: text_input, action_id: poster, label: Poster Image URL }, ], }, { type: codepen, label: CodePen, icon: code, placeholder: Paste CodePen URL..., }, ], }块配置字段对应源码中的PortableTextBlockConfig接口见 packages/core/src/plugins/types.ts各字段语义如下字段类型说明typestring块类型名用作 Portable Text 的_type。必填。labelstring斜杠命令菜单中显示的名称。必填。iconstring图标 key。可选。descriptionstring斜杠命令菜单中的描述。可选。placeholderstring输入框占位文本。可选。fieldsarray用于编辑 UI 的 Block Kit 表单字段。可选。categorystring斜杠菜单中的展示分组。可选默认Embeds。值得注意的增量是源码中新增的category字段插作者应选择有意义的分类如Sections、Marketing、Media、Embeds、Layout拥有相同category的块会在编辑器的斜杠菜单中被归组显示。图标命名图标支持video、code、link、link-external。未知或缺失的图标会回退到通用的立方体图标。字段Fields当声明了fields时编辑器会渲染一个 Block Kit 表单用于编辑省略fields时编辑器显示一个简单的 URL 输入框。字段使用 Block Kit 元素语法下面是一个覆盖主要元素类型的完整示例fields: [ { type: text_input, action_id: id, label: URL, placeholder: https://..., }, { type: text_input, action_id: title, label: Title }, { type: text_input, action_id: poster, label: Poster Image }, { type: number_input, action_id: start, label: Start Time (seconds) }, { type: toggle, action_id: autoplay, label: Autoplay }, { type: select, action_id: size, label: Size, options: [ { label: Small, value: small }, { label: Medium, value: medium }, { label: Large, value: large }, ], }, ];有关各元素的具体形态参见 Block Kit 参考文档。注意Portable Text 编辑器还会额外渲染repeater与media_picker两个创作类元素——不要假设共享联合类型shared union接受的每个元素都会在所有 Block Kit 表面上渲染skills/creating-plugins/references/block-kit.md 中明确说明repeater与media_picker是 admin 创作元素其中repeater的嵌套字段仅限text_input、number_input、select、toggle而media_picker会打开媒体库并把选中资源的 URL 字符串存入数据。每个字段的action_id会成为 Portable Text 块数据中的 key。其中action_id: id的字段被视为主要标识符通常是 URL。数据流用户在编辑器中输入/并选择一个块类型弹出模态框展示 Block Kit 表单若未声明fields则为简单 URL 输入框用户填写字段并提交块被插入_type设为块类型名字段值作为块属性编辑已有块时模态框会以已有数据预填充重新打开插入后的 Portable Text 输出示例{ _type: youtube, _key: abc123, id: https://youtube.com/watch?vdQw4w9WgXcQ, title: Never Gonna Give You Up, poster: https://img.youtube.com/vi/dQw4w9WgXcQ/0.jpg }站点侧渲染要在站点上渲染这些块类型需要从componentsEntry导出 Astro 组件。组件文件componentsEntry指向的模块必须导出名为blockComponents的对象其 key 与块type一一对应// src/astro/index.ts import YouTube from ./YouTube.astro; import CodePen from ./CodePen.astro; // This export name is required export const blockComponents { youtube: YouTube, codepen: CodePen, };Astro 组件每个渲染组件通过Astro.props.node接收完整的块数据包括_type、_key及各字段值--- // src/astro/YouTube.astro const { id, title, poster } Astro.props.node; // Extract video ID from URL const videoId id?.match(/(?:v|youtu\.be\/)([^])/)?.[1] ?? id; --- div classyoutube-embed iframe src{https://www.youtube-nocookie.com/embed/${videoId}} title{title || YouTube Video} allowaccelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture allowfullscreen /iframe /div插件描述符在描述符工厂中设置componentsEntry指向上面导出blockComponents的模块export function myPlugin(options {}): PluginDescriptor { return { id: my-plugin, entrypoint: my-org/my-plugin, componentsEntry: my-org/my-plugin/astro, version: 1.0.0, options, }; }包导出配置在插件的package.json中为./astro添加导出子路径确保站点构建器能解析到组件模块{ exports: { .: { types: ./dist/index.d.ts, import: ./dist/index.js }, ./admin: { types: ./dist/admin.d.ts, import: ./dist/admin.js }, ./astro: { types: ./dist/astro/index.d.ts, import: ./dist/astro/index.js } } }自动装配原理插件块组件会被自动合并进站点上的PortableText合并顺序如下EmDash 默认组件优先级最低插件块组件用户提供的组件优先级最高站点作者无需手动 import 任何东西用户组件优先于插件默认组件。这一机制的底层实现位于 packages/core/src/components/PortableText.astro组件通过mergeComponents依次合并emdashComponents、来自virtual:emdash/block-components的pluginBlockComponents以及用户传入的components形成默认 插件 用户的优先级链。而虚拟模块virtual:emdash/block-components的内容由 packages/core/src/astro/integration/virtual-modules.ts 中的generateBlockComponentsModule在构建期生成它筛选所有声明了componentsEntry的描述符为每个描述符生成import { blockComponents as bcN } from componentsEntry语句再以展开形式合并为pluginBlockComponents对象若无任何描述符声明componentsEntry则生成空对象export const pluginBlockComponents {};。这正是站点构建时加载组件入口这一要求的实现所在。完整示例一个完整的 embeds 插件同时声明块类型与站点渲染组件// src/index.ts import { definePlugin } from emdash; import type { PluginDescriptor } from emdash; export function embedsPlugin(options {}): PluginDescriptor { return { id: embeds, version: 1.0.0, entrypoint: my-org/plugin-embeds, componentsEntry: my-org/plugin-embeds/astro, options, }; } export function createPlugin() { return definePlugin({ id: embeds, version: 1.0.0, admin: { portableTextBlocks: [ { type: youtube, label: YouTube Video, icon: video, placeholder: Paste YouTube URL..., fields: [ { type: text_input, action_id: id, label: YouTube URL }, { type: text_input, action_id: title, label: Title }, { type: text_input, action_id: poster, label: Poster Image URL, }, ], }, { type: linkPreview, label: Link Preview, icon: link-external, placeholder: Paste any URL..., }, ], }, }); } export default createPlugin;// src/astro/index.ts import YouTube from ./YouTube.astro; import LinkPreview from ./LinkPreview.astro; export const blockComponents { youtube: YouTube, linkPreview: LinkPreview, };注意linkPreview未声明fields因此编辑器会为其显示简单的 URL 输入框youtube则声明了三个text_input字段编辑器会渲染完整的 Block Kit 表单。验证要点与进阶阅读从源码角度看本主题的关键验证点是类型定义PortableTextBlockConfig与PluginAdminConfig.portableTextBlocks位于 packages/core/src/plugins/types.ts组件装配generateBlockComponentsModule与虚拟模块 IDvirtual:emdash/block-components位于 packages/core/src/astro/integration/virtual-modules.ts渲染合并PortableText.astro的三层合并逻辑位于 packages/core/src/components/PortableText.astro沙箱边界emdash-plugin build对portableTextBlocks的 trusted-mode 警告位于 packages/plugin-cli/src/bundle/api.ts。由于自定义 Portable Text 块依赖 Astro 渲染组件与站点构建期装配它只能作为原生插件特性分发。若你需要编写运行在 Plugin CLI / registry 沙箱环境中的插件请参考 skills/creating-plugins/SKILL.md 中关于沙箱插件能力与声明式 Block Kit 管理界面的说明以及 Block Kit 参考文档 了解所有可用的块与元素形态。在把自定义块交付给站点前务必完整验证编辑器插入 → 数据落库 → 站点渲染这条链路确保action_id、_type与blockComponents的 key 三者严格一致。【免费下载链接】emdashEmDash is a full-stack TypeScript CMS based on Astro; the spiritual successor to WordPress项目地址: https://gitcode.com/gh_mirrors/emdas/emdash创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
企业数字化 ERP 产品动态
相关推荐
Windows账户机制手写实现:面试必考3大坑 Windows账户机制手写实现:面试必考3大坑 版本升级后 API 全变了,原本能跑的代码突然报错,这种痛谁懂?很多开发在面试中被问到“Windows账户”相关底层原理时,往往只停留在调用 CreateUser 这种表层… · 2026/9/23 16:02:12
飞书知识空间删除指南:lark-cli `wiki +delete-space` 同步/异步任务与安全确认全解析 CLIAI 技能 【免费下载链接】cli The official Lark/飞书 CLI tool, maintained by the larksuite team — built for humans and AI Agents. Covers core business domains including Messenger, Docs, Base, Sheets, Calendar, Mail, Tasks, Meetings, and more, with 200 co… · 2026/9/23 16:02:06
在 Kubernetes 中使用 GlusterFS 构建可扩展的分布式持久化存储 在 Kubernetes 中使用 GlusterFS 构建可扩展的分布式持久化存储 【免费下载链接】kubernetes-handbook Kubernetes 架构与生态:从云原生到 AI 原生基础设施的构建指南 项目地址: https://gitcode.com/gh_mirrors/ku/kubernetes-handbook
GlusterFS 是 Scale-… · 2026/9/23 16:02:06
初中生的学习方法图解原理 初中生学习方法图解:3个高频面试坑,别让死记硬背毁了你的路 刚接手一个老旧的 Python 爬虫项目,复制了一堆网上的“高深”代码,跑起来直接报错 IndexError: list index out of range… · 2026/9/23 18:49:04
联想拯救者原厂系统恢复指南:香港官网镜像下载与U盘安装避坑 1. 为什么拯救者用户都在找“原厂系统”联想拯救者系列游戏本这几年出货量非常大,R7000、Y7000、Y9000P、R9000P 这些型号在玩家群体里保有量极高。机器用久了,系统卡顿、驱动冲突、蓝屏报错、预装软件互相打架,很多人第一反应就是重装。但重… · 2026/9/23 18:49:04
AutoCAD二次开发实战:从LISP到.NET API进阶指南 1. AutoCAD二次开发概述AutoCAD作为工业设计领域的标杆软件,其二次开发能力一直是工程师提升效率的利器。我从业十年间,从最初记录简单的LISP脚本,到后来构建完整的行业插件,深刻体会到掌握AutoCAD二次开发就像获得了一把瑞士军刀… · 2026/9/23 18:48:58
Shell条件测试:文件、字符串与数值比较详解 1. Shell条件测试基础概念在Linux系统管理和自动化脚本编写中,条件测试是构建逻辑判断的核心能力。不同于其他编程语言,Shell通过特定的测试命令和操作符来实现条件判断,这种设计源于Unix哲学"一个工具只做一件事"的理念。测试命令… · 2026/9/23 18:48:58
跳伞俱乐部管理后台避坑实录:新手如何搞定证书年审逻辑 跳伞俱乐部管理后台避坑实录:新手如何搞定证书年审逻辑 看了一堆教程还是不会写项目?别急,这不是你的问题,是大多数人学编程时都踩过的坑。很多转岗过来的朋友,前端页面画得挺漂亮,后端逻辑一写就乱,特别是涉及到“时间”、“状态”和“权限”这种业务… · 2026/9/23 18:48:58
Vue响应式与缓存机制原理及优化实践 1. Vue响应式数据与缓存机制深度解析在Vue.js开发中,响应式系统和缓存机制是框架最核心的特性之一。它们共同构成了Vue高效渲染的基础,但同时也带来了不少"坑",特别是当开发者对底层原理理解不深时。本文将结合生产环境中的实际案例… · 2026/9/23 18:48:58
3招搞定手机怎么下载微信面试难题实战项目解析 3招搞定手机怎么下载微信面试难题实战项目解析 面试被问“手机怎么下载微信”背后的原理,90%的人答不上来。别笑,这看似弱智的问题,实则是考察你对移动应用分发机制、安全校验及网络协议理解的试金石。我带过不少校招新人,他们背了八股文,却连一个A… · 2026/9/23 0:00:03
你有新短消息请注意查收:3个新手避坑指南搞定消息系统选型 你有新短消息请注意查收:3个新手避坑指南搞定消息系统选型 面试被问“高并发下如何保证消息不丢失”,你张口就是“用Redis”,结果面试官追问“如果Redis宕机了怎么办”,你瞬间卡壳。这种场景太常见了,很多新手在背八股文时,只记住了技术名词… · 2026/9/23 0:00:29