块:从格式标记到服务端渲染的完整实现解析)
Gutenberg 的脚注Footnotes块从格式标记到服务端渲染的完整实现解析【免费下载链接】gutenbergThe Block Editor project for WordPress and beyond. Plugin is available from the official repository.项目地址: https://gitcode.com/GitHub_Trending/gu/gutenberg导读本文基于 Gutenberg 仓库中 packages/block-library/src/footnotes/README.md 的官方块 API 文档结合该目录下的block.json、index.php、edit.jsx、format.jsx、style.scss及端到端测试用例深入解析core/footnotes动态块的设计与实现。读完本文你将掌握该块的元数据声明attributes/supports/context、正文内脚注引用的插入机制core/footnote富文本格式、脚注内容的服务端渲染链路以及它与文章修订revisions系统的集成方式并能在自己的主题或插件中复用这套模式。一、块概览一个自动化的“参考文献”动态块core/footnotes是 Gutenberg 内置核心块之一官方文档的描述是Display footnotes added to the page.即“展示页面中添加的脚注”。它被归入 text 分类category: textkeyword 为references参考文献并使用了 API 版本 3apiVersion: 3。该块最显著的特点是Dynamic动态块它不在文章内容里保存任何 HTML 标记而是在服务端渲染时由 PHP 回调实时生成最终的ol脚注列表。其块名称为core/footnotes。在文章内容中该块仅以一个块注释block comment的形式存储!-- wp:footnotes /--正因如此它是整个脚注体系中的“展示层”真正负责“采集脚注内容”的是正文内的富文本格式rich text format两者配合构成一套完整的“引用-收集-渲染”流程。二、块元数据block.json深度解读块的声明全部位于 packages/block-library/src/footnotes/block.json下面逐一拆解其中的关键字段。1. Attributes零自定义属性文档明确说明This block has no custom attributes.该块没有自定义属性attributes属性未声明。因为脚注的原始内容并不存放在块属性里而是存放在文章的 post metameta 键footnotes中服务端渲染时从 meta 读取。这与动态块“内容不入库”的设计相辅相成。2. Supports受支持的样式与行为supports字段决定了编辑器与前台对块的支持程度该块声明了以下能力含__experimental*实验性项能力项值说明anchortrue允许为块设置 HTML 锚点color.backgroundtrue支持背景色color.linktrue支持链接颜色默认开启color.texttrue支持文字颜色默认开启htmlfalse禁止以 HTML 方式编辑multiplefalse每篇文章只允许出现一次reusablefalse不允许被保存为复用块inserterfalse不出现在块插入器列表中spacing.margin/spacing.paddingtrue支持外边距与内边距typography.fontSize/typography.lineHeighttrue支持字号与行高字号默认开启interactivity.clientNavigationtrue支持客户端导航视图交互此外block.json中还额外声明了__experimentalBorderradius/color/width/style默认不开启、__experimentalFontFamily、__experimentalTextDecoration、__experimentalFontStyle、__experimentalFontWeight、__experimentalLetterSpacing、__experimentalTextTransform、__experimentalWritingMode等实验性排版与边框支持而自动生成的 README 只收录了稳定项。值得特别注意的是multiple: false与inserter: false的组合inserter: false意味着用户不能手动从块插入器添加它它只会在用户为某段文字添加脚注时由代码自动插入到文章底部multiple: false保证整篇文章只存在一个脚注容器避免出现重复列表。3. Context跨块上下文传递该块声明了usesContext: [postId, postType]即它需要读取当前文章的 ID 与类型。这依赖块上下文block context机制由编辑器注入。只有postId与postType都存在时服务端渲染才能定位到正确的 meta 数据——这一点直接决定了index.php中的渲染逻辑。三、前端实现脚注格式与编辑体验1. 插入脚注引用core/footnote富文本格式用户之所以能在段落内插入脚注靠的并不是一个普通块而是一个名为core/footnote的富文本格式format。它定义在 packages/block-library/src/footnotes/format.jsxexport const formatName core/footnote; export const format { title: __( Footnote ), tagName: sup, className: fn, attributes: { data-fn: data-fn, }, interactive: true, contentEditable: false, [ usesContextKey ]: [ postType, postId ], // edit: ... };这段声明非常关键tagName: sup——脚注引用在正文中渲染为上标className: fn——引用元素携带fn类名attributes: { data-fn: data-fn }——用data-fn属性承载脚注的唯一 ID该 ID 同时是脚注内容的锚点目标interactive: true与contentEditable: false——引用对象不参与内容编辑避免用户误改通过usesContextKey声明它同样需要postId/postType用于判断当前环境是否支持脚注。在format.jsx的onClick逻辑中插入新脚注时 ID 的生成方式值得借鉴// The ID doubles as the anchor target of the footnote link. // A CSS identifier cannot start with a digit, so a bare UUID // (which often does) breaks querySelector( # id ) and // #id style rules on the front end. Prefix it with a letter. id fn-${ createId() };即用 UUID 生成唯一 ID并强制加上fn-前缀保证 ID 以字母开头从而保证 CSS 选择器#id与querySelector在前台始终可用。随后通过insertObject把如下 HTML 对象插入到光标位置a href#fn-xxx idfn-xxx-link*/a其中href#fn-xxx指向脚注列表项列表项id就是fn-xxx引用自身的idfn-xxx-link是前台“跳回正文”返回链接的目标锚点。2. 自动插入脚注容器块format.jsx中还实现了一个非常实用的行为当正文中已有脚注引用、但文章里还没有core/footnotes块时点击“Footnote”工具栏按钮会自动在文章底部创建并插入一个脚注块// When there is no footnotes block in the post, create one and // insert it at the bottom. if ( ! fnBlock ) { // ...向上找到 post-content 根块 fnBlock createBlock( core/footnotes ); insertBlock( fnBlock, undefined, rootClientId ); } selectionChange( fnBlock.clientId, id, 0, 0 );从实现看插入后还会通过selectionChange把光标直接定位到新脚注的编辑区让用户可以立即输入脚注内容。这在页面编辑器site editor中同样生效——代码会先沿块树向上寻找core/post-content祖先块再在正确的层级插入。3. 编辑视图直接编辑脚注内容脚注块在编辑器中的渲染由 packages/block-library/src/footnotes/edit.jsx 负责通过useEntityProp(postType, postType, meta, postId)读写当前文章的footnotesmeta读取 meta 时对数据做了健壮性处理meta 本质是字符串可能被其他代码写坏因此用JSON.parse包裹try/catch解析失败或形状不对一律按“无脚注”处理let parsed; try { parsed JSON.parse( meta?.footnotes || [] ); } catch { // Left undefined, which the check below treats as no footnotes... } const footnotes Array.isArray( parsed ) ? parsed : [];当 meta 不可用例如当前文章类型不支持 meta时显示占位提示 “Footnotes are not supported here. Add this block to post or page content.”当脚注列表为空时显示提示 “Footnotes found in blocks within this document will be displayed here.”有脚注时渲染为ol列表每项用RichText可编辑并通过updateMeta把最新的footnotesJSON 写回 metaupdateMeta( { ...meta, footnotes: JSON.stringify( footnotes.map( ( footnote ) { return footnote.id id ? { content: nextFootnote, id } : footnote; } ) ), } );由此可以总结出编辑器内的数据流正文输入脚注 →core/footnote格式在文本中留下data-fn引用 →edit.jsx把脚注内容写入footnotespost metaJSON 数组→ 前台由服务端渲染读出。块注册入口在 packages/block-library/src/footnotes/index.jsinit()中同时执行registerFormatType( formatName, format )与initBlock(...)即富文本格式与块是成对注册的二者缺一不可。四、服务端渲染index.php 全链路解析动态块的渲染回调定义在 packages/block-library/src/footnotes/index.php 的render_block_core_footnotes()中注册方式为register_block_type_from_metadata( __DIR__ . /footnotes, array( render_callback render_block_core_footnotes ) )即直接读取同目录footnotesblock.json注册。渲染回调的执行流程可以概括为四步守卫 一次循环输出function render_block_core_footnotes( $attributes, $content, $block ) { // 1. postId 为空 → 不渲染 if ( empty( $block-context[postId] ) ) { return ; } // 2. 文章受密码保护 → 不渲染 if ( post_password_required( $block-context[postId] ) ) { return ; } // 3. 读取 post meta footnotes为空 → 不渲染 $footnotes get_post_meta( $block-context[postId], footnotes, true ); if ( ! $footnotes ) { return ; } // 4. JSON 解码失败或不是数组/空数组 → 不渲染 $footnotes json_decode( $footnotes, true ); if ( ! is_array( $footnotes ) || count( $footnotes ) 0 ) { return ; } // 循环输出 li 列表项附返回链接 $wrapper_attributes get_block_wrapper_attributes(); $footnote_index 1; $block_content ; foreach ( $footnotes as $footnote ) { $aria_label sprintf( __( Jump to footnote reference %1$d ), $footnote_index ); $block_content . sprintf( li id%1$s%2$s a href#%1$s-link aria-label%3$s↩︎/a/li, esc_attr( $footnote[id] ), wp_kses_post( $footnote[content] ), esc_attr( $aria_label ) ); $footnote_index; } return sprintf( ol %1$s%2$s/ol, $wrapper_attributes, $block_content ); }几个值得展开的细节上下文依赖渲染依赖$block-context[postId]这正是block.json中usesContext的服务端对应物安全转义id用esc_attr()、内容用wp_kses_post()、aria-label用esc_attr()避免注入风险双向跳转列表项id与正文中href的锚点一致fn-xxx而返回链接href#%1$s-link恰好对应正文引用元素上的idfn-xxx-link形成“正文 ↔ 脚注”的往返导航可访问性每个返回链接带aria-labelJump to footnote reference NN 为从 1 递增的序号方便屏幕阅读器用户包装属性get_block_wrapper_attributes()会把block.json支持的anchor、颜色、间距、排版等样式统一输出到ol上。1. footnotes post meta 的注册为了让脚注内容能够存取index.php还以优先级 20 在init钩子上注册了footnotesmeta 字段register_block_core_footnotes_post_meta()register_post_meta( $post_type, footnotes, array( show_in_rest true, // 暴露给 REST API编辑器读取 single true, type string, revisions_enabled true, // 支持修订 ) );注册范围是“在 REST 中可见、且同时支持编辑器、自定义字段与修订”的所有文章类型if ( post_type_supports( $post_type, editor ) post_type_supports( $post_type, custom-fields ) post_type_supports( $post_type, revisions ) ) {show_in_rest: true使前端useEntityProp能够读取/写入single: true表示单值 metatype: string表示存放的是 JSON 字符串revisions_enabled: true让脚注内容随修订一起被追踪。2. 与修订Revisions系统的集成index.php末尾还挂接了两个修订相关过滤器把脚注纳入 WordPress 修订对比界面add_filter( _wp_post_revision_fields, wp_add_footnotes_to_revision ); add_filter( _wp_post_revision_field_footnotes, wp_get_footnotes_from_revision, 10, 3 );wp_add_footnotes_to_revision()把footnotes字段加入修订字段列表显示名 “Footnotes”wp_get_footnotes_from_revision()从修订对象中读取该 metafunction wp_get_footnotes_from_revision( $revision_field, $field, $revision ) { return get_metadata( post, $revision-ID, $field, true ); }这意味着每一次保存草稿脚注内容都会作为一个可对比、可恢复的修订维度被记录下来。这一行为有端到端测试直接验证见下文第五节。五、样式与兼容性style.scss 的取舍脚注块的样式位于 packages/block-library/src/footnotes/style.scss文件头部注释点明其定位// These styles are for backwards compatibility with the old footnotes anchors. // Can be removed in the future.即这些样式主要用于向后兼容旧的脚注锚点。核心规则如下.editor-styles-wrapper, .entry-content { counter-reset: footnotes; } a[data-fn].fn { vertical-align: super; font-size: smaller; counter-increment: footnotes; display: inline-flex; text-decoration: none; text-indent: -9999999px; } a[data-fn].fn::after { content: [ counter(footnotes) ]; text-indent: 0; float: left; }可以看到通过 CSS 计数器counter-reset/counter-increment自动为脚注引用编号输出格式为[1]、[2]…利用text-indent: -9999999px将原始的“*”号移出可视区域只显示计数结果block.json中声明的style: wp-block-footnotes使块在前台自动加载该样式表。从测试快照 test/integration/fixtures/blocks/core__footnotes.html 也可以看到该块在内容中的标准序列化形式仅块注释无 HTML 内容印证其动态块的存储方式。六、测试验证行为如何被锁定仓库中针对脚注功能的端到端测试很好地充当了“行为规格说明书”。1. 插入流程测试test/e2e/specs/editor/various/footnotes.spec.js 覆盖了完整的“插入脚注”用户路径新建文章输入两段文字选中文本后通过工具栏 “More → Footnote” 插入脚注并输入内容断言当前激活元素脚注编辑区的 ID 匹配/^fn-[0-9a-f-]{36}$/即fn-前缀 UUID断言生成的块序列为段落 1 → 段落 2含sup>ol classwp-block-footnotes ... li idfn-xxx脚注内容 a href#fn-xxx-link aria-labelJump to footnote reference 1↩︎/a/li ... /ol主题开发者可以直接针对.wp-block-footnotes编写自定义 CSS例如改变列表缩进、返回链接样式、优化移动端排版无需改动任何 PHP 代码。常见问题排查现象可能原因前台不显示脚注列表文章类型不支持editor/custom-fields/revisions之一导致footnotesmeta 未注册文章受密码保护时无脚注post_password_required()守卫主动跳过渲染设计如此编辑器内脚注不可用当前环境不是文章/页面内容如某些模板部件meta 不可用edit.jsx会显示占位提示页面编辑器site editor中脚注位置异常块插入逻辑依赖core/post-content祖先块定位需确认模板结构包含 post-content对开发者可复用的模式从core/footnotes的实现中可以提炼出一套通用模式适合任何“正文内标注 文末聚合”的功能需求富文本格式收集引用用registerFormatType注册一个tagName为sup/mark等的格式用属性承载唯一 IDinteractive: true防止内容被误编辑动态块渲染聚合声明apiVersion: 3、multiple: false、inserter: false的块配合render_callback在服务端输出聚合列表post meta 承载数据用register_post_meta注册type: string、show_in_rest: true、revisions_enabled: true的 JSON 字符串字段即可同时获得 REST 可读写与修订追踪能力上下文传递usesContext: [postId, postType]让格式与块都拿到正确的数据归属。结语core/footnotes虽然只占据块目录中的一小块空间却是 Gutenberg“动态块 富文本格式 post meta 修订系统”协同工作的经典范例格式负责在正文中埋下data-fn引用动态块负责在文末聚合渲染meta 负责持久化修订过滤器负责历史追踪。理解它的元数据声明、渲染守卫与数据流不仅能帮助你在使用中排查问题更能为设计类似的“引用-聚合”型功能提供一份高质量的实现蓝图。相关资源速查块元数据packages/block-library/src/footnotes/block.json服务端渲染与 meta/修订集成packages/block-library/src/footnotes/index.php编辑器编辑视图packages/block-library/src/footnotes/edit.jsx脚注富文本格式与自动建块packages/block-library/src/footnotes/format.jsx样式与兼容性packages/block-library/src/footnotes/style.scss端到端测试test/e2e/specs/editor/various/footnotes.spec.js、test/e2e/specs/editor/various/footnotes-revisions.spec.js【免费下载链接】gutenbergThe Block Editor project for WordPress and beyond. Plugin is available from the official repository.项目地址: https://gitcode.com/GitHub_Trending/gu/gutenberg创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考