commit 2080fa3878105c4808fb0e916d268cfcf1fc8f91 Author: unknown <2551654928@qq.com> Date: Wed Oct 22 01:33:45 2025 +0800 first commit diff --git a/README.md b/README.md new file mode 100644 index 0000000..53c040d --- /dev/null +++ b/README.md @@ -0,0 +1,58 @@ +## 扩展资源 + +本手册应被视为区块开发相关领域的权威资源,但以下其他资源也能为您提供帮助: + +- **[WordPress 开发者博客](https://developer.wordpress.org/news/):** 持续更新的技术文章库,涵盖区块开发专题及各类应用场景。该博客也是[跟进WordPress最新动态](https://developer.wordpress.org/news/tag/roundup/)的理想渠道。 +- **[Learn WordPress](https://learn.wordpress.org/):** WordPress官方学习中心,提供[《区块开发入门:创建你的第一个自定义区块》](https://learn.wordpress.org/course/introduction-to-block-development-build-your-first-custom-block/)、[《将简码转换为区块》](https://learn.wordpress.org/course/converting-a-shortcode-to-a-block/)、[《使用WordPress数据层》](https://learn.wordpress.org/course/using-the-wordpress-data-layer/)等系列课程。 +- **[WordPress.tv](https://wordpress.tv/):** 由WordPress社区维护的视频资源库,收录WordCamp演讲实录及在线研讨会录像。您一定能在此找到关于[区块开发](https://wordpress.tv/?s=block%20development&sort=newest)或[区块编辑器](https://wordpress.tv/?s=block%20editor&sort=relevance)的学习资料。 +- **[Gutenberg代码库](https://github.com/WordPress/gutenberg/):** 区块编辑器的开发在GitHub上进行。该仓库包含[`block-library`](https://github.com/WordPress/gutenberg/tree/trunk/packages/block-library/src)(核心区块)与[`components`](https://github.com/WordPress/gutenberg/tree/trunk/packages/components)(通用UI组件)等重要代码包。*[block-development-examples](https://github.com/WordPress/block-development-examples)代码库也是极佳的参考资源。* +- **[终端用户文档](https://wordpress.org/documentation/):** 该文档站点面向终端用户(非开发者),同样收录了[区块编辑器](https://wordpress.org/documentation/category/block-editor/)与[区块操作指南](https://wordpress.org/documentation/article/work-with-blocks/)相关内容。 + +## 确认您的需求场景 + +《区块编辑器手册》专为基于区块编辑器进行创作和开发的用户设计。请注意,[开发者资源中心](https://developer.wordpress.org/)还提供了多本可能对您有益的手册: + +- [主题开发手册](https://developer.wordpress.org/themes) +- [插件开发手册](https://developer.wordpress.org/plugins) +- [通用API手册](https://developer.wordpress.org/apis) +- [高级管理手册](https://developer.wordpress.org/advanced-administration) +- [REST API手册](https://developer.wordpress.org/rest-api/) +- [编码标准手册](https://developer.wordpress.org/coding-standards) + +# 区块编辑器手册 + +欢迎使用区块编辑器手册。 + +[**区块编辑器**](https://wordpress.org/gutenberg/) 是用于构建和发布 WordPress 网站的现代范式。它采用模块化的**区块**系统来创作和格式化内容,旨在为网站和数字产品创建丰富灵活的布局。 + +区块编辑器包含以下几个主要元素,如下图所示: + +![区块编辑器概览](https://raw.githubusercontent.com/WordPress/gutenberg/trunk/docs/assets/overview-block-editor-2023.png) + +图中高亮显示的元素包括: + +1. **插入器:** 用于将区块插入内容画布的面板 +2. **内容画布:** 内容编辑器,承载使用区块创建的内容 +3. **设置面板:** 配置选定区块设置或文章设置的面板 + +通过区块编辑器,您可以使用区块以模块化方式创建内容。WordPress 默认提供多种[区块](https://developer.wordpress.org/block-editor/reference-guides/core-blocks/),您也可以[创建自己的区块](https://developer.wordpress.org/block-editor/getting-started/create-block/)。 + +[区块](https://developer.wordpress.org/block-editor/explanations/architecture/key-concepts/#blocks)是一个独立元素,例如段落、标题、媒体或嵌入内容。每个区块都被视为具有独立编辑和格式控件的单独元素。当所有这些组件组合在一起时,就构成了页面或文章的内容,随后这些内容将[存储到 WordPress 数据库](https://developer.wordpress.org/block-editor/explanations/architecture/data-flow/#serialization-and-parsing)中。 + +区块编辑器是 [**Gutenberg 项目**](https://developer.wordpress.org/block-editor/getting-started/faq/#what-is-gutenberg)的工作成果,该项目旨在彻底改变 WordPress 的编辑体验。 + +除了通过可视化内容创建工具提供[增强的编辑体验](https://wordpress.org/gutenberg/)外,区块编辑器还是一个强大的开发平台,拥有[丰富的 API 功能集](https://developer.wordpress.org/block-editor/reference-guides/),可通过无数种方式进行操作和扩展。 + +## 手册使用指南 + +本手册主要关注区块开发,分为五个主要部分: + +- **[入门指南](https://developer.wordpress.org/block-editor/getting-started/):** 适合刚接触区块开发的用户,您可以在此设置[开发环境](https://developer.wordpress.org/block-editor/getting-started/devenv/)并学习[区块开发基础知识](https://developer.wordpress.org/block-editor/getting-started/fundamentals/)。其中的[快速入门指南](https://developer.wordpress.org/block-editor/getting-started/quick-start-guide/)和[教程:构建您的第一个区块](https://developer.wordpress.org/block-editor/getting-started/tutorial/)是开始学习区块开发的理想起点。 + +- **[操作指南](https://developer.wordpress.org/block-editor/how-to-guides/):** 在此部分,您可以基于入门指南所学知识,进一步学习如何解决特定问题。您还可以找到教程和示例代码,以便在自己的项目中重复使用,例如[处理 WordPress 数据](https://developer.wordpress.org/block-editor/how-to-guides/data-basics/)或[优化编辑器体验](https://developer.wordpress.org/block-editor/how-to-guides/curating-the-editor-experience/)。 + +- **[参考指南](https://developer.wordpress.org/block-editor/reference-guides/):** 这是手册的核心部分,您可以在此深入了解细节,查找正在使用或需要了解的特定 API 的详细信息。[区块 API 参考](https://developer.wordpress.org/block-editor/reference-guides/block-api/)涵盖了您可能需要对区块执行的大部分操作,每个[组件](https://developer.wordpress.org/block-editor/reference-guides/components/)和[包](https://developer.wordpress.org/block-editor/reference-guides/packages/)也在此处记录。_组件还通过 [Storybook](https://wordpress.github.io/gutenberg/?path=/story/docs-introduction--page) 进行文档记录。_ + +- **[原理解释](https://developer.wordpress.org/block-editor/explanations/):** 此部分让您能够更深入地学习,通过对区块编辑器[架构](https://developer.wordpress.org/block-editor/explanations/architecture/)的理论理解来巩固您的实践知识。 + +- **[贡献者指南](https://developer.wordpress.org/block-editor/contributors/):** Gutenberg 是开源软件,欢迎所有人为此项目做出贡献。此部分详细说明了如何贡献,无论是通过[代码](https://developer.wordpress.org/block-editor/contributors/code/)、[设计](https://developer.wordpress.org/block-editor/contributors/design/)、[文档](https://developer.wordpress.org/block-editor/contributors/documentation/)还是其他方式。 \ No newline at end of file diff --git a/assets/Locking comparison visual.png b/assets/Locking comparison visual.png new file mode 100644 index 0000000..c223d68 Binary files /dev/null and b/assets/Locking comparison visual.png differ diff --git a/assets/Locking interface.png b/assets/Locking interface.png new file mode 100644 index 0000000..8ada25b Binary files /dev/null and b/assets/Locking interface.png differ diff --git a/assets/fancy-quote-in-inspector.png b/assets/fancy-quote-in-inspector.png new file mode 100644 index 0000000..6bd8c06 Binary files /dev/null and b/assets/fancy-quote-in-inspector.png differ diff --git a/assets/fancy-quote-with-style.png b/assets/fancy-quote-with-style.png new file mode 100644 index 0000000..31f3806 Binary files /dev/null and b/assets/fancy-quote-with-style.png differ diff --git a/assets/inspector.png b/assets/inspector.png new file mode 100644 index 0000000..bd1a5ee Binary files /dev/null and b/assets/inspector.png differ diff --git a/assets/js-tutorial-console-log-error.png b/assets/js-tutorial-console-log-error.png new file mode 100644 index 0000000..836a663 Binary files /dev/null and b/assets/js-tutorial-console-log-error.png differ diff --git a/assets/js-tutorial-console-log-success.png b/assets/js-tutorial-console-log-success.png new file mode 100644 index 0000000..7b42853 Binary files /dev/null and b/assets/js-tutorial-console-log-success.png differ diff --git a/assets/js-tutorial-error-blocks-undefined.png b/assets/js-tutorial-error-blocks-undefined.png new file mode 100644 index 0000000..1f27c36 Binary files /dev/null and b/assets/js-tutorial-error-blocks-undefined.png differ diff --git a/assets/meta-block.png b/assets/meta-block.png new file mode 100644 index 0000000..4fe3ebf Binary files /dev/null and b/assets/meta-block.png differ diff --git a/assets/overview-block-editor-2023.png b/assets/overview-block-editor-2023.png new file mode 100644 index 0000000..ad3a6e1 Binary files /dev/null and b/assets/overview-block-editor-2023.png differ diff --git a/assets/plugin-block-settings-menu-item-screenshot.png b/assets/plugin-block-settings-menu-item-screenshot.png new file mode 100644 index 0000000..bc33b4f Binary files /dev/null and b/assets/plugin-block-settings-menu-item-screenshot.png differ diff --git a/assets/plugin-more-menu-item.png b/assets/plugin-more-menu-item.png new file mode 100644 index 0000000..23fa73d Binary files /dev/null and b/assets/plugin-more-menu-item.png differ diff --git a/assets/plugin-post-publish-panel.png b/assets/plugin-post-publish-panel.png new file mode 100644 index 0000000..b4cd931 Binary files /dev/null and b/assets/plugin-post-publish-panel.png differ diff --git a/assets/plugin-post-status-info-location.png b/assets/plugin-post-status-info-location.png new file mode 100644 index 0000000..fa35405 Binary files /dev/null and b/assets/plugin-post-status-info-location.png differ diff --git a/assets/plugin-pre-publish-panel.png b/assets/plugin-pre-publish-panel.png new file mode 100644 index 0000000..ea765f4 Binary files /dev/null and b/assets/plugin-pre-publish-panel.png differ diff --git a/assets/plugin-sidebar-more-menu-item.gif b/assets/plugin-sidebar-more-menu-item.gif new file mode 100644 index 0000000..8518984 Binary files /dev/null and b/assets/plugin-sidebar-more-menu-item.gif differ diff --git a/assets/plugin-sidebar-open-state.png b/assets/plugin-sidebar-open-state.png new file mode 100644 index 0000000..a114f71 Binary files /dev/null and b/assets/plugin-sidebar-open-state.png differ diff --git a/assets/plugin-sidebar-text-control-custom-field.png b/assets/plugin-sidebar-text-control-custom-field.png new file mode 100644 index 0000000..0a0d6d2 Binary files /dev/null and b/assets/plugin-sidebar-text-control-custom-field.png differ diff --git a/assets/quick-view-of-the-block-editor.png b/assets/quick-view-of-the-block-editor.png new file mode 100644 index 0000000..1bab7e8 Binary files /dev/null and b/assets/quick-view-of-the-block-editor.png differ diff --git a/assets/sidebar-style-and-controls.png b/assets/sidebar-style-and-controls.png new file mode 100644 index 0000000..725ddfd Binary files /dev/null and b/assets/sidebar-style-and-controls.png differ diff --git a/assets/sidebar-up-and-running.png b/assets/sidebar-up-and-running.png new file mode 100644 index 0000000..12bc294 Binary files /dev/null and b/assets/sidebar-up-and-running.png differ diff --git a/assets/text-decoration-component.png b/assets/text-decoration-component.png new file mode 100644 index 0000000..4626f95 Binary files /dev/null and b/assets/text-decoration-component.png differ diff --git a/assets/text-transform-component.png b/assets/text-transform-component.png new file mode 100644 index 0000000..2155fbd Binary files /dev/null and b/assets/text-transform-component.png differ diff --git a/assets/toolbar-text.png b/assets/toolbar-text.png new file mode 100644 index 0000000..b71a342 Binary files /dev/null and b/assets/toolbar-text.png differ diff --git a/assets/toolbar-with-custom-button.png b/assets/toolbar-with-custom-button.png new file mode 100644 index 0000000..3b22afa Binary files /dev/null and b/assets/toolbar-with-custom-button.png differ diff --git a/assets/xcode-accessibility-inspector-screenshot.png b/assets/xcode-accessibility-inspector-screenshot.png new file mode 100644 index 0000000..648db05 Binary files /dev/null and b/assets/xcode-accessibility-inspector-screenshot.png differ diff --git a/contributors/README.md b/contributors/README.md new file mode 100644 index 0000000..5e6b607 --- /dev/null +++ b/contributors/README.md @@ -0,0 +1,27 @@ +# 贡献者指南 + +欢迎来到古腾堡项目贡献者指南。本指南旨在帮助您完成环境配置并开始为项目贡献力量。如有任何疑问,您可以在WordPress核心Slack的#core-editor频道中找到我们,[欢迎免费加入](https://make.wordpress.org/chat/)。 + +古腾堡是WordPress核心的子项目。更多信息请参阅[核心贡献者手册](https://make.wordpress.org/core/handbook/)。 + +## 章节导航 + +请根据您的贡献方向选择对应章节: + +- **代码开发?** 请参阅[开发者章节](/docs/contributors/code/README.md) +- **设计相关?** 请参阅[设计章节](/docs/contributors/design/README.md) +- **文档编写?** 请参阅[文档章节](/docs/contributors/documentation/README.md) +- **问题分类?** 请参阅[问题分类章节](/docs/contributors/triage.md) +- **国际化?** 请参阅[本地化与翻译章节](/docs/contributors/localizing.md) + +### 代码库管理 + +古腾堡项目使用GitHub进行代码管理和问题追踪。请参阅以下章节了解基于GitHub的项目管理方法: + +- [问题管理](/docs/contributors/repository-management.md#issues) +- [拉取请求](/docs/contributors/repository-management.md#pull-requests) +- [团队与项目](/docs/contributors/repository-management.md#teams) + +## 指导原则 + +请查阅[贡献准则](https://github.com/WordPress/gutenberg/blob/HEAD/CONTRIBUTING.md)了解相关规范,其中包含行为准则与许可协议信息。 \ No newline at end of file diff --git a/contributors/accessibility-testing.md b/contributors/accessibility-testing.md new file mode 100644 index 0000000..17c8788 --- /dev/null +++ b/contributors/accessibility-testing.md @@ -0,0 +1,65 @@ +# 无障碍功能测试 + +本文是关于如何在Gutenberg平台上进行无障碍功能测试的指南。这是一份动态文档,将随着新方法和新技术的出现不断完善。 + +## 快速开始 + +请确保已按照[快速开始](/docs/contributors/code/getting-started-with-code-contribution.md)指南完成本地环境配置。 + +## 键盘操作测试 + +除鼠标操作外,请确保界面完全支持纯键盘用户操作。尝试仅使用键盘进行交互测试: + +- 确保交互元素可通过TabShift+Tab或方向键获取焦点 +- 按钮应支持Enter空格键激活 +- 单选按钮和复选框应支持空格键选中(不适用Enter键) + +若元素可通过方向键聚焦但无法使用TabShift+Tab操作,建议采用[WAI-ARIA复合子类角色](https://www.w3.org/TR/wai-aria-1.1/#composite)进行分组,例如[`toolbar`](https://www.w3.org/TR/wai-aria-1.1/#toolbar)、[`menu`](https://www.w3.org/TR/wai-aria-1.1/#menu)和[`listbox`](https://www.w3.org/TR/wai-aria-1.1/#listbox)。 + +若交互逻辑对您而言复杂难懂,请意识到这同样会影响纯键盘用户的使用体验。 + +## 屏幕阅读器测试 + +根据[WebAIM第八次屏幕阅读器用户调查](https://webaim.org/projects/screenreadersurvey8/#usage),以下是最常见的屏幕阅读器与浏览器组合: + +| 屏幕阅读器与浏览器组合 | 受访人数 | 占比 | +| ----------------------------- | -------- | ------ | +| JAWS + Chrome | 259 | 21.4% | +| NVDA + Firefox | 237 | 19.6% | +| NVDA + Chrome | 218 | 18.0% | +| JAWS + Internet Explorer | 139 | 11.5% | +| VoiceOver + Safari | 110 | 9.1% | +| JAWS + Firefox | 71 | 5.9% | +| VoiceOver + Chrome | 36 | 3.0% | +| NVDA + Internet Explorer | 14 | 1.2% | +| 其他组合 | 126 | 10.4% | + +进行屏幕阅读器测试时,建议优先使用上表中的主流组合。例如测试VoiceOver时,推荐使用Safari浏览器。 + +### NVDA + Firefox 组合 + +[NVDA](https://www.nvaccess.org/about-nvda/)是Windows平台免费的屏幕阅读器,也是[目前最流行的解决方案](https://webaim.org/projects/screenreadersurvey8/#primary)。 + +安装完成后,您可像启动常规程序一样激活NVDA。系统托盘将出现功能图标,提供更多选项设置。建议启用"语音查看器"对话框,便于在截屏时直观展示NVDA的语音提示内容。 + +启用语音查看器的NVDA选项界面 + +在Gutenberg编辑器中激活NVDA后,可按Insert+F7打开元素列表。该功能将页面元素按类型分类展示,包括链接、标题、表单字段、按钮和地标区域。 + +NVDA元素列表对话框 + +请确保元素具有规范标签,建议优先通过地标导航,再结合Tab键与方向键在地标区域内移动焦点。 + +### VoiceOver + Safari 组合 + +[VoiceOver](https://support.apple.com/guide/voiceover-guide/welcome/web)是macOS原生屏幕阅读器。可通过系统偏好设置>辅助功能>VoiceOver>启用VoiceOver激活,或在按住Command键的同时快速连按三次Touch ID启用。 + +macOS辅助功能设置面板 + +在Gutenberg编辑器中激活VoiceOver后,可按Control+Option+U启动转子功能,快速定位页面中的不同区域与元素。这也是检验元素标签是否规范的有效方式,若转子列表中的名称表述不清,则需要优化改进。 + +通过VoiceOver转子导航表单控件与地标区域 + +建议在转子中优先选择区域或较大范围开始测试,而非直接选择独立元素,以便更完整地验证区域内的导航逻辑。 + +定位目标区域后,可使用Control+Option配合左右方向键在页面元素间移动。请遵循VoiceOver的语音提示进行操作指引。 \ No newline at end of file diff --git a/contributors/assets/gutenberg-logo-black.svg b/contributors/assets/gutenberg-logo-black.svg new file mode 100644 index 0000000..467a13b --- /dev/null +++ b/contributors/assets/gutenberg-logo-black.svg @@ -0,0 +1,256 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/contributors/code/README.md b/contributors/code/README.md new file mode 100644 index 0000000..2cf62f1 --- /dev/null +++ b/contributors/code/README.md @@ -0,0 +1,27 @@ +# 代码贡献指南 + +关于如何开始为古腾堡项目贡献代码的指南。 + +## 讨论交流 + +[Make WordPress Core 博客](https://make.wordpress.org/core/)是获取WordPress开发最新信息的主要平台,包括公告、产品目标、会议记录、会议议程等。 + +开发讨论将在[Make WordPress Slack](https://make.wordpress.org/chat)(需要注册)的`#core-editor`和`#core-js`频道实时进行。 + +## 开发中心 + +古腾堡项目使用GitHub管理代码并跟踪问题。主代码库位于:[https://github.com/WordPress/gutenberg](https://github.com/WordPress/gutenberg)。 + +浏览[问题列表](https://github.com/wordpress/gutenberg/issues)寻找可参与解决的问题。[初试身手](https://github.com/wordpress/gutenberg/issues?q=is%3Aopen+is%3Aissue+label%3A%22Good+First+Issue%22)和[初次审阅](https://github.com/WordPress/gutenberg/pulls?q=is%3Aopen+is%3Apr+label%3A%22Good+First+Review%22)标签是不错的入门起点。 + +## 贡献者资源 + +- [入门指南](/docs/contributors/code/getting-started-with-code-contribution.md):记录开发环境搭建流程,包括测试站点和开发者工具建议 +- [Git工作流](/docs/contributors/code/git-workflow.md):说明使用拉取请求部署更改的git流程 +- [编码规范](/docs/contributors/code/coding-guidelines.md):概述古腾堡项目中使用的额外模式与约定 +- [测试概览](/docs/contributors/code/testing-overview.md):针对古腾堡中PHP和JavaScript开发的测试指南 +- [无障碍测试](/docs/contributors/accessibility-testing.md):记录古腾堡无障碍功能测试流程 +- [包管理](/docs/contributors/code/managing-packages.md):说明npm包的管理流程 +- [古腾堡发布流程](/docs/contributors/code/release.md):古腾堡项目不同类型发布的检查清单 +- [React Native移动编辑器](/docs/contributors/code/react-native/README.md):参与React Native移动编辑器开发的指南 +- [React Native集成测试指南](/docs/contributors/code/react-native/integration-test-guide.md):创建移动编辑器集成测试的指南 \ No newline at end of file diff --git a/contributors/code/auto-cherry-picking.md b/contributors/code/auto-cherry-picking.md new file mode 100644 index 0000000..8de2446 --- /dev/null +++ b/contributors/code/auto-cherry-picking.md @@ -0,0 +1,91 @@ +# 自动化精选合并 + +`npm run other:cherry-pick` 可自动将带有特定标签的拉取请求精选合并至**当前分支**。 + +该功能在WordPress主要版本发布时尤为实用,因为脚本默认会查找带有`Backport to WP Beta/RC`标签的已合并拉取请求。 + +您也可以通过传递自定义标签作为第一个参数,在不同场景中使用此功能。具体示例可参阅本文档末尾的Gutenberg插件发布案例。 + +运行`npm run other:cherry-pick`会出现以下提示: + +``` +当前位于 "wp/6.2" 分支 +本脚本将执行以下操作: +• 将标记为"Backport to WP Beta/RC"的已合并PR精选合并至此分支 +• 询问是否推送该分支 +• 在每个PR中添加注释 +• 移除每个PR的标签 + +后两项操作将使用您关联至GitHub CLI(gh命令)的账户执行 + +是否继续?(Y/n) +``` + +同意后将会执行以下流程: + +``` +开始逐个执行精选合并.. + +$ git pull origin wp/6.2 --rebase... +$ git fetch origin trunk... + +发现以下待精选合并的PR: + #41198 – 站点编辑器:设置样式预览最小宽度 + +正在获取提交ID... 完成! + #41198 – 860a39665c318d33027d – 站点编辑器:设置样式预览... + +开始逐个执行精选合并... + +第一轮精选合并: + 精选提交:afe9b757b4 对应PR:#41198 – 站点编辑器:设置样式预览... +精选合并完成! + +执行结果: + 成功精选合并 1 个PR + 合并失败 0 个PR + +即将推送至 origin/wp/6.2 +是否继续?(Y/n) +``` + +本次运行成功!此时您可以确认是否精选合并了正确的PR。 + +如果精选合并出现冲突怎么办?脚本会继续处理其他PR并自动重试。 +若部分PR仍合并失败,脚本将跳过这些PR并告知需要手动解决冲突的节点。 + +无论哪种情况,当您通过精选合并阶段后: + +``` +正在推送至 origin/wp/6.2 +添加注释并移除标签... + 41198: 已将此PR精选合并至wp/6.2分支以便纳入下一版本:afe9b757b4 +完成! +``` + +注释功能为可选操作,仅当您安装[`gh`命令行工具](https://cli.github.com/)时可用。 + +### 能否使用`Backport to WP Beta/RC`之外的标签? + +可以!只需将其作为第一个参数传递: + +``` +npm run other:cherry-pick "其他标签" +``` + +### 如何用于Gutenberg插件发布? + +``` +# 切换到发布分支 +git checkout release/X.Y + +# 精选合并所有带有相关回溯标签的已合并PR +npm run other:cherry-pick "Backport to Gutenberg RC" +``` + +### 未来改进方向 + +未来有望实现根据当前所选分支自动匹配对应标签: + +* release/X.Y - 插件发布分支 → "Backport to Gutenberg RC" +* wp/X.Y - WordPress发布分支 → "Backport to WP Beta/RC" \ No newline at end of file diff --git a/contributors/code/back-merging-to-wp-core.md b/contributors/code/back-merging-to-wp-core.md new file mode 100644 index 0000000..1d2d1dd --- /dev/null +++ b/contributors/code/back-merging-to-wp-core.md @@ -0,0 +1,38 @@ +# 将代码反向合并至WordPress核心 + +在WordPress软件的主要版本发布时,需要将Gutenberg的功能合并到WordPress核心代码中。通常这涉及提取Gutenberg代码库中`.php`文件的变更,并在WP核心代码库中进行相应更新。 + +## 合并标准 + +### 文件/目录范围 + +以下文件/目录内的变更通常需要反向合并至WP核心: + +- `lib/` 目录 +- `phpunit/` 目录 + +### 排除目录/文件 + +以下目录/文件_无需_反向合并至WP核心: + +- `lib/load.php` - 插件专用代码 +- `lib/experiments-page.php` - 实验性功能为插件专用 +- `packages/block-library` - 将在程序包同步过程中自动处理 +- `packages/e2e-tests/plugins` - 仅限端到端测试相关的PHP文件(主要为测试数据生成器) +- `phpunit/blocks` - 该代码由Gutenberg维护,测试文件也应保留在此 + +请注意此列表并未涵盖所有情况。 + +### 拉取请求标准 + +通常来说,自[上一稳定版WP核心](https://developer.wordpress.org/block-editor/contributors/versions-in-wordpress/)所包含的最终版Gutenberg发布之日起,所有提交至Gutenberg代码库的PHP代码都应考虑反向合并至WP核心。 + +但存在以下例外情况,符合这些标准的PR_无需_反向合并至WP核心: + +- 未包含PHP代码变更 +- 具有`Backport from WordPress Core`标签 - 该代码已存在于WP核心,正在同步回Gutenberg +- 具有`Backported to WordPress Core`标签 - 该代码已完成向WP核心的同步 + +## 扩展阅读 + +另请参阅关于[Gutenberg PHP代码](/lib/README.md)的补充文档。 \ No newline at end of file diff --git a/contributors/code/backward-compatibility.md b/contributors/code/backward-compatibility.md new file mode 100644 index 0000000..52fd656 --- /dev/null +++ b/contributors/code/backward-compatibility.md @@ -0,0 +1,79 @@ +# 向后兼容性 + +历史上,WordPress 以其跨版本保持向后兼容性而闻名。Gutenberg 在其生产环境的公共 API 中尽可能遵循这一原则。在极少数情况下,破坏向后兼容性不可避免,此时需确保: + +- 破坏范围应尽可能限制在 API 的小部分区域内。 +- 应通过开发者说明文档向第三方开发者尽可能清晰地记录破坏性变更。 + +## 生产环境公共 API 的界定标准 + +Gutenberg 代码库由两种不同类型的包组成: + +- **生产环境包**:这些包作为 WordPress 脚本发布(例如:wp-components、wp-editor...)。 +- **开发环境包**:这些包由开发者工具组成,可供第三方开发者用于检查、测试、格式化和构建其主题和插件(例如:@wordpress/scrips、@wordpress/env...)。通常,这些包在第三方项目中作为 npm 依赖项使用。 + +向后兼容性保证仅适用于生产环境包,因为更新是通过 WordPress 升级进行的。 + +生产环境包使用 `wp` 全局变量向第三方开发者提供 API。这些 API 可以是 JavaScript 函数、变量和 React 组件。 + +### 如何保持 JavaScript 函数的向后兼容性 + +- 函数名称不应更改。 +- 函数的参数顺序不应更改。 +- 函数的返回值类型不应更改。 +- 如果保证所有之前的调用仍然有效,可以对参数进行更改(新增参数、修改语义)。 + +### 如何保持 React 组件的向后兼容性 + +- 组件名称不应更改。 +- 组件的属性不应被移除。 +- 应继续支持现有的属性值。如果组件接受函数作为属性,可以更新组件以接受同一属性的新类型,但不应破坏现有用法。 +- 允许添加新属性。 +- 只有在确保之前的上下文契约不被破坏的情况下,才能添加或移除 React Context 依赖。 + +### 如何保持区块的向后兼容性 + +- 当编辑器加载时,区块的现有用法不应被破坏或标记为无效。 +- 应保证现有区块的样式。 +- 标记更改应尽可能限制在最小范围内,但如果区块需要更改其保存的标记,导致先前版本无效,则应添加该区块的[**废弃版本**](/docs/reference-guides/block-api/block-deprecation.md)。 + +## 类名和 DOM 更新 + +React 组件树内部使用的类名和 DOM 节点不被视为公共 API 的一部分,可以进行修改。 + +对这些内容的更改应谨慎进行,因为它们可能影响第三方代码的样式和行为(即使它们本不应依赖这些内容)。如果可能,保留旧的类名和 DOM 节点。如果无法保留,请记录更改并撰写开发者说明。 + +## 废弃 + +随着项目的发展,现有 API 的缺陷会被发现,或者需要更新以支持新功能。在这种情况下,我们尽量保证现有 API 不被破坏,并构建新的替代 API。 + +为了鼓励第三方开发者采用新的 API,我们可以使用[**废弃**](/packages/deprecated/README.md)辅助工具来显示一条消息,说明废弃情况并在使用旧 API 时提供替代方案。 + +明确说明功能何时被废弃。使用辅助方法的 `since` 和 `plugin` 选项。 + +示例: + +```js +deprecated( 'wp.components.ClipboardButton', { + since: '10.3', + plugin: 'Gutenberg', + alternative: 'wp.compose.useCopyToClipboard', +} ); +``` + +## 开发者说明 + +开发者说明是在 WordPress 发布之前[发布在 make/core 网站上的文章](https://make.wordpress.org/core/tag/dev-notes/),用于向第三方开发者通报开发者 API 的重要变更,这些变更可能包括: + +- 新的 API。 +- 可能影响现有插件和主题的现有 API 变更(例如:类名更改等)。 +- 不可避免的向后兼容性破坏,附带理由和迁移流程。 +- 重要的废弃(即使没有破坏性变更),附带理由和迁移流程。 + +### 开发者说明工作流程 + +- 在处理拉取请求时,如果发现需要开发者说明,请为 PR 添加 **Needs Dev Note** 标签。 +- 如果可能,在 PR 中添加评论,说明为什么需要开发者说明。 +- 当即将发布的 WordPress 版本的第一个测试版发布时,检查发布中包含的已合并 PR 列表,这些 PR 标记有 **Needs Dev Note** 标签。 +- 对于每一个这样的 PR,撰写开发者说明,并与 WordPress 发布负责人协调发布开发者说明。 +- 一旦 PR 的开发者说明发布,从 PR 中移除 **Needs Dev Note** 标签。 \ No newline at end of file diff --git a/contributors/code/coding-guidelines.md b/contributors/code/coding-guidelines.md new file mode 100644 index 0000000..bef0346 --- /dev/null +++ b/contributors/code/coding-guidelines.md @@ -0,0 +1,766 @@ +### 记录 React 组件文档 + +在可能的情况下,所有组件都应实现为[函数式组件](https://react.dev/learn/your-first-component),并使用[钩子](https://react.dev/reference/react/hooks)来管理组件的生命周期和状态。 + +记录函数式组件的方式应与记录其他函数相同。在记录组件时需要注意的主要问题是,该函数通常只接受一个参数(即“props”),该参数可能包含多个属性成员。使用[参数属性的点语法](https://jsdoc.app/tags-param.html#parameters-with-properties)来记录各个属性的类型。 + +````js +/** + * 将区块配置的标题渲染为字符串,如果无法确定标题则返回空值。 + * + * @example + * + * ```jsx + * + * ``` + * + * @param {Object} props + * @param {string} props.clientId 区块的客户端 ID。 + * + * @return {?string} 区块标题。 + */ +```` + +对于类组件,没有关于记录组件属性的推荐方式。Gutenberg 不使用也不认可 [`propTypes` 静态类成员](https://react.dev/reference/react/Component#static-proptypes)。 + +## PHP + +我们使用 [`phpcs`(PHP_CodeSniffer)](https://github.com/squizlabs/PHP_CodeSniffer) 和 [WordPress 编码标准规则集](https://github.com/WordPress-Coding-Standards/WordPress-Coding-Standards) 对本项目中的所有 PHP 代码进行大量自动化检查。这确保我们符合 WordPress 的 PHP 编码标准。 + +使用 PHPCS 最简单的方式是通过[本地环境](/docs/contributors/code/getting-started-with-code-contribution.md#local-environment)。安装完成后,可以通过运行 `npm run lint:php` 来检查 PHP 代码。 + +如果更倾向于在本地安装 PHPCS,应使用 `composer`。在计算机上[安装 `composer`](https://getcomposer.org/download/),然后运行 `composer install`。这将安装 `phpcs` 和 `WordPress-Coding-Standards`,之后可以通过 `composer lint` 运行检查。 + +# 编程规范 + +本文档旨在为古腾堡项目制定专属的编程规范。基础编程规范遵循[WordPress编码标准](https://developer.wordpress.org/coding-standards/wordpress-coding-standards/)。以下章节将阐述古腾堡项目中使用的额外模式与约定。 + +## CSS + +### 命名规范 + +为避免类名冲突,所有类名**必须**遵循以下准则,这些准则借鉴了[BEM(块、元素、修饰符)方法论](https://en.bem.info/methodology/)的核心思想。 + +所有分配给元素的类名都必须以软件包名称为前缀,后接连字符和组件所在目录的名称。组件根元素的所有后代元素必须追加以连字符分隔的描述符,并通过双下划线`__`与基础类名分隔。 + +- 根元素:`package-directory` +- 子元素:`package-directory__descriptor-foo-bar` + +根元素是指`index.js`中默认导出返回的最高层级祖先元素。需要注意的是,如果文件夹包含多个文件,且每个文件都有各自默认导出的组件,则只有`index.js`渲染的元素可视为根元素,其余所有元素都应视为后代元素。 + +**示例:** + +假设存在位于`packages/components/src/notice/index.js`的组件: + +```jsx +export default function Notice( { children, onRemove } ) { + return ( +
+
{ children }
+
+ ); +} +``` + +组件可被赋予表示状态的类名(例如“激活”标签页或“展开”面板)。这些修饰符应作为独立类名应用,通过`is-`前缀构成形容词表达式(如`is-active`或`is-opened`)。特殊情况下,可能会遇到修饰符前缀的变体,通常是为了提升可读性(如`has-warning`)。由于修饰符类名不限定于特定组件,在样式表中应始终与被修饰的组件配合使用(`.components-panel.is-opened`)。 + +**示例:** + +再次以通知组件为例。我们可能需要为可关闭通知应用特定样式。[`clsx`工具包](https://www.npmjs.com/package/clsx)可辅助实现条件化应用修饰符类名。 + +```jsx +import clsx from 'clsx'; + +export default function Notice( { children, onRemove, isDismissible } ) { + const classes = clsx( 'components-notice', { + 'is-dismissible': isDismissible, + } ); + + return
{ /* ... */ }
; +} +``` + +组件的类名**绝不应**在其所属文件夹之外使用(极少数例外情况如[`_z-index.scss`](https://github.com/WordPress/gutenberg/blob/HEAD/packages/base-styles/_z-index.scss))。若需在自己组件中继承其他组件的样式,应渲染该组件的实例。最不推荐的做法是在自身组件的样式表中复制样式。此举旨在通过将共享组件隔离为可复用接口来提升可维护性,通过适配有限通用组件支持多样化使用场景,从而减少相似UI元素的实现复杂度。 + +#### 区块组件的SCSS文件命名规范 + +当Webpack运行时,构建过程会将区块库目录内的SCSS拆分为两个独立的CSS文件。 + +置于`style.scss`文件中的样式将被编译至`blocks/build/style.css`,同时在前端主题和编辑器中加载。如需针对区块在编辑器中的显示添加特定样式,请将其加入`editor.scss`文件。 + +同时出现在主题和编辑器中的样式示例包括图库列数和首字下沉效果。 + +### React 组件 + +推荐将所有组件实现为[函数式组件](https://react.dev/learn/your-first-component),并使用[钩子](https://react.dev/reference/react/hooks)来管理组件状态和生命周期。除[错误边界](https://react.dev/reference/react/Component#catching-rendering-errors-with-an-error-boundary)外,您不应遇到必须使用类组件的情况。请注意,[WordPress 代码重构指南](https://make.wordpress.org/core/handbook/contribute/code-refactoring/)在此适用:无需集中批量更新类组件,而应将其视为结合其他更改进行重构的良好机会。 + +## 使用 JSDoc 的 JavaScript 文档 + +Gutenberg 遵循 [WordPress JavaScript 文档标准](https://make.wordpress.org/core/handbook/best-practices/inline-documentation-standards/javascript/),并针对其在文件组织中对[导入语义](/docs/contributors/code/coding-guidelines.md#imports)的特殊使用、[使用 TypeScript 工具](/docs/contributors/code/testing-overview.md#javascript-testing)进行类型验证,以及使用 [`@wordpress/docgen`](https://github.com/WordPress/gutenberg/tree/HEAD/packages/docgen) 自动生成文档,制定了相关附加指南。 + +更多指导请参考以下资源: + +- [JSDoc 官方文档](https://jsdoc.app/index.html) +- [TypeScript 支持的 JSDoc](https://www.typescriptlang.org/docs/handbook/jsdoc-supported-types.html) + +### 自定义类型 + +使用 [JSDoc `@typedef` 标签](https://jsdoc.app/tags-typedef.html)定义自定义类型。 + +自定义类型应包含描述,并始终注明其基础类型。命名应尽可能简洁,同时保持含义清晰并避免与其他全局或作用域内的类型冲突。所有自定义类型需添加 `WP` 前缀,避免多余或冗余的前缀和后缀(例如 `Type` 后缀或 `Custom` 前缀)。自定义类型默认非全局类型,因此无需过度特指某个特定包,但命名应具备足够特异性,以避免在与其他类型处于同一作用域时产生歧义或命名冲突。 + +```js +/** + * 块选择对象。 + * + * @typedef WPBlockSelection + * + * @property {string} clientId 块客户端 ID。 + * @property {string} attributeKey 块属性键。 + * @property {number} offset 基于富文本值的属性值偏移量。 + */ +``` + +注意 `@typedef` 和类型名之间没有 `{Object}`。由于下方的 `@property` 表明这是对象类型,建议在定义对象类型时不使用 `{Object}`。 + +自定义类型也可用于描述一组预定义选项。虽然[类型联合](https://jsdoc.app/tags-type.html)可与字面值一起用作内联类型,但要在保持 80 字符最大行宽的同时对齐标签可能较为困难。使用自定义类型定义联合类型既能描述这些选项的用途,又有助于避免行宽问题。 + +```js +/** + * 命名的断点尺寸。 + * + * @typedef {'huge'|'wide'|'large'|'medium'|'small'|'mobile'} WPBreakpoint + */ +``` + +注意定义字符串字面量集合时需使用引号。根据 [JavaScript 编码标准](https://make.wordpress.org/core/handbook/best-practices/coding-standards/javascript/),在为类型或[默认函数参数](#nullable-undefined-and-void-types)分配字符串字面量时,或在[指定导入类型的路径](#importing-and-exporting-types)时,应使用单引号。 + +### 可空类型、undefined 类型与 void 类型 + +你可以使用前置问号 `?` 表示可空类型。仅当描述类型本身或显式的 `null` 值时,才使用可空类型形式。不要将可空形式作为可选参数的标识符。 + +```js +/** + * 获取指定键的配置值(若存在)。若未配置该值则返回 null。 + * + * @param {string} key 要获取的配置键名。 + * + * @return {?*} 配置值(若存在)。 + */ +function getConfigurationValue( key ) { + return config.hasOwnProperty( key ) ? config[ key ] : null; +} +``` + +类似地,仅当需要显式的 `undefined` 值时,才使用 `undefined` 类型。 + +```js +/** + * 若下一个 HTML 标签闭合当前标签,则返回 true。 + * + * @param {WPHTMLToken} currentToken 要比对的当前标签。 + * @param {WPHTMLToken|undefined} nextToken 要比对的下一个标签。 + * + * @return {boolean} 若 `nextToken` 闭合 `currentToken` 则返回 true,否则返回 false。 + */ +``` + +若参数为可选参数,请使用[方括号标记法](https://jsdoc.app/tags-param.html#optional-parameters-and-default-values)。如果可选参数具有默认值,且该值可通过函数表达式中的[默认参数](https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Functions/Default_parameters)语法表示,则无需在 JSDoc 中包含该值。若函数参数的默认值需要复杂逻辑且无法通过默认参数语法表示,可选择在 JSDoc 中包含该默认值。 + +```js +/** + * 渲染工具栏组件。 + * + * @param {Object} props 组件属性。 + * @param {string} [props.className] 要设置到容器 `
` 上的类名。 + */ +``` + +当函数不包含 `return` 语句时,称其具有 `void` 返回值。若返回类型为 `void`,则无需包含 `@return` 标签。 + +若函数存在多个代码路径,且部分(非全部)条件分支包含 `return` 语句,可将其标注为包含 `void` 类型的联合类型。 + +```js +/** + * 获取指定键的配置值(若存在)。 + * + * @param {string} key 要获取的配置键名。 + * + * @return {*|void} 配置值(若存在)。 + */ +function getConfigurationValue( key ) { + if ( config.hasOwnProperty( key ) ) { + return config[ key ]; + } +} +``` + +当标注[函数类型](#泛型类型)时,必须始终包含 `void` 返回值类型,否则该函数会被推断为返回混合值("any"),而非 void 结果。 + +```js +/** + * apiFetch 中间件处理器。在传入获取选项后,中间件需在完成处理时调用 `next` 中间件。 + * + * @typedef {(options:WPAPIFetchOptions,next:WPAPIFetchMiddleware)=>void} WPAPIFetchMiddleware + */ +``` + +### 示例文档规范 + +由于使用 `@wordpress/docgen` 工具生成的文档会包含已定义的 `@example` 标签,因此为函数和组件包含使用示例被视为最佳实践。这对于记录包公开 API 的成员尤为重要。 + +编写示例时,请使用 Markdown 的 \`\`\` 代码块标记来界定代码段的起始与结束。示例可跨越多行。 + +````js +/** + * 根据已注册存储的名称,返回该存储选择器对象。选择器函数已预绑定,可自动传递当前状态。 + * 作为使用者,仅需传递选择器所需参数(若适用)。 + * + * @param {string} name 存储名称。 + * + * @example + * ```js + * select( 'my-shop' ).getPrice( 'hammer' ); + * ``` + * + * @return {Record} 包含存储选择器的对象。 + */ +```` + +## JavaScript + +Gutenberg 中的 JavaScript 采用了 [ECMAScript 语言规范](https://www.ecma-international.org/ecma-262/) 的现代语言特性以及 [JSX 语言语法扩展](https://react.dev/learn/writing-markup-with-jsx)。这些功能通过预设配置组合实现,特别是项目中 [Babel](https://babeljs.io/) 配置使用的预设 [`@wordpress/babel-preset-default`](https://github.com/WordPress/gutenberg/tree/HEAD/packages/babel-preset-default)。 + +虽然引入新 JavaScript 语言特性的 [分阶段流程](https://tc39.es/process-document/) 提供了在特性尚未完全定型前使用的机会,**但 Gutenberg 项目和 `@wordpress/babel-preset-default` 配置仅支持已达到第 4 阶段("已完成")的提案**。 + +### 导入方式 + +在 Gutenberg 项目中,我们使用 [ES2015 导入语法](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/import) 来创建模块化代码,明确区分特定功能代码、跨 WordPress 功能共享代码以及第三方依赖代码。 + +这些区分通过文件顶部的多行注释来标识,这些注释标注了从其他文件或源导入的代码。 + +#### 外部依赖 + +外部依赖是指不由 WordPress 贡献者维护的第三方代码,而是 [作为默认脚本包含在 WordPress 中](https://developer.wordpress.org/reference/functions/wp_enqueue_script/#default-scripts-included-and-registered-by-wordpress) 或通过外部包管理器(如 [npm](https://www.npmjs.com/))引用的代码。 + +示例: + +```js +/** + * 外部依赖 + */ +import moment from 'moment'; +``` + +#### WordPress 依赖 + +为了促进功能间的可复用性,我们的 JavaScript 被拆分为特定领域的模块,这些模块 [`export`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/export) 一个或多个函数或对象。在 Gutenberg 项目中,我们在顶级目录下区分这些模块。每个模块服务于独立的目的,并且经常在它们之间共享代码。例如,为了本地化其文本,编辑器代码需要包含来自 `i18n` 模块的函数。 + +示例: + +```js +/** + * WordPress 依赖 + */ +import { __ } from '@wordpress/i18n'; +``` + +#### 内部依赖 + +在特定功能内,代码被组织到不同的文件和文件夹中。与外部和 WordPress 依赖的情况一样,您可以使用 `import` 关键字将这些代码引入作用域。这里的主要区别在于,当导入内部文件时,应使用相对于您正在使用的顶级目录的特定相对路径。 + +示例: + +```js +/** + * 内部依赖 + */ +import VisualEditor from '../visual-editor'; +``` + +### 遗留实验性 API、仅限插件使用的 API 和私有 API + +#### 遗留实验性 API + +历史上,Gutenberg 使用 `__experimental` 和 `__unstable` 前缀来表示给定的 API 尚未稳定,可能会发生变化。这是一种遗留约定,应避免使用,转而采用下面描述的仅限插件使用的 API 模式或私有 API 模式。 + +使用前缀的问题在于这些 API 很少被稳定或移除。截至 2022 年 6 月,WordPress 核心包含了 280 个公开导出的实验性 API,这些 API 是在主要的 WordPress 发布期间从 Gutenberg 插件合并而来的。许多插件和主题开始依赖这些实验性 API 来实现无法通过其他方式访问的关键功能。 + +遗留的 `__experimental` API 不能再随意移除。它们已成为 WordPress 公共 API 的一部分,并受 [WordPress 向后兼容性政策](https://developer.wordpress.org/block-editor/contributors/code/backward-compatibility/) 的约束。移除它们涉及一个弃用过程。对于某些 API 来说可能相对容易,但对于其他 API 可能需要付出努力并跨越多个 WordPress 版本。 + +总之,不要为新 API 使用 `__experimental` 前缀。请改用仅限插件使用的 API 和私有 API。 + +### 类型导入与导出 + +使用 [TypeScript 的 `import` 函数](https://www.typescriptlang.org/docs/handbook/jsdoc-supported-types.html#import-types) 可从其他文件或第三方依赖项导入类型声明。 + +由于导入的类型声明可能占用过多行长度,并在多次引用时显得冗长,建议在文件顶部的 [`import` 分组](/docs/contributors/code/coding-guidelines.md#imports) 之后,立即使用 `@typedef` 声明为外部类型创建别名。 + +```js +/** @typedef {import('@wordpress/data').WPDataRegistry} WPDataRegistry */ +``` + +注意,所有在其他文件中定义的自定义类型均可导入。 + +在确定应从 WordPress 包中提供哪些类型时,包入口脚本中的 `@typedef` 语句应视为与其公共 API 等效。了解这一点很重要,既可避免无意中将内部类型暴露在公共接口上,也可作为项目公开类型的一种方式。 + +```js +// packages/data/src/index.js + +/** @typedef {import('./registry').WPDataRegistry} WPDataRegistry */ +``` + +在此代码片段中,`@typedef` 将支持前例中 `import('@wordpress/data')` 的用法。 + +#### 外部依赖 + +许多第三方依赖会分发其自带的 TypeScript 类型定义。对于这些依赖,`import` 语义应“直接可用”。 + +![工作示例:`import` 类型](https://user-images.githubusercontent.com/1779930/70167742-62198800-1695-11ea-9c21-82a91d4a60e2.png) + +如果您的编辑器使用了 [TypeScript 集成](https://github.com/Microsoft/TypeScript/wiki/TypeScript-Editor-Support),通常可以看到此功能生效,只要类型解析结果不是回退的 `any` 类型即可。 + +对于未分发自带 TypeScript 类型的包,如果存在 [DefinitelyTyped](https://definitelytyped.org/) 社区维护的类型定义,欢迎安装并使用。 + +### 泛型类型 + +在记录泛型类型(如 `Object`、`Function`、`Promise` 等)时,请始终包含有关预期记录类型的详细信息。 + +```js +// 不推荐: + +/** @type {Object} */ +/** @type {Function} */ +/** @type {Promise} */ + +// 推荐: + +/** @type {Record} */ /* 或 */ /** @type {{[setting:string]:any}} */ +/** @type {(key:string)=>boolean} */ +/** @type {Promise} */ +``` + +当对象用作字典时,可通过两种方式定义其类型:可索引接口(`{[setting:string]:any}`)或 `Record`。当对象的键名(如 `setting`)能为开发者提供使用提示时,使用可索引接口;否则,使用 `Record`。 + +此处的函数表达式使用了 TypeScript 的函数类型语法,有助于提供有关预期参数名称和类型的更详细信息。更多内容请参考 [TypeScript 的 `@type` 标签函数建议](https://www.typescriptlang.org/docs/handbook/jsdoc-supported-types.html#type)。 + +在更高级的情况下,您可以使用 [TypeScript 的 `@template` 标签](https://www.typescriptlang.org/docs/handbook/jsdoc-supported-types.html#template) 将自定义类型定义为泛型类型。 + +类似于关于类型联合和字面量值的“自定义类型”建议,您可以考虑创建自定义类型 `@typedef`,以更好地描述对象记录的预期键值,或提取复杂的函数签名。 + +```js +/** + * apiFetch 中间件处理程序。传入获取选项后,中间件应在完成处理时调用 `next` 中间件。 + * + * @typedef {(options:WPAPIFetchOptions,next:WPAPIFetchMiddleware)=>void} WPAPIFetchMiddleware + */ +``` + +```js +/** + * 命名的断点尺寸。 + * + * @typedef {"huge"|"wide"|"large"|"medium"|"small"|"mobile"} WPBreakpoint + */ + +/** + * 断点名称及其生效像素宽度的哈希表。 + * + * @type {Record} + */ +const BREAKPOINTS = { huge: 1440 /* , ... */ }; +``` + +##### 私有函数、类与变量 + +```js +// 在 packages/package1/index.js 中: +import { lock } from './lock-unlock'; + +export const privateApis = {}; +/* 将私有数据附加到导出对象 */ +lock( privateApis, { + privateCallback: function () {}, + privateReactComponent: function PrivateComponent() { + return
; + }, + privateClass: class PrivateClass {}, + privateVariable: 5, +} ); +``` + +```js +// 在 packages/package2/index.js 中: +import { privateApis } from '@wordpress/package1'; +import { unlock } from './lock-unlock'; + +const { + privateCallback, + privateReactComponent, + privateClass, + privateVariable, +} = unlock( privateApis ); +``` + +请务必始终在**已注册**的存储上注册私有操作和选择器。 + +有时这很简单: + +```js +export const store = createReduxStore( STORE_NAME, storeConfig() ); +// `register` 使用与 `createReduxStore` 创建的相同 `store` 对象 +register( store ); +unlock( store ).registerPrivateActions( { + // ... +} ); +``` + +但某些包可能同时调用 `createReduxStore` **和** `registerStore`。此时请始终选择已注册的存储: + +```js +export const store = createReduxStore( STORE_NAME, { + ...storeConfig, + persist: [ 'preferences' ], +} ); +const registeredStore = registerStore( STORE_NAME, { + ...storeConfig, + persist: [ 'preferences' ], +} ); +unlock( registeredStore ).registerPrivateActions( { + // ... +} ); +``` + +#### 私有函数参数 + +若需为稳定函数添加私有参数,需要准备该函数的稳定版本和私有版本。然后导出稳定函数,并将不稳定函数在其内部进行 `lock()` 锁定: + +```js +// 在 @wordpress/package1/index.js 中: +import { lock } from './lock-unlock'; + +// 私有函数包含所有逻辑 +function privateValidateBlocks( formula, privateIsStrict ) { + let isValid = false; + // ...不想重复的复杂逻辑... + if ( privateIsStrict ) { + // ... + } + // ...不想重复的复杂逻辑... + + return isValid; +} + +// 稳定的公共函数是一个轻量包装器,用于调用禁用私有功能的私有函数 +export function validateBlocks( blocks ) { + privateValidateBlocks( blocks, false ); +} + +export const privateApis = {}; +lock( privateApis, { privateValidateBlocks } ); +``` + +```js +// 在 @wordpress/package2/index.js 中: +import { privateApis as package1PrivateApis } from '@wordpress/package1'; +import { unlock } from './lock-unlock'; + +// 可通过稳定函数"解锁"私有函数: +const { privateValidateBlocks } = unlock( package1PrivateApis ); +privateValidateBlocks( blocks, true ); +``` + +#### 私有React组件属性 + +若需为稳定组件添加私有参数,需要准备该组件的稳定版本和私有版本。然后导出稳定函数,并将不稳定函数在其内部进行 `lock()` 锁定: + +```js +// 在 @wordpress/package1/index.js 中: +import { lock } from './lock-unlock'; + +// 私有组件包含所有逻辑 +const PrivateMyButton = ( { title, privateShowIcon = true } ) => { + // ...不想重复的复杂逻辑... + + return ( + + ); +}; + +// 稳定的公共组件是一个轻量包装器,用于调用禁用私有功能的私有组件 +export const MyButton = ( { title } ) => ( + +); + +export const privateApis = {}; +lock( privateApis, { PrivateMyButton } ); +``` + +```js +// 在 @wordpress/package2/index.js 中: +import { privateApis } from '@wordpress/package1'; +import { unlock } from './lock-unlock'; + +// 可通过稳定组件"解锁"私有组件: +const { PrivateMyButton } = unlock( privateApis ); +export function MyComponent() { + return ; +} +``` + +#### 插件专用 API + +插件专用 API 是从模块导出的临时值,其存在性可能待未来修订,或仅为达成特定目标而提供的临时方案。 + +_面向外部使用者:_ + +**插件专用 API 不提供任何支持承诺。** 它们可能在任何时候被移除或变更,且不会提前通知,包括在次要版本或补丁版本中。作为外部使用者,您应避免使用这些 API。 + +_面向项目贡献者:_ + +**插件专用 API** 是指计划最终对外开放,但尚需进一步实验、测试和讨论的接口。应尽快将其稳定或移除。 + +插件专用 API 被排除在 WordPress 核心之外,仅可在 Gutenberg 插件中使用: + +```js +// 使用 globalThis.IS_GUTENBERG_PLUGIN 允许 Webpack 将此导出 +// 从 WordPress 核心中排除: +if ( globalThis.IS_GUTENBERG_PLUGIN ) { + export { doSomethingExciting } from './api'; +} +``` + +这类 API 的公共接口尚未最终确定。除代码内部的引用外,它们不应在任何变更日志中被记录或提及。从外部视角来看,它们实际上应被视为不存在。在大多数情况下,它们仅用于满足本代码库中维护的包之间的内部需求。 + +尽管插件专用 API 最终可能稳定成为公共 API,但这并非必然结果。 + +#### 私有 API + +每个需要内部访问或暴露私有 API 的 `@wordpress` 包,可通过启用 `@wordpress/private-apis` 来实现: + +```js +// 在 packages/block-editor/private-apis.js 中: +import { __dangerousOptInToUnstableAPIsOnlyForCoreModules } from '@wordpress/private-apis'; +export const { lock, unlock } = + __dangerousOptInToUnstableAPIsOnlyForCoreModules( + '我知晓私有功能不可在主题或插件中使用,否则将在下一版 WordPress 中失效。', + '@wordpress/block-editor' // 调用 __dangerousOptInToUnstableAPIsOnlyForCoreModules 的包名称 + //(非目标访问包的名称) + ); +``` + +每个 `@wordpress` 包仅可启用一次。该流程明确告知扩展者不应使用此机制。本文档将重点介绍使用示例,但您可[在其 README.md 中了解更多关于 `@wordpress/private-apis` 包的信息](/packages/private-apis/README.md)。 + +启用后,您可使用 `lock()` 和 `unlock()` 工具: + +```js +// 假设此对象从包中导出: +export const publicObject = {}; + +// 但此字符串属于内部内容,不应公开: +const privateString = '私有信息'; + +// 解决方案:将字符串"锁定"在对象内部: +lock( publicObject, privateString ); + +// 字符串不会嵌套在对象中,也无法从中提取: +console.log( publicObject ); +// {} + +// 访问字符串的唯一方式是"解锁"该对象: +console.log( unlock( publicObject ) ); +// "私有信息" + +// lock() 接受所有数据类型,不仅限于字符串: +export const anotherObject = {}; +lock( anotherObject, function privateFn() {} ); +console.log( unlock( anotherObject ) ); +// function privateFn() {} +``` + +继续阅读了解如何使用 `lock()` 和 `unlock()` 来避免公开导出各类私有 API。 + +##### 私有选择器与操作 + +您可将私有选择器和操作附加到公共存储: + +```js +// 在 packages/package1/store.js 中: +import { privateHasContentRoleAttribute } from './private-selectors'; +import { privateToggleFeature } from './private-actions'; +// `lock` 函数从内部 private-apis.js 文件导出 +// 该文件已调用启用函数 +import { lock, unlock } from './lock-unlock'; + +export const store = registerStore( /* ... */ ); +// 将私有操作附加到导出的存储: +unlock( store ).registerPrivateActions( { + privateToggleFeature, +} ); + +// 将私有选择器附加到导出的存储: +unlock( store ).registerPrivateSelectors( { + privateHasContentRoleAttribute, +} ); +``` + +```js +// 在 packages/package2/MyComponent.js 中: +import { store } from '@wordpress/package1'; +import { useSelect } from '@wordpress/data'; +// `unlock` 函数从内部 private-apis.js 文件导出 +// 该文件已调用启用函数 +import { unlock } from './lock-unlock'; + +function MyComponent() { + const hasRole = useSelect( + ( select ) => + // 使用私有选择器: + unlock( select( store ) ).privateHasContentRoleAttribute() + // 注意必须使用 unlock()。以下代码将无效: + // select( store ).privateHasContentRoleAttribute() + ); + + // 使用私有操作: + unlock( useDispatch( store ) ).privateToggleFeature(); + + // ... +} +``` + +#### 私有编辑器设置 + +WordPress扩展开发者无法自行更新私有区块设置。`@wordpress/block-editor`存储库的`updateSettings()`操作会过滤掉所有不属于公共API的设置。实际存储这些设置的唯一方式是通过私有操作`__experimentalUpdateSettings()`。 + +若要将区块编辑器设置设为私有,请将其添加到[/packages/block-editor/src/store/actions.js](/packages/block-editor/src/store/actions.js)中的`privateSettings`列表: + +```js +const privateSettings = [ + 'inserterMediaCategories', + // 在此列出区块编辑器设置以使其私有化 +]; +``` + +#### 私有block.json与theme.json接口 + +截至目前,尚无法将`block.json`和`theme.json`接口限制在Gutenberg代码库内使用。但未来新的私有接口将仅适用于WordPress核心区块,插件和主题将无法访问这些接口。 + +#### 在thunk中内联小型操作 + +最后,与其创建新的操作生成器,不如考虑使用[thunk](/docs/how-to-guides/thunks.md): + +```js +export function toggleFeature( scope, featureName ) { + return function ( { dispatch } ) { + dispatch( { type: '__private_BEFORE_TOGGLE' } ); + // ... + }; +} +``` + +### 公开私有接口 + +某些私有接口可通过社区反馈获益,因此有必要向WordPress扩展开发者公开。但同时,将其转化为WordPress核心的公共接口并不合理。此时该如何处理? + +您可以将该私有接口重新导出为仅限插件使用的接口,使其仅在Gutenberg插件中公开: + +```js +// 此函数在任何上下文中都不能被扩展开发者使用: +function privateEverywhere() {} + +// 此函数可由使用Gutenberg插件的扩展开发者使用,但不能在原生WordPress核心中使用: +function privateInCorePublicInPlugin() {} + +// Gutenberg在内部将这两个函数都视为私有接口: +const privateApis = {}; +lock( privateApis, { privateEverywhere, privateInCorePublicInPlugin } ); + +// privateInCorePublicInPlugin函数被显式导出, +// 但由于globalThis.IS_GUTENBERG_PLUGIN检查,该导出不会合并到WordPress核心中。 +if ( globalThis.IS_GUTENBERG_PLUGIN ) { + export const privateInCorePublicInPlugin = + unlock( privateApis ).privateInCorePublicInPlugin; +} +``` + +### 对象 + +在定义对象属性值时,尽可能使用[简写表示法](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Object_initializer#New_notations_in_ECMAScript_2015): + + +```js +const a = 10; + +// 不推荐: +const object = { + a: a, + performAction: function () { + // ... + }, +}; + +// 推荐: +const object = { + a, + performAction() { + // ... + }, +}; +``` + +### 字符串 + +字符串字面量应使用单引号声明,除非字符串本身包含需要转义的单引号——此时应使用双引号。如果字符串同时包含单引号和双引号,可使用ES6模板字符串来避免转义引号。 + +**注意:** 在面向用户的字符串中,切勿将单引号字符(`'`)用作撇号(`’`)(如`it’s`或`haven’t`)。在测试代码中仍鼓励使用真正的撇号。 + +通常应避免使用反斜杠转义引号: + + +```js +// 不推荐: +const name = "Matt"; +// 推荐: +const name = 'Matt'; + +// 不推荐: +const pet = "Matt's dog"; +// 同样不推荐(未使用撇号): +const pet = "Matt's dog"; +// 推荐: +const pet = 'Matt’s dog'; +// 同样推荐: +const oddString = "She said 'This is odd.'"; +``` + +应尽可能使用ES6模板字符串替代字符串拼接: + + +```js +const name = 'Stacey'; + +// 不推荐: +alert( 'My name is ' + name + '.' ); +// 推荐: +alert( `My name is ${ name }.` ); +``` + +### 可选链 + +[可选链](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Optional_chaining)是ECMAScript 2020版本引入的新语言特性。虽然该特性对于可能为空值(`null`或`undefined`)的对象属性访问非常方便,但在使用可选链时需要注意一些常见陷阱。未来或许可以通过代码检查或类型检查来避免这些问题。目前需要警惕以下情况: + +- 当对通过可选链求值的值进行取反(`!`)操作时,需注意:如果可选链执行到无法继续的位置,会产生一个[假值](https://developer.mozilla.org/en-US/docs/Glossary/Falsy),该值取反后会转换为`true`。这在多数情况下不符合预期。 + - 示例:`const hasFocus = ! nodeRef.current?.contains( document.activeElement );` 在`nodeRef.current`未赋值时将返回`true` + - 相关issue:[#21984](https://github.com/WordPress/gutenberg/issues/21984) + - 类似ESLint规则:[`no-unsafe-negation`](https://eslint.org/docs/rules/no-unsafe-negation) +- 当赋值布尔值时,注意可选链可能产生非严格`false`的[假值](https://developer.mozilla.org/en-US/docs/Glossary/Falsy)(`undefined`、`null`)。当该值以期望为布尔值(`true`或`false`)的方式传递时,可能引发问题。虽然布尔值常出现这种情况——因为布尔值通常用于考虑广义真值性的逻辑中——但当急切假设属性访问链末端的结果类型时,其他类型的可选链也可能出现此类问题。[类型检查](https://github.com/WordPress/gutenberg/blob/HEAD/packages/README.md#typescript)有助于预防这类错误。 + - 示例:`document.body.classList.toggle( 'has-focus', nodeRef.current?.contains( document.activeElement ) );` 可能错误地添加类,因为[第二个参数是可选的](https://developer.mozilla.org/en-US/docs/Web/API/DOMTokenList/toggle)。如果传入`undefined`,将不会像传入`false`时那样取消设置该类 + - 示例:`` 可能因在[受控和非受控输入](https://reactjs.org/docs/uncontrolled-components.html)间切换而触发React警告。当急切假设`trim()`始终返回字符串值,却忽略了可选链可能导致求值提前中止并返回`undefined`时,很容易陷入此陷阱 \ No newline at end of file diff --git a/contributors/code/deprecations.md b/contributors/code/deprecations.md new file mode 100644 index 0000000..dc5742c --- /dev/null +++ b/contributors/code/deprecations.md @@ -0,0 +1,309 @@ +## 3.2.0 + +- `wp.data.withRehydratation` 已重命名为 `wp.data.withRehydration` +- `wp.editor.ImagePlaceholder` 组件已移除,请改用 `wp.editor.MediaPlaceholder` +- `wp.utils.deprecated` 函数已移除,请改用 `wp.deprecated` +- `wp.utils.blob` 已移除,请改用 `wp.blob` +- `getInserterItems`:移除了 `allowedBlockTypes` 参数,新增了 `parentUID` 参数 +- `getFrecentInserterItems` 选择器已移除,请改用 `getInserterItems` +- `getSupportedBlocks` 选择器已移除,请改用 `canInsertBlockType` + +## 3.1.0 + +- `wp.blocks.*` 中的所有组件已移除,请改用 `wp.editor.*` +- `wp.blocks.withEditorSettings` 已移除,请使用数据模块访问编辑器设置 `wp.data.select( "core/editor" ).getEditorSettings()` +- `wp.utils.*` 中的所有 DOM 工具已移除,请改用 `wp.dom.*` +- 区块 API 中的 `isPrivate: true` 已移除,请改用 `supports.inserter: false` +- `wp.utils.isExtraSmall` 函数已移除,请改用 `wp.viewport` 模块 +- `getEditedPostExcerpt` 选择器已移除(`core/editor`),请改用 `getEditedPostAttribute( 'excerpt' )` + +## 3.0.0 + +- `wp.blocks.registerCoreBlocks` 函数已移除,请改用 `wp.coreBlocks.registerCoreBlocks` +- `RichText` 的原始 TinyMCE 事件处理程序已弃用,请改用[文档化属性](https://github.com/WordPress/gutenberg/blob/v3.0.0/editor/components/rich-text/README.md)、祖先事件处理程序,或通过 onSetup 访问内部编辑器实例事件中心 + +## 2.8.0 + +- `wp.components.Autocomplete` 中的原始自动补全接口已更新,请改用最新自动补全接口。更多信息请参阅[自动补全文档](https://github.com/WordPress/gutenberg/blob/v2.8.0/components/autocomplete/README.md) +- `getInserterItems`:`allowedBlockTypes` 参数现为必填项 +- `getFrecentInserterItems`:`allowedBlockTypes` 参数现为必填项 + +## 2.7.0 + +- `wp.element.getWrapperDisplayName` 函数已移除,请改用 `wp.element.createHigherOrderComponent` + +## 2.6.0 + +- `wp.blocks.getBlockDefaultClassname` 函数已移除,请改用 `wp.blocks.getBlockDefaultClassName` +- `wp.blocks.Editable` 组件已移除,请改用 `wp.blocks.RichText` 组件 + +## 2.5.0 + +- 不再支持从区块 `save` 返回原始 HTML,请改用 `wp.element.RawHTML` 组件 +- `wp.data.query` 高阶组件已移除,请改用 `wp.data.withSelect` + +## 2.4.0 + +- `wp.blocks.BlockDescription` 组件已移除,请改用 `description` 区块属性 +- `wp.blocks.InspectorControls.*` 组件已移除,请改用 `wp.components.*` 组件 +- `wp.blocks.source.*` 匹配器已移除,请改用声明式属性。更多信息请参阅[区块属性文档](/docs/reference-guides/block-api/block-attributes.md) +- `wp.data.select( 'selector', ...args )` 已移除,请改用 `wp.data.select( reducerKey' ).*` +- `wp.blocks.MediaUploadButton` 组件已移除,请改用 `wp.blocks.MediaUpload` 组件 + +```markdown +## 3.6.0 + +- 已移除 `wp.editor.editorMediaUpload`,请改用 `wp.editor.mediaUpload` +- 已移除 `wp.utils.getMimeTypesArray` +- 已移除 `wp.utils.mediaUpload`,请改用 `wp.editor.mediaUpload` +- 已移除 `wp.utils.preloadImage` +- 区块 API 中的 `supports.wideAlign` 已移除,请改用 `supports.alignWide` +- 已移除 `wp.blocks.isSharedBlock`,请改用 `wp.blocks.isReusableBlock` +- 已移除 fetchSharedBlocks 操作(`core/editor`),请改用 fetchReusableBlocks +- 已移除 receiveSharedBlocks 操作(`core/editor`),请改用 receiveReusableBlocks +- 已移除 saveSharedBlock 操作(`core/editor`),请改用 saveReusableBlock +- 已移除 deleteSharedBlock 操作(`core/editor`),请改用 deleteReusableBlock +- 已移除 updateSharedBlockTitle 操作(`core/editor`),请改用 updateReusableBlockTitle +- 已移除 convertBlockToSaved 操作(`core/editor`),请改用 convertBlockToReusable +- 已移除 getSharedBlock 选择器(`core/editor`),请改用 getReusableBlock +- 已移除 isSavingSharedBlock 选择器(`core/editor`),请改用 isSavingReusableBlock +- 已移除 isFetchingSharedBlock 选择器(`core/editor`),请改用 isFetchingReusableBlock +- 已移除 getSharedBlocks 选择器(`core/editor`),请改用 getReusableBlocks + +## 3.5.0 + +- 已移除 `wp.components.ifCondition`,请改用 `wp.compose.ifCondition` +- 已移除 `wp.components.withGlobalEvents`,请改用 `wp.compose.withGlobalEvents` +- 已移除 `wp.components.withInstanceId`,请改用 `wp.compose.withInstanceId` +- 已移除 `wp.components.withSafeTimeout`,请改用 `wp.compose.withSafeTimeout` +- 已移除 `wp.components.withState`,请改用 `wp.compose.withState` +- 已移除 `wp.element.pure`,请改用 `wp.compose.pure` +- 已移除 `wp.element.compose`,请改用 `wp.compose.compose` +- 已移除 `wp.element.createHigherOrderComponent`,请改用 `wp.compose.createHigherOrderComponent` +- 已移除 `wp.utils.buildTermsTree` +- 已移除 `wp.utils.decodeEntities`,请改用 `wp.htmlEntities.decodeEntities` +- 所有区块 `uid` 的引用已替换为等效的 `clientId` 属性和选择器 +- `wp.editor.MediaPlaceholder` 组件的 `onSelectUrl` 属性已重命名为 `onSelectURL` +- `wp.editor.UrlInput` 组件已重命名为 `wp.editor.URLInput` +- 已移除文本列区块,请改用列区块 +- 已移除 `InnerBlocks` 分组布局,请改用中间嵌套内部区块。参考列/栏目块的实现方案 +- 已移除 `RichText` 显式 `element` 格式,请改用兼容的 `children` 格式 + +## 3.4.0 + +- `Popover` 组件中的 `focusOnMount` 属性已从仅布尔值改为枚举式属性,接受 `"firstElement"`、`"container"` 或 `false`。请将 `` 用法转换为 `` +- 已移除 `wp.utils.keycodes` 工具,请改用 `wp.keycodes` +- 已移除 `edit` 函数中的区块 `id` 属性,请改用区块 `clientId` 属性 +- 已移除 `property` 源,请改用等效的 `text`、`html` 或 `attribute` 源,或注释属性 + +## 3.3.0 + +- 已从区块 API 中移除 `useOnce: true`,请改用 `supports.multiple: false` +- 使用 `componentWillMount` 生命周期方法序列化组件,请改用构造函数 +- 已移除 `blocks.Autocomplete.completers` 过滤器,请改用 `editor.Autocomplete.completers` +- 已移除 `blocks.BlockEdit` 过滤器,请改用 `editor.BlockEdit` +- 已移除 `blocks.BlockListBlock` 过滤器,请改用 `editor.BlockListBlock` +- 已移除 `blocks.MediaUpload` 过滤器,请改用 `editor.MediaUpload` +``` + +- PHP 函数 `gutenberg_show_privacy_policy_help_text` 已被移除。 +- PHP 函数 `gutenberg_common_scripts_and_styles` 已被移除。请改用 [`wp_common_block_scripts_and_styles`](https://developer.wordpress.org/reference/functions/wp_common_block_scripts_and_styles/)。 +- PHP 函数 `gutenberg_enqueue_registered_block_scripts_and_styles` 已被移除。请改用 [`wp_enqueue_registered_block_scripts_and_styles`](https://developer.wordpress.org/reference/functions/wp_enqueue_registered_block_scripts_and_styles/)。 +- PHP 函数 `gutenberg_meta_box_save` 已被移除。 +- PHP 函数 `gutenberg_meta_box_save_redirect` 已被移除。 +- PHP 函数 `gutenberg_filter_meta_boxes` 已被移除。 +- PHP 函数 `gutenberg_intercept_meta_box_render` 已被移除。 +- PHP 函数 `gutenberg_override_meta_box_callback` 已被移除。 +- PHP 函数 `gutenberg_show_meta_box_warning` 已被移除。 +- PHP 函数 `the_gutenberg_metaboxes` 已被移除。请改用 [`the_block_editor_meta_boxes`](https://developer.wordpress.org/reference/functions/the_block_editor_meta_boxes/)。 +- PHP 函数 `gutenberg_meta_box_post_form_hidden_fields` 已被移除。请改用 [`the_block_editor_meta_box_post_form_hidden_fields`](https://developer.wordpress.org/reference/functions/the_block_editor_meta_box_post_form_hidden_fields/)。 +- PHP 函数 `gutenberg_toggle_custom_fields` 已被移除。 +- PHP 函数 `gutenberg_collect_meta_box_data` 已被移除。请改用 [`register_and_do_post_meta_boxes`](https://developer.wordpress.org/reference/functions/register_and_do_post_meta_boxes/)。 +- `window._wpLoadGutenbergEditor` 已被移除。请改用 `window._wpLoadBlockEditor`。注意:这是一个私有 API,不供公共使用,未来可能会被移除。 +- PHP 函数 `gutenberg_get_script_polyfill` 已被移除。请改用 [`wp_get_script_polyfill`](https://developer.wordpress.org/reference/functions/wp_get_script_polyfill/)。 +- PHP 函数 `gutenberg_add_admin_body_class` 已被移除。如果需要将样式限定在块编辑器界面,请在样式表中使用 `.block-editor-page` 类选择器。 + +## 4.5.0 + +- `Dropdown.refresh()` 已被弃用,因为其包含的 `Popover` 现在会自动刷新。 +- `wp.editor.PostPublishPanelToggle` 已被弃用,请改用 `wp.editor.PostPublishButton`。 + +## 4.4.0 + +- `wp.date.getSettings` 已被移除。请改用 `wp.date.__experimentalGetSettings`。 +- `wp.compose.remountOnPropChange` 已被移除。 +- 以下编辑器存储操作已被移除:`createNotice`、`removeNotice`、`createSuccessNotice`、`createInfoNotice`、`createErrorNotice`、`createWarningNotice`。请使用 `@wordpress/notices` 模块中同名的等效操作。 +- `wp.nux.DotTip` 的 `id` 属性已被移除。请改用 `tipId` 属性。 +- `wp.blocks.isValidBlock` 已被移除。请改用 `wp.blocks.isValidBlockContent`,但请注意参数顺序已更改。 +- `wp.data` 的 `registry.registerReducer` 已被弃用。请改用 `registry.registerStore`。 +- `wp.data` 的 `registry.registerSelectors` 已被弃用。请改用 `registry.registerStore`。 +- `wp.data` 的 `registry.registerActions` 已被弃用。请改用 `registry.registerStore`。 +- `wp.data` 的 `registry.registerResolvers` 已被弃用。请改用 `registry.registerStore`。 +- `moment` 已从日期模块的公共 API 中移除。 + +## 4.3.0 + +- `isEditorSidebarPanelOpened` 选择器(`core/edit-post`)已被移除。请改用 `isEditorPanelEnabled`。 +- `toggleGeneralSidebarEditorPanel` 操作(`core/edit-post`)已被移除。请改用 `toggleEditorPanelOpened`。 +- `wp.components.PanelColor` 组件已被移除。请改用 `wp.editor.PanelColorSettings`。 +- `wp.editor.PanelColor` 组件已被移除。请改用 `wp.editor.PanelColorSettings`。 + +## 4.2.0 + +- 已将异步生成器形式的解析器移除,请改用 controls 插件 +- `wp.components.AccessibleSVG` 组件已移除,请改用 `wp.components.SVG` +- `wp.editor.UnsavedChangesWarning` 组件不再接受 `forceIsDirty` 属性 +- `setActiveMetaBoxLocations` 操作(`core/edit-post`)已移除 +- `initializeMetaBoxState` 操作(`core/edit-post`)已移除 +- `wp.editPost.initializeEditor` 不再返回对象,请使用 `setActiveMetaBoxLocations` 操作(`core/edit-post`)替代原对象的 `initializeMetaBoxes` 函数 +- `setMetaBoxSavedData` 操作(`core/edit-post`)已移除 +- `getMetaBoxes` 选择器(`core/edit-post`)已移除,请改用 `getActiveMetaBoxLocations` 选择器(`core/edit-post`) +- `getMetaBox` 选择器(`core/edit-post`)已移除,请改用 `isMetaBoxLocationActive` 选择器(`core/edit-post`) +- 属性类型强制转换已移除,需省略源以通过序列化注释标记保留类型 +- `wp.editor.mediaUpload` 的 `onFileChange` 回调所传对象中的 `mediaDetails` 已移除,请改用 `media_details` 属性 +- `wp.components.CodeEditor` 已移除,请直接使用 `wp.codeEditor` +- `wp.blocks.setUnknownTypeHandlerName` 已移除,请改用 `setFreeformContentHandlerName` 和 `setUnregisteredTypeHandlerName` +- `wp.blocks.getUnknownTypeHandlerName` 已移除,请改用 `getFreeformContentHandlerName` 和 `getUnregisteredTypeHandlerName` +- 可重用块数据 API 标记为实验性功能,未来可能变更 + +## 4.1.0 + +- `wp.data.dispatch( 'core/editor' ).checkTemplateValidity` 已移除,区块重置时会自动进行有效性验证 + +## 4.0.0 + +- `wp.editor.RichTextProvider` 已移除,请改用 `wp.data.select( 'core/editor' )` 方法 +- `wp.components.Draggable` 作为 DOM 节点拖拽处理器的功能已移除,请将 `wp.components.Draggable` 作为包装组件用于 DOM 节点拖拽处理器 +- `wp.i18n.getI18n` 已移除,请改用 `__`、`_x`、`_n` 或 `_nx` +- `wp.i18n.dcnpgettext` 已移除,请改用 `__`、`_x`、`_n` 或 `_nx` + +## 3.9.0 + +- RichText 的 `getSettings` 属性已移除,如需继续使用请改用 `unstableGetSettings` 属性(强烈不建议使用不稳定 API,此类 API 可能随时被移除) +- RichText 的 `onSetup` 属性已移除,如需继续使用请改用 `unstableOnSetup` 属性(强烈不建议使用不稳定 API,此类 API 可能随时被移除) +- `wp.editor.getColorName` 已移除,请改用 `wp.editor.getColorObjectByColorValue` +- `wp.editor.getColorClass` 已重命名,请改用 `wp.editor.getColorClassName` +- `wp.editor.withColors` 所传颜色对象中的 `value` 属性已移除,请改用 color 属性 +- 子标题区块已移除,请改用段落区块 +- `wp.blocks.getDefaultBlockForPostFormat` 已移除 + +## 3.8.0 + +- `wp.components.withContext` 已移除,请改用 `wp.element.createContext`,参见:https://react.dev/reference/react/createContext +- `wp.coreBlocks.registerCoreBlocks` 已移除,请改用 `wp.blockLibrary.registerCoreBlocks` +- `wp.editor.DocumentTitle` 组件已移除 +- `getDocumentTitle` 选择器(`core/editor`)已移除 + +## 3.7.0 + +- `wp.components.withAPIData` 已移除,请直接使用核心数据模块或 `wp.apiFetch` +- `wp.data.dispatch("core").receiveTerms` 已弃用,请改用 `wp.data.dispatch("core").receiveEntityRecords` +- `getCategories` 解析器已弃用,请改用 `getEntityRecords` 解析器 +- `wp.data.select("core").getTerms` 已弃用,请改用 `wp.data.select("core").getEntityRecords` +- `wp.data.select("core").getCategories` 已弃用,请改用 `wp.data.select("core").getEntityRecords` +- `wp.data.select("core").isRequestingCategories` 已弃用,请改用 `wp.data.select("core/data").isResolving` +- `wp.data.select("core").isRequestingTerms` 已弃用,请改用 `wp.data.select("core").isResolving` +- `wp.data.restrictPersistence`、`wp.data.setPersistenceStorage` 和 `wp.data.setupPersistence` 已移除,请改用数据持久化插件 + +# 已弃用功能 + +对于Gutenberg插件中包含的功能,弃用政策旨在尽可能支持两个次要插件版本的向后兼容性。WordPress稳定版本中包含的功能和代码不在此弃用时间线内,而是遵循[WordPress项目的版本管理政策](https://make.wordpress.org/core/handbook/about/release-cycle/version-numbering/)。当前已弃用的功能如下所示,并按*将完全移除这些功能的版本*进行分组。如果您的插件依赖这些行为,则必须在指定版本之前更新至推荐的替代方案。 + +## 未发布版本 + +- `wp.blocks.isValidBlockContent` 已被移除。请改用 `wp.blocks.validateBlock`。 + +## 11.0.0 + +- `wp.blocks.registerBlockTypeFromMetadata` 方法已被移除。请改用 `wp.blocks.registerBlockType` 方法。 + +## 10.3.0 + +- 不再支持向 `ActionItem.Slot` 组件传递带有 `as` 属性的组件元组。请改为传递带有 `as` 属性的组件。示例: + ```diff + + ``` + +## 9.7.0 + +- `InterfaceSkeleton` 组件中的 `leftSidebar` 属性已被移除。请改用 `secondarySidebar` 属性。 + +## 8.6.0 + +- 更新了与[区块上下文](/docs/reference-guides/block-api/block-context.md)的区块API集成。注册区块时,请在JavaScript文件中使用 `usesContext` 和 `providesContext` 对,在PHP文件中使用 `uses_context` 和 `provides_context` 对,而不是之前的 `context` 和 `providesContext` 对。 + +## 8.3.0 + +- PHP函数 `gutenberg_get_post_from_context` 已被移除。请改用[区块上下文](/docs/reference-guides/block-api/block-context.md)。 +- 旧的区块模式API `register_pattern`/`unregister_pattern` 已被移除。请改用[新函数](/docs/reference-guides/block-api/block-patterns.md#register_block_pattern)。 + +## 5.5.0 + +- PHP函数 `gutenberg_init` 已被移除。 +- PHP函数 `is_gutenberg_page` 已被移除。请改用 [`WP_Screen::is_block_editor`](https://developer.wordpress.org/reference/classes/wp_screen/is_block_editor/)。 +- PHP函数 `the_gutenberg_project` 已被移除。 +- PHP函数 `gutenberg_default_post_format_template` 已被移除。 +- PHP函数 `gutenberg_get_available_image_sizes` 已被移除。 +- PHP函数 `gutenberg_get_autosave_newer_than_post_save` 已被移除。 +- PHP函数 `gutenberg_editor_scripts_and_styles` 已被移除。 + +## 5.4.0 + +- PHP函数 `gutenberg_load_plugin_textdomain` 已被移除。 +- PHP函数 `gutenberg_get_jed_locale_data` 已被移除。 +- PHP函数 `gutenberg_load_locale_data` 已被移除。 + +## 5.3.0 + +- PHP函数 `gutenberg_redirect_to_classic_editor_when_saving_posts` 已被移除。 +- PHP函数 `gutenberg_revisions_link_to_editor` 已被移除。 +- PHP函数 `gutenberg_remember_classic_editor_when_saving_posts` 已被移除。 +- PHP函数 `gutenberg_can_edit_post_type` 已被移除。请改用 [`use_block_editor_for_post_type`](https://developer.wordpress.org/reference/functions/use_block_editor_for_post_type/)。 +- PHP函数 `gutenberg_can_edit_post` 已被移除。请改用 [`use_block_editor_for_post`](https://developer.wordpress.org/reference/functions/use_block_editor_for_post/)。 + +## 5.2.0 + +- PHP函数 `gutenberg_parse_blocks` 已被移除。请改用 [`parse_blocks`](https://developer.wordpress.org/reference/functions/parse_blocks/)。 +- PHP函数 `get_dynamic_blocks_regex` 已被移除。 +- PHP函数 `gutenberg_render_block` 已被移除。请改用 [`render_block`](https://developer.wordpress.org/reference/functions/render_block/)。 +- PHP函数 `strip_dynamic_blocks` 已被移除。如需用于摘要准备,请考虑改用 [`excerpt_remove_blocks`](https://developer.wordpress.org/reference/functions/excerpt_remove_blocks/)。 +- PHP函数 `strip_dynamic_blocks_add_filter` 已被移除。 +- PHP函数 `strip_dynamic_blocks_remove_filter` 已被移除。 +- PHP函数 `gutenberg_post_has_blocks` 已被移除。请改用 [`has_blocks`](https://developer.wordpress.org/reference/functions/has_blocks/)。 +- PHP函数 `gutenberg_content_has_blocks` 已被移除。请改用 [`has_blocks`](https://developer.wordpress.org/reference/functions/has_blocks/)。 +- PHP函数 `gutenberg_register_rest_routes` 已被移除。 +- PHP函数 `gutenberg_add_taxonomy_visibility_field` 已被移除。 +- PHP函数 `gutenberg_get_taxonomy_visibility_data` 已被移除。 +- PHP函数 `gutenberg_add_permalink_template_to_posts` 已被移除。 +- PHP函数 `gutenberg_add_block_format_to_post_content` 已被移除。 +- PHP函数 `gutenberg_add_target_schema_to_links` 已被移除。 +- PHP函数 `gutenberg_register_post_prepare_functions` 已被移除。 +- PHP函数 `gutenberg_silence_rest_errors` 已被移除。 +- PHP函数 `gutenberg_filter_post_type_labels` 已被移除。 +- PHP函数 `gutenberg_preload_api_request` 已被移除。请改用 [`rest_preload_api_request`](https://developer.wordpress.org/reference/functions/rest_preload_api_request/)。 +- PHP函数 `gutenberg_remove_wpcom_markdown_support` 已被移除。 +- PHP函数 `gutenberg_add_gutenberg_post_state` 已被移除。 +- PHP函数 `gutenberg_bulk_post_updated_messages` 已被移除。 +- PHP函数 `gutenberg_kses_allowedtags` 已被移除。 +- PHP函数 `gutenberg_add_responsive_body_class` 已被移除。 +- PHP函数 `gutenberg_add_edit_link_filters` 已被移除。 +- PHP函数 `gutenberg_add_edit_link` 已被移除。 +- PHP函数 `gutenberg_block_bulk_actions` 已被移除。 +- PHP函数 `gutenberg_replace_default_add_new_button` 已被移除。 +- PHP函数 `gutenberg_content_block_version` 已被移除。请改用 [`block_version`](https://developer.wordpress.org/reference/functions/block_version/)。 +- PHP函数 `gutenberg_get_block_categories` 已被移除。请改用 [`get_block_categories`](https://developer.wordpress.org/reference/functions/get_block_categories/)。 +- PHP函数 `register_tinymce_scripts` 已被移除。请改用 [`wp_register_tinymce_scripts`](https://developer.wordpress.org/reference/functions/wp_register_tinymce_scripts/)。 +- PHP函数 `gutenberg_register_post_types` 已被移除。 +- `gutenberg` 主题支持选项已被移除。请改用 [`align-wide`](https://developer.wordpress.org/block-editor/developers/themes/theme-support/#wide-alignment)。 +- PHP函数 `gutenberg_prepare_blocks_for_js` 已被移除。请改用 [`get_block_editor_server_block_settings`](https://developer.wordpress.org/reference/functions/get_block_editor_server_block_settings/)。 +- PHP函数 `gutenberg_load_list_reusable_blocks` 已被移除。 +- PHP函数 `_gutenberg_utf8_split` 已被移除。请改用 `_mb_substr`。 +- PHP函数 `gutenberg_disable_editor_settings_wpautop` 已被移除。 +- PHP函数 `gutenberg_add_rest_nonce_to_heartbeat_response_headers` 已被移除。 +- PHP函数 `gutenberg_check_if_classic_needs_warning_about_blocks` 已被移除。 +- PHP函数 `gutenberg_warn_classic_about_blocks` 已被移除。 \ No newline at end of file diff --git a/contributors/code/e2e/README.md b/contributors/code/e2e/README.md new file mode 100644 index 0000000..1faa52d --- /dev/null +++ b/contributors/code/e2e/README.md @@ -0,0 +1,117 @@ +# 端到端测试 + +本文档旨在为 Gutenberg 项目中如何使用 Playwright 编写端到端测试提供操作指南和最佳实践。 + +
+ +若您正在使用旧版 Jest + Puppeteer 框架,请参阅专用指南。若需从 Jest + Puppeteer 迁移测试,请查阅迁移指南。 + +
+ +## 运行测试 + +```bash +# 运行所有可用测试 +npm run test:e2e + +# 以图形界面模式运行 +npm run test:e2e -- --headed + +# 在特定浏览器中运行测试(支持 `chromium`、`firefox` 或 `webkit`) +npm run test:e2e -- --project=webkit --project=firefox + +# 运行单个测试文件 +npm run test:e2e -- <测试文件路径> # 例如:npm run test:e2e -- site-editor/title.spec.js + +# 调试模式 +npm run test:e2e -- --debug +``` + +若您在 Linux 环境下开发,目前需以图形界面模式测试 Webkit 浏览器。若不愿或无法使用图形界面运行(例如无图形界面环境),可在命令前添加 [`xvfb-run`](https://manpages.ubuntu.com/manpages/xenial/man1/xvfb-run.1.html) 在虚拟环境中运行: + +```bash +# 运行所有可用测试 +xvfb-run npm run test:e2e + +# 仅运行 webkit 测试 +xvfb-run -- npm run test:e2e -- --project=webkit +``` + +若您正在使用 VS Code 进行编辑,可安装 [Playwright 扩展插件](https://playwright.dev/docs/getting-started-vscode)来辅助运行、编写和调试测试。 + +## 最佳实践 + +请阅读 Playwright 的[最佳实践指南](https://playwright.dev/docs/best-practices)。 + +### 禁止使用 `$`,改用 `locator` + +实际上,任何返回 `ElementHandle` 的 API 均[不推荐使用](https://playwright.dev/docs/api/class-page#page-query-selector),包括 `$`、`$$`、`$eval`、`$$eval` 等。[`Locator`](https://playwright.dev/docs/api/class-locator) 是更优秀的 API,可与 Playwright 的[断言功能](https://playwright.dev/docs/api/class-locatorassertions)配合使用。由于定位器采用惰性求值且不返回 Promise,该特性在页面对象模型中表现尤为出色。 + +### 使用可访问性选择器 + +尽可能使用 [`getByRole`](https://playwright.dev/docs/locators#locate-by-role) 构建查询。这样既能编写无障碍查询,又无需依赖内部实现细节: + +```js +// 选择包含无障碍名称"Hello World"的按钮(不区分大小写) +page.getByRole( 'button', { name: 'Hello World' } ); +``` + +还支持链式调用以执行复杂查询: + +```js +// 在"区块库"区域下选择名称为"按钮"的选项 +page.getByRole( 'region', { name: 'Block Library' } ) + .getByRole( 'option', { name: 'Buttons' } ) +``` + +更多使用技巧请参阅[官方文档](https://playwright.dev/docs/locators)。 + +### 选择器默认采用严格模式 + +为提升元素查询的最佳实践,选择器默认启用[严格模式](https://playwright.dev/docs/api/class-browser#browser-new-page-option-strict-selectors),当查询结果包含多个元素时将抛出错误。 + +### 避免过度封装测试工具,简单工具应内联实现 + +`e2e-test-utils` 因包含过多工具函数而变得臃肿。其中大部分函数足够简单,可直接在测试中内联实现。借助可访问性选择器,现在编写简单工具函数更加容易。对于特定页面的工具函数,建议采用页面对象模型(但使用 `requestUtils` 清理状态的情况除外,这类工具更适合放在 `e2e-test-utils` 中)。仅当操作足够复杂且需要重复使用时,才考虑创建工具函数。 + +### 优先采用页面对象模型而非工具函数 + +如前所述,[页面对象模型](https://playwright.dev/docs/pom)是在特定页面创建可复用工具函数的首选方案。 + +使用页面对象模型的核心优势在于能将工具函数按命名空间分组,更便于发现和使用。实际上,`e2e-test-utils-playwright` 包中的 `PageUtils` 本身就是页面对象模型,既避免了全局变量的使用,又支持通过 `this` 实现工具函数间的相互调用。 + +### 通过 REST 接口清理或设置状态 + +在测试前后手动设置状态效率低下,特别是在测试间需要重复设置时。推荐通过 API 调用来设置状态。请使用 `requestUtils.rest` 和 `requestUtils.batchRest` 调用 [REST API](https://developer.wordpress.org/rest-api/reference/)(如有需要可将其添加到 `requestUtils` 中)。我们仍需为手动设置状态编写测试,但此类测试只需编写一次。 + +### 避免使用全局变量 + +在之前的 Jest + Puppeteer 端到端测试中,`page` 和 `browser` 被暴露为全局变量。当同一测试中涉及多个页面/标签页,或需要并行运行多个测试时,这种方式会使工作变得困难。`@playwright/test` 引入了[测试固件](https://playwright.dev/docs/test-fixtures)概念,允许我们将 `page`、`browser` 及其他参数注入到测试中。 + +### 使用显式断言 + +单个测试中可根据需要插入多个断言。尽可能使用显式断言是更佳实践。例如,若需在点击按钮前确认其存在,可在执行 `locator.click()` 前使用 `expect( locator ).toBeVisible()`。这样能使测试流程更清晰易读。 + +## 常见误区 + +### [过度使用快照](https://github.com/WordPress/gutenberg/tree/HEAD/docs/contributors/code/e2e/overusing-snapshots.md) + +## 跨浏览器测试 + +默认情况下测试仅在 Chromium 中运行。您可以通过给测试添加标签来指定运行浏览器。在测试标题任意位置使用 `@browser` 即可在对应浏览器中运行测试。默认情况下测试始终会在 Chromium 中运行,可通过添加 `-chromium` 后缀禁用 Chromium 测试。支持的浏览器包括 `chromium`、`firefox` 和 `webkit`。 + +```js +test( '我将在 @firefox 和 @webkit 中运行(默认包含 chromium)', async ( { page } ) => { + // ... +} ); + +test( '我仅在 @firefox 中运行(排除 -chromium)', async ( { page } ) => { + // ... +} ); + +test.describe( '测试分组 (@webkit, -chromium)', () => { + test( '我仅在 webkit 中运行', async ( { page } ) => { + // ... + } ); +} ); +``` \ No newline at end of file diff --git a/contributors/code/e2e/migration.md b/contributors/code/e2e/migration.md new file mode 100644 index 0000000..168d4d3 --- /dev/null +++ b/contributors/code/e2e/migration.md @@ -0,0 +1,37 @@ +# 迁移指南 + +本文档概述了将 Jest + Puppeteer 测试迁移至 Playwright 的典型流程。请注意,迁移过程也是重构或重写部分测试的良好契机。开始迁移前,请先阅读[最佳实践](https://github.com/WordPress/gutenberg/tree/HEAD/docs/contributors/code/e2e/README.md#best-practices)指南。 + +## 测试迁移步骤 + +1. 在 `packages/e2e-tests/specs` 中选择要迁移的测试套件,将 `.test.js` 重命名为 `.spec.js`,并置于 `test/e2e/specs` 内的相同文件夹结构中。 +2. 从 `@wordpress/e2e-test-utils-playwright` 引入测试辅助工具: + ```js + const { test, expect } = require( '@wordpress/e2e-test-utils-playwright' ); + ``` +3. 将所有出现的 `describe`、`beforeAll`、`beforeEach`、`afterEach` 和 `afterAll` 加上 `test.` 前缀。例如,`describe` 变为 `test.describe`。 +4. 使用[夹具 API](https://playwright.dev/docs/test-fixtures) 引入之前全局的变量,如 `page` 和 `browser`。 +5. 删除所有对 `e2e-test-utils` 的导入。改为使用夹具 API 直接获取 `admin`、`editor`、`pageUtils` 和 `requestUtils`。(注意:`admin`、`editor` 和 `pageUtils` 不能在 `beforeAll` 和 `afterAll` 中使用,需改用 `requestUtils` 重写。) +6. 如果缺少某个工具函数,且步骤较少,可尝试直接在测试中内联实现操作。如果认为有必要作为测试工具函数实现,请参考以下[指南](#测试工具函数迁移步骤)。 +7. 根据推荐的[最佳实践](https://github.com/WordPress/gutenberg/tree/HEAD/docs/contributors/code/e2e/README.md#best-practices)手动迁移测试中的其他细节。请注意,尽管 Playwright 和 Puppeteer 的 API 差异较小,但仍需进行一些手动调整。 + +## 测试工具函数迁移步骤 + +在迁移测试工具函数前,请仔细考虑是否必要。Playwright 提供了许多易读且强大的 API,使得许多工具函数不再需要。可先尝试在测试中直接内联实现相同功能。仅当此方法不适用时,再参考以下指南。适合在 `e2e-test-utils-playwright` 包中实现的工具函数示例包括复杂的浏览器 API(如 `pageUtils.dragFiles` 和 `pageUtils.pressKeys`)以及状态设置 API(如 `requestUtils.*`)。 + +
+e2e-test-utils-playwright 包并非旨在完全替代 Jest + Puppeteer 的 e2e-test-utils 包。部分工具函数仅为简化迁移过程而创建,并非必需。 +
+ +Playwright 工具函数的组织方式与 `e2e-test-utils` 包略有不同。`e2e-test-utils-playwright` 包将工具函数分为以下文件夹: +- `admin` - 与 WordPress 后台或 WordPress 后台用户界面相关的工具函数(例如 `visitAdminPage`)。 +- `editor` - 用于块编辑器的工具函数(例如 `clickBlockToolbarButton`)。 +- `pageUtils` - 用于与浏览器交互的通用工具函数(例如 `pressKeys`)。 +- `requestUtils` - 用于发起 REST API 请求的工具函数(例如 `activatePlugin`)。这些工具函数用于测试的初始化和清理。 + +1. 复制 `e2e-test-utils` 中的现有文件,根据工具函数类型粘贴到 `e2e-test-utils-playwright` 的 `admin`、`editor`、`page` 或 `request` 文件夹中。 +2. 更新工具函数中需要重写的部分: + - `page` 和 `browser` 变量在 `admin`、`editor` 和 `pageUtils` 中可通过 `this.page` 和 `this.browser` 访问。 + - 同一类中的所有其他工具函数可通过 `this` 访问,并绑定到同一实例。可移除所有 `import` 语句,改用 `this` 访问。 + - 如果熟悉 TypeScript,可考虑将工具函数更新为 TypeScript 版本。 +3. 在 `index.ts` 中导入新迁移的工具函数,并将其作为实例字段放入 `Admin`/`Editor`/`PageUtils`/`RequestUtils` 类中。 \ No newline at end of file diff --git a/contributors/code/e2e/overusing-snapshots.md b/contributors/code/e2e/overusing-snapshots.md new file mode 100644 index 0000000..4c6afea --- /dev/null +++ b/contributors/code/e2e/overusing-snapshots.md @@ -0,0 +1,125 @@ +# 过度使用快照测试 + +看看下面的代码。你第一眼能理解这个测试想要做什么吗? + +```js +await editor.insertBlock( { name: 'core/quote' } ); +await page.keyboard.type( '1' ); +await page.keyboard.press( 'Enter' ); +await page.keyboard.press( 'Enter' ); + +expect( await editor.getEditedPostContent() ).toMatchSnapshot(); + +await page.keyboard.press( 'Backspace' ); +await page.keyboard.type( '2' ); + +expect( await editor.getEditedPostContent() ).toMatchSnapshot(); +``` + +这段代码改编自Gutenberg项目中的真实代码,移除了测试标题和注释,并重构为Playwright版本。理想情况下,端到端测试应该具备自文档化和最终用户可读性;毕竟它们试图模拟最终用户与应用程序的交互方式。然而,这段代码中存在几个值得警惕的问题。 + +## 快照测试存在的问题 + +由Jest推广的[快照测试](https://jestjs.io/docs/snapshot-testing)在恰当使用时是个很好的工具。但可能因为它功能强大,开发者经常过度使用。已有许多[文章](https://kentcdodds.com/blog/effective-snapshot-testing)讨论过这个问题。在这个具体案例中,快照测试未能清晰体现开发者的意图。如果不查看其他信息,我们无法明确断言的具体内容。这使得代码难以理解,给除原作者外的所有读者增加了认知负担。作为读者,我们不得不反复查看代码才能完全理解它们。增加的代码复杂性阻碍了贡献者根据需求修改测试的意愿。有时甚至会让原作者感到困惑,导致意外[提交错误的快照](https://github.com/WordPress/gutenberg/pull/42780#discussion_r949865612)。 + +以下是包含测试标题和注释的同一测试。现在你明白这些断言的实际含义了: + +```js +it( '可以在末尾拆分', async () => { + // ... + + // 期望在引用块外有空段落 + expect( await getEditedPostContent() ).toMatchSnapshot(); + + // ... + + // 期望段落被合并到引用块中 + expect( await getEditedPostContent() ).toMatchSnapshot(); +} ); +``` + +开发者的意图现在稍微清晰了些,但仍感觉与测试本身脱节。你可能会想尝试[内联快照](https://jestjs.io/docs/snapshot-testing#inline-snapshots),这确实解决了需要在文件间跳转的问题,但它们仍然不具备自文档化特性也不够明确。我们可以做得更好。 + +## 解决方案 + +与其在注释中写断言说明,不如尝试直接明确地写出来。借助`editor.getBlocks`方法,我们可以将其重写为更简单和原子化的断言: + +```js +// ... + +// 期望在引用块外有空段落 +await expect.poll( editor.getBlocks ).toMatchObject( [ + { + name: 'core/quote', + innerBlocks: [ + { + name: 'core/paragraph', + attributes: { content: '1' }, + }, + ], + }, + { + name: 'core/paragraph', + attributes: { content: '' }, + } +] ); + +// ... + +// 期望段落被合并到引用块中 +await expect.poll( editor.getBlocks ).toMatchObject( [ { + name: 'core/quote', + innerBlocks: [ + { + name: 'core/paragraph', + attributes: { content: '1' }, + }, + { + name: 'core/paragraph', + attributes: { content: '2' }, + }, + ], +} ] ); +``` + +这些断言更加可读和明确。你可以添加额外的断言或将现有断言拆分成多个,以突出它们的重要性。是否保留注释取决于你,但当代码本身已经足够可读时,通常可以省略注释。 + +## 快照测试的变体 + +由于Playwright缺乏内联快照功能,一些迁移的测试使用字符串断言(`toBe`)来模拟类似效果,避免创建大量快照文件: + +```js +expect( await editor.getEditedPostContent() ).toBe( ` +

段落

+` ); +``` + +我们可以将这种模式视为快照测试的变体,在编写时应遵循相同的规则。通常最好使用`editor.getBlocks`或其他方法重写它们,以进行明确断言: + +```js +await expect.poll( editor.getBlocks ).toMatchObject( [ { + name: 'core/paragraph', + attributes: { content: '段落' }, +} ] ); +``` + +## 测试覆盖率怎么办? + +将明确断言与快照测试比较,我们确实在这个测试中损失了一些测试覆盖率。当我们需要断言块的完整序列化内容时,快照测试仍然有用。但幸运的是,集成测试中的一些测试已经对每个核心块的[完整内容](https://github.com/WordPress/gutenberg/blob/trunk/test/integration/fixtures/blocks/README.md)进行了断言。这些测试在Node.js中运行,比在Playwright中重复相同测试要快得多。在我的机器上运行273个测试用例仅需约5.7秒。这类测试在单元或集成级别效果很好,我们可以在不损失测试覆盖率的情况下更快地运行它们。 + +## 最佳实践 + +在端到端测试中很少需要快照测试,通常有更好的替代方案可以利用明确断言。在确实没有其他合适替代方案时,我们应遵循使用快照测试的最佳实践。 + +### 避免巨大的快照 + +巨大的快照难以阅读和审查。而且,当所有内容都重要时,实际上没有任何内容是重要的。巨大的快照会阻碍我们关注快照的重要部分。 + +### 避免重复的快照 + +如果你发现在同一个测试中创建了多个相似内容的快照,这可能表明你应该进行更原子化的断言。重新思考你想要测试什么,如果第一个快照只是第二个的参考,那么你真正需要的是快照之间的**差异**。将第一个结果存储在变量中,然后断言结果之间的差异。 + +## 延伸阅读 + +- [有效的快照测试 - Kent C. Dodds](https://kentcdodds.com/blog/effective-snapshot-testing) +- [常见测试错误 - Kent C. Dodds](https://kentcdodds.com/blog/common-testing-mistakes) \ No newline at end of file diff --git a/contributors/code/getting-started-with-code-contribution.md b/contributors/code/getting-started-with-code-contribution.md new file mode 100644 index 0000000..32f1c3a --- /dev/null +++ b/contributors/code/getting-started-with-code-contribution.md @@ -0,0 +1,254 @@ +# 代码贡献入门指南 + +以下指南将帮助您设置本地环境,以便为 Gutenberg 项目做出贡献。用于贡献代码的环境与用于扩展 WordPress 区块编辑器的环境存在显著重叠。您可以查阅[开发环境教程](/docs/getting-started/devenv/README.md)获取更多设置信息。 + +## 环境要求 + +- Node.js + Gutenberg 是一个 JavaScript 项目,需要安装 [Node.js](https://nodejs.org/)。项目目前基于 Node.js v20 和 npm v10 构建。虽然我们尽力使用 Node.js 的 Active LTS 版本,但并非总能实现。更多详细信息请参考 [Node.js 发布计划](https://github.com/nodejs/Release#release-schedule)。 + +我们推荐使用 [Node Version Manager](https://github.com/nvm-sh/nvm) (nvm),这是在 macOS、Linux 和 Windows 10(使用 WSL2)上安装和管理 Node.js 的最简单方式。如需更多安装说明,请参阅[我们的开发工具指南](/docs/getting-started/devenv/README.md#development-tools)或 Node.js 官网。 + +- Git + Gutenberg 使用 git 进行版本控制。请确保您的计算机上安装了最新版本的 git,并拥有 GitHub 账户。您可以阅读 [Git 工作流程](/docs/contributors/code/git-workflow.md)了解如何在 Gutenberg 中使用 git 和 GitHub。 + +- [推荐] Docker Desktop + 我们推荐使用 [wp-env 包](/packages/env/README.md)在本地设置 WordPress 环境。使用 `wp-env` 需要安装 Docker。更多详细信息请参阅[开发环境教程](/docs/getting-started/devenv/README.md)。 + > 注意:若要在 Windows 10 家庭版上安装 Docker,请按照 [Docker for Windows with WSL2 的安装说明](https://docs.docker.com/docker-for-windows/wsl/)操作。 + +作为 Docker 设置的替代方案,您可以使用 [Local](https://localwp.com/)、[WampServer](https://wampserver.aviatechno.net/) 或 [MAMP](https://www.mamp.info/),甚至可以使用远程服务器。 + +- GitHub CLI + 虽然不是必需,但 [GitHub CLI](https://cli.github.com/) 能极大帮助您在本地检出拉取请求,包括来自 Gutenberg 主仓库和分叉仓库的请求。这在代码审查和测试拉取请求时能显著节省时间。 + +## 获取 Gutenberg 代码 + +请先 Fork Gutenberg 仓库,然后克隆到您的计算机,并将 WordPress 仓库添加为上游源。 + +```bash +$ git clone https://github.com/您的GitHub用户名/gutenberg.git +$ cd gutenberg +$ git remote add upstream https://github.com/WordPress/gutenberg.git +``` + +## 将 Gutenberg 构建为插件 + +安装 Gutenberg 依赖项并以开发模式构建代码: + +```bash +npm install +npm run dev +``` + +> 注意:安装脚本要求系统已安装 [Python](https://www.python.org/) 并配置在环境变量中。根据操作系统的不同,Python 可能已默认安装或需要手动下载安装。 + +有两种构建代码的方式。在开发过程中,您可能希望使用 `npm run dev` 在源文件更改时自动持续构建。开发构建还包含额外的警告和错误信息,便于开发过程中进行故障排除。完成更改后,可以运行 `npm run build` 创建优化的生产构建。 + +构建完成后,Gutenberg 即可作为 WordPress 插件使用! + +## 本地 WordPress 环境 + +要测试 WordPress 插件,您需要先安装 WordPress 本体。如果您已设置好 WordPress 本地环境,只需将 gutenberg 目录放入 wp-content/plugins/ 目录即可将构建好的 Gutenberg 作为标准 WordPress 插件使用。 + +如果尚未设置本地 WordPress 环境,请按照本节剩余步骤创建环境。 + +## 开发者工具 + +我们建议将编辑器配置为自动检查语法和代码规范错误。这能帮助您在开发过程中自动修复细微的格式问题,从而节省时间。以下是为 Visual Studio Code(许多核心开发者常用的编辑器)的设置指南,这些工具也适用于其他编辑器。 + +### EditorConfig + +[EditorConfig](https://editorconfig.org/) 定义了编辑器的标准配置,例如使用制表符代替空格。您应安装 [VS Code 的 EditorConfig 扩展](https://marketplace.visualstudio.com/items?itemName=editorconfig.editorconfig),它将自动配置您的编辑器以符合 [.editorconfig](https://github.com/WordPress/gutenberg/blob/HEAD/.editorconfig) 中定义的规则。 + +### ESLint + +[ESLint](https://eslint.org/) 通过静态分析代码来发现问题。代码规范检查规则已集成到持续集成流程中,必须通过检查才能提交代码。您应为 Visual Studio Code 安装 [ESLint 扩展](https://marketplace.visualstudio.com/items?itemName=dbaeumer.vscode-eslint),其他编辑器的集成方案请参阅 [eslint 文档](https://eslint.org/docs/user-guide/integrations)。 + +安装扩展后,ESLint 将使用 Gutenberg 代码库根目录中的 [.eslintrc.js](https://github.com/WordPress/gutenberg/blob/HEAD/.eslintrc.js) 文件作为格式规则。它会在开发时高亮显示问题,您还可以通过以下设置实现在保存时自动修复规范问题: + +```json + "editor.codeActionsOnSave": { + "source.fixAll.eslint": "explicit" + }, +``` + +### Prettier + +[Prettier](https://prettier.io/) 是一款能够定义规范化格式并自动修复代码以匹配该格式的工具。Prettier 与 ESLint 功能相似,但 Prettier 更侧重于格式和样式,而 ESLint 主要用于检测编码错误。 + +若要在 Visual Studio Code 中使用 Prettier,请安装 [Prettier 代码格式化扩展](https://marketplace.visualstudio.com/items?itemName=esbenp.prettier-vscode)。随后可通过在设置中添加以下配置,将其设为默认格式化工具并实现保存时自动修复问题: +**_注意_:根据文档查看环境的不同,方括号可能显示为双括号,实际正确格式应为单层方括号。** + +```json +"[javascript]": { + "editor.defaultFormatter": "esbenp.prettier-vscode", + "editor.formatOnSave": true +}, +"[markdown]": { + "editor.defaultFormatter": "esbenp.prettier-vscode", + "editor.formatOnSave": true +}, +``` + +此配置将使用 Gutenberg 代码库根目录中的 `.prettierrc.js` 文件,该配置继承自 [@wordpress/prettier-config](/packages/prettier-config/README.md) 包。 + +如果仅希望在 Gutenberg 项目中使用此配置,请在项目根目录创建 `.vscode` 文件夹,并将设置存入其中的 `settings.json` 文件。Visual Studio Code 将其称为工作区设置,且仅适用于当前项目。 + +其他编辑器的配置请参阅 [Prettier 编辑器集成文档](https://prettier.io/docs/en/editors.html)。 + +### TypeScript + +**TypeScript** 是 JavaScript 语言的类型化超集。Gutenberg 项目通过 JSDoc 使用 TypeScript 来实现 [JavaScript 文件的类型检查](https://www.typescriptlang.org/docs/handbook/type-checking-javascript-files.html)。如果您使用 Visual Studio Code,其已内置 TypeScript 支持,其他编辑器的集成方案请参阅 [TypeScript 编辑器支持](https://github.com/Microsoft/TypeScript/wiki/TypeScript-Editor-Support)。 + +### 使用 Docker 与 wp-env + +[wp-env 工具包](/packages/env/README.md)最初为古腾堡项目开发,旨在通过 Docker 快速创建标准 WordPress 环境。该工具已作为 `@wordpress/env` npm 包发布。 + +默认情况下,在插件目录中运行 `wp-env` 即可创建并启动 WordPress 环境,同时自动挂载并激活对应插件。您还可以配置 `wp-env` 以使用现有安装、多插件或主题。完整文档请参阅 [wp-env 工具包](/packages/env/README.md#wp-envjson)。 + +确保 Docker 处于运行状态,在古腾堡目录中执行以下命令启动 `wp-env`: + +```bash +npm run wp-env start +``` + +该脚本将在后台创建基于最新 WordPress Docker 镜像的实例,并将本地古腾堡插件代码通过 Docker 卷映射到环境中。这样您在本地对代码的任何修改都会即时同步到 WordPress 实例中。 + +> 注意:`npm run` 将使用古腾堡项目内指定的 `wp-env` / `WordPress`?? 版本,确保您运行的是最新的 wp-env 版本。 + +停止运行环境: + +```bash +npm run wp-env stop +``` + +若一切正常,终端将显示如下信息: + +```bash +WordPress 开发站点已启动:http://localhost:8888/ +WordPress 测试站点已启动:http://localhost:8889/ +MySQL 正在监听端口 51220 + + ✔ 完成!(耗时 261 秒 898 毫秒) +``` + +通过右键点击菜单栏(Mac)或系统托盘(Linux/Windows)中的 Docker 图标并选择「控制台」,您将看到脚本已下载若干 Docker 镜像,并正在运行包含完整 WordPress 环境的容器: +![运行中的 WordPress Docker 容器截图](https://cldup.com/mt9cKES-YZ.png) +![WordPress 开发环境所需的 Docker 镜像截图](https://cldup.com/bNpgaRSkcG.png) + +彻底删除安装环境: + +```bash +npm run wp-env destroy +``` + +更多命令请查阅[工具包文档](/packages/env/README.md)。 + +#### 访问本地 WordPress 安装 + +WordPress 安装现已可通过 `http://localhost:8888` 访问 + +管理后台地址:`http://localhost:8888/wp-admin/`,登录凭据为:**用户名**:`admin`,**密码**:`password`。您会注意到古腾堡插件已安装并激活,这正是您的本地构建版本。 + +#### 访问 MySQL 数据库 + +古腾堡项目默认集成 phpMyAdmin。您可通过以下地址访问 MySQL 数据库:`http://localhost:9000/`。 + +若需通过其他工具访问数据库,请先获取连接信息: + +1. 在终端中进入本地古腾堡代码库目录 +2. 运行 `npm run wp-env start` - 终端将输出 `wp-env` 环境的相关信息 +3. 在输出信息中查找 _MySQL_ 端口号: + 例如: + +> MySQL 正在监听端口 {MYSQL端口号} + +4. 复制/记录该端口号(注意此端口号会在每次 `wp-env` 重启时变更) +5. 使用以下信息连接 MySQL 实例(请将 `{MYSQL端口号}` 替换为第三步获取的端口号): + +``` +主机:127.0.0.1 +用户名:root +密码:password +数据库:wordpress +端口:{MYSQL端口号} +``` + +**请注意**:MySQL 端口号会在每次 `wp-env` 重启时变更。若发现无法访问数据库,请重复上述步骤获取新端口号以恢复连接。 + +**技巧**:[Sequel Ace](https://sequel-ace.com/) 是访问 MySQL 数据库的实用图形化工具。其他工具及其使用方式可参阅 [WordPress 数据库访问指南](https://developer.wordpress.org/advanced-administration/before-install/creating-database/)。 + +#### 故障排除 + +若遇到问题,请查阅 [`wp-env` 文档中的故障排除章节](/packages/env/README.md#troubleshooting-common-problems)。 + +### 使用 Local 或 MAMP + +作为 Docker 和 `wp-env` 的替代方案,您也可以使用 [Local](https://localwp.com/)、[WampServer](https://wampserver.aviatechno.net/) 或 [MAMP](https://www.mamp.info/) 来运行本地 WordPress 环境。为此,请通过创建符号链接或将目录复制到相应的 `wp-content/plugins` 目录中,将 Gutenberg 作为常规插件克隆并安装到您的环境中。 + +您还需要进行一些额外配置才能运行端到端测试。 + +将当前目录切换到插件文件夹,并为所有端到端测试插件创建符号链接: + +```bash +ln -s gutenberg/packages/e2e-tests/plugins/* . +``` + +如果添加了新插件,您需要再次运行此命令。运行端到端测试的命令如下: + +```bash +WP_BASE_URL=http://localhost:8888/gutenberg/ npm run test:e2e +``` + +#### PHP 文件缓存 + +您需要禁用 OPCache 才能正确编辑 PHP 文件。修复方法如下: + +- 转到 **MAMP > 首选项 > PHP** +- 在 **缓存** 下选择 **关闭** +- 点击 **确定** 确认 + +#### 传入连接 + +默认情况下,MAMP 启动的 Web 服务器(Apache)会监听所有传入连接,而不仅仅是本地连接。这意味着同一本地网络上的任何人(在某些情况下,甚至是互联网上的任何人)都可以访问您的 Web 服务器。这可能是故意的,对于在其他设备上测试站点很有用,但大多数情况下这可能会引发隐私或安全问题。请记住这一点,不要在此服务器上存储敏感信息。 + +虽然可以修复此问题,但您需要自行承担风险,因为它会破坏 MAMP 解析 Web 服务器配置的能力,从而导致 MAMP 认为 Apache 正在监听错误的端口。建议考虑改用其他工具替代 MAMP。否则,您可以按照以下步骤操作: + +- 编辑 `/Applications/MAMP/conf/apache/httpd.conf` +- 将 `Listen 8888` 改为 `Listen 127.0.0.1:8888` + +#### 链接到其他目录 + +您可能希望在 `plugins` 和 `themes` 目录中创建指向其他文件夹的链接,例如: + +- wp-content/plugins/gutenberg -> ~/projects/gutenberg +- wp-content/themes/twentytwenty -> ~/projects/twentytwenty + +如果是这样,您需要配置 Apache 以允许跟随此类链接: + +- 打开或新建文件 `/Applications/MAMP/htdocs/.htaccess` +- 添加以下行:`Options +SymLinksIfOwnerMatch` + +#### 使用 WP-CLI + +像 MAMP 这样的工具倾向于将 MySQL 配置为使用非默认端口 3306,通常偏好使用 8889。这可能会影响 WP-CLI,使其在尝试连接数据库后失败。要解决此问题,请编辑 `wp-config.php` 并将 `DB_HOST` 常量从 `define( 'DB_HOST', 'localhost' )` 改为 `define( 'DB_HOST', '127.0.0.1:8889' )`。 + +### 在远程服务器上 + +您可以通过在本地构建然后将构建的文件作为插件上传到远程服务器,在开发中使用远程服务器。 + +构建步骤:打开终端(如果在 Windows 上,则打开命令提示符)并导航到您克隆的代码库。输入 `npm ci` 以设置所有依赖项。完成后,输入 `npm run build`。 + +构建完成后,克隆的 Gutenberg 目录包含完整的插件,您可以将整个代码库上传到 `wp-content/plugins` 目录,并在 WordPress 管理后台激活插件。 + +另一种构建后上传的方法是运行 `npm run build:plugin-zip` 来创建插件压缩文件——这需要 `bash` 和 `php` 环境。该脚本会创建 `gutenberg.zip`,您可以通过 WordPress 管理后台安装 Gutenberg。 + +## Storybook + +> Storybook 是一个开源工具,用于独立开发 React、React Native 等的 UI 组件。它使构建出色的 UI 变得有条理且高效。 + +Gutenberg 代码库还集成了 [Storybook](https://storybook.js.org/),允许在与 WordPress 无关的环境中进行测试和开发。这对于开发可复用组件和尝试通用 JavaScript 模块非常有帮助,无需任何后端依赖。 + +您可以通过本地运行 `npm run storybook:dev` 启动 Storybook。它会自动在浏览器中打开。 + +您还可以在 GitHub Pages 上测试当前 `trunk` 分支的 Storybook:[https://wordpress.github.io/gutenberg/](https://wordpress.github.io/gutenberg/) \ No newline at end of file diff --git a/contributors/code/git-workflow.md b/contributors/code/git-workflow.md new file mode 100644 index 0000000..a93b3e7 --- /dev/null +++ b/contributors/code/git-workflow.md @@ -0,0 +1,155 @@ +## 分支命名规范 + +命名分支时应使用前缀+简短描述的形式,例如:`[类型]/[变更内容]` + +推荐使用的前缀: + +- `add/` = 新增功能 +- `try/` = 实验性功能(暂定添加) +- `update/` = 更新现有功能 +- `remove/` = 移除现有功能 +- `fix/` = 修复现存问题 + +例如:`add/gallery-block` 表示您正在开发新增画廊区块功能 + +## 保持分支同步 + +当多人同时参与项目开发时,拉取请求很容易过时。"过时"的拉取请求是指与主干开发进度脱节的分支,在合并前需要重新同步。 + +同步方式有两种:合并与变基。在Gutenberg项目中推荐使用变基操作。变基会将您的修改重写为基于开发主线的增量提交,从而确保提交历史的整洁与线性。在处理拉取请求期间,您可以随时执行变基操作。**请尽早开启拉取请求**并持续保持提交历史的变基状态。 + +开发主线即 `trunk` 分支。当拉取请求分支因冲突无法合并至主干时(长期存续的拉取请求常出现此情况),您需要在变基过程中手动解决本地副本的冲突。具体操作请参阅《如何对拉取请求执行变基》中的[「执行变基」章节](https://github.com/edx/edx-platform/wiki/How-to-Rebase-a-Pull-Request#perform-a-rebase)。 + +本地解决冲突后,可通过 `git push --force-with-lease` 更新拉取请求。使用 `--force-with-lease` 参数至关重要,它能有效避免意外覆盖他人代码。 + +同步流程可总结为:获取仓库更新 → 基于trunk执行变基 → 推送至远程仓库。具体命令如下: + +```sh +git fetch +git rebase trunk +git push --force-with-lease origin 您的分支名称 +``` + +## 维护派生仓库同步 + +参与拉取请求需先派生Gutenberg仓库作为独立工作副本。随着新代码不断并入主仓库,您的派生仓库容易失去同步。这里您的工作仓库称为 `fork`,主Gutenberg仓库称为 `upstream`。在创建新分支 (`git checkout -b my-new-branch`) 进行功能开发前,请务必先更新派生仓库。 + +需配置 upstream 远程仓库以保持同步: + +```sh +git remote add upstream https://github.com/WordPress/gutenberg.git +git remote -v +origin git@github.com:您的账户/gutenberg.git (fetch) +origin git@github.com:您的账户/gutenberg.git (push) +upstream https://github.com/WordPress/gutenberg.git (fetch) +upstream https://github.com/WordPress/gutenberg.git (push) +``` + +同步派生仓库需先获取上游更新并合并至本地: + +```sh +git fetch upstream +git checkout trunk +git merge upstream/trunk +``` + +本地更新完成后,推送至GitHub完成派生仓库更新: + +```sh +git push +``` + +上述命令将更新您的 `trunk` 分支与上游同步。如需更新其他分支,将 `trunk` 替换为对应分支名即可。 + +## 扩展应用 + +### 提交追溯 + +当需要定位引入特定修改的提交时,忽略仅含样式或格式修改的提交会提升效率。 + +新版 `git` 支持在历史记录中跳过指定提交: + +```sh +git blame --ignore-rev f63053cace3c02e284f00918e1854284c85b9132 -L 66,73 packages/api-fetch/src/middlewares/media-upload.js +``` + +Gutenberg仓库通过 `.git-blame-ignore-revs` 文件记录所有样式与格式修订。使用该文件可一次性忽略所有相关提交: + +```sh +git blame --ignore-revs-file .git-blame-ignore-revs -L 66,73 packages/api-fetch/src/middlewares/media-upload.js +``` + +# Git 工作流程 + +本文档旨在帮助您开始使用 Git 与 Gutenberg 进行协作。Git 是一款强大的源代码管理工具;若要深入学习 Git,请查阅基于 CC BY-NC-SA 3.0 许可证免费在线提供的 [Pro Git 书籍](https://git-scm.com/book/zh/v2)。 + +如果您不熟悉 Git 的使用,值得探索和实践。请尝试 [Git 教程](https://git-scm.com/docs/gittutorial) 以及 [Git 用户手册](https://git-scm.com/docs/user-manual) 来帮助入门。 + +Gutenberg 项目遵循标准的拉取请求流程进行贡献。有关拉取请求的更多详细信息,请参阅 GitHub 的[相关文档](https://docs.github.com/zh/github/collaborating-with-issues-and-pull-requests)。 + +## 概述 + +贡献者的流程概述如下: + +- 复刻(Fork)Gutenberg 代码库。 +- 克隆复刻后的代码库。 +- 创建新分支。 +- 进行代码更改。 +- 确认测试通过。 +- 在新创建的分支中提交代码更改。 +- 将分支推送到复刻的代码库。 +- 向 Gutenberg 代码库提交拉取请求。 + +有关 Gutenberg 项目如何使用 GitHub 的更多信息,请参阅[代码库管理文档](/docs/contributors/repository-management.md)。 + +## Git 工作流程详解 + +代码和文档的工作流程是相同的,因为两者都在 GitHub 中进行管理。您可以观看[贡献文档的视频详解](https://wordpress.tv/2020/09/02/marcus-kazmierczak-contribute-developer-documentation-to-gutenberg/)以及相应的[贡献给 Gutenberg 的教程](https://mkaz.blog/wordpress/contribute-developer-documentation-to-gutenberg/)。 + +以下是 Git 工作流程的视觉概览: + +![Git 工作流程视觉概览](https://developer.wordpress.org/files/2020/09/git-workflow.png) + +**步骤 1**:前往 GitHub 上的 Gutenberg 代码库,点击 Fork。这将在您的账户下创建主 Gutenberg 代码库的副本。 + +![GitHub 上复刻按钮的截图](https://developer.wordpress.org/files/2020/09/gutenberg-fork.png) + +**步骤 2**:在本地克隆您复刻的代码库。其地址为:`https://github.com/您的用户名/gutenberg`。克隆操作会将所有文件复制到您的计算机上。打开终端并运行: + +```bash +git clone https://github.com/您的用户名/gutenberg +``` + +这将创建一个名为 `gutenberg` 的目录,其中包含项目的所有文件。由于需要下载 Gutenberg 项目的完整历史记录,此过程可能需要几分钟。 + +**步骤 3**:为您的更改创建一个分支(分支命名规则见下文)。在此示例中,分支名称为完整字符串:`update/my-branch` + +```bash +git switch -c update/my-branch +``` + +**步骤 4**:进行代码更改。全面构建、确认并测试您的更改。请参阅[编码指南](/docs/contributors/code/coding-guidelines.md)和[测试概述](/docs/contributors/code/testing-overview.md)以获取指导。 + +**步骤 5**:使用[良好的提交信息](https://make.wordpress.org/core/handbook/best-practices/commit-messages/)提交您的更改。这会将您的更改提交到本地的代码库副本中。 + +```bash +git commit -m "您的良好提交信息" 路径/到/文件 +``` + +**步骤 6**:将您的更改推送到 GitHub。更改将被推送到您在 GitHub 上复刻的代码库中。 + +```bash +git push -u origin update/my-branch +``` + +**步骤 7**:前往您在 GitHub 上复刻的代码库——它将自动检测到更改,并为您提供创建拉取请求的链接。 + +![显示拉取请求链接的截图](https://developer.wordpress.org/files/2020/09/pull-request-create.png) + +**步骤 8**:创建拉取请求。这将在 WordPress Gutenberg 代码库上创建请求,以集成来自您复刻代码库的更改。 + +**步骤 9**:关注拉取请求上的新动态。如果要求进行任何额外的更改或更新,请在本地进行更改并按照步骤 4-6 推送它们。 + +请勿为更新创建新的拉取请求;通过将更改推送到您的代码库,它将更新同一个 PR。从这个意义上说,PR 是 WordPress Gutenberg 代码库指向您副本的指针。因此,当您更新副本时,PR 也会相应更新。 + +就是这样!一旦获得批准并合并,您的更改将被纳入主代码库。🎉 \ No newline at end of file diff --git a/contributors/code/how-to-get-your-pull-request-reviewed.md b/contributors/code/how-to-get-your-pull-request-reviewed.md new file mode 100644 index 0000000..f9c36ae --- /dev/null +++ b/contributors/code/how-to-get-your-pull-request-reviewed.md @@ -0,0 +1,83 @@ +# 如何让你的拉取请求获得审阅? + +有时我们发布了拉取请求,却无人[审阅](/docs/contributors/repository-management.md#code-review)我们的工作。该怎么办? + +吸引审阅主要不在于代码本身——而在于让审阅过程变得轻松。 + +如果你发布的拉取请求未获得任何评论或审阅,可以尝试核心贡献者们使用的策略: + +## 创建最合理的精简PR + +审批一个2000行的PR需要数月时间且令人望而生畏。 + +审批一个50行的PR仅需数日或数小时且轻松自如。 + +大规模提交会拖慢进度。将工作拆分成小模块进行提交,才能更快合并代码、加速学习进程。 + +## 提供相关背景信息: + +请阐明: +* 你要解决什么问题? +* 你的PR如何解决该问题? +* 你需要什么反馈? +* 哪些内容不在讨论范围? +* 哪些设计不符合直觉? +* 如何进行测试? + +总结所有相关议题和PR。 + +这比让他人自行摸索要简单得多。 + +## 让你的PR引人注目 + +所有贡献都在争夺注意力。让你的作品脱颖而出。 + +最简单的方法?说明其重要性: + +❌ 一个获取数据的新React钩子 +✅ `useEntityRecord`:用减少90%模板代码的方式获取数据 + +然后用代码示例、可视化效果和屏幕录像证明其价值。 + +## 展示你的工作 + +在相关议题和PR中发布你的PR链接。 + +提醒相关议题的评论者、先前提交者和技术负责人关注。 + +在WordPress.org Slack的#core-editor频道中提出。获取反馈最便捷的方式是在每周[核心编辑器会议](/docs/getting-started/README.md)的[自由发言环节](https://make.wordpress.org/core/tag/core-editor-agenda/)主动发声。 + +分配相关标签、里程碑和项目(或请他人协助分配)。 + +## 审阅他人的工作 + +这是进入他人视野的最简单途径。 + +查阅相关议题评论者、先前提交者和技术负责人的PR,然后进行审阅。 + +对他们的工作不熟悉?可以: + +* 花时间理解内容 +* 提议结对编程环节 +* 跳过此项,审阅下一个PR + +## 通过清晰表述降低风险 + +风险会增加阻力——当前的批准可能在日后产生反效果。 + +清晰的阐述如同润滑剂。请明确记录: + +* 涉及哪些风险?为何要承担这些风险? +* 为何此PR是最佳解决方案? +* 如何将风险最小化? +* 已尝试过哪些其他方案? + +## 关注热点领域 + +某些PR天然比其他PR更容易获得关注。 + +请重点投入这些领域。 + +部分议题比其他议题更具时效性(例如列入下一版发布目标的议题),因此能获得更多关注。专注于这些议题将更容易吸引审阅者。 + +如何快速切入?参与WordPress路线图中的活跃项目提供协助 \ No newline at end of file diff --git a/contributors/code/managing-packages.md b/contributors/code/managing-packages.md new file mode 100644 index 0000000..911d409 --- /dev/null +++ b/contributors/code/managing-packages.md @@ -0,0 +1,7 @@ +# 包管理 + +本代码库采用 [npm 工作区](https://docs.npmjs.com/cli/v10/using-npm/workspaces)来管理 WordPress 软件包,并使用 [lerna](https://lerna.js.org/) 将这些软件包发布至 [npm](https://www.npmjs.com/)。这一机制在工作流程中设定了特定步骤,具体说明详见[软件包](https://github.com/WordPress/gutenberg/blob/HEAD/packages/README.md)文档。 + +维护数十个 npm 软件包颇具挑战——追踪变更内容尤为困难。因此我们为每个软件包配置 `CHANGELOG.md` 文件来简化发布流程。作为贡献者,当您提交涉及生产环境的代码时,请按照[维护更新日志](https://github.com/WordPress/gutenberg/blob/HEAD/packages/README.md#maintaining-changelogs)章节的说明,在前述文件中添加对应条目。 + +通过与双周发布的 Gutenberg 插件 RC1 版本保持同步,实现了 WordPress 软件包发布至 npm 的自动化流程。您可以在[Gutenberg 发布流程文档](/docs/contributors/code/release.md#packages-releases-to-npm-and-wordpress-core-updates)中了解此过程及其他发布 npm 软件包新版本的方式。 \ No newline at end of file diff --git a/contributors/code/react-native/README.md b/contributors/code/react-native/README.md new file mode 100644 index 0000000..d4c3033 --- /dev/null +++ b/contributors/code/react-native/README.md @@ -0,0 +1,35 @@ +# React Native 移动编辑器 + +Gutenberg 代码库包含了基于 [React Native](https://reactnative.dev/) 的移动端编辑器源码。 + +## 移动端注意事项 + +贡献者需确保在代码重构期间更新所有受影响的本地移动文件,因为我们目前还无法依赖自动化工具完成这一工作。例如,重命名函数或属性时也需在原生模块中同步修改,否则移动客户端将出现故障。我们已在 PR 中设置了移动端专项 CI 测试作为防护机制,但仍有诸多待完善之处。感谢您的理解与支持。❤️🙇‍ + +## 移动端专属文件 + +与移动端共享的代码大多位于相同的 JavaScript 模块和 SASS 样式文件中。当代码路径需要区分时,会创建 `.native.js` 或 `.native.scss` 格式的文件变体。某些情况下还可找到针对 Android (`.android.js`) 或 iOS (`.ios.js`) 的平台专属文件。 + +## 在 Android 和 iOS 上运行 Gutenberg Mobile + +如需了解如何在 Android 或 iOS 上运行 **Gutenberg Mobile 演示应用**,请参阅 [React Native 移动版 Gutenberg 入门指南](/docs/contributors/code/react-native/getting-started-react-native.md) + +此外,移动客户端通过[官方 WordPress 应用](https://wordpress.org/mobile/)进行打包和发布。虽然构建流程与移动演示应用略有不同,且目前存放在独立代码库中([此处为移动端原生代码库](https://github.com/wordpress-mobile/gutenberg-mobile)),但其源代码直接取自本代码库及“网页”端代码路径。 + +## 持续集成中的移动端端到端测试 + +若在拉取请求中遇到 Android/iOS 测试失败,建议采取以下步骤: + +1. 重新运行失败的 GitHub Action 任务([重新运行指南](https://docs.github.com/en/actions/configuring-and-managing-workflows/managing-a-workflow-run#viewing-your-workflow-history))—— 多数情况下可解决测试失败问题 +2. 按照[端到端测试文档](/packages/react-native-editor/__device-tests__/README.md)中的步骤在本地运行测试,验证是否会出现相同故障 +3. 除了查看端到端测试日志外,还可从 GitHub 任务的 Artifacts 区域下载视频记录以获取更多有效信息 +4. 检查 PR 中的变更是否需要对 `.native.js` 格式的文件进行相应修改 +5. 若最终仍无法解决移动测试失败问题,欢迎通过 Slack 在 #mobile 或 #core-editor 频道联系贡献者([免费加入](https://make.wordpress.org/chat/)) + +## 调试移动端单元测试 + +需要时可按照[移动端原生测试指南](/docs/contributors/code/react-native/integration-test-guide.md)中的说明在本地调试移动端单元测试。 + +## 国际化 (i18n) + +关于此主题的更多信息请参阅 [React Native 国际化指南](/docs/contributors/code/react-native/internationalization-guide.md)。 \ No newline at end of file diff --git a/contributors/code/react-native/getting-started-react-native.md b/contributors/code/react-native/getting-started-react-native.md new file mode 100644 index 0000000..bfaa6e0 --- /dev/null +++ b/contributors/code/react-native/getting-started-react-native.md @@ -0,0 +1,148 @@ +# React Native 版移动端 Gutenberg 入门指南 + +欢迎阅读!本文档是针对 Android 和 iOS 设备的区块编辑器原生移动端移植版的入门指南。总体而言,这是一个可在全新项目或现有项目中使用的 React Native 库。请继续阅读了解如何构建、测试和运行。 + +## 环境准备 + +为了获得与项目维护者相近的开发体验,请确保安装以下工具: + +- git +- [nvm](https://github.com/nvm-sh/nvm) +- Node.js 和 npm(使用 nvm 安装) +- [Android Studio](https://developer.android.com/studio/)(用于编译 Android 版应用) +- [Xcode](https://developer.apple.com/xcode/)(用于编译 iOS 应用) +- CocoaPods(通过 `sudo gem install cocoapods` 安装)用于获取 React 及第三方依赖 + +请注意,维护者使用的操作系统平台是 macOS,但相关工具和设置也应适用于其他平台。 + +## 克隆项目 + +```sh +git clone https://github.com/WordPress/gutenberg.git +``` + +## 环境设置 + +请注意,此处描述的命令应在克隆项目的顶层目录中运行。在运行演示应用之前,需要下载并安装项目依赖。通过以下命令完成: + +```sh +nvm install +npm ci +npm run native preios +``` + +## 运行项目 + +```sh +npm run native start:reset +``` + +该命令将在开发模式下运行打包程序(Metro)。打包程序会持续运行,向请求的客户端提供应用包。 + +在打包程序运行的同时,打开另一个终端窗口,使用以下命令编译并运行 Android 应用: + +```sh +npm run native android +``` + +此时应用应在连接的设备或运行的模拟器中打开,并从正在运行的打包程序获取 JavaScript 代码。 + +要使用默认模拟器设备编译并运行 iOS 版本的应用,请使用: + +```sh +npm run native ios +``` + +如果您使用的是 Mac 并已安装相关环境,该命令将尝试在 iOS 模拟器中打开您的应用。 + +### 在其他 iOS 设备模拟器上运行 + +要使用其他设备模拟器编译并运行应用,请使用以下命令。注意使用双 `--` 将模拟器选项传递给 `react-native` CLI: + +```sh +npm run native ios -- -- --simulator="设备名称" +``` + +例如,如果要在 iPhone Xs Max 上运行,请尝试: + +```sh +npm run native ios -- -- --simulator="iPhone Xs Max" +``` + +要查看所有可用 iOS 设备列表,请使用 `xcrun simctl list devices`。 + +### 自定义演示编辑器 + +默认情况下,演示编辑器会渲染大多数受支持的核心区块。这有助于展示编辑器的功能,但在专注于特定区块或功能时可能会分散注意力。可以通过在 `packages/react-native-editor/src/setup-local.js` 文件中使用 `native.block_editor_props` 钩子来自定义编辑器的初始状态。 + +
setup-local.js 示例 + +```js +/** + * WordPress 依赖 + */ +import { addFilter } from '@wordpress/hooks'; + +export default () => { + addFilter( + 'native.block_editor_props', + 'core/react-native-editor', + ( props ) => { + return { + ...props, + initialHtml, + }; + } + ); +}; + +const initialHtml = ` + +

仅一个标题

+ +`; +``` + +
+ +### 故障排除 + +如果 Android 模拟器无法正常启动,或编译失败并显示 `Could not initialize class org.codehaus.groovy.runtime.InvokerHelper` 或类似错误,建议根据 [React Native 文档](https://reactnative.dev/docs/environment-setup)中的最新要求仔细检查开发环境设置。例如,使用 Android Studio 时,需要配置 `ANDROID_HOME` 环境变量并确保 JDK 版本符合最新要求。 + +有时,特别是在调整 `package.json`、Babel 配置(`.babelrc`)或 Jest 配置(`jest.config.js`)中的任何内容时,您的更改可能似乎未按预期生效。此时,可能需要停止 Metro 打包进程并使用 `npm run native start:reset` 重新启动。其他时候,您可能需要重新安装 NPM 包,这时 `npm run native clean:install` 脚本会很有用。 + +## 使用 Visual Studio Code 进行开发 + +虽然不要求必须使用 Visual Studio Code 来开发 gutenberg-mobile,但它是推荐的 IDE,我们为其提供了一些配置。 + +首次在 Visual Studio 中打开项目时,系统会提示安装一些推荐的扩展。这将有助于类型检查和调试等工作。 + +我们使用的扩展之一是 [React Native Tools](https://marketplace.visualstudio.com/items?itemName=vsmobile.vscode-react-native)。它允许您从 VSCode 运行打包程序,或在 iOS 或 Android 上启动应用程序。它还添加了一些调试配置,以便您可以直接在 VSCode 中设置断点和调试应用程序。有关更多详细信息,请参阅[扩展文档](https://marketplace.visualstudio.com/items?itemName=vsmobile.vscode-react-native)。 + +## 单元测试 + +使用以下命令运行测试套件: + +```sh +npm run test:native +``` + +该命令将在您的测试上运行 [jest](https://github.com/facebook/jest) 测试运行器。测试在桌面上针对 Node.js 运行。 + +要在调试器支持下运行测试,请使用以下 CLI 命令启动: + +```sh +npm run test:native:debug +``` + +然后,在 Chrome 中打开 `chrome://inspect` 以附加调试器(查看“远程目标”部分)。在测试/开发过程中,可以随意在代码中的任何位置添加 `debugger` 语句,以便在调试器中中断。 + +## 编写和运行单元测试 + +本项目配置使用 [jest](https://jestjs.io/) 进行测试。您可以配置任何喜欢的测试策略,但 jest 可以开箱即用。在名为 `__tests__` 的目录中创建测试文件,或使用 `.test.js` 扩展名,以便 jest 加载这些文件。请参阅[此处的示例测试](https://github.com/WordPress/gutenberg/blob/HEAD/packages/react-native-editor/src/test/api-fetch-setup.test.js)。[jest 文档](https://jestjs.io/docs/getting-started)和 [React Native 测试教程](https://jestjs.io/docs/tutorial-react-native)也是极好的资源。 + +## 端到端测试 + +除了单元测试之外,Mobile Gutenberg(MG)项目还依赖端到端(E2E)测试,在类似于最终用户的环境中自动化测试关键流程。我们通常更倾向于单元测试,因为它们速度快且易于维护。但是,对于需要操作系统级功能(例如复杂手势、文本选择)或视觉回归测试(例如深色模式、对比度级别)的断言,我们使用 E2E 测试。 + +E2E 测试位于 [`packages/react-native-editor/__device-tests__`](/packages/react-native-editor/__device-tests__) 目录中。有关运行和贡献这些测试的其他文档,请参阅[测试目录](/packages/react-native-editor/__device-tests__#readme)。 \ No newline at end of file diff --git a/contributors/code/react-native/integration-test-guide.md b/contributors/code/react-native/integration-test-guide.md new file mode 100644 index 0000000..40ba9d0 --- /dev/null +++ b/contributors/code/react-native/integration-test-guide.md @@ -0,0 +1,362 @@ +### `waitFor` 超时设置 + +`waitFor` 函数的默认超时时间设定为 1000 毫秒。目前该值足以满足我们测试的所有渲染逻辑,但若在测试过程中发现某个元素需要更长的渲染时间,则应适当增加该设定值。 + +### 替换现有 UI 单元测试 + +部分组件已具备覆盖组件渲染的单元测试。虽然并非强制要求,但在这些情况下,建议评估将其迁移为集成测试的可能性。 + +若需同时保留两种测试类型,我们将在集成测试文件名中加入"integration"字样以避免命名冲突,具体示例可参考:[packages/block-library/src/missing/test/edit-integration.native.js](https://github.com/WordPress/gutenberg/blob/9201906891a68ca305daf7f8b6cd006e2b26291e/packages/block-library/src/missing/test/edit-integration.native.js)。 + +### 平台选择配置 + +默认情况下,Jest 中的所有测试均在 Android 平台环境下运行。如需测试特定平台的相关行为,则需要支持多平台测试文件。 + +若仅需测试由 Platform 对象控制的逻辑,可通过以下代码模拟模块(本例将平台切换为 iOS): + +```js +jest.mock( 'Platform', () => { + const Platform = jest.requireActual( 'Platform' ); + Platform.OS = 'ios'; + Platform.select = jest.fn().mockImplementation( ( select ) => { + const value = select[ Platform.OS ]; + return ! value ? select.default : value; + } ); + return Platform; +} ); +``` + +### 打开区块设置 + +点击"打开设置"按钮即可访问区块设置,以下是一个示例: + +```js +fireEvent.press( block ); + +const settingsButton = await findByLabelText( '打开设置' ); +fireEvent.press( settingsButton ); +``` + +#### 使用作用域组件方法 + +当采用作用域组件方法时,我们需要先渲染 `SlotFillProvider` 和 `BottomSheetSettings`(注意我们通过传递 `isVisible` 属性强制显示底部面板)以及区块: + +```js + + + + +``` + +参考示例: + +- [封面区块](https://github.com/WordPress/gutenberg/blob/b403b977b029911f46247012fa2dcbc42a5aa3cf/packages/block-library/src/cover/test/edit.native.js#L37-L42) + +### FlatList 项 + +`FlatList` 组件根据滚动位置、视图和内容尺寸来渲染其项。这意味着在渲染此组件时,某些项可能因尚未渲染而无法被查询到。为解决此问题,我们需要显式触发事件使 `FlatList` 渲染所有项。 + +以下是插入器菜单中用于渲染区块列表的 FlatList 示例: + +```js +const blockList = getByTestId( '插入器界面-区块' ); +// 通过 onScroll 事件强制 FlatList 渲染所有项 +fireEvent.scroll( blockList, { + nativeEvent: { + contentOffset: { y: 0, x: 0 }, + contentSize: { width: 100, height: 100 }, + layoutMeasurement: { width: 100, height: 100 }, + }, +} ); +``` + +### 滑块控件 + +在底部面板中的滑块应使用其 `testID` 进行查询: + +```js +const radiusSlider = await findByTestId( '滑块 边框圆角' ); +fireEvent( radiusSlider, 'valueChange', '30' ); +``` + +注意滑块的 `testID` 是"滑块 " + 标签文本。因此对于标签为"边框圆角"的滑块,其 `testID` 即为"滑块 边框圆角"。 + +### 选择内部区块 + +添加区块时需注意:如果区块包含内部区块,这些内部区块默认不会渲染。以下示例展示如何让按钮区块渲染其内部的按钮区块(假设已获得按钮区块的引用 `buttonsBlock`): + +```js +const innerBlockListWrapper = await within( buttonsBlock ).findByTestId( + '区块列表包装器' +); +fireEvent( innerBlockListWrapper, 'layout', { + nativeEvent: { + layout: { + width: 100, + }, + }, +} ); + +const buttonInnerBlock = await within( buttonsBlock ).findByLabelText( + /按钮区块\. 第1行/ +); +fireEvent.press( buttonInnerBlock ); +``` + +## 工具 + +### 使用无障碍检查器 + +如果难以定位元素的标识符,建议使用 Xcode 的无障碍检查器。大多数标识符是跨平台的,因此即使测试默认在 Android 上运行,仍可通过无障碍检查器查找正确的标识符。 + +Xcode无障碍检查器应用截图。截图展示了如何在设备下拉菜单中选择正确目标、启用目标模式,以及点击屏幕元素后定位无障碍标签的方法 + +## 常见陷阱与注意事项 + +### 省略 `waitFor` 前的 `await` 会导致误判 + +在 `waitFor` 前省略 `await` 可能导致测试通过但未验证预期行为的情况。例如,若使用 `toBeDefined` 来断言 `waitFor` 的调用结果,由于 `waitFor` 始终会返回值,断言将通过——即使该值并非我们想要检查的 `ReactTestInstance`。因此建议使用自定义匹配器 `toBeVisible`,可有效防范此类误判情况。 + +### 使用 `find` 类查询 + +组件渲染或事件触发后,可能因状态更新产生副作用,导致目标元素尚未渲染。此时需要等待元素可用,为此可使用查询函数的 `find*` 版本,这些函数内部采用 `waitFor` 机制周期性检测元素是否出现。 + +示例如下: + +```js +const mediaLibraryButton = await findByText( 'WordPress媒体库' ); +``` + +```js +const missingBlock = await findByLabelText( /不支持的块\. 第1行/ ); +``` + +```js +const radiusSlider = await findByTestId( '滑块圆角半径' ); +``` + +多数情况下我们会使用 `find*` 函数,但需注意应仅限于真正需要等待元素出现的查询场景。 + +### `within` 查询 + +通过 `within` 函数可查询其他元素内部包含的元素,示例如下: + +```js +const missingBlock = await findByLabelText( /不支持的块\. 第1行/ ); +const translatedTableTitle = within( missingBlock ).getByText( '表格' ); +``` + +## 触发事件 + +除了查询元素,触发事件模拟用户交互同样重要。为此可使用 `fireEvent` 函数([文档](https://callstack.github.io/react-native-testing-library/docs/api#fireevent))。 + +点击事件示例: + +**点击事件:** + +```js +fireEvent.press( settingsButton ); +``` + +我们也可以触发任意类型事件(包括自定义事件)。以下示例展示如何触发滑块组件的 `onValueChange` 事件([代码参考](https://github.com/WordPress/gutenberg/blob/520cbd9d2af4bbc275d388edf92a6cadb685de56/packages/components/src/mobile/bottom-sheet/range-cell.native.js#L227)): + +**自定义事件 – onValueChange:** + +```js +fireEvent( heightSlider, 'valueChange', '50' ); +``` + +## 验证元素行为 + +完成元素查询和事件触发后,需验证逻辑是否符合预期。可使用与单元测试相同的 Jest `expect` 函数,推荐使用自定义匹配器 `toBeVisible` 来确保元素已定义、属于有效React元素且可见。 + +示例如下: + +```js +const translatedTableTitle = within( missingBlock ).getByText( '表格' ); +expect( translatedTableTitle ).toBeVisible(); +``` + +当渲染完整编辑器时,还可验证HTML输出是否符合预期: + +```js +expect( getEditorHtml() ).toBe( + '\n\n' +); +``` + +## 清理操作 + +最后需要清理可能影响后续测试的修改。以下是注册区块后的典型清理示例(需取消注册所有区块): + +```js +afterAll( () => { + // 清理已注册区块 + getBlockTypes().forEach( ( block ) => { + unregisterBlockType( block.name ); + } ); +} ); +``` + +## 辅助工具 + +为简化原生版本集成测试的编写,可在[此README文件](https://github.com/WordPress/gutenberg/blob/HEAD/test/native/integration-test-helpers/README.md)中查看辅助函数列表。 + +## 常见流程 + +### 查询区块 + +通过无障碍访问标签查询区块是常见方式,示例如下: + +```js +const spacerBlock = await waitFor( () => + getByLabelText( /间距区块\. 第1行/ ) +); +``` + +关于区块无障碍访问标签的更多信息,可查阅 [`getAccessibleBlockLabel` 函数](https://github.com/WordPress/gutenberg/blob/520cbd9d2af4bbc275d388edf92a6cadb685de56/packages/blocks/src/api/utils.js#L167-L234)的代码实现。 + +### 添加区块 + +以下是插入段落区块的示例: + +```js +// 打开插入器菜单 +fireEvent.press( await findByLabelText( '添加区块' ) ); + +const blockList = getByTestId( 'InserterUI-区块列表' ); +// 通过onScroll事件强制FlatList渲染所有项 +fireEvent.scroll( blockList, { + nativeEvent: { + contentOffset: { y: 0, x: 0 }, + contentSize: { width: 100, height: 100 }, + layoutMeasurement: { width: 100, height: 100 }, + }, +} ); + +// 插入段落区块 +fireEvent.press( await findByText( `段落` ) ); +``` + +# React Native 集成测试指南 + +## 什么是集成测试? + +集成测试被定义为将不同部分作为整体进行测试的一种测试类型。在我们的场景中,需要测试的部件是指为特定区块或编辑器逻辑所需渲染的不同组件。最终它们与单元测试非常相似,因为它们都是使用 Jest 库通过相同命令运行的。主要区别在于,对于集成测试,我们将使用特定库 [`react-native-testing-library`](https://testing-library.com/docs/react-native-testing-library/intro/) 来测试编辑器如何渲染不同组件。 + +## 集成测试的结构 + +测试可以包含以下部分: + +- [设置](#设置) +- [渲染](#渲染) +- [查询元素](#查询元素) +- [触发事件](#触发事件) +- [验证元素行为](#验证元素行为) +- [清理](#清理) + +我们还在后续章节中提供了常见任务示例和技巧: + +- [辅助工具](#辅助工具) +- [常见流程](#常见流程) +- [工具](#工具) +- [常见陷阱与注意事项](#常见陷阱与注意事项) + +## 设置 + +这部分通常通过使用 Jest 回调函数 `beforeAll` 和 `beforeEach` 来完成,目的是准备测试可能需要的所有内容,比如注册区块或模拟部分逻辑。 + +以下是一个预期所有核心区块可用时的常见模式示例: + +```js +beforeAll( () => { + // 注册所有核心区块 + registerCoreBlocks(); +} ); +``` + +## 渲染 + +在引入测试逻辑之前,我们需要先渲染要测试的组件。根据是否使用作用域组件方法或完整编辑器方法,这部分会有所不同。 + +### 使用作用域组件方法 + +以下是渲染封面区块的示例(摘自[此代码](https://github.com/WordPress/gutenberg/blob/86cd187873984f80ddeeec3e82454b486dd1860f/packages/block-library/src/cover/test/edit.native.js#L82-L91)): + +```js +// 此导入指向区块的索引文件 +import { metadata, settings, name } from '../index'; + +... + +const setAttributes = jest.fn(); +const attributes = { + backgroundType: IMAGE_BACKGROUND_TYPE, + focalPoint: { x: '0.25', y: '0.75' }, + hasParallax: false, + overlayColor: { color: '#000000' }, + url: 'mock-url', +}; + +... + +// 在插槽内渲染封面编辑器的简化树结构 +const CoverEdit = ( props ) => ( + + + + +); + +const { getByText, findByText } = render( + +); +``` + +### 使用完整编辑器方法 + +以下是渲染按钮区块的示例(摘自[此代码](https://github.com/WordPress/gutenberg/blob/9201906891a68ca305daf7f8b6cd006e2b26291e/packages/block-library/src/buttons/test/edit.native.js#L32-L39)): + +```js +const initialHtml = ` +
+ +
+`; +const { getByLabelText } = initializeEditor( { + initialHtml, +} ); +``` + +## 查询元素 + +组件渲染完成后,就可以进行元素查询。关于这个主题的一个重要注意事项是:我们应该从用户角度进行测试,这意味着理想情况下应该通过用户可访问的文本或无障碍标签来查询元素。 + +查询时应遵循以下优先级顺序: + +1. `getByText`:通过文本查询是最接近用户视角的操作流程,因为文本是用户识别元素的视觉线索。 +2. `getByLabelText`:在某些情况下,我们需要查询不提供文本的元素,这时可以回退到使用无障碍标签。 +3. `getByTestId`:如果前面的选项都不适用,并且没有任何可依赖的视觉元素,就必须回退到特定的测试ID,这可以通过 `testID` 属性来定义(参见[此示例](https://github.com/WordPress/gutenberg/blob/e5b387b19ffc50555f52ea5f0b415ab846896def/packages/block-editor/src/components/block-types-list/index.native.js#L80))。 + +以下是一些示例: + +```js +const mediaLibraryButton = getByText( 'WordPress媒体库' ); +``` + +```js +const missingBlock = getByLabelText( /不支持的区块\. 第1行/ ); +``` + +```js +const radiusSlider = getByTestId( '圆角滑块' ); +``` + +请注意,这些查询可以传入纯字符串或正则表达式。正则表达式最适合查询部分字符串(例如,任何包含"不支持的区块. 第1行"的无障碍标签元素)。注意特殊字符如 `.` 需要进行转义。 \ No newline at end of file diff --git a/contributors/code/react-native/internationalization-guide.md b/contributors/code/react-native/internationalization-guide.md new file mode 100644 index 0000000..5144556 --- /dev/null +++ b/contributors/code/react-native/internationalization-guide.md @@ -0,0 +1,85 @@ +# React Native 国际化指南 + +编辑器原生版本涉及两种类型的字符串: +1. 在网页和原生平台共同使用的字符串 +2. 仅限原生平台使用的字符串 + +对于第一类字符串,其翻译流程与网页版本遵循相同的[操作指南](https://github.com/WordPress/gutenberg/blob/trunk/docs/how-to-guides/internationalization.md),但第二类字符串需要您自行提供翻译方案。 + +## 提取仅限原生平台使用的字符串 + +要识别这类字符串,您可以使用位于 `packages/react-native-editor/bin/extract-used-strings.js` 的 [`extract-used-strings`](https://github.com/WordPress/gutenberg/blob/trunk/packages/react-native-editor/bin/extract-used-strings.js) 脚本生成 JSON 对象,该对象包含所有被引用的字符串及其使用平台和引用文件信息。格式示例如下: +``` + { + "gutenberg": { + "<字符串>": { + "string": 字符串值, + "stringPlural": 包含复数形式的字符串值,[可选] + "comments": 给译者的注释,[默认值为空字符串] + "reference": 包含引用该字符串的源文件路径的数组, + "platforms": 包含字符串使用平台的数组,可选值为 "android" | "ios" | "web" + }, + ... + }, + "其他域插件": { + ... + }, + ... +} +``` + +该命令还支持传入额外插件参数,适用于编辑器生成的 React Native 包包含其他插件的情况。 + +需要注意的是,该 JSON 对象包含所有已使用的字符串,因此要识别仅限原生平台使用的字符串,您需要自行编写脚本/流程进行提取。这可以通过遍历字符串并过滤掉包含 "web" 平台的字符串来实现。 + +### NPM 命令 + +提取已使用字符串: +```sh +npm run native i18n:extract-used-strings -- "$PWD/used-strings.json" +``` + +***注意:** 需要传入绝对路径,否则会以 `packages/react-native-editor` 作为相对路径的根目录* + +提取包含额外插件的已使用字符串: +```sh +npm run native i18n:extract-used-strings -- "$PWD/used-strings.json" "域插件-1" <插件1源路径> "域插件-2" <插件2源路径> ... +``` + +## 提供自有翻译(针对仅限原生平台使用的字符串) + +获取原生平台使用的字符串列表后,需要对字符串进行翻译。但此过程不在原生版本支持范围内,需要您自行提供翻译方案。 + +通过编辑器初始化时传递的 `translations` 初始属性注入翻译数据: +- [Android 参考](https://github.com/WordPress/gutenberg/blob/72854b4d6b09bd7fb7f996a5c55dd3cc0613ddf8/packages/react-native-bridge/android/react-native-bridge/src/main/java/org/wordpress/mobile/WPAndroidGlue/GutenbergProps.kt#L34) +- [iOS 参考](https://github.com/WordPress/gutenberg/blob/72854b4d6b09bd7fb7f996a5c55dd3cc0613ddf8/packages/react-native-bridge/ios/GutenbergBridgeDataSource.swift#L39-L43) + +由于移动客户端集成方案具有特异性且实现方式多样,本文不赘述如何通过 `translations` 初始属性集成翻译数据。但需确保通过该属性提供翻译,因为编辑器会负责将其与内置翻译进行合并。 + +**注意:** 与编辑器内置字符串相匹配的翻译将被覆盖。 + +## 获取翻译文件(针对跨平台使用的字符串) + +翻译文件本质上是包含每个字符串翻译键值对的 JSON 对象。这些内容从 [translate.wordpress.org](https://translate.wordpress.org/) 获取,该网站存储了 WordPress 及 Gutenberg 等插件的翻译数据。 + +这些文件可缓存至指定文件夹并进行优化。同时会生成作为导入入口点的索引文件。 + +获取的翻译文件包含插件所有可翻译字符串,包括编辑器原生版本未使用的字符串。但可通过已使用字符串 JSON 文件过滤未引用的字符串来减小文件体积。 + +默认情况下,在安装依赖项时,如果缓存不存在,可能会下载未优化的 Gutenberg 翻译文件并存放于 `i18n-cache` 文件夹。 + +编辑器初始化时会导入这些翻译文件中的字符串([参考](https://github.com/WordPress/gutenberg/blob/154918b5770ac07c851169eaa35961c636eac5ba/packages/react-native-editor/src/index.js#L43-L49)),这些字符串将与通过 `translations` 初始属性提供的额外翻译进行合并。 + +### NPM 命令 + +获取未优化翻译: +```sh +npm run native i18n:fetch-translations -- "gutenberg" <输出路径> +``` + +***注意:** 需要传入绝对路径,否则会以 `packages/react-native-editor` 作为相对路径的根目录* + +获取优化翻译: +```sh +npm run native i18n:fetch-translations -- "gutenberg" <输出路径> <已使用字符串文件> +``` \ No newline at end of file diff --git a/contributors/code/react-native/osx-setup-guide.md b/contributors/code/react-native/osx-setup-guide.md new file mode 100644 index 0000000..9683248 --- /dev/null +++ b/contributors/code/react-native/osx-setup-guide.md @@ -0,0 +1,273 @@ +### 运行演示应用 + +启动 Metro 打包工具: + +``` +npm run native start:reset +``` + +在另一个终端中运行以下命令,在 Android 模拟器中启动演示应用(如果模拟器尚未运行,此命令也会自动启动模拟器): + +``` +npm run native android +``` + +稍等片刻后,我们将看到类似以下界面: + +Android模拟器中区块编辑器的截图 + +## 单元测试 + +```sh +npm run test:native +``` + +## 集成测试 + +[Appium](https://appium.io/) 自带诊断工具。通过以下命令运行: + +```sh +npx appium-doctor +``` + +终端中运行的appium-doctor工具截图 + +请解决所有必需的依赖项。 + +### iOS 集成测试 + +若能确保 iOS 本地环境正常运行,iOS 端到端测试将十分简单。首先停止所有正在运行的 Metro 进程(之前通过 `npm run native start:reset` 启动)。 + +然后在终端中输入: + +```sh +npm run native test:e2e:ios:local +``` + +通过指定文件名可运行部分测试用例: + +```sh +npm run native test:e2e:ios:local gutenberg-editor-paragraph.test.js +``` + +若一切顺利,将呈现如下效果: + + + +### Android 集成测试 + +**创建新的虚拟设备**,需与 [packages/react-native-editor/**device-tests**/helpers/caps.js](https://github.com/WordPress/gutenberg/blob/trunk/packages/react-native-editor/__device-tests__/helpers/caps.js#L30) 中指定的设备参数匹配。截至本文撰写时,需使用 Pixel 3 XL 镜像配合 Android 9(API 28)系统。 + +首先启动虚拟设备:点击手机图标进入 AVD 管理器,然后点击绿色启动按钮。 + +启动Android模拟器的操作截图 + +确保没有 Metro 进程正在运行(之前通过 `npm run native start:reset` 启动)。 + +然后在终端中运行: + +```sh +npm run native test:e2e:android:local +``` + +通过指定文件名可运行部分测试用例: + +``` +npm run native test:e2e:android:local gutenberg-editor-paragraph.test.js +``` + +稍等片刻后应显示: + +Android模拟器中区块编辑器集成测试截图 + +# React Native 开发环境配置指南(macOS 版) + +是否对移动端原生编辑器开发感兴趣?本指南将手把手带您完成开发环境配置! + +请注意,本文说明主要针对 macOS 环境。若需配置其他环境,请参考 [React Native 快速入门文档](https://reactnative.dev/docs/environment-setup) 获取相关指引和步骤。 + +## 克隆 Gutenberg 项目 + +```sh +git clone git@github.com:WordPress/gutenberg.git +``` + +### 安装 node 与 npm + +若您同时参与多个 JS 项目,建议使用 node 版本管理器。管理器可让您自由切换不同的 node 和 npm 版本。 + +我们推荐使用 [nvm](https://github.com/nvm-sh/nvm)。 + +安装 nvm 后,在克隆项目的根目录下执行: + +```sh +nvm install 'lts/*' +nvm alias default 'lts/*' # 设置为新终端打开时的默认版本 +nvm use # 切换至项目配置版本 +``` + +随后安装依赖: + +``` +npm ci +``` + +### 已有旧版 Gutenberg 代码? + +若您已持有 Gutenberg 代码,请务必彻底清理 `node_modules` 并重新安装依赖。 +这将有助于避免后续出现错误。 + +```sh +npm run distclean +npm ci +``` + +## iOS 环境配置 + +### CocoaPods 依赖管理 + +需要安装 [CocoaPods](https://guides.cocoapods.org/using/getting-started.html) 来获取 React 及第三方依赖。安装步骤因 Ruby 管理方式而异。 + +#### 系统自带 Ruby + +若使用 MacOS 默认 Ruby,需通过 `sudo` 命令安装 Cocoapods: + +``` +sudo gem install cocoapods +``` + +注意:Mac M1 芯片与 Cocoapods 存在兼容性问题。若遇安装问题,可尝试执行以下命令安装 ffi 包(确保以正确架构安装 pods): + +``` +sudo arch -x86_64 gem install ffi +arch -x86_64 pod install +``` + +#### Ruby 版本管理器 + +若使用 Ruby 版本管理器,可能无需手动安装 Cocoapods 或 `ffi` 包。请参照所选管理器的文档进行操作。 + +若需在 [WordPress iOS 应用](https://github.com/wordpress-mobile/WordPress-iOS) 中运行 Gutenberg(而非仅演示应用),推荐使用 [`rbenv`](https://github.com/rbenv/rbenv) 管理器。 + +### 配置 Xcode + +通过 App Store 安装 [Xcode](https://developer.apple.com/xcode/) 后启动: + +- 接受许可协议 +- 确认 `Xcode > 偏好设置 > 位置 > 命令行工具` 指向当前 Xcode 版本 + +Xcode 命令行工具设置界面截图 + +### 环境诊断工具 + +可通过 [react-native doctor](https://reactnative.dev/blog/2019/11/18/react-native-doctor) 检测开发环境缺失项。在 Gutenberg 项目根目录或 `/packages/react-native-editor` 文件夹内运行: + +```sh +npx @react-native-community/cli doctor +``` + +终端中运行的 react-native-community/cli doctor 工具截图 + +尝试让 `doctor` 工具修复所有“通用”和“iOS”问题(此时“Android”项显示❌无需担心,后续会处理!) + +### 运行演示应用 + +当所有通用和 iOS 问题解决后,尝试运行: + +``` +npm run native start:reset # 启动 metro 打包器 +``` + +另开终端窗口执行: + +``` +npm run native ios +``` + +等待构建完成后,演示应用将在 iOS 模拟器中运行: + +iOS 模拟器中的区块编辑器运行截图 + +## Android 环境配置 + +### Java开发工具包(JDK) + +[React Native文档](https://reactnative.dev/docs/environment-setup)推荐的JDK名为Azul Zulu。可通过[Homebrew](https://brew.sh/)安装。安装Homebrew后,在终端执行以下命令: + +``` +brew tap homebrew/cask-versions +brew install --cask zulu11 +``` + +若系统已安装JDK,需确保版本为JDK 11或更高。 + +### 配置Android Studio + +编译Android应用需先[下载Android Studio](https://developer.android.com/studio)。 + +打开现有项目,选择已克隆的Gutenberg文件夹。 + +点击下图高亮的立方体图标进入SDK管理器,也可通过`工具 > SDK管理器`进入: + +Android Studio中包管理器按钮位置示意图 + +在此界面可下载SDK平台、软件包及其他工具。需勾选“显示包详细信息”查看特定版本,因为构建过程对E2E测试和开发环境有特定版本要求: + +Android Studio包管理器界面,突出显示包详细信息复选框 + +根据[build.gradle](https://github.com/WordPress/gutenberg/blob/trunk/packages/react-native-editor/android/build.gradle)勾选所有相关软件包,点击“应用”开始下载。node_modules中的build.gradle文件可能包含其他依赖项。 + +若不想逐行查阅文件,系统会在堆栈跟踪中提示缺失包,但此方法需多次尝试。 + +build.gradle配置文件截图 + +包管理器显示SDK平台界面 + +包管理器显示SDK工具界面 + +### 更新路径配置 + +导出以下环境变量并更新$PATH。若终端使用zsh可添加到`~/.zshrc`文件,若使用bash则添加到`~/.bash_profile`: + +```sh +### Android Studio自带的Java: +export JAVA_HOME=/Applications/Android\ Studio.app/Contents/jre/Contents/Home +### Android Home可在Android Studio中配置:偏好设置 > 系统设置 > Android SDK +export ANDROID_HOME=$HOME/Library/Android/sdk +export PATH=$PATH:$ANDROID_HOME/emulator +export PATH=$PATH:$ANDROID_HOME/tools +export PATH=$PATH:$ANDROID_HOME/tools/bin +export PATH=$PATH:$ANDROID_HOME/platform-tools +``` + +保存后执行source命令或重启终端使配置生效: + +```sh +source ~/.zshrc +``` + +或 + +```sh +source ~/.bash_profile +``` + +若找不到SDK路径,可通过Android Studio > 偏好设置 > 系统设置 > Android SDK验证位置: + +Android Studio中SDK路径定位示意图 + +### 创建设备镜像 + +点击右下角带Android标识的手机图标创建虚拟设备镜像: + +Android设备管理器按钮位置示意图 + +进入“Android虚拟设备管理器(AVD)”,点击“创建虚拟设备”,选择手机类型: + +虚拟设备配置界面截图 + +选择目标SDK版本(对应[build.gradle](https://github.com/WordPress/gutenberg/blob/trunk/packages/react-native-editor/android/build.gradle)中的targetSdkVersion设置): + +设备管理器中选择系统镜像界面 + +可根据需要调整高级设置(可选),最后点击完成。 \ No newline at end of file diff --git a/contributors/code/release/README.md b/contributors/code/release/README.md new file mode 100644 index 0000000..2d5eef3 --- /dev/null +++ b/contributors/code/release/README.md @@ -0,0 +1,23 @@ +# Gutenberg 发布流程 + +GitHub 上的 [Gutenberg 代码库](https://github.com/WordPress/gutenberg) 用于执行多种类型的发布。本页概述了不同的发布流程,并为您提供每种类型对应的文档指引。 + +## 前置条件 + +在开始任何发布流程之前,需满足以下要求才能成功发布稳定版的 Gutenberg 插件: + +- 成为 [Gutenberg 开发团队](https://developer.wordpress.org/block-editor/contributors/repository-management/#teams) 的成员。这将使您具备启动与发布流程相关的 GitHub 操作以及将拉取请求(PR)回溯到发布分支的权限。 +- 拥有 [Make WordPress Core](https://make.wordpress.org/core) 博客的写权限。这将允许您起草发布文稿。 +- 获得 Gutenberg 核心团队成员的批准,以便将新版本的 Gutenberg 上传至 WordPress.org 插件目录。 + +## 插件发布 + +插件发布涉及创建新版本的 Gutenberg 插件并将其发布到 WordPress.org 插件目录。此流程包括创建候选版本、测试以及最终发布。 + +有关如何执行插件发布的详细说明,请参阅 [Gutenberg 插件发布](https://developer.wordpress.org/block-editor/contributors/code/release/plugin-release/)。 + +## 软件包发布 + +软件包发布涉及将更新后的 WordPress 软件包版本发布到 npm。此流程包括与插件发布、WordPress 核心更新以及独立的错误修复发布同步。 + +有关软件包发布和 WordPress 核心更新的完整说明,请参阅 [发布软件包至 NPM 及 WordPress 核心更新](https://developer.wordpress.org/block-editor/contributors/code/release/package-release-and-core-updates/)。 \ No newline at end of file diff --git a/contributors/code/release/package-release-and-core-updates.md b/contributors/code/release/package-release-and-core-updates.md new file mode 100644 index 0000000..afe34fb --- /dev/null +++ b/contributors/code/release/package-release-and-core-updates.md @@ -0,0 +1,156 @@ +- 系统会多次要求您输入一次性密码(OTP),即您使用的双因素认证应用生成的验证码。根据待发布软件包的数量,您可能需要输入多个OTTP,因为这些代码往往在所有软件包发布前就会失效。 +- 若发布流程意外中断(可能因超时或输错OTP导致),可通过执行 [`npx lerna publish from-package`](https://lerna.js.org/docs/features/version-and-publish#from-package) 命令恢复操作。 + +6. 最后,当 npm 软件包发布完成后,请将 Lerna 生成的提交记录("发布"和更新日志更新)遴选合并到 Gutenberg 的 `trunk` 分支。 + +## 开发版发布 + +如[同步 Gutenberg 插件](#synchronizing-the-gutenberg-plugin)章节所述,软件包每两周会从 `wp/latest` 分支发布一次。此外,开发者可随时通过开发版来测试 `trunk` 分支即将推出的变更。我们利用[软件包分发标签](https://docs.npmjs.com/cli/v7/commands/npm-dist-tag)功能,使得根据 npm 指南使用未来版本代码成为可能: + +> 默认情况下,npm 使用 `latest` 标签标识软件包当前版本,执行 `npm install `(不附带任何 `@` 或 `@` 限定符)时会安装 `latest` 标签对应的版本。通常项目仅将 `latest` 标签用于稳定版本,其他标签则用于预发布版等不稳定版本。 + +在本项目中,我们使用 `next` 分发标签标识开发版代码。开发者如需安装该版本的软件包,需输入: + +```bash +npm install @wordpress/components@next +``` + +若要启动 npm 软件包开发版的发布流程,请前往 Gutenberg 的 GitHub 仓库的 Actions 标签页,找到["发布 npm 软件包"操作](https://github.com/WordPress/gutenberg/actions/workflows/publish-npm-packages.yml)。注意标有“此工作流由 `workflow_dispatch` 事件触发”的蓝色横幅,展开其右侧的“运行工作流”下拉菜单。 + +![npm 发布工作流运行下拉菜单](https://developer.wordpress.org/files/2023/07/image-4.png) + +如需将开发版软件包发布至 npm,请从“发布类型”下拉菜单选择 `development`,并保持“WordPress 主版本号”输入框为空。最后点击绿色“运行工作流”按钮。这将触发 npm 发布任务,该任务需要经过 Gutenberg 核心团队成员的批准。请找到当前发布任务对应的["发布 npm 软件包"操作](https://github.com/WordPress/gutenberg/actions/workflows/publish-npm-packages.yml),并完成[审批流程](https://docs.github.com/en/actions/how-tos/managing-workflow-runs-and-deployments/managing-deployments/reviewing-deployments#approving-or-rejecting-a-job)。 + +幕后流程中,发布操作完全通过 `./bin/plugin/cli.js npm-next` 命令实现自动化。该命令会确保 `wp/next` 分支与为 Gutenberg 插件创建的最新发布分支(`release/X.Y`)保持同步。为避免软件包版本冲突,我们始终会加入最新提交的 SHA 值,例如:`@wordpress/block-editor@5.2.10-next.645224df70.0`。 + +[插件代码库]: https://plugins.trac.wordpress.org/browser/gutenberg/ +[软件包发布流程]: https://github.com/WordPress/gutenberg/blob/HEAD/packages/README.md#releasing-packages + +# 发布到 NPM 的软件包与 WordPress 核心更新 + +Gutenberg 代码库遵循 [WordPress SVN 代码库](https://make.wordpress.org/core/handbook/about/release-cycle/)的分支策略,适用于每个主要的 WordPress 版本发布。除此之外,它还包含另外两个特殊的控制 npm 发布流程的分支: + +- `wp/latest` 分支包含与通过 `latest` 分发标签发布到 npm 的相同版本的软件包。此分支的目标是与最新的 Gutenberg 插件版本保持同步,唯一的例外是计划外的 [Bugfix 发布](#独立错误修复软件包发布)。 +- `wp/next` 分支包含与通过 `next` 分发标签发布到 npm 的相同版本的软件包。它始终与 `trunk` 分支保持同步。项目应仅将这些软件包用于开发或测试目的。 +- 针对特定 WordPress 主要版本(包括其后续的次要版本更新)的 Gutenberg 分支 `wp/X.Y`(例如 `wp/6.2`)会在计划包含在下一个主要 WordPress 版本中的最后一次发布后不久,基于当前的 Gutenberg 插件发布分支 `release/X.Y`(例如 `release/15.1`)创建。 + +发布类型及其时间表: + +- [同步 Gutenberg 插件](#同步-gutenberg-插件)(`latest` 分发标签)—— 基于新创建的 `release/X.Y`(例如 `release/12.8`)分支,每两周自动发布一次,该分支包含 Gutenberg 插件的 RC1 版本。 +- [WordPress 发布](#wordpress-发布)(`wp-X.Y` 分发标签,例如 `wp-6.2`)—— 根据需求从 `wp/X.Y`(例如 `wp/6.2`)分支触发发布。一旦达到 WordPress 主要发布周期中的某个时间点(在 Beta 1 之前不久),我们仅从 Gutenberg 代码库中挑选提交到 WordPress 核心,此时我们使用 `wp/X.Y` 分支(从 `release/X.Y` 分支创建,例如 `release/15.1`)通过 `wp-X.Y` 分发标签进行 npm 发布。也可以使用旧分支将错误或安全修复反向移植到相应旧版本的 WordPress 核心。 +- [开发版本发布](#开发版本发布)(`next` 分发标签)—— 也可以在需要测试即将到来的更改时随时执行开发版本发布。 + +还可以选择随时执行[独立错误修复软件包发布](#独立错误修复软件包发布)。这应仅用于在常规周期之外必须发布到 _npm_ 的关键错误修复或安全发布。 + +## 同步 Gutenberg 插件 + +对于每个 Gutenberg 插件发布,我们还会将更新后的 WordPress 软件包版本发布到 npm。这是通过处理 Gutenberg 插件发布的[发布工具](https://github.com/WordPress/gutenberg/blob/trunk/.github/workflows/build-plugin-zip.yml)自动完成的。成功的 RC1 发布会触发 npm 发布任务,这需要由 Gutenberg 核心团队成员批准。找到新版本的 ["构建 Gutenberg 插件 Zip" 工作流](https://github.com/WordPress/gutenberg/actions/workflows/build-plugin-zip.yml),并[批准](https://docs.github.com/en/actions/how-tos/managing-workflow-runs-and-deployments/managing-deployments/reviewing-deployments#approving-or-rejecting-a-job)它。 + +我们特意在 Gutenberg RC1 发布时,使用 Gutenberg 发布分支 `release/X.Y`(例如 `release/12.7`)的内容更新 Gutenberg 代码库中的 `wp/latest` 分支。这样做是为了确保 `wp/latest` 分支尽可能接近最新版本的 Gutenberg 插件。同时,这实际上也消除了在将提交反向移植到 `trunk` 时,因发布过程中应用到 `package.json` 和 `CHANGELOG.md` 文件的更新而产生冲突的可能性。过去,当我们在常规 Gutenberg 发布一周后进行 npm 发布时,在这方面遇到了许多问题。在将新的软件包版本发布到 npm 时,我们至少选择 `minor` 版本升级,以便为未来的错误修复或安全发布留出空间。 + +在幕后,所有步骤都通过 `./bin/plugin/cli.js npm-latest` 命令自动完成。作为记录,手动过程将非常接近以下步骤: + +1. 确保 WordPress `trunk` 分支对增强功能开放。 +2. 使用 `git fetch` 获取最后发布的 Gutenberg 发布分支。 +3. 检出 `wp/latest` 分支。 +4. 从当前分支中删除所有文件:`git rm -r .`。 +5. 从发布分支检出所有文件:`git checkout release/x.x -- .`。 +6. 使用 `git commit -m "Merge changes published in the Gutenberg plugin vX.X release"` 将所有更改提交到 `wp/latest` 分支,并推送到代码库。 +7. 使用计算出的新发布版本更新软件包的 `CHANGELOG.md` 文件,并提交到 `wp/latest` 分支。假设软件包版本使用 `major.minor.patch` 格式编写,请确保至少应用 `minor` 版本升级。例如,如果要发布的软件包的 CHANGELOG 指示下一个未发布版本是 `5.6.1`,则在 `minor` 版本的情况下选择 `5.7.0` 作为版本。这一点很重要,因为如果需要为次要的 WordPress 发布进行错误修复,应保留补丁版本号(见下文)。 +8. 通过控制台登录 npm:`npm login`。请注意,您应启用双因素认证(2FA)。 +9. 从 `wp/latest` 分支,使用 `npm ci` 安装 npm 依赖项。 +10. 运行脚本 `npx lerna publish --no-private`。 + - 当被要求选择每个软件包的版本号时,请选择更新的 CHANGELOG 文件中的值。 + - 您将被多次要求输入一次性密码(OTP)。这是您使用的 2FA 验证器应用程序中的代码。根据要发布的软件包数量,您可能需要输入多次 OTP,因为它们往往在所有软件包发布之前就过期了。 + - 如果发布过程未完成(可能是因为超时或输入了错误的 OTP),您可以通过 [`npx lerna publish from-package`](https://lerna.js.org/docs/features/version-and-publish#from-package) 恢复它。 +11. 最后,既然 npm 软件包已发布,将 lerna 创建的提交("Publish" 和 CHANGELOG 更新)挑选到 Gutenberg 的 `trunk` 分支中。 + +## WordPress 版本发布 + +当需要将错误修复或安全补丁移植到 WordPress 核心时,需遵循以下工作流程。适用场景包括: + +- 在 WordPress 发布周期的 `beta` 和 `RC` 阶段,当发布分支 `wp/X.Y`(例如 `wp/5.7`)已存在时。 +- 针对 WordPress 小版本和安全版本发布(例如 `5.1.1`)。 + +1. 检出对应的 WordPress 主版本分支(若小版本为 `5.2.1`,则检出 `wp/5.2`)。 +2. 基于该分支创建功能分支,并将所需错误修复的合并提交拣选到该分支。可使用 [`npm run other:cherry-pick`](/docs/contributors/code/auto-cherry-picking.md) 脚本自动完成拣选操作。 +3. 基于此分支创建拉取请求,目标分支选择前述 WordPress 主版本分支。 +4. 点击 "Rebase and Merge" 按钮合并拉取请求以保留提交历史。 + +此时 `wp/X.Y` 分支已准备就绪可发布 npm 软件包。请前往 Gutenberg 的 GitHub 仓库 Actions 标签页,找到 ["发布 npm 软件包" 操作](https://github.com/WordPress/gutenberg/actions/workflows/publish-npm-packages.yml)。注意蓝色横幅提示 "此工作流程具有 `workflow_dispatch` 事件触发器",展开右侧的 "Run workflow" 下拉菜单。 + +![npm 发布工作流运行下拉菜单](https://developer.wordpress.org/files/2023/07/image-2.png) + +要为 WordPress 主版本发布 npm 软件包,请选择 `trunk` 作为运行工作流的分支(这意味着运行工作流所用的脚本来自 trunk 分支,但软件包本身将通过下文选择正确的"发布类型"后从发布分支发布),然后在"发布类型"下拉菜单中选择 `wp`,并在"WordPress 主版本"输入框中填入 `X.Y`(例如 `5.2`)。最后点击绿色 "Run workflow" 按钮。这将触发 npm 发布任务,需经 Gutenberg 核心团队成员批准。请找到本次发布的 ["发布 npm 软件包" 操作](https://github.com/WordPress/gutenberg/actions/workflows/publish-npm-packages.yml),并完成[审批流程](https://docs.github.com/en/actions/how-tos/managing-workflow-runs-and-deployments/managing-deployments/reviewing-deployments#approving-or-rejecting-a-job)。 + +备案记录:手动操作流程如下: + +1. 检出之前使用的 WordPress 分支(例如 `wp/5.2`)。 +2. 执行 `git pull`。 +3. 运行 `npx lerna publish patch --no-private --dist-tag wp-5.2` 命令(详见[软件包发布流程]),但在选择各软件包版本号时(假设版本号格式为 `主版本.次版本.修订号`),请确保仅提升 `修订号`。例如,若该 WordPress 分支最后发布的软件包版本为 `5.6.0`,则选择 `5.6.1` 作为新版本。 + +**注意:** 对于 WordPress `5.0` 和 `5.1` 版本,曾采用不同的发布流程。这意味着针对这两个版本选择 npm 软件包版本时,可能无法使用下一个 `修订号` 版本(因其可能已被占用)。此时应使用"元数据"修饰符。例如,若该 WordPress 分支最后发布的软件包版本为 `5.6.1`,则选择 `5.6.1+patch.1` 作为版本号。 + +3. (可选)更新已发布软件包的 `CHANGELOG.md` 文件,添加新发布版本信息,并提交至对应分支(例如 `wp/5.2`)。 +4. 将 CHANGELOG 更新提交(如有)拣选至 Gutenberg 的 `trunk` 分支。 + +至此 npm 软件包应已准备就绪,可以创建补丁并提交至对应的 WordPress SVN 分支。 + +## 独立错误修复包发布 + +当软件包需要在常规发布周期外向 _npm_ 发布错误修复或安全更新时,需遵循以下工作流程。 + +注意:`trunk` 和 `wp/latest` 分支均为受限分支,仅 Gutenberg 核心团队具有 _推送_ 权限。 + +识别需要从代码库 `trunk` 分支移植到 `wp/latest` 分支的拉取请求对应的提交哈希值。 + +接下来需要准备 `wp/latest` 分支以发布软件包至 _npm_。 + +打开终端并执行以下步骤: + +1. `git checkout trunk` +2. `git pull` +3. `git checkout wp/latest` +4. `git pull` + +在移植提交前,请检查 `wp/latest` 分支是否存在待发布的软件包: + +1. `git checkout wp/latest` +2. `npx lerna updated` + +现在将提交从 `trunk` 分支 _拣选_ 到 `wp/latest` 分支,若提交为拉取请求的合并提交,请使用 `-m 1 commithash`: + +1. `git cherry-pick -m 1 cb150a2` +2. `git push` + +在等待 GitHub Actions 构建 [通过 `wp/latest` 分支检查](https://github.com/WordPress/gutenberg/actions?query=branch%3Awp%2Ftrunk) 期间,识别并开始更新 `CHANGELOG.md` 文件: + +1. `git checkout wp/latest` +2. `npx lerna updated` + 示例: + ```shell + npx lerna updated + @wordpress/e2e-tests + @wordpress/jest-preset-default + @wordpress/scripts + lerna success found 3 packages ready to publish + ``` + +检查当前 `CHANGELOG.md` 文件中列出的版本,浏览软件包提交历史(例如 [@wordpress/scripts](https://github.com/WordPress/gutenberg/commits/HEAD/packages/scripts)),注意查找 _"chore(release): publish"_ 和 _"Update changelogs"_ 提交以确定最近的版本更新,然后查看自最近发布以来的提交记录,有助于了解自上次发布以来的变更内容。 + +注意:您可能会发现某些软件包的当前版本不是最新的,如遇此情况,建议更新先前发布的版本。 + +此时 `wp/latest` 分支已准备就绪可发布 npm 软件包。请前往 Gutenberg 的 GitHub 仓库 Actions 标签页,找到 ["发布 npm 软件包" 操作](https://github.com/WordPress/gutenberg/actions/workflows/publish-npm-packages.yml)。注意蓝色横幅提示 "此工作流程具有 `workflow_dispatch` 事件触发器",展开右侧的 "Run workflow" 下拉菜单。 + +![npm 发布工作流运行下拉菜单](https://developer.wordpress.org/files/2023/07/image-6.png) + +要发布包含错误修复的 npm 软件包,请在"发布类型"下拉菜单中选择 `bugfix`,并保持"WordPress 主版本"输入框为空。最后点击绿色 "Run workflow" 按钮。这将触发 npm 发布任务,需经 Gutenberg 核心团队成员批准。请找到本次发布的 ["发布 npm 软件包" 操作](https://github.com/WordPress/gutenberg/actions/workflows/publish-npm-packages.yml),并完成[审批流程](https://docs.github.com/en/actions/how-tos/managing-workflow-runs-and-deployments/managing-deployments/reviewing-deployments#approving-or-rejecting-a-job)。 + +在后台,后续流程由 `./bin/plugin/cli.js npm-bugfix` 命令自动完成。备案记录:手动操作流程与以下步骤高度相似: + +1. 检出 `wp/latest` 分支。 +2. 使用计算得出的新发布版本更新各软件包的 `CHANGELOG.md` 文件,并提交至 `wp/latest` 分支。 +3. 通过控制台登录 npm:`npm login`。注意应启用双因素认证。 +4. 在 `wp/latest` 分支上,运行 `npm ci` 安装 npm 依赖项。 +5. 运行脚本 `npx lerna publish --no-private`。 + - 当提示选择各软件包版本号时,请选择已更新的 CHANGELOG 文件中的对应值。 \ No newline at end of file diff --git a/contributors/code/release/plugin-release.md b/contributors/code/release/plugin-release.md new file mode 100644 index 0000000..592b49d --- /dev/null +++ b/contributors/code/release/plugin-release.md @@ -0,0 +1,431 @@ +### 发布 @wordpress 软件包至 NPM + +作为版本发布流程的一部分,所有 @wordpress 软件包都会发布到 NPM。当[构建 Gutenberg 插件压缩包](https://github.com/WordPress/gutenberg/actions/workflows/build-plugin-zip.yml)操作完成草稿版本的创建后,你可能会看到一条提示,要求具备相应权限的人员触发[发布 npm 软件包](https://github.com/WordPress/gutenberg/actions/workflows/publish-npm-packages.yml)操作。 + +这条提示存在误导性。你无需手动执行任何操作来发布 @wordpress 软件包至 NPM。该过程已实现自动化,会在发布说明正式发布后自动运行。 + +### 查看版本草稿 + +工作流完成后,你可以在 [Gutenberg 发布页面](https://github.com/WordPress/gutenberg/releases)找到版本草稿。该草稿会预填充基于此版本先前候选版(RC)的更新日志条目,以及后续精选至发布分支的任何更改。因此,当发布某个系列的首个稳定版本时,请删除所有仅作为参考信息的候选版标题,并将最新更改移至正确章节(详见下文)。 + +### 整理版本更新日志 + +整理更新日志的最佳时机是在候选版工作流首次创建时。此时会触发更新日志自动化流程,生成初始版本的更新日志。虽然该过程主要依赖自动化,但正如前文所述,其效果很大程度上取决于里程碑中 PR 的标签是否准确。 + +稳定版发布流程会提取各候选版的更新日志并整合至稳定版。但需特别注意:稳定版仅会保留首次生成的更新日志内容(即 RC1 发布时的版本),后续对 RC1 更新日志的修改将不会同步至稳定版。 + +这意味着如果你在发布 RC1 前就完成了整个更新日志的整理,那么除了后续 RC2 或 RC3 版本中少量需要追加至稳定版的内容外,稳定版发布时无需再额外整理更新日志。 + +当版本更新日志出现在草稿中后,请仔细审阅并编辑内容,确保其清晰准确。此环节不必急于求成,重点在于保证发布说明的条理性,你可以随时保存草稿后续继续完善。 + +若担心在整理更新日志期间他人无法获取候选版本,可通过 Slack 频道 [#core-editor](https://wordpress.slack.com/messages/C02QB2JS7) 分享发布构件,这样他人即可在你完善更新日志时使用候选版本。 + +以下是一些编写清晰简明更新日志的补充建议: + +- 将`杂项`分类下的所有条目移至更合适的章节 +- 修正拼写错误并优化表述,确保面向插件用户或持续关注开发进展的受众时易于理解 +- 适时创建新分组并调整 PR 归属 +- 当多个 PR 关联同一任务时(如后续修正的 PR),尝试将其合并为单一条目。典型范例包括:为性能优化移除 Lodash 的系列 PR、用 Playwright 替代 Puppeteer 的端到端测试更新、或将公共组件迁移至 TypeScript 的相关工作 +- 若关联 PR 集的子任务内容充实,可考虑采用嵌套列表进行组织 +- 删除同一版本中相互抵消的 PR(即代码净变化为零的还原类 PR) +- 移除所有仅涉及移动端应用的 PR(唯一例外是同时影响网页端功能的移动端 PR) +- 若某子标题下仅列有一条 PR,则移除该子标题并将 PR 移至包含多条目的相邻子标题 + +### 撰写版本发布公告 + +版本管理员需依据[谷歌文档模板](https://docs.google.com/document/d/1D-MTOCmL9eMlP9TDTXqlzuKVOg_ghCPm9_whHFViqMk/edit)起草版本发布公告。鉴于公告内容的特殊性,若事先达成共识,相关工作可拆分委托给团队成员完成。草案完成后需进行同行评审。 + +### 发布版本公告 + +内容准备就绪后,拥有[make.wordpress.org/core](https://make.wordpress.org/core/)发布权限的作者将创建新草稿并导入内容。公告需包含以下标签: + +- [#block-editor](https://make.wordpress.org/core/tag/block-editor/) +- [#core-editor](https://make.wordpress.org/core/tag/core-editor/) +- [#gutenberg](https://make.wordpress.org/core/tag/gutenberg/) +- [#gutenberg-new](https://make.wordpress.org/core/tag/gutenberg-new/) + +作者需开启公告的公开预览功能,并申请最终同行评审。这一流程符合[make/core发布指南](https://make.wordpress.org/core/handbook/best-practices/post-comment-guidelines/#peer-review)的要求。 + +最终发布应在稳定版于WordPress.org上线后进行,此举有助于外部媒体同步传播版本信息。 + +## 招募下个版本的志愿者 + +完成版本发布后,请在#core-editor Slack频道发布信息,招募负责下个Gutenberg版本的志愿者。 + +具体示例可[参考此处](https://wordpress.slack.com/archives/C02QB2JS7/p1751595983193709)。 + +## 创建次要版本 + +有时需要为插件创建次要版本(即X.Y.**Z**),通常用于快速修复严重回归问题或程序缺陷。虽然`Backport to Gutenberg Minor Release`标签常用于标识需纳入次要版本的PR,但作为版本协调员,您也可能通过Slack收到非正式通知。即便如此,仍需确保所有相关PR都正确标注。 + +需要注意:次要版本不会单独创建分支(例如`release/12.5.0`),而是以[标签](https://github.com/WordPress/gutenberg/releases/tag/v12.5.1)形式存在。 + +次要版本的发布流程与主插件版本基本一致(参见上文),但存在重要差异。请务必通读完整指南后再进行操作。 + +### 更新发布分支 + +次要版本应仅包含必要的特定提交。操作时需在本地检出前一个主要稳定版本(非RC版本)分支(如`release/12.5`),然后精选所需提交至该分支。 + +
+若新版本已存在RC版本,您必须将相同提交精选至对应发布分支,因为它们不会自动包含。例如:若您正准备发布12.5的新次要版本,仅向release/12.5分支精选了提交,但12.6.0-rc.1已发布,则需将相同提交精选至release/12.6分支,否则这些提交不会包含在后续12.6版本中!通常建议与下个版本的发布协调员协同处理此流程。 +
+ +精选过程可通过[`npm run cherry-pick`](/docs/contributors/code/auto-cherry-picking.md)脚本自动化执行,但运行脚本时请确保使用`Backport to Gutenberg Minor Release`标签。 + +同时必须确保所有纳入的PR都已分配至该次要版本对应的GitHub里程碑。需注意:PR合并时会自动分配至下一个稳定版本的里程碑,因此您需要在GitHub中逐一手动调整PR的里程碑归属。 + +例如,若您正在发布`12.5.4`版本,所有精选至该版本的PR必须从`12.6`里程碑移除,并重新分配至`12.5`里程碑。 + +完成精选后,可移除PR上的`Backport to Gutenberg Minor Release`标签。 + +当稳定发布分支整理就绪且PR分配正确里程碑后,即可将分支推送至GitHub,并通过GitHub网站图形界面继续发布流程。 + +### 运行小版本发布 + +![插件发布的工作流下拉菜单](https://developer.wordpress.org/files/2023/07/image-1.png) + +前往 Gutenberg 的 GitHub 仓库的 Actions 标签页,找到 [“构建 Gutenberg 插件压缩包” 操作](https://github.com/WordPress/gutenberg/actions/workflows/build-plugin-zip.yml)。现在,你需要**仔细**根据当前插件发布版本的信息选择下一步操作: + +**如果**上一个发布版本是**稳定版**(`X.Y.Z`,例如 `12.5.0`、`12.5.1` 等),请将 `Use workflow from` 字段保留为 `trunk`,并在文本输入框中指定 `stable`。工作流将自动创建一个小版本,根据需要递增 z 值(`x.y.(z+1)`)。 + +**但如果**上一个发布版本是 **RC 版本**(例如 `X.Y.0-rc.1`),你需要在创建发布时**手动**选择**稳定版本的发布分支**(例如 `12.5.0`)。如果不这样做,工作流将发布下一个主要的**稳定**版本(例如 `12.6.0`),而这并不是你想要的。 + +为此,在运行工作流时,从 `Use workflow from` 下拉菜单中选择适当的 `release/` 分支(例如 `release/12.5`),并在文本输入框中指定 `stable`。 + +#### 为之前的稳定版本创建小版本 + +即使已经发布了更新的稳定版本,也可以为任何发布分支创建小版本。这可以用于**任何**之前的发布分支,从而更灵活地向用户提供更新。过去,用户必须等待下一个稳定版本,可能需要几天时间。现在,可以根据需要迅速将修复推送到任何之前的发布分支。 + +该过程与上述已有 RC 版本时的流程相同:选择一个之前的发布分支,输入 `stable`,然后点击 “Run workflow”。发布将在 Gutenberg 的 GitHub 发布页面和 WordPress 核心仓库的 SVN 中以 `tag` 的形式发布到 [https://plugins.svn.wordpress.org/gutenberg/tags/](https://plugins.svn.wordpress.org/gutenberg/tags/)。SVN 的 `trunk` 目录不会被修改。 + +**重要提示:** 在发布由 [“构建插件压缩包” 工作流](https://github.com/WordPress/gutenberg/actions/workflows/build-plugin-zip.yml) 创建的草稿时,请确保**取消勾选** “Set as last release” 复选框。如果不小心勾选了,[“将 Gutenberg 插件上传到 WordPress.org 插件仓库” 工作流](https://github.com/WordPress/gutenberg/actions/workflows/upload-release-to-plugin-repo.yml) 仍会正确将其**作为标签(并且不会替换 `trunk` 版本)** 上传到 WordPress 插件仓库的 SVN——工作流会执行一些版本计算以确定插件的发布方式——但你仍然需要在 GitHub 上通过将正确的发布设置为 `latest` 来修复状态,具体操作在 [发布页面](https://github.com/WordPress/gutenberg/releases/) 上进行! + +### 故障排除 + +> 发布草稿已创建,但内容为空/包含错误消息 + +如果你忘记将正确的里程碑分配给你精选的 PR,那么生成的更新日志可能不符合预期。 + +务必手动验证更新日志中显示的 PR 是否与精选到发布分支的 PR 一致。 + +此外,如果发布仅包含一个 PR,但未将该 PR 分配到正确的里程碑,则在生成更新日志时会显示错误。在这种情况下,你可以编辑发布说明,手动添加缺失的 PR 的详细信息(格式可参考之前的发布)。 + +如果由于某种原因里程碑已关闭,你可以为了发布的目的重新打开它。 + +> 发布草稿仅包含 1 个资源文件,而其他发布包含 3 个。 + +这是正常现象。发布草稿仅包含插件压缩包。只有在发布后,其他资源才会生成并添加到发布中。 + +> 是否需要将点发布版本发布到 WordPress.org? + +是的。方法与主要的插件发布流程相同。你需要 Gutenberg 核心团队或 Gutenberg 发布团队的成员批准发布工作流。 + +> 发布过程未能将版本更新提交精选到 `trunk` 分支。 + +首先,通过检查 `trunk` 上的最新提交是否包含版本更新提交来确认步骤失败。然后在发布分支上回退版本更新提交——使用命令 `git revert --no-edit {commitHash}`。最后,推送更改并重新开始发布过程。 + +# Gutenberg 插件发布指南 + +## 快速参考 + +### 时间安排 + +- 在里程碑日期(通常为周三)发布 RC1 版本 +- 下周三发布正式稳定版 + +### 常规发布流程 + +#### 步骤 1:准备工作 + +- 使用模板创建[发布工单](https://github.com/WordPress/gutenberg/issues/new?template=New_release.md)(可选操作,但有助于逐步执行流程) +- 审核里程碑中的所有 PR 并添加适当标签(`[Type] Bug`、`[Type] Enhancement` 等) +- 测试更新日志:`npm run other:changelog -- --milestone="Gutenberg X.Y"` + +#### 步骤 2:构建发布版本 + +- 在 [#core-editor](https://wordpress.slack.com/messages/C02QB2JS7) Slack 频道发布公告 +- 前往 GitHub Actions → [构建插件压缩包工作流](https://github.com/WordPress/gutenberg/actions/workflows/build-plugin-zip.yml) +- 保持 `Use workflow from` 选项为 `trunk`(默认值) +- 输入 `rc`(候选版本)或 `stable`(正式版本) +- 点击 `Run workflow` +- 当[GitHub Releases](https://github.com/WordPress/gutenberg/releases)中生成发布草稿后,立即发布以继续工作流 +- 仅限稳定版:等待团队批准上传至 WordPress.org——这是工作流的最后一步,用于将插件部署到插件目录([示例](https://github.com/WordPress/gutenberg/actions/runs/18559811968)) + +#### 步骤 3:编辑发布说明 + +- 在[GitHub Releases](https://github.com/WordPress/gutenberg/releases)中找到草稿 +- 清理更新日志:修正拼写错误,调整分类不当的条目,合并相关 PR +- 移除仅限移动端的更改和已回滚的 PR + +#### 步骤 4:撰写发布文章 + +- 使用[谷歌文档模板](https://docs.google.com/document/d/1D-MTOCmL9eMlP9TDTXqlzuKVOg_ghCPm9_whHFViqMk/edit) +- 重点介绍本次发布的 3-5 项关键功能 +- 稳定版发布后,在 [make.wordpress.org/core](https://make.wordpress.org/core/) 上发布文章 + +### 额外候选版本与次要版本 (X.Y.Z) + +针对 RC1 后的紧急修复或主要版本间的关键错误修复: + +#### 遴选错误修复 + +- 新 RC 版本:使用标有 `Backport to Gutenberg RC` 的 PR +- 次要版本:使用标有 `Backport to Gutenberg Minor Release` 的 PR +- 切换到相应发布分支:`git checkout release/X.Y` +- 运行:`npm run other:cherry-pick "[Label Name]"` +- 在运行工作流**之前**,将 PR 重新分配到正确的里程碑(例如从 `12.6` 改为 `12.5`) + +#### 运行发布工作流 + +- 前往[构建插件压缩包工作流](https://github.com/WordPress/gutenberg/actions/workflows/build-plugin-zip.yml) +- 从 `Use workflow from` 下拉菜单中选择发布分支 +- 继续执行上述步骤 2-4 + +--- + +## 详细流程 + +发布 Gutenberg 插件稳定版的第一步是在 Gutenberg 代码库中[创建工单](https://github.com/WordPress/gutenberg/issues/new?template=New_release.md)。该工单模板名为 "Gutenberg Release",包含从候选版本到更新日志整理、遴选代码、稳定版发布及发布文章的完整清单。[Gutenberg 21.2](https://github.com/WordPress/gutenberg/issues/70662) 的工单就是一个很好的范例。 + +该清单可帮助您与参与发布流程的开发人员及其他团队协调,确保所有必要步骤均已完成,且所有人员都清楚时间安排和重要里程碑。 + +## 发布周期 + +Gutenberg 的主要版本大约每两周发布一次。当前及后续版本会在 [GitHub 里程碑](https://github.com/WordPress/gutenberg/milestones) 中追踪,同时标注每个版本的发布日期。 + +**在当前里程碑日期**(也称为标记日期),Gutenberg 的首个候选版本(RC)将发布。这是插件的预发布版本,供插件作者和用户测试。如果发现任何回归问题,可以发布新的 RC 版本。 + +候选版本按增量方式编号,从 `-rc.1` 开始,然后是 `-rc.2`,依此类推。一旦首个 RC(RC1)发布,发布文章的准备工作随即启动。 + +**RC1 发布一周后**,基于最后一个 RC 及必要的回归修复创建稳定版本。稳定版发布后,发布文章将同步上线。 + +如果在插件的稳定版本中发现关键错误,可随时发布修补版本。 + +## 版本管理 + +每个主要的 Gutenberg 版本由一位版本管理员(也称为版本负责人)负责运作。该负责人或小型团队在更广泛的 [Gutenberg 开发团队](https://developer.wordpress.org/block-editor/contributors/repository-management/#teams)支持下,负责 Gutenberg 版本的发布工作。 + +版本管理员负责启动所有发布活动,任何对发布计划的更改都需要他们的批准。在紧急情况下或版本管理员无法参与时,其他团队成员可以采取适当行动,但应随时向版本管理员通报情况。 + +
+如果您是 Gutenberg 开发团队成员,并且有兴趣主导 Gutenberg 版本发布,请在 #core-editor Slack 频道中联系我们。 +
+ +## 准备发布 + +插件发布流程大部分是自动化的,并在 GitHub 上进行。您无需在本地计算机上执行任何步骤。不过,建议在本地保留一份 Gutenberg 副本,用于准备更新日志、进行常规测试,以及应对可能需要多个候选版本的情况。关于这一点,后续会详细说明。 + +这里有一个[11分钟的视频](https://youtu.be/TnSgJd3zpJY),演示了插件发布流程。如果您对该流程不熟悉,建议先观看视频。以下段落也记录了该流程,并提供了更详细的说明。 + +### 组织和标记里程碑 PR + +
+ 快速参考 +
    +
  • 确保所有 PR 都正确标记。
  • +
  • 每个 PR 必须有一个以 [Type] 为前缀的标签。
  • +
+
+ +准备 Gutenberg 版本的第一步是组织所有分配给当前[里程碑](https://github.com/WordPress/gutenberg/milestones)的 PR,并确保每个 PR 都正确标记。[标签](https://github.com/WordPress/gutenberg/labels)用于自动生成更新日志,更改 PR 上的标签比事后在发布部分重新组织现有更新日志要快得多。 + +要测试将在发布工作流中运行的更新日志自动化功能,您可以在本地 Gutenberg 副本中使用以下命令,并指定您正在处理的稳定版本里程碑: + +``` +npm run other:changelog -- --milestone="Gutenberg 16.2" +``` + +该命令的输出是所提供里程碑的更新日志,上述示例中是 Gutenberg 16.2。您可以将输出复制并粘贴到 Markdown 文档中,这样可以更方便地查看,并允许您跟踪每个 PR 的链接。 + +所有 PR 都应有一个以 `[Type]` 为前缀的标签以及子类别标签。两个最常见的标签是 `[Type] Bug` 和 `[Type] Enhancement`。在查看生成的更新日志时,请特别注意以下内容: + +- **增强功能:** 查找没有附加任何子类别的 PR。 +- **错误修复:** 同样查找没有附加任何子类别的 PR。 +- **其他:** 此部分中的 PR 没有任何标签。 + +根据需要更新每个 PR 的标签。您可以继续生成更新日志,直到您满意为止。现在,您可以开始候选版本工作流了。 + +
+您可以在 changelog.js 文件中查看如何根据 PR 标签生成更新日志。 +
+ +### 运行发布工作流 + +
+ 快速参考 + +
+ +在开始之前,请在 [#core-editor](https://wordpress.slack.com/messages/C02QB2JS7) Slack 频道中宣布您即将开始工作流,并说明您是要发布 Gutenberg 的稳定版本还是候选版本。 + +然后转到 Gutenberg 仓库,点击 Actions 标签页,找到 [构建 Gutenberg 插件 Zip](https://github.com/WordPress/gutenberg/actions/workflows/build-plugin-zip.yml) 操作。注意蓝色的横幅,上面写着“此工作流有一个 `workflow_dispatch` 事件触发器。” 展开其右侧的“Run workflow”下拉菜单。 + +![插件发布的运行工作流下拉菜单](https://developer.wordpress.org/files/2023/07/image-3-1.png) + +要发布插件的候选版本,请在文本字段中输入 `rc`。要发布稳定版本,请输入 `stable`。在每种情况下,按下“Run workflow”按钮。 + +这将触发一个 GitHub Actions (GHA) 工作流,该工作流将提升插件版本、构建 Gutenberg 插件 `.zip` 文件、创建发布草稿并附加插件 `.zip` 文件。这部分过程通常需要大约六分钟。工作流将出现在列表顶部,紧挨着蓝色横幅下方。完成后,工作流的状态图标将从黄色圆点变为绿色对勾。您可以通过点击工作流来查看更详细的视图。 + +## 排查发布问题 + +> 插件已成功发布至 WordPress.org 插件目录,但工作流却显示失败。 + +此类情况偶有发生,例如可参考[该案例](https://github.com/WordPress/gutenberg/actions/runs/16325916698/job/46115920213)。 + +请务必确认以下事项: + +- 插件在目录中功能正常 +- ZIP 包内容(参见[下载页面](https://plugins.trac.wordpress.org/browser/gutenberg/))完整无误(无明显文件缺失) +- [Gutenberg SVN 仓库](https://plugins.trac.wordpress.org/browser/gutenberg/)包含两次新提交(查看[提交记录](https://plugins.trac.wordpress.org/browser/gutenberg/)): + - `trunk` 目录应包含 "Committing version X.Y.Z" 提交 + - 新增的 `tags/X.Y.Z` 目录内容与 `trunk` 一致,且最新提交为 "Tagging version X.Y.Z" + +最可能的情况是标签目录未能自动创建。这是[已知问题](https://github.com/WordPress/gutenberg/issues/55295),可[通过手动操作修复](https://github.com/WordPress/gutenberg/issues/55295#issuecomment-1759292978)。 + +请将 `SVN_USERNAME`、`SVN_PASSWORD` 和 `VERSION` 替换为实际值,或先将其设为全局环境变量: + +```sh +# 检出代码库 +svn checkout https://plugins.svn.wordpress.org/gutenberg/trunk --username "$SVN_USERNAME" --password "$SVN_PASSWORD" gutenberg-svn + +# 进入本地目录 +cd gutenberg-svn + +# 若本地已存在代码库 +# 且未执行检出操作,请确保更新至最新版本 +svn up . + +# 将当前主干内容复制至新标签目录 +svn copy https://plugins.svn.wordpress.org/gutenberg/trunk https://plugins.svn.wordpress.org/gutenberg/tags/$VERSION -m 'Tagging version $VERSION' --no-auth-cache --non-interactive --username "$SVN_USERNAME" --password "$SVN_PASSWORD" +``` + +操作过程中如需帮助,请及时向团队求助。 + +## 发布文档撰写 + +发布文档由发布经理主导,并依托[古腾堡开发团队](https://developer.wordpress.org/block-editor/contributors/repository-management/#teams)成员协作完成。该流程包含一系列顺序步骤,因涉及人员众多且需协调配合,需在发布候选版与正式版之间的时间窗口内严格执行。古腾堡正式版于每周三发布,距首个候选版间隔一周。 + +
+ 时间线 +
    +
  1. 复制发布公告谷歌文档模板——周三至周五
  2. +
  3. 选定发布亮点——周五至周一
  4. +
  5. 亮点确定后向设计团队申请素材(图片、视频)——周五至周一
  6. +
  7. 起草发布公告并申请同行评审——周一至周三
  8. +
  9. 正式版发布后公开公告——周三
  10. +
+
+ +### 选定发布亮点 + +整理完更新日志后,下一步是选取若干重要变更作为发布公告的亮点。这些亮点通常聚焦于新功能与体验优化(包括性能与无障碍改进),也可包含重要的 API 变更或关键缺陷修复。 + +鉴于古腾堡涉及面广且每个里程碑周期会合并大量 PR,时有值得重点介绍的变更被疏漏。因此本环节需要发布经理与其他古腾堡开发团队成员协同完成。若不确定如何选择,可随时向团队成员寻求建议。 + +### 发布素材准备 + +发布公告需配置若干视觉素材。公告特色图片建议沿用上一版本使用的图片(媒体库中文件名应为 'gb-featured')。 + +公告正文中的横幅可通过名为「Gutenberg What's New Banner」的同步模式插入。使用该模式后请将版本号更新为正确数值。 + +重点功能还需配套视觉素材。针对核心功能可向设计团队申请专业素材,其他功能若操作熟练也可自行制作。申请设计素材时,请在 Slack 的 [#design](https://wordpress.slack.com/archives/C02S78ZAL) 频道中提出请求,可参考[15.8 版本申请范例](https://wordpress.slack.com/archives/C02S78ZAL/p1684161811926279)。素材将存放于专为该版本创建的[谷歌网盘文件夹](https://drive.google.com/drive/folders/1U8bVbjOc0MekWjpMjNaVFVhHFEzQkYLB)中。 + +制作 WordPress 发布素材时,可使用动画(视频或 GIF)或静态图片展示亮点。参考[往期发布公告](https://make.wordpress.org/core/tag/gutenberg-new/)的呈现方式,注意动画更适合演示操作流程,而直接的功能亮点用静态图片即可。创作过程中请避免使用版权素材,并移除浏览器画布中可见的插件标识。 + +### 创建候选发布补丁(代码拣选) + +
+ 快速参考 +
    +
  • 确保所有需要拣选的PR都标记有Backport to Gutenberg RC标签。
  • +
  • 在本地克隆的Gutenberg仓库中,切换到发布分支:git checkout release/X.Y
  • +
  • 使用自动化脚本拣选所有已合并的PR:npm run other:cherry-pick "Backport to Gutenberg RC"
  • +
+
+ +在RC版本发布后、最终稳定版发布前,一些与发布相关的错误可能会被修复并提交到`trunk`分支。稳定版本不会自动包含这些修复。需要手动将这些修复纳入,这个过程称为代码拣选。 + +作为发布经理,您可能会通过以下几种方式了解到这些错误: + +- 贡献者可能会在已关闭的PR上添加`Backport to Gutenberg RC`标签。[在发布最终版本前,请搜索这些PR](https://github.com/WordPress/gutenberg/pulls?q=is%3Apr+label%3A%22Backport+to+Gutenberg+RC%22+is%3Aclosed)。 +- 您可能会在[#core-editor](https://wordpress.slack.com/messages/C02QB2JS7) Slack频道收到直接消息或通知,告知您需要纳入RC的PR。即使在这种情况下,也应在PR上添加`Backport to Gutenberg RC`标签。 + +#### 自动化代码拣选 + +代码拣选过程可以通过Gutenberg中包含的`npm run other:cherry-pick "[Insert Label]"`脚本自动完成。运行命令时需要使用`Backport to Gutenberg RC`标签,并确保所有需要拣选的PR都已分配该标签。 + +
+要拣选PR,您必须克隆(而不是分叉)Gutenberg仓库并具有写入权限。只有Gutenberg开发团队的成员才有执行此操作的必要权限。
+ +将Gutenberg仓库克隆到本地开发环境后,首先切换到发布分支: + +``` +git checkout release/X.Y +``` + +接下来,使用适当的反向移植标签拣选所有已合并的PR: + +``` +npm run other:cherry-pick "Backport to Gutenberg RC" +``` + +在后台,该脚本将执行以下操作: + +- 拣选所有带有`Backport to Gutenberg RC`标签的PR +- 将它们添加到发布里程碑 +- 将所有更改`git push`到发布分支 +- 在PR上添加评论,表明它已被拣选 +- 从PR中移除`Backport to Gutenberg RC`标签 + +以下是该过程的截图: + +![自动化代码拣选](https://developer.wordpress.org/files/2023/07/image-7.png) + +#### 手动代码拣选 + +如果您需要逐个、逐步处理代码拣选,可以手动按照以下顺序操作。在检出相应的发布分支后: + +1. 使用`git cherry-pick [SHA]`按时间顺序拣选每个PR。 +2. 完成后,使用`git push`将更改推送到GitHub。 +3. 移除所有已拣选PR的`Backport to Gutenberg RC`标签,并将里程碑更新为当前发布版本。 + +要查找拉取请求的`[SHA]`,请打开PR,您将在末尾附近看到“`[用户名]`将提交`[SHA]`合并到`trunk`”的消息。 + +![手动代码拣选](https://developer.wordpress.org/files/2023/07/image-5.png) + +如果拣选的修复值得在稳定版本发布前再发布一个候选版本,请按照上述说明创建一个。在[#core-editor](https://wordpress.slack.com/messages/C02QB2JS7) Slack频道中告知其他贡献者已发布新的候选版本。 + +### 发布版本 + +
+ 快速参考 +
    +
  • 在发布草稿中,点击“发布版本”按钮。
  • +
  • 如果发布稳定版本,需获得Gutenberg发布团队Gutenberg核心团队WordPress核心团队成员的批准,才能将新插件版本上传到WordPress.org插件仓库(SVN)。
  • +
  • 上传后,确认可以从WordPress插件仪表盘下载和更新最新版本。
  • +
+
+ +只有当您对发布草稿中的更新日志内容满意时,才点击“发布版本”按钮。 + +请注意,您无需更改按钮上方的复选框。如果您发布的是RC版本,“设置为预发布版本”将自动被选中;如果您发布的是稳定版本,“设置为最新版本”将被选中。 + +![发布RC版本的复选框](https://developer.wordpress.org/files/2023/07/image.png) + +发布版本将为该版本创建一个`git`标签,发布版本,并触发[另一个GHA工作流](https://github.com/WordPress/gutenberg/actions/workflows/upload-release-to-plugin-repo.yml),其目的有两个: + +1. 使用您刚刚编辑的发布说明更新`changelog.txt`; +2. 将新插件版本上传到WordPress.org插件仓库(SVN)(仅当您发布稳定版本时)。 + +最后一步需要[Gutenberg发布团队](https://github.com/orgs/WordPress/teams/gutenberg-release)、[Gutenberg核心团队](https://github.com/orgs/WordPress/teams/gutenberg-core)或[WordPress核心团队](https://github.com/orgs/WordPress/teams/wordpress-core)成员的批准。这些团队在发布准备就绪时会收到通知邮件,但如果时间紧迫,您可以在`#core-editor` Slack频道中询问或通知[Gutenberg发布团队](https://github.com/orgs/WordPress/teams/gutenberg-release)以加速流程。建议在启动发布流程之前提前联系,以便有人准备好批准。找到新版本的[“将Gutenberg插件上传到WordPress.org插件仓库”工作流](https://github.com/WordPress/gutenberg/actions/workflows/upload-release-to-plugin-repo.yml),并[批准](https://docs.github.com/en/actions/how-tos/managing-workflow-runs-and-deployments/managing-deployments/reviewing-deployments#approving-or-rejecting-a-job)它。 + +一旦获得批准,新版本的Gutenberg将对全球的WordPress用户可用。上传后,请确认可以从WordPress插件仪表盘下载和更新最新版本。 + +最后一步是在[make.wordpress.org/core](https://make.wordpress.org/core/)上撰写发布文章。您可以在下面找到一些相关提示。 \ No newline at end of file diff --git a/contributors/code/scripts.md b/contributors/code/scripts.md new file mode 100644 index 0000000..3fe898c --- /dev/null +++ b/contributors/code/scripts.md @@ -0,0 +1,75 @@ +## Polyfill 脚本 + +编辑器还为某些可能并非所有现代浏览器都支持的功能提供了 polyfill。 + +建议使用主要的 `wp-polyfill` 脚本句柄,它会负责加载以下所有提到的 polyfill。 + +| 脚本名称 | 句柄 | 描述 | +| ------------------------------------------------------------------------- | --------------------------- | -------------------------------------------------------------------------------------------------- | +| [Babel Polyfill](https://babeljs.io/docs/en/babel-polyfill) | wp-polyfill | 模拟完整的 ES2015+ 环境。主脚本,用于加载以下所有附加的 polyfill | +| [Fetch Polyfill](https://www.npmjs.com/package/whatwg-fetch) | wp-polyfill-fetch | 实现标准 Fetch 规范子集的 polyfill | +| [Promise Polyfill](https://www.npmjs.com/package/promise-polyfill) | wp-polyfill-promise | 适用于浏览器和 Node 的轻量级 ES6 Promise polyfill | +| [Formdata Polyfill](https://www.npmjs.com/package/formdata-polyfill) | wp-polyfill-formdata | 有条件地替换原生实现的 polyfill | +| [Node Contains Polyfill](https://www.npmjs.com/package/polyfill-library) | wp-polyfill-node-contains | 用于 Node.contains 的 polyfill | +| [Element Closest Polyfill](https://www.npmjs.com/package/element-closest) | wp-polyfill-element-closest | 返回 DOM 树中与选择器匹配的最接近的元素 | + +## 打包与代码共享 + +当使用如 [webpack](https://webpack.js.org/) 这样的 JavaScript 打包工具时,此处提到的脚本可以从打包文件中排除,并通过 WordPress 以脚本依赖的形式提供,参见 [`wp_enqueue_script`](https://developer.wordpress.org/reference/functions/wp_enqueue_script/#default-scripts-included-and-registered-by-wordpress)。 + +[`@wordpress/dependency-extraction-webpack-plugin`](https://github.com/WordPress/gutenberg/tree/HEAD/packages/dependency-extraction-webpack-plugin) 提供了一个 webpack 插件,用于帮助从打包文件中提取 WordPress 依赖项。`@wordpress/scripts` 的 [`build`](https://github.com/WordPress/gutenberg/tree/HEAD/packages/scripts#build) 脚本默认包含此插件。 + +| [NUX](/packages/nux/README.md) | wp-nux | 包含用于引导新用户熟悉WordPress管理界面的组件及wp.data相关方法 | +| [Plugins](/packages/plugins/README.md) | wp-plugins | WordPress插件管理模块 | +| [Redux Routine](/packages/redux-routine/README.md) | wp-redux-routine | 用于生成器协程的Redux中间件 | +| [Rich Text](/packages/rich-text/README.md) | wp-rich-text | 实现HTML/DOM树与富文本值相互转换的辅助函数 | +| [Shortcode](/packages/shortcode/README.md) | wp-shortcode | WordPress短代码模块 | +| [Token List](/packages/token-list/README.md) | wp-token-list | 可构造的纯JavaScript [DOMTokenList](https://developer.mozilla.org/en-US/docs/Web/API/DOMTokenList)实现,支持非浏览器运行时环境 | +| [URL](/packages/url/README.md) | wp-url | 用于操作URL的实用工具集 | +| [Viewport](/packages/viewport/README.md) | wp-viewport | 用于响应浏览器视口尺寸变化的模块 | +| [Wordcount](/packages/wordcount/README.md) | wp-wordcount | WordPress字数统计工具 | + +## 第三方脚本 + +编辑器还使用了一些流行的第三方程序包和脚本。插件开发者同样可以直接使用这些脚本,无需将其打包到代码中(避免增加文件体积)。 + +| 脚本名称 | 注册句柄 | 功能描述 | +| ---------------------------------------------------- | --------- | ----------------------------------------------------------------------------------------------------- | +| [React](https://reactjs.org) | react | 用于构建用户界面的JavaScript库 | +| [React Dom](https://reactjs.org/docs/react-dom.html) | react-dom | 作为React的DOM和服务器渲染器入口点,需与React配合使用 | +| [Moment](https://momentjs.com/) | moment | 用于解析、验证、操作和显示JavaScript中的日期时间 | +| [Lodash](https://lodash.com) | lodash | 提供通用编程任务工具函数的JavaScript库 | + +# 脚本 + +编辑器为插件开发者提供了多个供应商和内部脚本。下表记录了脚本名称、句柄和描述。 + +## WordPress 脚本 + +编辑器包含多个功能包以实现各种功能。插件开发者可以利用它们来创建区块、编辑器插件或通用插件。 + +| 脚本名称 | 句柄 | 描述 | +| -------------------------------------------------------------------------------------------- | ------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| [Blob](/packages/blob/README.md) | wp-blob | Blob 实用工具 | +| [区块库](/packages/block-library/README.md) | wp-block-library | 编辑器的区块库 | +| [区块](/packages/blocks/README.md) | wp-blocks | 区块创建 | +| [区块序列化默认解析器](/packages/block-serialization-default-parser/README.md) | wp-block-serialization-default-parser | WordPress 文档的默认区块序列化解析器实现 | +| [区块序列化规范解析器](/packages/block-serialization-spec-parser/README.md) | wp-block-serialization-spec-parser | WordPress 文章的语法文件 (grammar.pegjs) | +| [组件](/packages/components/README.md) | wp-components | 用于创建通用 UI 元素的通用组件 | +| [组合](/packages/compose/README.md) | wp-compose | 一系列便捷的高阶组件 (HOCs) | +| [核心数据](/packages/core-data/README.md) | wp-core-data | 简化和操作核心 WordPress 实体的访问 | +| [数据](/packages/data/README.md) | wp-data | 数据模块作为管理插件和 WordPress 本身应用状态的枢纽 | +| [日期](/packages/date/README.md) | wp-date | WordPress 的日期模块 | +| [弃用](/packages/deprecated/README.md) | wp-deprecated | 用于记录消息以通知开发者某个功能已弃用的实用工具 | +| [DOM](/packages/dom/README.md) | wp-dom | WordPress 的 DOM 实用工具模块 | +| [DOM 就绪](/packages/dom-ready/README.md) | wp-dom-ready | DOM 加载完成后执行回调 | +| [编辑器](/packages/editor/README.md) | wp-editor | WordPress 编辑器的构建块 | +| [编辑文章](/packages/edit-post/README.md) | wp-edit-post | WordPress 的编辑文章模块 | +| [元素](/packages/element/README.md) | wp-element | 元素简单来说是基于 [React](https://reactjs.org/) 的抽象层 | +| [HTML 转义](/packages/escape-html/README.md) | wp-escape-html | HTML 转义实用工具 | +| [钩子](/packages/hooks/README.md) | wp-hooks | 一个轻量高效的 JavaScript 事件管理器 | +| [HTML 实体](/packages/html-entities/README.md) | wp-html-entities | WordPress 的 HTML 实体实用工具 | +| [国际化](/packages/i18n/README.md) | wp-i18n | 客户端本地化的国际化实用工具 | +| [浅比较](/packages/is-shallow-equal/README.md) | wp-is-shallow-equal | 用于对两个对象或数组执行浅比较的函数 | +| [键码](/packages/keycodes/README.md) | wp-keycodes | WordPress 的键码实用工具,用于检查 `onKeyDown` 等事件中按下的键 | +| [列出可重用区块](/packages/list-reusable-blocks/README.md) | wp-list-reusable-blocks | 用于在可重用区块列表页面添加导入/导出链接的包 | \ No newline at end of file diff --git a/contributors/code/testing-overview.md b/contributors/code/testing-overview.md new file mode 100644 index 0000000..70ba941 --- /dev/null +++ b/contributors/code/testing-overview.md @@ -0,0 +1,647 @@ +#### 最佳实践 + +若你正着手重构,快照会是个不错的选择。你可以将其作为分支的首个提交,并观察其演变过程。 + +快照本身并不体现我们的预期目标。快照最适合与描述期望的其他测试结合使用,如下例所示: + +```jsx +test( '当启用planets属性时应包含mars', () => { + const { container } = render( ); + + // 快照将捕获意外变更 + expect( container ).toMatchSnapshot(); + + // 这才是我们测试中真正期望发现的内容 + expect( screen.getByText( /mars/i ) ).toBeInTheDocument(); +} ); +``` + +另一项实用技巧是使用 `toMatchDiffSnapshot` 函数(由 [`snapshot-diff` 包](https://github.com/jest-community/snapshot-diff)提供),该函数支持仅对DOM两种状态间的差异生成快照。这种方法适用于测试属性变更对DOM产生的影响,同时生成更精简的快照,示例如下: + +```jsx +test( '当启用isShady属性时应渲染更深背景色', () => { + const { container } = render( 正文 ); + const { container: containerShady } = render( + 正文 + ); + expect( container ).toMatchDiffSnapshot( containerShady ); +} ); +``` + +类似地,`toMatchStyleDiffSnapshot` 函数支持仅记录组件两种状态间关联样式的差异,如下例所示: + +```jsx +test( '应渲染外边距', () => { + const { container: spacer } = render( ); + const { container: spacerWithMargin } = render( ); + expect( spacerWithMargin ).toMatchStyleDiffSnapshot( spacer ); +} ); +``` + +#### 故障排查 + +某些使用refs的组件场景需要进行模拟。查阅以下文档了解更多: + +- 了解为何在React中需使用[模拟Refs进行快照测试](https://reactjs.org/blog/2016/11/16/react-v15.4.0.html#mocking-refs-for-snapshot-testing)。 + +遇到此类情况时,可能会在尝试访问 `ref.current` 属性的代码行看到Jest报出的测试失败及 `TypeError`。 + +### 调试Jest单元测试 + +执行 `npm run test:unit:debug` 将启动调试模式的测试,以便[节点检查器客户端](https://nodejs.org/en/docs/guides/debugging-getting-started/#inspector-clients)能够连接至进程并监控执行情况。在[wp-scripts文档](/packages/scripts/README.md#debugging-jest-unit-tests)中可查看使用Google Chrome或Visual Studio Code作为检查器客户端的指南。 + +## 原生移动端测试 + +单元测试套件包含一组基于React Native开发、用于验证原生移动代码路径的Jest测试。由于这些测试运行在Node环境,可直接在开发机本地运行,无需安装特定的Android或iOS原生开发工具或SDK。这也意味着可以使用常规开发工具进行调试。具体调试指南如下: + +### 调试原生移动端单元测试 + +本地调试模式运行测试需遵循以下步骤: + +0. 确保已执行 `npm ci` 安装所有依赖包 +1. 在CLI中进入Gutenberg根目录,运行 `npm run test:native:debug`。此时Node正在等待调试器连接 +2. 在Chrome浏览器中打开 `chrome://inspect` +3. 在“Remote Target”区域找到 `../../node_modules/.bin/jest` 目标项,点击“inspect”链接。这将打开附接到进程的新Chrome DevTools调试窗口,并暂停在 `jest.js` 文件起始处。若未显示目标项,可点击同一页面中的 `Open dedicated DevTools for Node` 链接 +4. 可在代码(包括测试代码)中设置断点或 `debugger;` 语句来暂停执行并检查 +5. 点击“Play”按钮继续执行 +6. 尽享原生移动端单元测试的调试过程! + +# 测试概述 + +Gutenberg 项目包含 PHP 和 JavaScript 代码,并鼓励对两者进行测试和代码规范检查。 + +## 为何需要测试? + +除了测试将为生活带来的乐趣之外,测试的重要性不仅在于确保应用程序按预期运行,还在于它们提供了如何使用代码的简明示例。 + +测试也是代码库的一部分,这意味着我们对测试代码采用与应用程序代码相同的标准。 + +与所有代码一样,测试也需要维护。为了测试而编写测试并非目标——我们应努力在覆盖预期与非预期行为、执行速度与代码维护之间找到平衡点。 + +编写测试时请考虑以下问题: + +- 我们正在测试什么行为? +- 运行此代码时可能出现哪些错误? +- 测试是否验证了我们想要验证的内容?还是产生了误报/漏报? +- 测试是否具备可读性?其他贡献者能否通过查看对应测试来理解代码行为? + +## JavaScript 测试 + +JavaScript 测试使用 [Jest](https://jestjs.io/) 作为测试运行器,并采用其提供的 [全局 API](https://jestjs.io/docs/en/api.html)(`describe`、`test`、`beforeEach` 等)、[断言方法](https://jestjs.io/docs/en/expect.html)、[模拟函数](https://jestjs.io/docs/en/mock-functions.html)、[监视器](https://jestjs.io/docs/en/jest-object.html#jestspyonobject-methodname) 和 [模拟函数 API](https://jestjs.io/docs/en/mock-function-api.html)。如需测试 React 组件,还可使用 [React Testing Library](https://testing-library.com/docs/react-testing-library/intro)。 + +_需要注意的是,过去我们使用 [Enzyme](https://github.com/airbnb/enzyme) 进行 React 组件单元测试。但现在所有现有及新增测试均已改用 React Testing Library (RTL)。_ + +若已按照 [指南](/docs/contributors/code/getting-started-with-code-contribution.md) 安装 Node 和项目依赖,可通过 NPM 在命令行中运行测试: + +``` +npm test +``` + +代码规范检查是通过静态代码分析来强制执行编码标准并避免潜在错误。本项目使用 [ESLint](https://eslint.org/) 和 [TypeScript 的 JavaScript 类型检查](https://www.typescriptlang.org/docs/handbook/type-checking-javascript-files.html) 来发现此类问题。虽然上述 `npm test` 会同时执行单元测试和代码检查,但也可通过运行 `npm run lint` 单独进行代码规范验证。部分 JavaScript 问题可通过运行 `npm run lint:js:fix` 自动修复。 + +为提升开发效率,建议配置编辑器集成检查功能。详细信息请参阅 [入门文档](/docs/contributors/code/getting-started-with-code-contribution.md)。 + +若需仅运行单元测试而不执行规范检查,请使用 `npm run test:unit`。 + +### 目录结构 + +请将测试文件保存在工作目录的 `test` 文件夹中。测试文件应与被测试文件同名。 + +``` ++-- test +| +-- bar.js ++-- bar.js +``` + +只有测试文件(至少包含一个测试用例)应直接存放在 `/test` 目录下。如需添加外部模拟数据或固定装置,请将其置于子文件夹中,例如: + +- `test/mocks/[文件名].js` +- `test/fixtures/[文件名].js` + +### 测试导入 + +根据上述目录结构,在导入 **被测试代码** 时请尽量使用相对路径,而非项目路径。 + +**推荐做法** + +`import { bar } from '../bar';` + +**不推荐做法** + +`import { bar } from 'components/foo/bar';` + +这将使您在决定将代码迁移至应用目录其他位置时更加轻松。 + +### 测试描述 + +使用 `describe` 代码块对测试用例进行分组。每个测试用例理想情况下应仅描述一种行为。 + +在测试用例中,请尝试用通俗语言描述预期行为。对于 UI 组件,这可能需要从用户角度描述预期行为,而非解释代码内部实现。 + +**推荐写法** + +```javascript +describe( 'CheckboxWithLabel', () => { + test( '勾选复选框应禁用表单提交按钮', () => { + ... + } ); +} ); +``` + +**不推荐写法** + +```javascript +describe( 'CheckboxWithLabel', () => { + test( '勾选复选框应将 this.state.disableButton 设置为 `true`', () => { + ... + } ); +} ); +``` + +### 快照测试 + +本文概述了[快照测试]及其最佳实践方法。 + +#### 快速指南:快照失败处理 + +当快照测试失败时,仅表示组件的渲染结果发生了变化。若这是意外变动,则快照测试成功拦截了一个程序缺陷 😊 + +但若属于预期变更,请按以下步骤更新快照: +```sh +# 使用 --testPathPattern 可加速测试过程(仅运行匹配的测试用例) +npm run test:unit -- --updateSnapshot --testPathPattern 测试路径 + +# 更新端到端测试的快照 +npm run test:e2e -- --update-snapshots 测试规范路径 +``` + +1. 仔细核对差异内容,确认变更符合预期 +2. 提交更新后的快照文件 + +#### 快照本质解析 + +快照是测试生成数据结构的序列化记录。这些快照文件与测试代码共同存储在版本库中。执行测试时,系统会将实时生成的数据结构与存储的快照版本进行比对。 + +创建快照非常简单: +```js +test( 'foobar测试示例', () => { + const foobar = { foo: 'bar' }; + + expect( foobar ).toMatchSnapshot(); +} ); +``` +对应生成的快照文件内容: +```js +exports[ `测试foobar测试示例 1` ] = ` + 对象 { + "foo": "bar", + } +`; +``` +注意:严禁手动创建或修改快照文件,它们必须通过测试流程自动生成和更新。 + +#### 优势分析 + +- 测试代码编写极其简洁 +- 有效预防意外变更 +- 操作流程简单直观 +- 无需启动应用即可探查内部结构 + +#### 局限说明 + +- 缺乏表达能力 +- 仅能检测发生变更的问题 +- 对非确定性场景支持不佳 + +#### 适用场景 + +快照测试主要应用于组件测试领域。它能敏锐感知组件结构变化,因此特别适合重构场景。若能在系列提交中持续维护快照,其差异记录将清晰展现组件结构的演进历程,堪称精妙 😎 + +```jsx +import { render, screen } from '@testing-library/react'; +import SolarSystem from 'solar-system'; + +describe( '太阳系组件', () => { + test( '基础渲染测试', () => { + const { container } = render( ); + + expect( container ).toMatchSnapshot(); + } ); + + test( '启用行星参数时应包含火星', () => { + const { container } = render( ); + + expect( container ).toMatchSnapshot(); + expect( screen.getByText( /mars/i ) ).toBeInTheDocument(); + } ); +} ); +``` + +Reducer测试同样适合快照方案。这类测试常涉及大规模复杂数据结构,且不应发生意外变动——这正是快照技术的优势所在! + +#### 实践技巧 + +持续集成环境可能因快照不匹配而中断测试。若变更为预期修改,请按指南[更新快照]。最快捷的方式是使用Jest的`--updateSnapshot`参数: +```sh +npm run test:unit -- --updateSnapshot --test路径模式 +``` +虽然`--testPathPattern`非必选参数,但指定路径能通过限定测试范围显著提升效率。 + +建议开发时在后台持续运行`npm run test:unit:watch`。Jest会自动运行与变更文件相关的测试,当快照测试失败时,仅需按下`u`键即可即时更新快照! + +#### 注意事项 + +非确定性测试可能产生不一致的快照,需要特别警惕。在处理随机数、时间相关或其他非确定性因素时,快照测试会面临挑战。 + +连接组件的测试需要特殊处理。要对已连接的组件进行快照测试,建议导出未连接状态的原始组件: +```js +// 组件文件 my-component.js +export { MyComponent }; +export default connect( mapStateToProps )( MyComponent ); + +// 测试文件 test/my-component.js +import { MyComponent } from '..'; +// 对原始MyComponent执行测试... +``` +需要手动提供连接所需的props参数,这恰是审查连接状态的良机。 + +### 原生移动端端到端测试 + +Gutenberg的贡献者会发现,PR中包含了在Android和iOS上运行原生移动端E2E测试的持续集成流程。若需排查测试失败问题,请查阅我们的[持续集成中的原生移动端测试指南](/docs/contributors/code/react-native/integration-test-guide.md)。关于在本地运行这些测试的更多信息,可[在此处](/packages/react-native-editor/__device-tests__/README.md)获取。 + +### 原生移动端集成测试 + +我们正在持续推进为原生移动项目添加集成测试的工作,使用的是[`react-native-testing-library`](https://testing-library.com/docs/react-native-testing-library/intro/)库。编写集成测试的指南可[在此处](/docs/contributors/code/react-native/integration-test-guide.md)找到。 + +## 端到端测试 + +目前大多数现有的端到端测试使用[Puppeteer](https://github.com/puppeteer/puppeteer)作为无头Chromium驱动,在`packages/e2e-tests`中运行测试,并仍由[Jest](https://jestjs.io/)测试运行器执行。 + +我们正在推进一个[项目](https://github.com/WordPress/gutenberg/issues/38851),将这些测试从Puppeteer迁移到Playwright。**建议尽可能使用Playwright编写新的端到端测试**。以下部分主要适用于旧的Jest + Puppeteer框架。若使用Playwright编写测试,请参阅专用[指南](/docs/contributors/code/e2e/README.md)。 + +### 使用wp-env环境 + +如果使用内置的[本地环境](/docs/contributors/code/getting-started-with-code-contribution.md#local-environment),可通过以下命令在本地运行端到端测试: + +```bash +npm run test:e2e +``` + +或交互式运行: + +```bash +npm run test:e2e:watch +``` + +有时在运行测试时观察浏览器行为会很有帮助。此时可使用: + +```bash +npm run test:e2e:watch -- --puppeteer-interactive +``` + +可通过`--puppeteer-slowmo`控制执行速度: + +```bash +npm run test:e2e:watch -- --puppeteer-interactive --puppeteer-slowmo=200 +``` + +还可启用开发者工具,在浏览器中进行交互式调试: + +```bash +npm run test:e2e:watch -- --puppeteer-devtools +``` + +### 使用其他环境 + +若使用`wp-env`之外的其他设置,需要先将端到端测试插件软链接到测试站点。从站点的插件目录运行: + +```bash +ln -s gutenberg/packages/e2e-tests/plugins/* . +``` + +运行测试时需指定站点的基础URL、用户名和密码。例如,若测试站点为`http://wp.test`,则使用: + +```bash +WP_BASE_URL=http://wp.test npm run test:e2e -- --wordpress-username=admin --wordpress-password=password +``` + +### 场景测试 + +若发现端到端测试在本地通过但在GitHub Actions中失败,可通过模拟低速CPU或网络来隔离CPU或网络相关的竞态条件: + +``` +THROTTLE_CPU=4 npm run test:e2e +``` + +`THROTTLE_CPU`为减速系数(此示例中为4倍减速) + +参阅[Chrome文档:setCPUThrottlingRate](https://chromedevtools.github.io/devtools-protocol/tot/Emulation#method-setCPUThrottlingRate) + +``` +SLOW_NETWORK=true npm run test:e2e +``` + +`SLOW_NETWORK`模拟相当于Chrome开发者工具中"快速3G"的网络速度。 + +参阅[Chrome文档:emulateNetworkConditions](https://chromedevtools.github.io/devtools-protocol/tot/Network#method-emulateNetworkConditions)和[NetworkManager.js](https://github.com/ChromeDevTools/devtools-frontend/blob/80c102878fd97a7a696572054007d40560dcdd21/front_end/sdk/NetworkManager.js#L252-L274) + +``` +OFFLINE=true npm run test:e2e +``` + +`OFFLINE`模拟网络断开情况。 + +参阅[Chrome文档:emulateNetworkConditions](https://chromedevtools.github.io/devtools-protocol/tot/Network#method-emulateNetworkConditions) + +### 核心区块测试 + +每个核心区块必须至少包含一套用于主要保存功能的测试固件文件,以及针对每个废弃功能的独立测试固件。这些测试固件用于验证区块的解析与序列化功能。更多详细信息及操作指南请参阅[集成测试固件说明文档](https://github.com/wordpress/gutenberg/blob/HEAD/test/integration/fixtures/blocks/README.md)。 + +### 不稳定性测试 + +当某个测试在未经代码修改的情况下,经过多次重试运行时出现时而过时而不通过的情况,该测试即被视为**不稳定性测试**。我们在持续集成环境中最多会自动重试失败测试**两次**,通过[`report-flaky-tests`](https://github.com/WordPress/gutenberg/tree/trunk/packages/report-flaky-tests) GitHub Action自动检测这类测试,并将其提交至GitHub问题区,标记为[`[Type] Flaky Test`](https://github.com/WordPress/gutenberg/issues?q=is%3Aissue+is%3Aopen+sort%3Aupdated-desc+label%3A%22%5BType%5D+Flaky+Test%22)标签。请注意,连续失败三次的测试不会被判定为不稳定性测试,也不会被提交至问题区。 + +## PHP测试 + +PHP测试采用[PHPUnit](https://phpunit.de/)作为测试框架。若使用内置的[本地环境](/docs/contributors/code/getting-started-with-code-contribution.md#local-environment),可通过以下命令在本地运行PHP测试: + +```bash +npm run test:php +``` + +若需在文件变更时自动重新运行测试(类似Jest功能),请执行: + +```bash +npm run test:php:watch +``` + +_注意:phpunit命令需要`wp-env`处于运行状态且composer依赖已安装。若wp-env未运行,包脚本将自动启动该环境。_ + +在其他环境中,请运行`composer run test`和`composer run test:watch`命令。 + +PHP代码规范通过[PHP_CodeSniffer](https://github.com/squizlabs/PHP_CodeSniffer)进行校验。建议通过[Composer](https://getcomposer.org/)安装PHP_CodeSniffer及[WordPress PHP编码标准规则集](https://github.com/WordPress-Coding-Standards/WordPress-Coding-Standards#installation)。安装Composer后,在项目目录下运行`composer install`即可安装依赖。前述的`npm run test:php`命令将同时执行单元测试和代码规范检查。若需单独验证代码规范,可运行`npm run lint:php`。 + +若需仅运行单元测试(不包含规范检查),请使用`npm run test:unit:php`命令。 + +[快照测试]: https://jestjs.io/docs/en/snapshot-testing.html +[更新快照]: https://jestjs.io/docs/en/snapshot-testing.html#updating-snapshots + +## 性能测试 + +为确保编辑器在功能增补过程中始终保持高性能,我们持续监控拉取请求和版本发布对以下关键指标的影响: + +- 编辑器加载时长 +- 输入时浏览器响应时长 +- 区块选中时长 + +性能测试通过端到端测试运行编辑器并采集这些指标。执行前请确保已配置好端到端测试环境。 + +配置端到端测试环境时,请先检出Gutenberg代码库并切换至待测试分支,随后运行以下命令完成环境准备: + +```bash +nvm use && npm install +npm run build +``` + +执行测试请运行以下命令: + +```bash +npm run test:performance +``` + +此命令将输出当前分支/代码在运行环境中的测试结果。 + +此外,您还可以通过`./bin/plugin/cli.js perf [分支名]`命令对比不同分支(或标签/提交)间的指标差异,例如: + +```bash +./bin/plugin/cli.js perf trunk v8.1.0 v8.0.0 +``` + +最后,您可通过`--tests-branch`参数指定要运行的性能测试文件所属分支。这在修改/扩展性能测试时特别有用: + +```bash +./bin/plugin/cli.js perf trunk v8.1.0 v8.0.0 --tests-branch add/perf-tests-coverage +``` + +**注意** 此基准测试可能需要较长时间。运行期间请避免操作计算机或运行过多后台进程,以尽量减少可能影响跨分支测试结果的外部因素。 + +### 设置与清理方法 + +Jest API 包含一些实用的[设置与清理方法](https://jestjs.io/docs/en/setup-teardown.html),允许您在每项测试前后、所有测试前后或特定 `describe` 代码块内的测试前后执行任务。 + +这些方法支持异步代码,可实现常规行内代码无法完成的设置。与[独立测试用例](https://jestjs.io/docs/en/asynchronous.html#promises)类似,您可以返回 Promise 对象,Jest 将等待其状态落定: + +```javascript +// 为*所有*测试执行一次性设置 +beforeAll( () => + someAsyncAction().then( ( resp ) => { + window.someGlobal = resp; + } ) +); + +// 为*所有*测试执行一次性清理 +afterAll( () => { + window.someGlobal = null; +} ); +``` + +`afterEach` 和 `afterAll` 提供了在测试后进行「清理」的完美(且推荐)方式,例如通过重置状态数据来实现。 + +请避免在断言之后放置清理代码,因为如果其中任何测试失败,清理操作将不会执行,并可能导致无关测试出现故障。 + +### 模拟依赖项 + +#### 依赖注入 + +将依赖项作为参数传递给函数通常能使代码更易于测试。在可能的情况下,请避免引用更高作用域中的依赖项。 + +**欠佳实践** + +```javascript +import VALID_VALUES_LIST from './constants'; + +function isValueValid( value ) { + return VALID_VALUES_LIST.includes( value ); +} +``` + +此时我们需要导入并使用 `VALID_VALUES_LIST` 中的值才能通过测试: + +`expect( isValueValid( VALID_VALUES_LIST[ 0 ] ) ).toBe( true );` + +上述断言同时测试了两个行为:1)函数能检测列表中的项目,2)函数能检测 `VALID_VALUES_LIST` 中的项目。 + +但如果我们不关心 `VALID_VALUES_LIST` 中存储的内容,或者该列表是通过 HTTP 请求获取的,而我们只想测试 `isValueValid` 能否检测列表中的项目呢? + +**推荐实践** + +```javascript +function isValueValid( value, validValuesList = [] ) { + return validValuesList.includes( value ); +} +``` + +通过将列表作为参数传递,我们可以在测试中传入模拟的 `validValuesList` 值,同时还能额外测试更多场景: + +`expect( isValueValid( 'hulk', [ 'batman', 'superman' ] ) ).toBe( false );` + +`expect( isValueValid( 'hulk', null ) ).toBe( false );` + +`expect( isValueValid( 'hulk', [] ) ).toBe( false );` + +`expect( isValueValid( 'hulk', [ 'iron man', 'hulk' ] ) ).toBe( true );` + +#### 导入的依赖项 + +当代码在多个位置使用来自外部和内部库的方法及属性时,通过参数传递会显得混乱且不切实际。对于这种情况,`jest.mock` 提供了一种简洁的存根化解决方案。 + +例如,假设我们有一个通过特性标志控制大量功能的 `config` 模块: + +```javascript +// bilbo.js +import config from 'config'; +export const isBilboVisible = () => + config.isEnabled( 'the-ring' ) ? false : true; +``` + +为了测试不同条件下的行为,我们存根化配置对象并使用 Jest 模拟函数来控制 `isEnabled` 的返回值: + +```javascript +// test/bilbo.js +import { isEnabled } from 'config'; +import { isBilboVisible } from '../bilbo'; + +jest.mock( 'config', () => ( { + // 比尔博默认可见 + isEnabled: jest.fn( () => false ), +} ) ); + +describe( '比尔博模块', () => { + test( '比尔博默认应可见', () => { + expect( isBilboVisible() ).toBe( true ); + } ); + + test( '当启用 `the-ring` 配置特性标志时,比尔博应不可见', () => { + isEnabled.mockImplementationOnce( ( name ) => name === 'the-ring' ); + expect( isBilboVisible() ).toBe( false ); + } ); +} ); +``` + +### 测试全局对象 + +我们可以使用 [Jest 监控器](https://jestjs.io/docs/en/jest-object.html#jestspyonobject-methodname)来测试调用全局方法的代码: + +```javascript +import { myModuleFunctionThatOpensANewWindow } from '../my-module'; + +describe( '我的模块', () => { + beforeAll( () => { + jest.spyOn( global, 'open' ).mockImplementation( () => true ); + } ); + + test( '特定功能', () => { + myModuleFunctionThatOpensANewWindow(); + expect( global.open ).toHaveBeenCalled(); + } ); +} ); +``` + +### 用户交互模拟 + +通过模拟用户交互来**从用户视角编写测试**是绝佳实践,能够有效避免测试实现细节。 + +使用 Testing Library 编写测试时,主要有两种模拟用户交互的方式: + +1. [`fireEvent`](https://testing-library.com/docs/dom-testing-library/api-events/#fireevent) API:Testing Library 核心 API 中用于触发 DOM 事件的工具 +2. [`user-event`](https://testing-library.com/docs/user-event/intro/) 库:Testing Library 的配套库,通过模拟浏览器中真实交互时触发的事件来模拟用户操作 + +内置的 `fireEvent` 是用于派发 DOM 事件的工具。它会精确触发测试规范中描述的事件——即使这些事件在真实浏览器交互中从未被触发过。 + +而 `user-event` 库则提供了更高级的方法(如 `type`、`selectOptions`、`clear`、`doubleClick`...),这些方法会像真实用户与文档交互时那样派发事件,并处理所有 React 相关的特殊行为。 + +基于以上原因,**在为用户交互编写测试时,推荐使用 `user-event` 库**。 + +**不够理想**:使用 `fireEvent` 派发 DOM 事件 + +```javascript +import { render, screen } from '@testing-library/react'; + +test( '输入新值时触发 onChange 事件', () => { + const spyOnChange = jest.fn(); + + // 包含一个 input 和一个 select 的组件 + render( ); + + const input = screen.getByRole( 'textbox' ); + input.focus(); + // 没有点击事件,没有按键事件 + fireEvent.change( input, { target: { value: 62 } } ); + + // onChange 回调被调用一次,参数为 '62' + expect( spyOnChange ).toHaveBeenCalledTimes( 1 ); + + const select = screen.getByRole( 'listbox' ); + select.focus(); + // 未派发指针事件 + fireEvent.change( select, { target: { value: 'optionValue' } } ); + + // ... +``` + +**推荐做法**:使用 `user-event` 模拟用户事件 + +```javascript +import { render, screen } from '@testing-library/react'; +import userEvent from '@testing-library/user-event'; + +test( '输入新值时触发 onChange 事件', async () => { + const user = userEvent.setup(); + + const spyOnChange = jest.fn(); + + // 包含一个 input 和一个 select 的组件 + render( ); + + const input = screen.getByRole( 'textbox' ); + // 聚焦元素,选中并清空所有内容 + await user.clear( input ); + // 点击元素,逐个字符输入(生成 keydown、keypress 和 keyup 事件) + await user.type( input, '62' ); + + // onChange 回调被调用 3 次,参数依次为: + // - 1: clear ('') + // - 2: '6' + // - 3: '62' + expect( spyOnChange ).toHaveBeenCalledTimes( 3 ); + + const select = screen.getByRole( 'listbox' ); + // 派发聚焦、指针、鼠标、点击和变更事件 + await user.selectOptions( select, [ 'optionValue' ] ); + + // ... +} ); +``` + +### 区块界面集成测试 + +集成测试是指将不同部件作为整体进行测试的方法。在此场景下,我们需要测试的是特定区块或编辑器逻辑需要渲染的各个组件。最终这些测试与单元测试非常相似,因为它们都使用 Jest 库通过相同命令运行。主要区别在于集成测试中的区块运行在[`特殊的区块编辑器实例`](https://github.com/WordPress/gutenberg/blob/trunk/test/integration/helpers/integration-test-editor.js#L60)中。 + +这种方法的优势在于,无需启动完整的端到端测试框架即可测试区块编辑器的大部分功能(如区块工具栏和检查器面板交互等)。这意味着测试运行速度更快、可靠性更高。建议尽可能使用集成测试覆盖区块的界面功能,而将对完整浏览器环境有需求的交互(例如文件上传、拖放操作等)留给端到端测试。 + +[`封面区块`](https://github.com/WordPress/gutenberg/blob/trunk/packages/block-library/src/cover/test/edit.js)就是运用此级别测试的典型范例,该测试覆盖了编辑器绝大部分的交互场景。 + +配置集成测试的 Jest 文件: + +```js +import { initializeEditor } from 'test/integration/helpers/integration-test-editor'; + +async function setup( attributes ) { + const testBlock = { name: 'core/cover', attributes }; + return initializeEditor( testBlock ); +} +``` + +`initializeEditor` 函数返回 `@testing-library/react` 的 `render` 方法输出结果。该函数也支持接收区块元数据对象数组,允许您设置包含多个区块的编辑器。 + +集成测试编辑器模块还导出了 `selectBlock` 方法,可通过区块包装器上的 aria-label(例如“区块: 封面”)来选择需要测试的区块。 \ No newline at end of file diff --git a/contributors/design/README.md b/contributors/design/README.md new file mode 100644 index 0000000..86fed3d --- /dev/null +++ b/contributors/design/README.md @@ -0,0 +1,61 @@ +# 设计贡献指南 + +关于如何开始为古腾堡项目贡献设计力量的指南。 + +## 讨论交流 + +[WordPress设计博客](https://make.wordpress.org/design/)是获取WordPress设计团队最新资讯的主要平台,包含公告通知、产品目标、会议记录和议程安排等内容。 + +设计相关的实时讨论在[WordPress官方Slack](https://make.wordpress.org/chat)的`#design`频道进行(需注册)。设计团队每周三UTC时间19:00举行例会。 + +## 设计师如何参与贡献? + +古腾堡项目使用GitHub管理代码和跟踪问题。主代码库位于:[https://github.com/WordPress/gutenberg](https://github.com/WordPress/gutenberg)。 + +若您希望参与设计或前端开发,欢迎处理标有[需要设计](https://github.com/WordPress/gutenberg/issues?q=is%3Aissue+is%3Aopen+label%3A%22Needs+Design%22)或[需要设计反馈](https://github.com/WordPress/gutenberg/issues?q=is%3Aissue+is%3Aopen+label%3A"Needs+Design+Feedback%22)标签的任务。我们期待您提供深思熟虑的回复、设计稿、动态演示、草图或手绘稿。建议的修改最好基于前期工作成果进行最小化且具体的迭代,以便对比参考。 + +[WordPress设计团队](https://make.wordpress.org/design/)使用[Figma](https://www.figma.com/)进行协作与成果分享。若您希望参与贡献,请加入[Slack](https://make.wordpress.org/chat/)的[#design频道](https://wordpress.slack.com/messages/design/),申请获取免费Figma账户。这将使您能够使用WordPress官方提供的实用[组件库](https://www.figma.com/file/ZtN5xslEVYgzU7Dd5CxgGZwq/WordPress-Components?node-id=0%3A1)。 + +## 设计原则 + +本章节将阐述编辑器界面的设计原则与模式——旨在说明设计背景、指导未来改进,并帮助人们设计出优秀的区块。 + +古腾堡标识 + +古腾堡标识由[Cristel Rossignol](https://twitter.com/cristelrossi)设计,基于GPL协议发布。[下载SVG格式标识](https://raw.githubusercontent.com/WordPress/gutenberg/HEAD/docs/contributors/assets/gutenberg-logo-black.svg)。 + +### 古腾堡的核心目标 + +古腾堡的终极目标是打造直观易用的文章与页面编辑体验,让用户轻松创建丰富布局。区块编辑器是首款基于此内容创作方法论推出的产品。 + +根据[项目启动宣言](https://make.wordpress.org/core/2017/01/04/focus-tech-and-design-leads/): + +> 该编辑器致力于打造全新的页面与文章构建体验,让富媒体内容创作变得轻松自如,并通过“区块”功能简化当前需要短代码、自定义HTML或复杂嵌入操作才能实现的效果。 + +我们可从中提炼出若干核心原则: + +- **富媒体内容创作是WordPress的核心优势** +- **区块将在统一界面中整合功能与交互模式**:用户无需再使用短代码、自定义HTML或粘贴嵌入链接。只需掌握区块使用方法,即可解锁全部功能 +- **提升核心功能的可发现性**:消除难以察觉的“隐藏功能”。WordPress支持大量区块和30余种嵌入类型,需增强其可见度 + +### 设计初衷 + +WordPress区别于其他系统的特质在于:只要用户掌握HTML和CSS并能构建自定义主题,即可实现无限可能的文章布局。 + +古腾堡将编辑器重塑为直观工具,让用户无需技术背景即可通过简单点击创作富媒体内容、构建精美布局。WordPress将成为人人可用的强大灵活内容创作平台。 + +### 愿景规划 + +古腾堡致力于降低富媒体内容创作门槛。这意味着需要确保默认设置的合理性,将高级布局选项集成至区块中,并使核心操作触手可及。任何人都应能无障碍地使用WordPress创作内容。 + +**WordPress站点的所有元素都将区块化**:无论是文本、图片、相册、小工具、短代码,还是通过插件添加的自定义HTML片段。用户仅需掌握区块界面这一种交互方式。 + +**所有区块平等共存**:它们统一位于插入器界面中。通过最近使用记录、搜索功能、标签分类和分组管理,确保常用区块随时可用。 + +**拖拽操作作为辅助**:为了更好的可访问性和平台兼容性,拖拽交互作为点击、跳转和空格等明确操作的增强功能存在。 + +**占位符设计至关重要**:支持中性占位状态的区块都应实现该特性。图片占位区块显示媒体库入口按钮,文本占位区块呈现输入提示。通过占位符设计,我们可以预定义可编辑布局,用户只需填充内容即可。 + +**直接操作符合直觉**:区块界面允许用户在页面上直接操控内容。插件和主题开发者将通过构建自定义区块来支持和扩展这种体验。 + +**定制化无需代码编辑**:传统定制方式需要复杂标记语言,而复杂标记极易出错。古腾堡让定制过程更直观可靠。开发者可提供直接渲染布局片段的定制区块(例如三栏功能网格),并明确指定用户可直接编辑的部分。这意味着用户能自主更新文本、替换图片、调整栏数,无需寻求开发者帮助,也不必担心破坏布局。 \ No newline at end of file diff --git a/contributors/design/the-block.md b/contributors/design/the-block.md new file mode 100644 index 0000000..7c5a24c --- /dev/null +++ b/contributors/design/the-block.md @@ -0,0 +1,30 @@ +# 区块即界面 + +古腾堡编辑器的核心在于区块概念。从技术角度看,区块既将抽象层级从单一文档提升至有意义元素的集合,又通过明确结构取代了HTML与生俱来的模糊性。 + +对用户而言,区块允许以更统一、更易用的方式直接向站点添加任意内容、媒体或功能。“添加区块”按钮使用户能在一个界面访问完整的功能库,无需反复翻查菜单或记忆短代码。 + +但最重要的是,古腾堡基于_直接操作_原则构建——这意味着控制元素显示方式的核心选项都_在区块本身的语境中_完成操作。这相较于传统WordPress模式是重大变革,后者往往将选项深藏在多层导航菜单中,通过间接机制控制页面元素。 + +例如用户可以在画布的区块界面内完成添加图片、编写图注、调整宽度版式、添加环绕链接等全套操作。这一原则同样适用于“导航菜单”等复杂区块,用户能够直接添加、编辑、移动并最终完成导航栏的完整呈现。 + +- 用户仅需掌握区块这一种界面,即可编辑站点的所有内容。用户不必编写短代码、自定义HTML或理解隐藏机制来嵌入内容 +- 古腾堡让核心功能更易被发现,减少了难以寻觅的“神秘元素”。WordPress支持大量区块和30多种嵌入内容,现在让我们提升它们的可见度 + +## 构建区块 + +这对设计师和开发者意味着什么?区块结构加直接操作原则意味着需要以全新思路设计WordPress组件。让我们再次审视区块的架构: + +![古腾堡架构图](https://cldup.com/LQrPNubkJY.png) + +### 区块内容区是主要交互界面 + +区块内容区的占位内容可视为引导用户执行操作指令或“填空”的界面指南(后续将详述占位符概念)。由于内容区直接对应网站实际呈现效果,此处的交互最贴合直接操作原则,也最符合用户直觉。这应被视为添加和操控内容、调整显示效果的主交互界面。 + +### 区块工具栏承载无法融入占位界面的关键选项 + +基础区块设置并非总能融入占位符/内容界面。作为次要选项,对区块功能至关重要的配置可置于区块工具栏。虽然区块工具栏与直接操作隔了一层,但仍保持高度情境化且全屏可见,因此是理想的次要选项。 + +### 设置侧栏仅应用于高级三级控件 + +设置侧栏在小屏/移动设备上默认隐藏,即使在桌面视图也可能被折叠。因此不应将区块基础运营的必要功能置于此处。选择合理的默认设置,将重要操作置于区块工具栏,把侧栏视作仅高级用户可能发现的拓展区域。 \ No newline at end of file diff --git a/contributors/documentation/README.md b/contributors/documentation/README.md new file mode 100644 index 0000000..1830360 --- /dev/null +++ b/contributors/documentation/README.md @@ -0,0 +1,213 @@ +### 提示说明 + +区块编辑器手册支持与其他WordPress手册相同的[提示样式](https://make.wordpress.org/docs/handbook/documentation-team-handbook/handbooks-style-and-formatting-guide/#formatting)。但由于区块编辑器手册文档发布位置(npm、GitHub)不同,短代码的实现方式并不理想。 + +推荐的Markdown实现方式是使用原始HTML和`callout callout-LEVEL`类。例如: + +```html +
这是一个**信息**提示框。
+``` + +可用的样式类包括:`info`、`tip`、`alert`、`warning` + +
+这是一个**技巧**提示框。 +
+ +
+这是一个**信息**提示框。 +
+ +
+这是一个**警示**提示框。 +
+ +
+这是一个**警告**提示框。 +
+ +
+注意:在提示框中,链接也需要使用HTML `<a href></a>`标签表示法。 +常规的链接转换不适用于提示框中的链接。 +例如,要访问"入门指南 > 创建区块"页面,GitHub中的URL是 +https://developer.wordpress.org/docs/getting-started/devenv/get-started-with-create-block.md +但在区块编辑器手册中需要硬编码为 +https://developer.wordpress.org/block-editor/getting-started/create-block/ 才能正确链接。 +
+ +### 编辑器配置 + +您应该配置编辑器使用Prettier自动格式化Markdown文档。完整详情请参阅[入门文档](/docs/contributors/code/getting-started-with-code-contribution.md)。 + +使用Visual Studio Code和Prettier扩展的配置示例: + +```json +"[[markdown]]": { + "editor.defaultFormatter": "esbenp.prettier-vscode", + "editor.formatOnSave": true +}, +``` + +
+根据您查看本文档的位置,方括号可能显示为双括号。正确格式应为单层方括号。 +
+ +### 视频嵌入 + +区块编辑器手册中的视频需要以未公开形式托管在[WordPress YouTube频道](https://www.youtube.com/@WordPress)。此过程需要额外权限。如需帮助,请在Slack的#marketing频道中联系。 + +视频上传到YouTube后,获取视频嵌入链接。链接格式如下: + +``` +https://www.youtube.com/embed/nrut8SfXA44?si=YxvmHmAoYx-BDCog +``` + +然后,在文档中需要嵌入视频的位置放置以下代码。请相应更新嵌入链接和视频标题。 + +```html + +``` + +
+视频应采用16:9宽高比,并以最高分辨率拍摄。 +
+ +## 资源 + +- [文案指南](/docs/contributors/documentation/copy-guide.md) - 为Gutenberg项目编写说明、文档或其他贡献的指南 + +- WordPress文档团队的[语气与风格指南](https://make.wordpress.org/docs/handbook/documentation-team-handbook/tone-and-voice-guide/) + +# 文档贡献指南 + +关于如何开始为古腾堡项目贡献文档的指南。 + +## 讨论渠道 + +[WordPress文档制作博客](https://make.wordpress.org/docs/)是获取WordPress文档最新信息的主要平台,包括公告、产品目标、会议记录、会议议程等。 + +文档相关的实时讨论在[WordPress官方Slack](https://make.wordpress.org/chat)的`#docs`频道进行(需要注册)。文档团队的每周例会于UTC时间每周二14:00举行。 + +古腾堡项目使用GitHub管理代码和跟踪问题。主代码库位于:[https://github.com/WordPress/gutenberg](https://github.com/WordPress/gutenberg)。如需查找可处理的文档问题,请浏览[带有文档标签的议题](https://github.com/WordPress/gutenberg/issues?utf8=%E2%9C%93&q=is%3Aopen+is%3Aissue+label%3A%22%5BType%5D+Documentation%22+)。 + +## 文档类型 + +古腾堡项目包含两大类文档: + +1. [用户文档](https://wordpress.org/documentation/article/wordpress-block-editor/):介绍作者如何使用编辑器发布文章。要贡献用户文档,请关注文档博客或通过#docs Slack频道了解当前优先事项。 +2. [区块编辑器手册](https://developer.wordpress.org/block-editor/):涵盖与古腾堡项目相关的所有内容,包括开发、扩展以及您正在阅读的专门针对古腾堡的贡献指南。 + +本文档剩余部分将重点说明如何贡献区块编辑器手册。 + +## 区块编辑器手册流程 + +区块编辑器手册由[古腾堡项目代码库](https://github.com/WordPress/gutenberg/)中`/docs/`目录的Markdown文件与软件包生成的文档共同组成。 + +自动化任务会每隔15分钟将文档发布至[区块编辑器手册站点](https://developer.wordpress.org/block-editor/)。 + +关于如何使用git通过拉取请求部署更改,请参阅[Git工作流程](/docs/contributors/code/git-workflow.md)文档。另可观看[视频教程](https://wordpress.tv/2020/09/02/marcus-kazmierczak-contribute-developer-documentation-to-gutenberg/)及配套的[古腾堡文档贡献幻灯片](https://mkaz.blog/wordpress/contribute-developer-documentation-to-gutenberg/)。 + +### 手册结构 + +手册根据文档功能类型划分为四个部分。[文档系统](https://documentation.divio.com/)详细阐述了各类文档的需求和功能,简而言之包括: + +- **入门教程** - 引导学习者逐步完成目标的完整课程,例如[创建区块教程](/docs/getting-started/devenv/get-started-with-create-block.md)。 +- **操作指南** - 针对完成特定小型任务的简短教程,例如[如何向区块工具栏添加按钮](/docs/how-to-guides/format-api.md)。 +- **参考指南** - API文档,纯功能说明。 +- **概念解析** - 侧重于知识学习而非具体任务的深度文档。 + +### 模板 + +我们提供[操作指南模板](https://raw.githubusercontent.com/WordPress/gutenberg/trunk/docs/contributors/documentation/how-to-guide-template.md)来规范指南结构。如需创建新操作指南,可复制模板中的Markdown内容开始编写。 + +该模板基于"优质文档项目"的范例制作。如需创建高质量文档,可参考其[模板库](https://github.com/thegooddocsproject/templates)获取更多示例。 + +### 更新文档 + +更新现有页面的步骤: + +1. 检出古腾堡代码库 +2. 创建功能分支,例如`docs/update-contrib-guide` +3. 对现有文档进行必要修改 +4. 提交更改 +5. 使用[[Type] Developer Documentation](https://github.com/WordPress/gutenberg/labels/%5BType%5D%20Developer%20Documentation)标签创建拉取请求 + +### 创建新文档 + +添加新文档需要配置可运行的JavaScript开发环境来构建文档,请参阅[JavaScript构建设置文档](/docs/how-to-guides/javascript/js-build-setup.md): + +1. 在[docs](https://github.com/WordPress/gutenberg/tree/HEAD/docs)文件夹中创建Markdown文件,使用小写字母、无空格(如需分隔请使用连字符)及`.md`扩展名 +2. 使用markdown语法添加内容。所有文档必须包含且仅包含一个`h1`标签 +3. 在[toc.json](https://github.com/WordPress/gutenberg/blob/HEAD/docs/toc.json)层级结构中添加文档条目。格式请参考现有条目 +4. 运行`npm run docs:build`以更新`manifest.json` +5. 将`manifest.json`与其他更新文件一并提交 + +如果忘记运行`npm run docs:build`,您的PR将无法通过静态分析检查,因为`manifest.json`文件存在未提交的本地变更必须被提交。 + +### 软件包文档编写 + +软件包文档由文档工具自动生成,通过提取软件包根目录下README.md文件的内容实现。但有时将README内容拆分为更易读的小节更为合适。 + +可通过在软件包中创建`docs`目录并添加`toc.json`文件来实现,该文件包含对`docs`目录下其他markdown文件的引用。`toc.json`文件应包含要作为主README文件子页面添加的页面数组,其格式遵循自动生成的[`manifest.json`](https://github.com/WordPress/gutenberg/blob/HEAD/docs/manifest.json)文件。 + +为确保这些页面嵌套在主软件包名称下,请正确设置`parent`属性。以下示例展示了如何为[`@wordpress/create-block`章节](https://developer.wordpress.org/block-editor/reference-guides/packages/packages-create-block/)添加子页面: + +```json +[ + { + "title": "@wordpress/create-block 外部模板", + "slug": "packages-create-block-external-template", + "markdown_source": "../packages/create-block/docs/external-template.md", + "parent": "packages-create-block" + } +] +``` + +### 链接使用规范 + +在某些情况下可能需要链接到其他内部文档页面。需要重点说明的是,所有文档可在不同上下文中浏览: + +- 区块编辑器手册 +- GitHub网站 +- npm网站 + +要创建适用于所有上下文的链接,必须使用不带`https://github.com/WordPress/gutenberg`前缀的绝对路径链接。可通过以下模式引用文件: + +- `/docs/*.md` +- `/packages/*/README.md` +- `/packages/components/src/**/README.md` + +这样就能在上述三种上下文中正确处理链接。 + +请使用Gutenberg代码库中的完整目录和文件名(而非发布路径);区块编辑器手册会生成短链接——您可以在教程部分看到这一点。同样地,手册中会省略`readme.md`部分,但链接中应包含该部分。 + +例如,本页面的链接为:`/docs/contributors/documentation/README.md` + +
+注意:常规链接转换不适用于标注框内的链接。详见下文。 +
+ +### 代码示例规范 + +Markdown中的代码示例应使用三个反引号```` ``` ````包裹,并需包含语言标识符。请参阅[GitHub关于代码块的文档](https://help.github.com/en/github/writing-on-github/creating-and-highlighting-code-blocks)。 + +Gutenberg文档的独特功能是`codetabs`切换器,可同时显示两个版本的代码,常用于展示`JSX`和`Plain`代码示例。 + +以下是`codetabs`章节的示例: + +````md + \{\% codetabs \%\} + \{\% JSX \%\} + ```js + // JSX代码示例 + ``` + \{\% Plain \%\} + ```js + // 原生代码示例 + ``` + \{\% end \%\} +```` + +代码示例的首选格式是JSX,这应作为默认视图。源代码中排在第一的示例将作为默认显示。 + +**注意:** 并非每个指南都需要提供原生JavaScript代码示例。建议仅为入门教程或简短示例提供原生代码,但Gutenberg软件包及更广泛的React和JavaScript生态系统中的大多数代码都采用需要构建过程的JSX格式。 \ No newline at end of file diff --git a/contributors/documentation/copy-guide.md b/contributors/documentation/copy-guide.md new file mode 100644 index 0000000..484dbbd --- /dev/null +++ b/contributors/documentation/copy-guide.md @@ -0,0 +1,294 @@ +# 文案撰写指南 + +## 长文本规范 + +适用于编写多行/多步骤说明,或对页面功能进行叙述性引导的规范。 + +具体内容会随语境变化,但以下通用建议可供参考: + +#### 第一:善用缩写形式! + +缩写更符合对话习惯,能轻松让文本显得亲切自然(同时还能节省篇幅:可谓双赢)。 + +#### 第二:删减冗余表达 + +以下两种情形常出现堆砌词藻却不表意的情况: + +首先是使用被动语态时: +> 该区块可用于展示单张图片。 + +当看到“可用于”“被用于”等表述时需警惕:你正在使用被动语态。改用主动语态能让句子更简洁有力: +> 该区块展示单张图片。 + +其次是回避明确断言而使用模糊表述时: +> 图库区块可帮助您以优雅布局展示多张图片。 + +到底能不能?既然是我们开发的软件,就应该明确说明其功能: +> 图库区块以优雅布局展示多张图片。 + +“允许您”这个短语也常被滥用: +> 预格式化文本允许您保留制表符和换行符。 + +功能本身并不“允许”用户操作,它们只是实现特定目标的工具。直接说明其作用即可: +> 预格式化文本会保留制表符和换行符。 + +更直接的表述往往更清晰。建议在审校时重点检查“可以”“能够”“可能”“允许您”“帮助”等高频冗余词,这些都是需要精简表述的常见信号。 + +#### 第三:慎用“简单”“便捷”“只需” + +我们无权定义何为简单——这应由用户判定。若宣称某项操作简单,而用户实际体验却并非如此,会削弱用户对我们及产品的信任。“只需”同样如此——许多人知道避免使用“简单”,却频繁使用“只需”。“只需点击这里”“只需输入用户名”,这种表述暗示操作轻而易举,但我们无法预知用户可能遇到的困难。 + +具体说明往往更安全有效。“简单”和“便捷”常是我们未充分解释的托辞。遇到这些词时,请思考它们替代的具体含义。例如“按回车键添加区块很简单”实际想表达的是“您无需将手离开键盘即可为页面添加内容”。既然如此,何不直接说明具体优势? + +当然并非要完全禁用这些词汇。当描述封面图区块需要更少配置,或介绍快速创建自定义区块工具时,完全可以说“封面图区块已简化”或“我们正让自定义区块创建更便捷”——此时这些词是相对且描述性的。重点是要避免将其用作绝对断言,并借机提供更具体明确的说明。 + +#### 第四:警惕“我们”滥用 + +当文本频繁出现“我们”,意味着焦点偏离了软件使用者而转向开发者。虽然有时确需如此,但多数情况下,重点应始终围绕用户需求及其获得的价值,而非“我们的成果”或“我们的意图”。 + +只有我们自己在意这些;用户要的只是好用的软件。若发现文本中“我们”频现,请考虑将表述重构为聚焦用户获益与成功体验。 + +#### 五、注意字母大小写规范 + +标题和副标题有两种书写格式: + +**标题格式**:每个单词的首字母大写 +**句子格式**:仅句首单词首字母大写 + +功能名称和仪表板区域通常采用标题格式(如"站点统计"或"最新发布"),而功能标签则多使用句子格式(如"显示按钮"或"评论点赞功能"——其中"点赞"因作为功能名称需首字母大写,但整体标签仍遵循句子格式)。 + +当处理整页界面文本时,请确保同类文本(标题、工具提示、按钮等)保持统一的大小写规范。 + +## 错误信息撰写指南 + +如何编写清晰实用的错误信息 + +#### 一、重视错误信息中的语气与语调——它们传递着重要信息 + +语气语调传递的信息量不亚于文字本身。错误信息需要承载大量信息且通常要求简洁,但切忌牺牲语调的平衡,避免走向负面苛责或过度乐观的极端。 + +假设用户因权限不足无法发布文章,以下是我们应避免的表述方式: + +> 您的用户角色不正确。 + +这种表述显得疏离冷漠。 + +> 停!您无权进行此操作。 + +这种表述带有不必要的警告意味和严厉感。 + +> 哎呀,我们不能让您这么操作哦! + +这种表述显得过于轻佻。 + +即便在错误提示中,我们仍可保持直接、积极且友善的沟通。具体如何实现?请看第二至第四条建议! + +#### 二、尽可能提供解决方案 + +优秀的错误信息不应仅停留在问题告知: + +> 您的用户角色不正确。 + +然后呢?为什么这很重要?我该如何解决?这条信息对我有何帮助?用户需要了解角色权限的重要性,以及如何获取相应权限来完成目标操作。若错误信息未提供指导,用户将陷入困境——如果我们不明确说明,用户很可能重复触发相同的错误。 + +#### 三、避免为节省空间而滥用专业术语 + +> 您的用户角色不正确。请联系站点管理员。 + +这个版本似乎有所改进:至少指明了解决方向。 + +但细想仍存缺陷:用户仍不清楚自身角色定位及其重要性,也不明白"站点管理员"具体指代谁、如何联系。 + +虽然这条错误信息在技术上完全准确,但并未传递有效信息。若以理解和解决问题为目标,技术准确性未必能直接达成。 + +"您的账号暂无文章发布权限"虽未采用用户角色界面的专业术语,但清晰说明了问题症结,即使用户不了解"用户角色"概念也能理解。基于这种认知,即使提示信息止步于此,用户也能意识到需要申请权限。 + +与现有界面术语保持一致固然重要,但绝不能以牺牲理解为代价。 + +#### 四、切勿假定用户理解错误来源 + +> 您的用户角色不正确。 + +在我们看来,用户显然是在尝试发布内容或修改无权限设置时触发此提示。但对用户而言未必如此:用户往往会频繁点击操作,特别是在不确定如何完成某项任务时,未必能立即记起前序操作页面或设置项(及其意图)。 + +优秀的错误信息应包含引导用户的上下文提示。"您的账号暂无文章发布权限"既提醒了用户尝试发布文章的操作意图,也明确了触发错误的具体障碍所在。 + +## 项目符号列表 + +(顾名思义)撰写项目符号列表的指南 + +#### 第一:保持所有条目句式结构平行 + +平行结构能让列表更便于快速阅读——其可预测性能减轻读者的认知负担。 + +**优秀范例:** +> 这个区块能实现什么功能?应有尽有! +> +> - 添加引用内容 +> - 高亮心仪链接 +> - 展示多张图片 +> - 创建项目符号列表 + +每个条目都是完整句子并以句号结尾(若列表均为单字或双字条目,通常可合并为常规单句——既提升可读性又节省空间)。每行皆以动词开头说明区块功能,且主语始终指向用户。用户能快速理解此列表,因为读完首条后即可掌握后续内容的阅读逻辑与信息类型。 + +**欠佳示例:** +> 这个区块能实现什么功能?应有尽有! +> +> - 您可以添加引用内容 +> - 高亮您喜欢的链接 +> - 它能展示多张图片,很适合创建相册! +> - 项目符号列表 + +本例中各行表述方式各异(有的以动词开头,有的以名词开头),句子主语不断切换(时而用户,时而区块),部分条目附加说明而部分没有,存在不完整句子且标点使用不统一。阅读此类列表需要耗费更多精力,因为读者必须逐条解析内容,无法预判各条目信息结构。 + +注意:这并非要求每个条目都必须简短且以行为动词开头!“可预测”不等于“简单化”,核心在于保持一致的句式结构。以下示例同样符合规范: +> 这个区块能实现什么功能?应有尽有! +> +> - 尝试添加引用——有时他人的表述更为精妙! +> - 用它高亮心仪链接,链接分享可是互联网的硬通货 +> - 创建展示多图的美术馆,尽情呈现您的摄影佳作 + +本例中各条目以更具用户视角的动词开头,并辅以补充信息增强趣味性。标点符号的灵活运用避免了刻板感,但由于基本结构一致,仍保持良好可读性。 + +#### 第二:犹豫不决时,以动词启句(但无需固守同一动词) + +必须使用动词开头吗?非也。但若举棋不定,选择动词开头通常稳妥(尤其当列表描述系列动作或可选操作时)。在纯粹的操作说明中(如需要用户做出选择的界面文案),所有条目使用相同动词开头并无不可: + +> 请选择后续操作: +> +> - 添加纯文本区块 +> - 添加引文区块 +> - 添加图片区块 + +若列表更具说服性质(如通过列举优势引导功能使用)或包含多步骤说明,则应变换动词以生动语言吸引读者: + +> 这个区块能实现什么功能?应有尽有! +> +> - 尝试添加引用——有时他人的表述更为精妙! +> - 用它高亮心仪链接,链接分享可是互联网的硬通货 +> - 创建展示多图的美术馆,尽情呈现您的摄影佳作 + +这些并非铁律——例如在说服性列表中,为增强聚焦性与冲击力亦可使用相同动词。但上述原则确是构建优质列表的基石。 + +#### 第三:当内容明显是列表时,无需刻意声明 + +**优秀范例:** +> 这个区块能实现什么功能?应有尽有! +> +> - 添加引用内容 +> - 高亮心仪链接 +> - 展示多张图片 + +**欠佳示例:** +> 这个区块能实现什么功能?应有尽有!以下是部分使用场景示例: +> +> - 您可以添加引用内容 +> - 高亮您喜欢的链接 +> - 它能展示多张图片,很适合创建相册! + +要在极致清晰与信任用户之间找到平衡。一方面我们深知用户未必细读说明,另一方面冗余提示又易让用户产生被低估智商的感受。 + +#### 四、善用粗体突出重点 + +在项目符号列表中,粗体能引导读者关注核心信息。当列表项包含补充性次要内容时,这种方法尤为有效。 + +“关键信息”是精髓所在:粗体自带视觉引力,因此请始终聚焦每个列表项中最核心的内容: + +> 这个区块有哪些功能?应有尽有! +> +> - 尝试插入**引述**——有时他人的精辟见解最值得分享 +> - 用它高亮你钟爱的**链接**——分享链接是互联网的硬通货 +> - 创建展示多张图片的**相册**,尽情呈现你的摄影佳作 + +反之,过度使用粗体会造成视觉混乱: + +> **这个区块有哪些功能?** 应有尽有! +> +> - 尝试插入**引述**——有时他人的精辟见解最值得分享 +> - 用它高亮你钟爱的**链接**——分享**链接**是互联网的硬通货 +> - 创建展示**多张图片**的**相册**,尽情呈现你的最佳**摄影作品** + +当列表简短基础时,无需使用粗体——徒增冗余: + +> 这个区块有哪些功能?应有尽有! +> +> - 插入**引述** +> - 高亮**链接** +> - 展示多张**图片** + +简洁的表述自带聚焦效果,无需额外强调。 + +## 界面描述指南 + +撰写功能简介或选项说明的单行文本规范 + +#### 一、清晰至上! + +若用户无法理解选择某项功能将实现什么效果,再巧妙的双关语也毫无意义。文字游戏和俚语往往表意模糊,容易引发误解。如确需使用,应仅作为补充信息——绝不可用于解释核心功能——且必须确保能被广泛理解。 + +#### 二、呼应首章原则,警惕冗余表达 + +主动语态通常更优,在有限的界面空间里,精简表达对用户快速决策至关重要。以下对比可见如何通过精简实现更清晰的指引: + +> 当您点击X时,将会触发Y效果 + +对比 + +> 点击X即可实现Y + +虽然补充说明看似能引导用户,实则模糊了核心信息: + +> 当您点击“设置”按钮时,弹窗将显示所有可用的高级设置 + +对比 + +> 点击“设置”访问高级选项 + +类似冗余表达还有“当您完成X后…”或“若需实现X…”。虽然在用户需要决策路径时后者确有必要,但多数情况下,“要实现X…”的简洁表达更为高效。 + +#### 三、明确具体 + +当操作需要前置条件时,必须明确说明具体要求及后续效果。避免使用“当您准备就绪时”这类模糊表述——准备什么?需具体说明前提条件。 + +“准备就绪”可能指: +- 当需要添加新内容区块时 +- 当对文章内容满意时 +- 完成文章校对后 +- 当需要设置特色图片时 +- 完成所有参数配置后 + +包罗万象的表述实则毫无意义。指引越具体,实用性越强,用户对产品的信任度也越高。 + +#### 四、保留文字温度 + +虽以清晰为首要原则,且界面空间有限,但说明文字仍可保有感染力。 + +单行描述也应保持完整句式: +> 列表。支持编号与符号形式 + +对比 + +> 添加列表,可选择编号或符号样式 + +适当使用缩略形式: +> 添加列表。系统将提供格式选项 + +对比 + +> 添加符号列表——我们会提供格式选项 + +善用标点构建语言节奏: +> 列表。支持编号与符号形式 + +对比 + +> 添加列表——编号或符号样式任君选择 + +坚持使用通俗表达: +> 添加无序列表或有序列表 + +对比 + +> 添加列表,可选择编号或符号样式 + +(再次强调:请勿使用文字游戏!“感染力”在界面指引中应是含蓄的——我们要的是自然的人性化表达,而非刻意的俏皮话。) \ No newline at end of file diff --git a/contributors/documentation/how-to-guide-template.md b/contributors/documentation/how-to-guide-template.md new file mode 100644 index 0000000..bf454f8 --- /dev/null +++ b/contributors/documentation/how-to-guide-template.md @@ -0,0 +1,56 @@ +# 操作指南模板 + +## 概述 + +操作指南通过一系列步骤引导用户完成单个任务。本指南的重点不在于概念教学,而是旨在回答"如何实现..."这一问题。 + +概述部分需总结问题所在,并可包含正确使用该解决方案的场景和背景信息。 + +## 开始之前 + +需包含前提条件和基本要求说明: + +- 前提条件一:WordPress开发环境 +- 前提条件二:熟悉JavaScript和Gutenberg编辑器 +- 前提条件三:自定义区块或主题 + +在此处包含其他重要信息,例如已知问题或程序缺陷。 + +## 分步指南 + +本指南应包含循序渐进的指导说明。可使用代码片段、图片或截图辅助说明每个步骤。根据实际需要设置步骤数量,尽量保持每个步骤简洁明了。 + +### 步骤一:[可选标题] + +阐述第一步操作的简要说明。 + +### 步骤二:[可选标题] + +有序列表的引导语句: + +1. 子步骤A +2. 子步骤B +3. 子步骤C + +### 步骤三:[可选标题] + +代码片段的解释说明。例如: + +```shell +npm install +npm run build +``` + +## 故障排除 + +- 可能出现哪些问题? +- 潜在错误信息及应对措施? + +## 结语 + +总结已完成的步骤,说明用户达成的目标。可附上相关文章链接、更复杂的示例,或提供深入学习的途径。 + + \ No newline at end of file diff --git a/contributors/folder-structure.md b/contributors/folder-structure.md new file mode 100644 index 0000000..2a7d687 --- /dev/null +++ b/contributors/folder-structure.md @@ -0,0 +1,145 @@ +# 目录结构 + +以下片段说明了 Gutenberg 代码库的组织结构,省略了不相关或显而易见的项目,并附有进一步说明: + + │ + ├── LICENSE + ├── README.md + ├── SECURITY.md + ├── CONTRIBUTING.md + │ + ├── .editorconfig + ├── .eslintignore + ├── .eslintrc + ├── .jshintignore + ├── .eslintignore + ├── .prettierrc.js + ├── .stylelintignore + ├── .stylelintrc.js + ├── .markdownlintignore + ├── .npmpackagejsonlintrc.json + ├── phpcs.xml.dist + │ 用于配置代码库中各种代码检查工具(PHP、JS、样式等)的 + │ 点文件和配置文件。 + │ + ├── .browserslistrc + ├── babel.config.js + ├── jsconfig.json + ├── tsconfig.json + ├── tsconfig.base.json + ├── webpack.config.js + │ 转译和打包配置文件。 + │ + ├── .wp-env.json + │ 开发和测试环境配置文件。 + │ 包含 WordPress 和 Gutenberg 插件。 + │ + ├── composer.lock + ├── composer.json + │ PHP 依赖管理文件。主要用于开发工具。 + │ 生产代码不使用外部 PHP 依赖。 + │ + ├── package-lock.json + ├── package.json + │ JavaScript 依赖管理文件。同时用于开发工具和生产依赖。 + │ package.json 还用于定义日常开发中使用的常用任务和脚本。 + │ + ├── changelog.txt + ├── readme.txt + │ 托管在 WordPress 插件仓库中的 Gutenberg 插件的 + │ 说明文档和更新日志。 + │ + ├── gutenberg.php + │ Gutenberg 插件的入口文件。 + │ + ├── post-content.php + │ Gutenberg 插件中用于展示编辑器的演示文章内容。 + │ + ├── .github/* + │ 各类 GitHub 功能配置(问题与 PR 模板、CI、负责人)。 + │ + ├── bin/api-docs + │ 用于生成 API 文档的工具/脚本。 + │ + ├── bin/packages + │ 用于构建 WordPress 包的脚本集合。 + │ + ├── bin/plugin + │ 用于执行 Gutenberg 插件发布和 npm 发布的工具。 + │ + ├── docs/tool + │ 用于生成区块编辑器手册 Markdown 页面的工具。 + │ + ├── docs/*.md + │ 构成[区块编辑器手册](https://developer.wordpress.org/block-editor/)的 + │ 文档页面集合。 + │ + ├── platform-docs + │ 面向非 WordPress 开发者的文档网站, + │ 这些开发者在自己的应用程序中使用 Gutenberg。 + │ 部署于 [https://wordpress.org/gutenberg-framework/](https://wordpress.org/gutenberg-framework/)。 + │ + │ + ├── lib + │ Gutenberg 插件的 PHP 源代码。 + │ + ├── lib/compact/wordpress-x.x + │ 已包含在 WordPress X.X 版本中的 PHP 代码。 + │ 保留该目录以确保插件与旧版 WordPress 的兼容性。 + │ + ├── packages + │ WordPress 包的源代码。 + │ 包可分为: + │ - 在 WordPress 和 Gutenberg 插件中加载的生产环境 JavaScript 脚本和样式, + │ 或作为 npm 包分发。 + │ - 可通过 npm 获取的开发工具。 + │ + ├── packages/{packageName}/package.json + │ 当前包的依赖项。 + │ + ├── packages/{packageName}/CHANGELOG.md + ├── packages/{packageName}/README.md + │ + ├── packages/{packageName}/src/**/*.js + ├── packages/{packageName}/src/**/*.scss + │ 指定包的源代码。 + | + ├── packages/{packageName}/src/**/*.test.js + │ JavaScript 单元测试。 + | + ├── packages/{packageName}/src/**/{ComponentName}/index.js + │ 指定组件的入口文件。 + | + ├── packages/{packageName}/src/**/{ComponentName}/style.scss + │ 指定组件的样式入口文件。 + │ + ├── packages/{packageName}/src/**/{ComponentName}/stories/*.js + │ 用于加载到 Gutenberg Storybook 的组件故事。 + │ + ├── phpunit + │ Gutenberg 插件 PHP 代码的单元测试。 + │ + ├── storybook + │ [Gutenberg Storybook](https://wordpress.github.io/gutenberg/) 的配置。 + │ + ├── test/integration + │ WordPress 包集成测试集合。 + │ + ├── test/native + │ Gutenberg Mobile 单元测试配置。 + │ + ├── test/unit + │ 包单元测试配置。 + │ + ├── test/e2e + │ Gutenberg 插件的端到端测试。 + │ + ├── test/performance + │ 性能指标测试。结果跟踪于 + │ [Gutenberg 性能看板](https://codevitals.run/project/gutenberg)。 + │ + ├── tools/eslint + │ ESLint 检查工具的配置文件。 + │ + ├── tools/webpack + │ webpack 构建的配置文件。 \ No newline at end of file diff --git a/contributors/localizing.md b/contributors/localizing.md new file mode 100644 index 0000000..97e4632 --- /dev/null +++ b/contributors/localizing.md @@ -0,0 +1,11 @@ +# Gutenberg本地化指南 + +Gutenberg插件通过通用插件翻译系统(GlotPress)进行翻译,访问地址:https://translate.wordpress.org。更多信息请参阅[GlotPress翻译流程文档](https://make.wordpress.org/polyglots/handbook/tools/glotpress-translate-wordpress-org/)。 + +若需为您的区域或语言翻译Gutenberg,请在此处[选择对应区域设置](https://translate.wordpress.org/projects/wp-plugins/gutenberg),然后翻译*开发版本*(包含插件字符串)和/或*开发版本说明文档*(请参考[插件页面](https://wordpress.org/plugins/gutenberg/)详情选项卡中的内容进行翻译)。 + +具备相应权限的全局翻译编辑(GTE)或项目翻译编辑(PTE)将在适当时间内处理您的翻译内容。 + +当某个区域设置的插件字符串翻译完成度达到95%并通过审核后,系统将自动生成语言包。 + +随着Gutenberg被纳入WordPress核心程序,这意味着在已安装翻译版WordPress的站点中,超过51%的站点会同时将Gutenberg的翻译字符串编译至核心语言包中。 \ No newline at end of file diff --git a/contributors/repository-management.md b/contributors/repository-management.md new file mode 100644 index 0000000..766bd1b --- /dev/null +++ b/contributors/repository-management.md @@ -0,0 +1,162 @@ +### 设计评审 + +若您的拉取请求涉及设计/用户界面改动,请务必添加相应标签以提醒设计团队。如需申请设计评审,请在PR中添加[需要设计反馈](https://github.com/WordPress/gutenberg/labels/Needs%20Design%20Feedback)标签。若存在需要更新设计/用户界面的PR,请使用[Figma库更新](https://github.com/WordPress/gutenberg/labels/Figma%20Library%20Update)标签。 + +需进行设计评审的变更类型指南: +- 基于既有设计的修改,需确认设计在变更后仍然有效 +- 任何涉及视觉效果的改动 +- 针对某个创意或探索性想法希望获得设计反馈 + +### 合并拉取请求 + +满足以下条件的拉取请求通常可被合并: +- 被认定对代码库具有重要价值 +- 符合所有相关代码评审标准 +- 必要时配备充分测试覆盖 +- 经过所有潜在边界情况验证 +- 已正确添加更新日志条目 +- 经过原作者以外的成员评审 +- 已[变基](/docs/contributors/code/git-workflow.md#keeping-your-branch-up-to-date)至`trunk`分支最新版本 + +最终合并决定由 **@wordpress/gutenberg-core** 团队作出。 + +GitHub上WordPress组织的所有成员均具备评审和合并拉取请求的权限。若您已完成PR评审并对代码质量有信心,请批准该拉取请求并通过评论提及**@wordpress/gutenberg-core**或参与该PR的核心成员。待其确认无异议后,您即可将PR合并至主干分支。 + +多数拉取请求将自动分配发布里程碑,但请确保已合并的PR获得相应分配。此举可建立代码变更的历史沿革记录,便于所有项目参与者(包括非技术人员)追溯信息。 + +### 关闭拉取请求 + +某些情况下,无论投入多少额外精力(如超出范围),拉取请求可能始终无法合并。此时应在关闭PR时礼貌地与贡献者沟通说明原因,这有助于促进未来的建设性参与。 + +请确保: +1. 感谢贡献者投入的时间与精力 +2. 完整阐述关闭拉取请求的决策依据 +3. 尽可能提供相关支持文档链接 + +参考沟通模板: +> 感谢____为本次拉取请求付出的时间。 +> +> 关闭此PR的原因是____。进一步说明,____。 +> +> 更多详情请参阅____和____。 + +## 团队架构 + +本项目使用两个GitHub团队: +- [Gutenberg核心团队](https://github.com/orgs/WordPress/teams/gutenberg-core):由积极参与项目的成员组成,包括定期参加会议、参与问题分类会议、执行代码评审、开发功能与修复缺陷、执行插件及npm发布等。 + +- [Gutenberg团队](https://github.com/orgs/WordPress/teams/gutenberg):由至少完成2-3项重要项目贡献的成员组成。 + +若您符合多项重要贡献已被代码库采纳的标准,并希望加入Gutenberg团队,欢迎在[#core-editor Slack频道](https://make.wordpress.org/chat/)提出申请。 + +## 项目管理 + +我们使用[GitHub项目](https://github.com/WordPress/gutenberg/projects)来追踪那些当前无需立即执行,但需要保留供未来参考的事项细节。 + +# 代码库管理 + +本文档为动态更新文档,旨在阐述我们如何协作管理 Gutenberg 代码库。若您希望提出修改建议,请通过提交议题进行讨论或直接向文档发起拉取请求。 + +本文档涵盖以下内容: + +- [议题管理](#议题) + - [标签分类](#标签) + - [里程碑设置](#里程碑) + - [议题分类流程](#议题分类流程) +- [拉取请求](#拉取请求) + - [代码审查](#代码审查) + - [设计评审](#设计评审) + - [合并拉取请求](#合并拉取请求) + - [关闭拉取请求](#关闭拉取请求) + - [如何让您的拉取请求获得审核?](/docs/contributors/code/how-to-get-your-pull-request-reviewed.md) +- [项目管理](#项目) + +## 议题管理 + +健康的议题列表应确保议题兼具相关性与可操作性。相关性指议题需符合项目当前优先级;可操作性则要求解决方案清晰明确。 + +对于无关或不可操作的议题应及时关闭,以免阻碍项目进展。不妨将议题列表视作工作台:堆积的杂物越多,有效工作空间就越受限。 + +### 标签分类 + +所有议题需配置[一个或多个标签](https://github.com/WordPress/gutenberg/labels)。 + +工作流标签以“Needs”开头,可根据需要灵活添加。理想情况下,每个工作流标签应有对应跟进团队(例如无障碍团队负责 `Needs Accessibility Feedback`,测试团队负责 `Needs Testing` 等)。 + +标有[优先级-高](https://github.com/WordPress/gutenberg/labels/%5BPriority%5D%20High)与[优先级-紧急](https://github.com/WordPress/gutenberg/labels/%5BPriority%5D%20OMGWTFBBQ)的议题需指定处理人员或纳入活跃里程碑。 + +求助类或操作指南类问题应首先发布于相关支持论坛。若不确定是否属于程序错误,支持团队或论坛志愿者将协助排查问题,为撰写有效的错误报告收集必要信息。 + +常见标签示例: + +- [初试推荐](https://github.com/WordPress/gutenberg/labels/Good%20First%20Issue) - 适合新贡献者处理的议题。请通过评论申领任务,并在提交拉取请求时标注议题编号。 +- [初审推荐](https://github.com/WordPress/gutenberg/labels/Good%20First%20Review) - 适合有意参与代码审查的新贡献者处理的拉取请求。 +- [需无障碍反馈](https://github.com/WordPress/gutenberg/labels/Needs%20Accessibility%20Feedback) - 影响无障碍访问的变更需经专项审查(如标记语言修改)。 +- [需设计反馈](https://github.com/WordPress/gutenberg/labels/Needs%20Design%20Feedback) - 涉及设计或用户体验的变更需获得设计确认。 +- [[类型]程序错误](https://github.com/WordPress/gutenberg/labels/%5BType%5D%20Bug) - 现有功能存在异常。 +- [[类型]功能增强](https://github.com/WordPress/gutenberg/labels/%5BType%5D%20Enhancement) - 可提升 Gutenberg 使用体验的改进方案。 +- [[类型]插件兼容性](https://github.com/WordPress/gutenberg/labels/%5BType%5D%20Plugin%20Interoperability) - 记录 Gutenberg 与插件/扩展的冲突情况。需通知插件作者并提供解决方案文档。 +- [[状态]需补充信息](https://github.com/WordPress/gutenberg/labels/%5BStatus%5D%20Needs%20More%20Info) - 议题需补充关键信息方可推进处理,通常需要发起者配合完善。 + +[查阅完整标签目录](https://github.com/WordPress/gutenberg/labels)获取所有标签说明。 + +### 里程碑 + +我们将事项归入[里程碑](https://github.com/wordpress/gutenberg/milestones)以便更好地分类。事项会被添加至以`WordPress`开头的里程碑,而拉取请求则会被添加至以`(Gutenberg)`结尾的里程碑。 + +以下是一些您可能会看到的里程碑: + +- [WordPress X.Y](https://github.com/WordPress/gutenberg/milestone/70):为未来WordPress版本需要完成的任务。 +- [X.Y (Gutenberg)](https://github.com/WordPress/gutenberg/milestone/85):针对Gutenberg插件X.Y版本的拉取请求。 +- [未来](https://github.com/WordPress/gutenberg/milestone/35):这是经大家一致确认为有益但不属于其他分类的事项。 + +### 事项分类 + +为了保持事项列表的健康状态,需要定期进行分类。_分类_是指审查现有事项,确保它们具有相关性、可操作性,并包含所有必要信息。 + +任何人都可以帮助分类,但您需要拥有Gutenberg仓库的贡献者权限才能修改事项的标签或编辑其标题。 + +详情请参阅[分类贡献者指南](/docs/contributors/triage.md)。 + +## 拉取请求 + +Gutenberg针对所有代码和文档变更采用功能分支拉取请求工作流。从高层次看,流程如下: + +1. 在本地检出一个新的功能分支。 +2. 进行修改,并彻底测试。 +3. 对修改满意后提交更改,并推送分支。 +4. 打开您的拉取请求。 +5. 如果您是拥有适当访问权限的常规贡献者,请正确标记和命名您的拉取请求(见下文)。 + +关于拉取请求的标记和命名,以下指南有助于更高效、有条理地编制变更日志。这些指南对常规贡献者尤为重要。但请不要因纠结于这些细节而阻碍您分享工作——出错是难免的,且易于修正! + +- 处理实验性界面和功能时,应用`[Type] Experimental`标签,而非`Feature`、`Enhancement`等。 +- 为技术包(脚本、create-block、添加React钩子等)开发新功能时,应用`[Type] New API`标签,而非`Feature`、`Enhancement`等。 +- 修复项目内部工具的缺陷或进行增强时,应用`[Type] Build Tooling`标签,而非`Bugs`、`Enhancement`等。 +- 在拉取请求标题中,与其描述为修复问题所做的代码变更,不如提及实际修复的缺陷。例如:与其说“在组件中检查可为空对象”,不如说“修复点击复制块按钮时编辑器崩溃的问题”。 + +除上述流程外,还有几点需要强调: + +- 重要的拉取请求应有相关事项作为前提,该事项需明确待解决的问题,并允许在实际编写代码前讨论最合适的解决方案。 +- 为使代码更易于合并,每个拉取请求应仅包含一个概念性变更。保持贡献的原子性可使拉取请求讨论聚焦于单一主题,并能够逐案审批变更。 +- 不同的拉取请求可处理其关联事项中的不同项目或待办事项,若事项较为复杂,无需单个拉取请求覆盖单个事项。 + +### 代码审查 + +每个拉取请求除自动测试外,还需经过人工代码审查。代码审查的目标可概括为: + +- **正确性**——变更是否实现了预期功能? +- **安全性**——恶意方是否会找到利用此变更的途径? +- **可读性**——数月后您自己能否理解此变更? +- **优雅性**——变更在整体风格和架构中是否协调? +- **利他性**——此变更如何为整体做出贡献? + +_作为审查者_,您的反馈应聚焦于想法而非个人。力求理解、保持尊重,并专注于建设性对话。 + +_作为贡献者_,您的责任是从建议中学习,并根据反馈迭代您的拉取请求(如有需要)。力求协作,为整体做出最佳贡献。 + +鼓励所有愿意尝试的人参与代码审查。若您审查了拉取请求并对变更充满信心,请批准它。若您觉得它尚未完全准备好合并,请添加审查意见,说明在最终批准前需要另一组人员复核。这有助于过滤明显缺陷,并简化核心成员的审查工作。关注后续审查也有助于未来提升您的审查信心。 + +若您尚未准备好进行完整审查,可尝试在PR中评论。关于功能或变更缘由的提问同样有益。您也可以在不进行完整审查的情况下,对您理解的代码部分变更发表评论。 + +若您在获取审查方面遇到困难,请参阅:[如何让您的拉取请求获得审查?](/docs/contributors/code/how-to-get-your-pull-request-reviewed.md) \ No newline at end of file diff --git a/contributors/triage.md b/contributors/triage.md new file mode 100644 index 0000000..969b1ab --- /dev/null +++ b/contributors/triage.md @@ -0,0 +1,109 @@ +## 问题关闭说明 + +问题会基于以下原因被关闭: + +- 某个拉取请求(PR)和/或最新版本已解决所报告的问题。 +- 与当前已有报告重复。 +- 更适合在 WordPress.org 论坛中处理的帮助请求。 +- 无法复现的问题。 +- 需要更多信息但问题提出者超过两周未回复的情况。 +- 被判定为无法修复或当前行为符合预期的项目。 + +## 专项分类流程 + +### 发布专项分类 + +以下是在版本发布期间进行分类时需遵循的指南。与常规分类不同,发布专项分类的重点在于准确识别可能阻碍发布的严重问题并推动解决方案。 + +- **如果某个错误是在候选版本(RC)中引入,且可能严重影响多个工作流程**,请将其添加到对应版本的里程碑中,并在 WordPress.org Slack 的 [#core-editor](https://wordpress.slack.com/archives/C02QB2JS7) 频道中标记。 +- **如果错误是在最新版本中引入,且下一个候选版本尚未发布**,理想情况下开发者应在候选版本发布前修复!修复的紧迫性应与问题可能造成的破坏程度成正比。此时,请将其添加到候选版本的里程碑中,如情况紧急,可在 WordPress.org Slack 的 [#core-editor](https://wordpress.slack.com/archives/C02QB2JS7) 频道中提醒。 +- **如果错误并非在最新版本中引入**,则无需设置里程碑。取而代之的是,若问题较为紧迫,可使用如 `[Priority] High`(高优先级)等标签,必要时也可在每周的核心会议中提出关注。 + +### 设计专项分类 + +除了前述常规分类流程外,针对参与分类的设计相关人员,以下补充了更侧重设计视角的专项流程。 + +- PR 测试与审查:这应是日常自主分类的首要步骤。 +- 标签 `Needs Design Feedback`(需设计反馈):检查问题是否确实需要设计反馈,并在可能的情况下提供反馈。可按优先级、项目看板或评论数量排序处理。收集到足够意见后,请移除此标签并确定后续步骤(例如添加 `Needs Design` 标签)。 +- 标签 `Needs Design`(需设计):问题是否真的需要设计介入?是否符合某个重点方向?若已有设计方案,请标记为 `Needs Design Feedback` 以便更好归类问题。 + +提醒事项: + +- 必要时请要求提供截图。 +- 在合并前要求进行迭代并记录所有变更。 +- 若问题未在看板中,请检查其是否适用于某个特定重点方向。 +- 若问题或拉取请求尚未确定优先级,可考虑添加优先级标签以推动进展。 + +有关每周设计分类的更多详细信息及参与方式,请[查阅本指南](https://make.wordpress.org/design/handbook/workflows/weekly-gutenberg-design-triage/)。 + +# 问题分类 + +为保持代码库的良好状态,需定期进行问题分类。**问题分类是指审查现有议题和拉取请求,确保它们具有相关性、可操作性且包含所有必要信息的实践**。任何人都可以协助分类,但若要对议题标签进行修改或编辑标题,您需要成为 Gutenberg 代码库分类团队的成员。 + +> 除本页面外,[GitHub 问题分类教程](https://learn.wordpress.org/tutorial/how-to-do-triage-on-github/)也是了解分类工作的优质资源 + +## 加入分类团队 + +分类团队是由志愿者组成的开放团体,其专项职责是确保 Gutenberg 代码库的问题分类工作持续开展。分类工作存在多种形式: + +- 由成员自主安排的定期个人分类时段 +- 在固定时间组织的集体分类会议(可通过[查阅会议页面](https://make.wordpress.org/meetings/)了解这些分类会议及对应的 Slack 频道) +- 针对特定看板、标签或功能的专项分类会议 + +分类团队成员需符合以下期望: + +- 每周至少完成一次分类工作(包括自主分类) +- 尽可能参加组织的集体分类会议 +- 若您为特定标签或看板加入分类团队,请向其他成员说明您的专注领域 + +若您希望加入该团队,可随时在 #core-editor [Slack 频道](https://make.wordpress.org/chat/)中提出申请。 + +## 处理首次分类任务 + +请从以下筛选列表中选择一项开始工作(注:在[议题总页面](https://github.com/wordpress/gutenberg/issues)点击“Sort”选项即可找到大部分筛选条件): + +- **所有[未贴标签的](https://github.com/WordPress/gutenberg/issues?q=is%3Aopen+is%3Aissue+no%3Alabel+sort%3Aupdated-asc) Gutenberg 议题**。通过添加标签进行分类,可帮助专注特定领域的贡献者更便捷地发现相关议题并着手处理 +- **所有[未贴标签的](https://github.com/WordPress/gutenberg/pulls?q=is%3Aopen+is%3Apr+no%3Alabel) Gutenberg 拉取请求**。此操作需具备代码基础能力,关于标签使用规范请[查阅拉取请求标注指南](/docs/contributors/repository-management.md#pull-requests)。您也可随时与拉取请求作者确认标签是否准确反映其意图 +- **[最近更新日期最早](https://github.com/WordPress/gutenberg/issues?q=is%3Aopen+is%3Aissue+sort%3Aupdated-asc)的 Gutenberg 议题**。对陈旧且可能过时的议题进行分类,可避免重要工作被遗漏 +- **所有[零评论](https://github.com/wordpress/gutenberg/issues?q=is%3Aissue+is%3Aopen+comments%3A0+)的 Gutenberg 议题**。处理此列表可确保所有议题获得关注,并识别需要补充信息或深入讨论的议题 +- **Gutenberg 中[评论数最少](https://github.com/wordpress/gutenberg/issues?q=is%3Aissue+is%3Aopen+sort%3Acomments-asc)的议题**。处理此列表有助于社区发现需要推动的提案 +- **Gutenberg 中[评论数最多](https://github.com/wordpress/gutenberg/issues?q=is%3Aissue+is%3Aopen+sort%3Acomments-desc)的议题**。若您能参与停滞的讨论,最佳处理方式是总结当前进展并明确待办事项/阻碍因素等,从而推动重要复杂议题的解决 +- 您还可**在 GitHub 上创建自定义筛选器**。若您认为某个筛选器对社区有价值,欢迎提交拉取请求将其加入本列表 + +## 常规问题分类流程 + +在处理问题分类时,无论是针对上述列表还是普通问题,请逐条处理。以下是针对每个问题可执行的步骤: + +1. 首先**搜索重复问题**。若问题重复,请评论"重复 #问题编号"并关闭,同时将相关新信息补充至现有问题中(切记同时搜索已关闭问题中的重复项!)。 +2. 若**问题缺少标签,请补充相应标签**以便更好归类(需加入分类团队后获得相应权限)。添加标签时,建议先使用带[Type]前缀的标签(如[Type]功能增强或[Type]程序错误)标明问题类型,随后可添加更具描述性的标签。若问题涉及特定核心区块,可添加[Block]前缀标签;若影响特定功能,可使用[Feature]标签。最后还有针对特定关注领域的标签,如无障碍访问和国际化。可在此处[查看所有可用标签](https://github.com/WordPress/gutenberg/labels)。 +3. 若**标题表述不够清晰,请编辑优化标题**(需相应权限)。特别建议将问题相关的主要功能置于标题开头([示例](https://github.com/WordPress/gutenberg/issues/6193)),并尽量使标题简洁且具描述性([示例](https://github.com/WordPress/gutenberg/issues/6193))。 +4. 若是**错误报告,请测试确认或添加`需测试`标签**。若信息不足无法确认,请添加`[状态]需补充信息`标签并索要必要细节。若错误报告缺少重现步骤,请要求提交者补充,这对问题排查尤为重要。 +5. **及时移除不再需要的`[状态]需补充信息`标签**,例如当问题作者已提供足够详情的回复时。 +6. **若作者超过两周未回复,可备注后关闭未处理的`[状态]需补充信息`问题**。 +7. 若问题存在讨论但**未明确可执行步骤,请跟进参与者商定行动方案**。在评论中回复时务必@每位参与者。 +8. 若您有信心进一步处理问题,还可: + - 通过调试验证错误报告有效性,尝试定位技术细节 + - 核查问题是否缺少细节并尝试补充(例如当错误报告缺少视觉细节时,可在本地重现问题并上传截图或GIF) + - 若认为该问题适合新手贡献者尝试解决,可考虑添加"新手推荐"标签 + +**常用标签** + +总体而言,以下标签在问题分类中非常实用,可能是您最常使用的标签。完整标签列表可在此处[查看](https://github.com/WordPress/gutenberg/labels)。 + +| 标签 | 适用场景 | +| ------------------------- | ------------------------------------------------------------------------ | +| `[类型]程序错误` | 现有功能出现故障时 | +| `[类型]功能增强` | 针对现有功能提出改进建议时 | +| `[类型]求助请求` | 用户咨询配置/实现相关问题时 | +| `需技术反馈` | 出现新功能提案或API变更建议时 | +| `需补充信息` | 问题描述不明确或需要补充细节时 | +| `需测试` | 新问题需要确认,或旧错误可能已失效时 | + +**优先级标签判定** + +若您对当前报告有充分了解且具备判断信心,可考虑添加优先级标签。请注意,未标注优先级即表示普通级别。 + +| 标签 | 适用场景 | +| ------------------ | ---------------------------------------------------------------------------------------------------------------------------------------- | +| `优先级:高` | 符合当前重点方向,且导致严重使用故障(包括流程问题、视觉错误和区块故障) | +| `优先级:低` | 非重点方向的增强功能、边缘场景错误、旧版浏览器兼容问题 | \ No newline at end of file diff --git a/contributors/versions-in-wordpress.md b/contributors/versions-in-wordpress.md new file mode 100644 index 0000000..dea717d --- /dev/null +++ b/contributors/versions-in-wordpress.md @@ -0,0 +1,229 @@ +```chinese +| 4.9-5.4 | 5.2.15 | +| 4.9-5.4 | 5.2.14 | +| 4.9-5.4 | 5.2.13 | +| 4.9-5.4 | 5.2.12 | +| 4.9-5.4 | 5.2.11 | +| 4.9-5.4 | 5.2.10 | +| 4.9-5.4 | 5.2.9 | +| 4.9-5.4 | 5.2.8 | +| 4.9-5.4 | 5.2.7 | +| 4.9-5.4 | 5.2.6 | +| 4.9-5.4 | 5.2.5 | +| 4.9-5.4 | 5.2.4 | +| 4.9-5.4 | 5.2.3 | +| 4.9-5.4 | 5.2.2 | +| 4.9-5.4 | 5.2.1 | +| 4.9-5.4 | 5.2 | +| 4.8 | 5.1.19 | +| 4.8 | 5.1.18 | +| 4.8 | 5.1.17 | +| 4.8 | 5.1.16 | +| 4.8 | 5.1.15 | +| 4.8 | 5.1.14 | +| 4.8 | 5.1.13 | +| 4.8 | 5.1.12 | +| 4.8 | 5.1.11 | +| 4.8 | 5.1.10 | +| 4.8 | 5.1.9 | +| 4.8 | 5.1.8 | +| 4.8 | 5.1.7 | +| 4.8 | 5.1.6 | +| 4.8 | 5.1.5 | +| 4.8 | 5.1.4 | +| 4.8 | 5.1.3 | +| 4.8 | 5.1.2 | +| 4.8 | 5.1.1 | +| 4.8 | 5.1 | +| 4.7.1 | 5.0.22 | +| 4.7.1 | 5.0.21 | +| 4.7.1 | 5.0.20 | +| 4.7.1 | 5.0.19 | +| 4.7.1 | 5.0.18 | +| 4.7.1 | 5.0.17 | +| 4.7.1 | 5.0.16 | +| 4.7.1 | 5.0.15 | +| 4.7.1 | 5.0.14 | +| 4.7.1 | 5.0.13 | +| 4.7.1 | 5.0.12 | +| 4.7.1 | 5.0.11 | +| 4.7.1 | 5.0.10 | +| 4.7.1 | 5.0.9 | +| 4.7.1 | 5.0.8 | +| 4.7.1 | 5.0.7 | +| 4.7.1 | 5.0.6 | +| 4.7.1 | 5.0.5 | +| 4.7.1 | 5.0.4 | +| 4.7.1 | 5.0.3 | +| 4.7.0 | 5.0.2 | +| 4.6.1 | 5.0.1 | +| 4.6.1 | 5.0 | +``` + +(表格内容为版本号对应关系,无需翻译,已完整保留原始格式) + +# WordPress中的Gutenberg版本 + +每个WordPress主要版本发布时,都会包含一个新的Gutenberg版本。长期以来,这给用户在调试问题和准确报告错误时带来了困惑。为简化这一过程,我们编制了本文档,作为各WordPress主要版本集成Gutenberg版本的权威对照表。需要注意的是,在WordPress测试期间,可能会根据需要引入后续Gutenberg版本中的额外错误修复。若需了解WordPress主要版本发布说明之外各Gutenberg版本的详细更新内容,请查阅[Make Core发布的版本说明](https://make.wordpress.org/core/tag/gutenberg-new/)。 + +如发现任何错误,请在[WordPress.org Slack](https://make.wordpress.org/chat/)的#core-editor频道中提出。 + +| Gutenberg版本 | WordPress版本 | +| ----------------- | ------------- | +| 19.4-20.4 | 6.8 | +| 18.6-19.3 | 6.7.2 | +| 18.6-19.3 | 6.7.1 | +| 18.6-19.3 | 6.7 | +| 17.8-18.5 | 6.6.2 | +| 17.8-18.5 | 6.6.1 | +| 17.8-18.5 | 6.6 | +| 16.8-17.7 | 6.5.5 | +| 16.8-17.7 | 6.5.4 | +| 16.8-17.7 | 6.5.3 | +| 16.8-17.7 | 6.5.2 | +| 16.8-17.7 | 6.5 | +| 16.2-16.7 | 6.4.4 | +| 16.2-16.7 | 6.4.5 | +| 16.2-16.7 | 6.4.3 | +| 16.2-16.7 | 6.4.2 | +| 16.2-16.7 | 6.4.1 | +| 16.2-16.7 | 6.4 | +| 15.2-16.1 | 6.3.5 | +| 15.2-16.1 | 6.3.4 | +| 15.2-16.1 | 6.3.3 | +| 15.2-16.1 | 6.3.2 | +| 15.2-16.1 | 6.3.1 | +| 15.2-16.1 | 6.3 | +| 14.2-15.1 | 6.2.6 | +| 14.2-15.1 | 6.2.5 | +| 14.2-15.1 | 6.2.4 | +| 14.2-15.1 | 6.2.3 | +| 14.2-15.1 | 6.2.2 | +| 14.2-15.1 | 6.2.1 | +| 14.2-15.1 | 6.2 | +| 13.1-14.1 | 6.1.7 | +| 13.1-14.1 | 6.1.6 | +| 13.1-14.1 | 6.1.5 | +| 13.1-14.1 | 6.1.4 | +| 13.1-14.1 | 6.1.3 | +| 13.1-14.1 | 6.1.2 | +| 13.1-14.1 | 6.1.1 | +| 13.1-14.1 | 6.1 | +| 12.0-13.0 | 6.0.9 | +| 12.0-13.0 | 6.0.8 | +| 12.0-13.0 | 6.0.7 | +| 12.0-13.0 | 6.0.6 | +| 12.0-13.0 | 6.0.5 | +| 12.0-13.0 | 6.0.4 | +| 12.0-13.0 | 6.0.3 | +| 12.0-13.0 | 6.0.2 | +| 12.0-13.0 | 6.0.1 | +| 12.0-13.0 | 6.0 | +| 10.8-11.9 | 5.9.10 | +| 10.8-11.9 | 5.9.9 | +| 10.8-11.9 | 5.9.8 | +| 10.8-11.9 | 5.9.7 | +| 10.8-11.9 | 5.9.6 | +| 10.8-11.9 | 5.9.5 | +| 10.8-11.9 | 5.9.4 | +| 10.8-11.9 | 5.9.3 | +| 10.8-11.9 | 5.9.2 | +| 10.8-11.9 | 5.9.1 | +| 10.8-11.9 | 5.9 | +| 10.0-10.7 | 5.8.10 | +| 10.0-10.7 | 5.8.9 | +| 10.0-10.7 | 5.8.8 | +| 10.0-10.7 | 5.8.7 | +| 10.0-10.7 | 5.8.6 | +| 10.0-10.7 | 5.8.5 | +| 10.0-10.7 | 5.8.4 | +| 10.0-10.7 | 5.8.3 | +| 10.0-10.7 | 5.8.2 | +| 10.0-10.7 | 5.8.1 | +| 10.0-10.7 | 5.8 | +| 9.3-9.9 | 5.7.12 | +| 9.3-9.9 | 5.7.11 | +| 9.3-9.9 | 5.7.10 | +| 9.3-9.9 | 5.7.9 | +| 9.3-9.9 | 5.7.8 | +| 9.3-9.9 | 5.7.7 | +| 9.3-9.9 | 5.7.6 | +| 9.3-9.9 | 5.7.5 | +| 9.3-9.9 | 5.7.4 | +| 9.3-9.9 | 5.7.3 | +| 9.3-9.9 | 5.7.2 | +| 9.3-9.9 | 5.7.1 | +| 9.3-9.9 | 5.7 | +| 8.6-9.2 | 5.6.14 | +| 8.6-9.2 | 5.6.13 | +| 8.6-9.2 | 5.6.12 | +| 8.6-9.2 | 5.6.11 | +| 8.6-9.2 | 5.6.10 | +| 8.6-9.2 | 5.6.9 | +| 8.6-9.2 | 5.6.8 | +| 8.6-9.2 | 5.6.7 | +| 8.6-9.2 | 5.6.6 | +| 8.6-9.2 | 5.6.5 | +| 8.6-9.2 | 5.6.4 | +| 8.6-9.2 | 5.6.3 | +| 8.6-9.2 | 5.6.2 | +| 8.6-9.2 | 5.6.1 | +| 8.6-9.2 | 5.6 | +| 7.6-8.5 | 5.5.15 | +| 7.6-8.5 | 5.5.14 | +| 7.6-8.5 | 5.5.13 | +| 7.6-8.5 | 5.5.12 | +| 7.6-8.5 | 5.5.11 | +| 7.6-8.5 | 5.5.10 | +| 7.6-8.5 | 5.5.9 | +| 7.6-8.5 | 5.5.8 | +| 7.6-8.5 | 5.5.7 | +| 7.6-8.5 | 5.5.6 | +| 7.6-8.5 | 5.5.5 | +| 7.6-8.5 | 5.5.4 | +| 7.6-8.5 | 5.5.3 | +| 7.6-8.5 | 5.5.2 | +| 7.6-8.5 | 5.5.1 | +| 7.6-8.5 | 5.5 | +| 6.6-7.5 | 5.4.16 | +| 6.6-7.5 | 5.4.15 | +| 6.6-7.5 | 5.4.14 | +| 6.6-7.5 | 5.4.13 | +| 6.6-7.5 | 5.4.12 | +| 6.6-7.5 | 5.4.11 | +| 6.6-7.5 | 5.4.10 | +| 6.6-7.5 | 5.4.9 | +| 6.6-7.5 | 5.4.8 | +| 6.6-7.5 | 5.4.7 | +| 6.6-7.5 | 5.4.6 | +| 6.6-7.5 | 5.4.5 | +| 6.6-7.5 | 5.4.4 | +| 6.6-7.5 | 5.4.3 | +| 6.6-7.5 | 5.4.2 | +| 6.6-7.5 | 5.4.1 | +| 6.6-7.5 | 5.4 | +| 5.5-6.5 | 5.3.18 | +| 5.5-6.5 | 5.3.17 | +| 5.5-6.5 | 5.3.16 | +| 5.5-6.5 | 5.3.15 | +| 5.5-6.5 | 5.3.14 | +| 5.5-6.5 | 5.3.13 | +| 5.5-6.5 | 5.3.12 | +| 5.5-6.5 | 5.3.11 | +| 5.5-6.5 | 5.3.10 | +| 5.5-6.5 | 5.3.9 | +| 5.5-6.5 | 5.3.8 | +| 5.5-6.5 | 5.3.7 | +| 5.5-6.5 | 5.3.6 | +| 5.5-6.5 | 5.3.5 | +| 5.5-6.5 | 5.3.4 | +| 5.5-6.5 | 5.3.3 | +| 5.5-6.5 | 5.3.2 | +| 5.5-6.5 | 5.3.1 | +| 5.5-6.5 | 5.3 | +| 4.9-5.4 | 5.2.21 | +| 4.9-5.4 | 5.2.20 | +| 4.9-5.4 | 5.2.19 | +| 4.9-5.4 | 5.2.18 | +| 4.9-5.4 | 5.2.17 | +| 4.9-5.4 | 5.2.16 | \ No newline at end of file diff --git a/explanations/README.md b/explanations/README.md new file mode 100644 index 0000000..4380006 --- /dev/null +++ b/explanations/README.md @@ -0,0 +1,12 @@ +# 说明 + +## [架构](/docs/explanations/architecture/README.md) + +- [核心概念](/docs/explanations/architecture/key-concepts.md) +- [数据格式与数据流](/docs/explanations/architecture/data-flow.md) +- [模块化与WordPress包](/docs/explanations/architecture/modularity.md) +- [区块编辑器性能](/docs/explanations/architecture/performance.md) +- 数据模块背后的设计决策是什么? +- [为什么选择Puppeteer作为端到端测试工具?](/docs/explanations/architecture/automated-testing.md) +- [不同编辑器包之间有什么区别?每个包的用途是什么?](/docs/explanations/architecture/modularity.md#whats-the-difference-between-the-different-editor-packages-whats-the-purpose-of-each-package) +- [模板与模板部件流程](/docs/explanations/architecture/full-site-editing-templates.md) \ No newline at end of file diff --git a/explanations/architecture/README.md b/explanations/architecture/README.md new file mode 100644 index 0000000..1c353b2 --- /dev/null +++ b/explanations/architecture/README.md @@ -0,0 +1,18 @@ +# 架构 + +让我们从宏观视角审视区块编辑器及Gutenberg代码库的架构设计与用户体验原则。 + +## 编辑器 + +- [核心概念](/docs/explanations/architecture/key-concepts.md) +- [数据格式与数据流](/docs/explanations/architecture/data-flow.md) +- [实体与撤销/重做机制](/docs/explanations/architecture/entities.md) +- [全站编辑模板](/docs/explanations/architecture/full-site-editing-templates.md) +- [编辑器样式体系](/docs/explanations/architecture/styles.md) +- [性能优化](/docs/explanations/architecture/performance.md) + +## Gutenberg代码库 + +- [模块化与WordPress包管理](/docs/explanations/architecture/modularity.md) +- [代码库目录结构解析](/docs/contributors/folder-structure.md) +- **已过时!** [为何选择Puppeteer作为端到端测试工具?](/docs/explanations/architecture/automated-testing.md) \ No newline at end of file diff --git a/explanations/architecture/assets/global-styles-input-output.excalidraw b/explanations/architecture/assets/global-styles-input-output.excalidraw new file mode 100644 index 0000000..5257050 --- /dev/null +++ b/explanations/architecture/assets/global-styles-input-output.excalidraw @@ -0,0 +1,922 @@ +{ + "type": "excalidraw", + "version": 2, + "source": "https://excalidraw.com", + "elements": [ + { + "type": "rectangle", + "version": 69, + "versionNonce": 1371893668, + "isDeleted": false, + "id": "dE1I-EnEvkRXGzhdq1JtQ", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 460, + "y": 200, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 180, + "height": 100, + "seed": 1590887460, + "groupIds": [], + "strokeSharpness": "sharp", + "boundElements": [ + { + "id": "whcfsZ0hp0-cjJAqlj0ob", + "type": "arrow" + }, + { + "id": "sL5M8MOmbWBPiIMWJPKAv", + "type": "arrow" + }, + { + "id": "iOHR_Txg1Rt_gfJPeXqZg", + "type": "arrow" + }, + { + "id": "yLz66yM1_qJAWngBli08H", + "type": "arrow" + } + ], + "updated": 1643633406183 + }, + { + "type": "text", + "version": 153, + "versionNonce": 670281809, + "isDeleted": false, + "id": "5rFvoYoEiSaqNhwmlPjxf", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 500, + "y": 220, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 106, + "height": 51, + "seed": 1103877284, + "groupIds": [], + "strokeSharpness": "sharp", + "boundElements": [], + "updated": 1643794034400, + "fontSize": 20, + "fontFamily": 1, + "text": "WordPress'\ntheme.json", + "baseline": 44, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "originalText": "WordPress'\ntheme.json" + }, + { + "type": "rectangle", + "version": 61, + "versionNonce": 47826468, + "isDeleted": false, + "id": "otWLW4l8pmq6prNMU4yVj", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 700, + "y": 200, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 160, + "height": 100, + "seed": 1273608996, + "groupIds": [], + "strokeSharpness": "sharp", + "boundElements": [ + { + "id": "yLz66yM1_qJAWngBli08H", + "type": "arrow" + } + ], + "updated": 1643633405414 + }, + { + "type": "text", + "version": 128, + "versionNonce": 1069768604, + "isDeleted": false, + "id": "r1nDCG4qF6PJ6TXDA8D0W", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 720, + "y": 220, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 100, + "height": 51, + "seed": 217970972, + "groupIds": [], + "strokeSharpness": "sharp", + "boundElements": [ + { + "id": "yLz66yM1_qJAWngBli08H", + "type": "arrow" + } + ], + "updated": 1643633404909, + "fontSize": 20, + "fontFamily": 1, + "text": "theme's\ntheme.json", + "baseline": 44, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "originalText": "theme's\ntheme.json" + }, + { + "type": "rectangle", + "version": 161, + "versionNonce": 516967708, + "isDeleted": false, + "id": "uyFiLHMSH6y7H-s3quAxV", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 920, + "y": 40, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 200, + "height": 80, + "seed": 412676132, + "groupIds": [], + "strokeSharpness": "sharp", + "boundElements": [ + { + "id": "rAP823QGa675R6VNN5_Fj", + "type": "arrow" + }, + { + "id": "zqYpQBYVH8MUQ1gbny0DW", + "type": "arrow" + }, + { + "id": "bZAzg4sYJCkziajbnNelc", + "type": "arrow" + }, + { + "id": "Gf8rkiJMUzlf_neDMa8Uo", + "type": "arrow" + } + ], + "updated": 1643633370772 + }, + { + "type": "text", + "version": 252, + "versionNonce": 788243108, + "isDeleted": false, + "id": "2fO2-BoHTfIU44g9j0n-S", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 940, + "y": 60, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 165, + "height": 51, + "seed": 231248924, + "groupIds": [], + "strokeSharpness": "sharp", + "boundElements": [ + { + "id": "Gf8rkiJMUzlf_neDMa8Uo", + "type": "arrow" + }, + { + "id": "RXAV87dIe7h-TdMpPkj8G", + "type": "arrow" + } + ], + "updated": 1643633370772, + "fontSize": 20, + "fontFamily": 1, + "text": "Global styles UI\nin site editor", + "baseline": 44, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "originalText": "Global styles UI\nin site editor" + }, + { + "type": "rectangle", + "version": 85, + "versionNonce": 1702175071, + "isDeleted": false, + "id": "hewO7U2_RQB8vdU_-mPPB", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 580, + "y": 660, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 340, + "height": 100, + "seed": 726898972, + "groupIds": [], + "strokeSharpness": "sharp", + "boundElements": [ + { + "id": "ccK0gnrFParTub-zbOTR2", + "type": "arrow" + }, + { + "id": "eP2wDSsiTc_n6dPEvV37C", + "type": "arrow" + } + ], + "updated": 1643794081747 + }, + { + "type": "text", + "version": 117, + "versionNonce": 581323025, + "isDeleted": false, + "id": "eU8Kg0BNmsuuDOweR3g88", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 660, + "y": 700, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 215, + "height": 26, + "seed": 404952740, + "groupIds": [], + "strokeSharpness": "sharp", + "boundElements": [], + "updated": 1643794068178, + "fontSize": 20, + "fontFamily": 1, + "text": "global-styles-inline-css", + "baseline": 18, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "originalText": "global-styles-inline-css" + }, + { + "type": "text", + "version": 13, + "versionNonce": 1960935025, + "isDeleted": false, + "id": "CnDgoEZ_UKEJ24A9Y8p-H", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 597, + "y": 667, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 38, + "height": 26, + "seed": 2110409124, + "groupIds": [], + "strokeSharpness": "sharp", + "boundElements": [], + "updated": 1643794076348, + "fontSize": 20, + "fontFamily": 1, + "text": "CSS", + "baseline": 18, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "originalText": "CSS" + }, + { + "type": "rectangle", + "version": 134, + "versionNonce": 827573105, + "isDeleted": false, + "id": "ymhNrWoCwMcy97bV3wcKR", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 480, + "y": 440, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 600.0000000000001, + "height": 60, + "seed": 856798492, + "groupIds": [], + "strokeSharpness": "sharp", + "boundElements": [ + { + "id": "rAP823QGa675R6VNN5_Fj", + "type": "arrow" + }, + { + "id": "zqYpQBYVH8MUQ1gbny0DW", + "type": "arrow" + }, + { + "id": "whcfsZ0hp0-cjJAqlj0ob", + "type": "arrow" + }, + { + "id": "bZAzg4sYJCkziajbnNelc", + "type": "arrow" + }, + { + "id": "sL5M8MOmbWBPiIMWJPKAv", + "type": "arrow" + }, + { + "id": "3BPPljmfDBwi9j0UFo72A", + "type": "arrow" + }, + { + "id": "iOHR_Txg1Rt_gfJPeXqZg", + "type": "arrow" + }, + { + "id": "Vb1dPpA_zGw4ibPc2H8XD", + "type": "arrow" + }, + { + "id": "4EUXGzEm8Fy3BBuebotFc", + "type": "arrow" + }, + { + "id": "celRUoUyUQIT-gGhAXyEK", + "type": "arrow" + }, + { + "id": "yLz66yM1_qJAWngBli08H", + "type": "arrow" + } + ], + "updated": 1643794050979 + }, + { + "type": "arrow", + "version": 761, + "versionNonce": 2121815743, + "isDeleted": false, + "id": "Vb1dPpA_zGw4ibPc2H8XD", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 779.6809331076444, + "y": 299.60856638674625, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 2.816506265121575, + "height": 132.78830939921954, + "seed": 1207657756, + "groupIds": [], + "strokeSharpness": "round", + "boundElements": [], + "updated": 1643794051290, + "startBinding": null, + "endBinding": { + "elementId": "ymhNrWoCwMcy97bV3wcKR", + "gap": 7.603124214034267, + "focus": 0.010960154267448993 + }, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": "arrow", + "points": [ + [ + 0, + 0 + ], + [ + 2.816506265121575, + 132.78830939921954 + ] + ] + }, + { + "type": "line", + "version": 116, + "versionNonce": 2071091108, + "isDeleted": false, + "id": "uIhYulJsyst7aUpzTX4go", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "dotted", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 340.5942201212048, + "y": 361.19947067946197, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 916.0579537755439, + "height": 0, + "seed": 1575735580, + "groupIds": [], + "strokeSharpness": "round", + "boundElements": [], + "updated": 1643633370772, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + 916.0579537755439, + 0 + ] + ] + }, + { + "type": "text", + "version": 57, + "versionNonce": 1217366463, + "isDeleted": false, + "id": "siLfF4byWfGnkekw1DNmu", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "dotted", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 380, + "y": 40, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 68, + "height": 26, + "seed": 941782436, + "groupIds": [], + "strokeSharpness": "sharp", + "boundElements": [], + "updated": 1643794093388, + "fontSize": 20, + "fontFamily": 1, + "text": "INPUT", + "baseline": 18, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "originalText": "INPUT" + }, + { + "type": "line", + "version": 155, + "versionNonce": 571458340, + "isDeleted": false, + "id": "2EOVSOSu-XvcXmsarGRNX", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "dotted", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 340.31421109884974, + "y": 599.7727356031537, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 916.0579537755439, + "height": 0, + "seed": 482945060, + "groupIds": [], + "strokeSharpness": "round", + "boundElements": [], + "updated": 1643633370772, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + 916.0579537755439, + 0 + ] + ] + }, + { + "type": "text", + "version": 65, + "versionNonce": 438071580, + "isDeleted": false, + "id": "iqUBVRUA4jHj817dlF-oa", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "dotted", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 360, + "y": 640, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 89, + "height": 26, + "seed": 578631196, + "groupIds": [], + "strokeSharpness": "sharp", + "boundElements": [], + "updated": 1643633370772, + "fontSize": 20, + "fontFamily": 1, + "text": "OUTPUT", + "baseline": 18, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "originalText": "OUTPUT" + }, + { + "type": "text", + "version": 160, + "versionNonce": 2029239972, + "isDeleted": false, + "id": "2Zb3N-iXP0MEwBqCay37k", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "dotted", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 520, + "y": 460, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 505, + "height": 26, + "seed": 934136228, + "groupIds": [], + "strokeSharpness": "sharp", + "boundElements": [], + "updated": 1643633370772, + "fontSize": 20, + "fontFamily": 1, + "text": "Internal Representation (WP_Theme_JSON object)", + "baseline": 18, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "originalText": "Internal Representation (WP_Theme_JSON object)" + }, + { + "type": "arrow", + "version": 694, + "versionNonce": 1024752273, + "isDeleted": false, + "id": "celRUoUyUQIT-gGhAXyEK", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 779.718628924312, + "y": 501, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 1.0791064091945373, + "height": 126.66251027234523, + "seed": 2100655268, + "groupIds": [], + "strokeSharpness": "round", + "boundElements": [], + "updated": 1643794051290, + "startBinding": { + "elementId": "ymhNrWoCwMcy97bV3wcKR", + "gap": 1, + "focus": 0.0018167083707156541 + }, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": "arrow", + "points": [ + [ + 0, + 0 + ], + [ + 1.0791064091945373, + 126.66251027234523 + ] + ] + }, + { + "type": "rectangle", + "version": 179, + "versionNonce": 1901269284, + "isDeleted": false, + "id": "4IJppZHOQRDTcVw-pjt1z", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 920, + "y": 200, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 180, + "height": 100, + "seed": 1157165980, + "groupIds": [], + "strokeSharpness": "sharp", + "boundElements": [ + { + "id": "rAP823QGa675R6VNN5_Fj", + "type": "arrow" + }, + { + "id": "zqYpQBYVH8MUQ1gbny0DW", + "type": "arrow" + }, + { + "id": "bZAzg4sYJCkziajbnNelc", + "type": "arrow" + }, + { + "id": "3BPPljmfDBwi9j0UFo72A", + "type": "arrow" + }, + { + "id": "4EUXGzEm8Fy3BBuebotFc", + "type": "arrow" + }, + { + "id": "Gf8rkiJMUzlf_neDMa8Uo", + "type": "arrow" + }, + { + "id": "RXAV87dIe7h-TdMpPkj8G", + "type": "arrow" + } + ], + "updated": 1643633370772 + }, + { + "type": "text", + "version": 289, + "versionNonce": 880880164, + "isDeleted": false, + "id": "CA1UwOP4UrmE6pla-3RZT", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 960, + "y": 220, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 100, + "height": 51, + "seed": 1509336100, + "groupIds": [], + "strokeSharpness": "sharp", + "boundElements": [ + { + "id": "RXAV87dIe7h-TdMpPkj8G", + "type": "arrow" + } + ], + "updated": 1643633398374, + "fontSize": 20, + "fontFamily": 1, + "text": "user's\ntheme.json", + "baseline": 44, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "originalText": "user's\ntheme.json" + }, + { + "type": "text", + "version": 84, + "versionNonce": 773068836, + "isDeleted": false, + "id": "loyhrrk-dULHcklwEwSld", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "dotted", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 360, + "y": 380, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 118, + "height": 26, + "seed": 1415224092, + "groupIds": [], + "strokeSharpness": "sharp", + "boundElements": [], + "updated": 1643633370773, + "fontSize": 20, + "fontFamily": 1, + "text": "INTERNALS", + "baseline": 18, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "originalText": "INTERNALS" + }, + { + "type": "arrow", + "version": 878, + "versionNonce": 400369375, + "isDeleted": false, + "id": "4EUXGzEm8Fy3BBuebotFc", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1020.5557521929989, + "y": 301, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 0.8847309846728422, + "height": 129, + "seed": 1086156700, + "groupIds": [], + "strokeSharpness": "round", + "boundElements": [], + "updated": 1643794051290, + "startBinding": { + "elementId": "4IJppZHOQRDTcVw-pjt1z", + "gap": 1, + "focus": -0.12071261013586085 + }, + "endBinding": { + "elementId": "ymhNrWoCwMcy97bV3wcKR", + "gap": 10.000000000000002, + "focus": 0.7974420373674143 + }, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": "arrow", + "points": [ + [ + 0, + 0 + ], + [ + -0.8847309846728422, + 129 + ] + ] + }, + { + "type": "arrow", + "version": 20, + "versionNonce": 39323804, + "isDeleted": false, + "id": "RXAV87dIe7h-TdMpPkj8G", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1020.9070453807711, + "y": 120.28604858589358, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 0.9070453807711374, + "height": 59.71395141410642, + "seed": 748070564, + "groupIds": [], + "strokeSharpness": "round", + "boundElements": [], + "updated": 1643633370773, + "startBinding": { + "elementId": "2fO2-BoHTfIU44g9j0n-S", + "focus": 0.012843458210903786, + "gap": 9.286048585893582 + }, + "endBinding": { + "elementId": "4IJppZHOQRDTcVw-pjt1z", + "focus": 0.09846585725336464, + "gap": 20 + }, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": "arrow", + "points": [ + [ + 0, + 0 + ], + [ + -0.9070453807711374, + 59.71395141410642 + ] + ] + }, + { + "type": "arrow", + "version": 897, + "versionNonce": 44630129, + "isDeleted": false, + "id": "yLz66yM1_qJAWngBli08H", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 540.1474954442242, + "y": 301, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 3.013965627388984, + "height": 131.59259259259267, + "seed": 1185633948, + "groupIds": [], + "strokeSharpness": "round", + "boundElements": [], + "updated": 1643794051290, + "startBinding": { + "elementId": "dE1I-EnEvkRXGzhdq1JtQ", + "gap": 1, + "focus": 0.1209125431173364 + }, + "endBinding": { + "elementId": "ymhNrWoCwMcy97bV3wcKR", + "gap": 7.407407407407392, + "focus": -0.7848083884682261 + }, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": "arrow", + "points": [ + [ + 0, + 0 + ], + [ + 3.013965627388984, + 131.59259259259267 + ] + ] + } + ], + "appState": { + "gridSize": 20, + "viewBackgroundColor": "#ffffff" + }, + "files": {} +} \ No newline at end of file diff --git a/explanations/architecture/assets/global-styles-input-output.png b/explanations/architecture/assets/global-styles-input-output.png new file mode 100644 index 0000000..ecfcdc6 Binary files /dev/null and b/explanations/architecture/assets/global-styles-input-output.png differ diff --git a/explanations/architecture/assets/modules.excalidraw b/explanations/architecture/assets/modules.excalidraw new file mode 100644 index 0000000..86f168e --- /dev/null +++ b/explanations/architecture/assets/modules.excalidraw @@ -0,0 +1,1946 @@ +{ + "type": "excalidraw", + "version": 2, + "source": "https://excalidraw.com", + "elements": [ + { + "type": "rectangle", + "version": 360, + "versionNonce": 330848466, + "isDeleted": false, + "id": "lUtT4B0Dcbh1jZWTf-Pmx", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 538, + "y": -24, + "strokeColor": "#000000", + "backgroundColor": "#15aabf", + "width": 296.99999999999994, + "height": 184, + "seed": 42288654, + "groupIds": [], + "strokeSharpness": "sharp", + "boundElementIds": [ + "QLn4BR5QLdchLbbb7y3fb", + "TzpUf-tsg6VY1wgAzUtsW" + ] + }, + { + "type": "text", + "version": 241, + "versionNonce": 1697895758, + "isDeleted": false, + "id": "h4SvuiM0FqPyeGvKT_Cgq", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 634, + "y": 42, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 110, + "height": 50, + "seed": 2011405010, + "groupIds": [], + "strokeSharpness": "sharp", + "boundElementIds": [], + "fontSize": 20, + "fontFamily": 1, + "text": "WordPress \nREST API", + "baseline": 43, + "textAlign": "left", + "verticalAlign": "top" + }, + { + "type": "rectangle", + "version": 305, + "versionNonce": 412708498, + "isDeleted": false, + "id": "McdqQdNimFO5S_blWnGqe", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 608.5, + "y": 239, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 181, + "height": 134, + "seed": 791925842, + "groupIds": [], + "strokeSharpness": "sharp", + "boundElementIds": [ + "QLn4BR5QLdchLbbb7y3fb", + "TzpUf-tsg6VY1wgAzUtsW", + "zBZdXkTFmJO9KXJqxovCk", + "KF9YfK1TYP8afMie5CvX_", + "80vcK5NnLsmPE0vh6TArx", + "dLVJH9bDssWAtSq5-YWqQ", + "9CDIL9ezNU7U6jmKxIj6S", + "s0syZkTZO3RyvCl68ZEKu" + ] + }, + { + "type": "arrow", + "version": 926, + "versionNonce": 1807571406, + "isDeleted": false, + "id": "TzpUf-tsg6VY1wgAzUtsW", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 697.7421379211554, + "y": 237.62686567164178, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 1.342798060683208, + "height": 76.25373134328356, + "seed": 2056431630, + "groupIds": [], + "strokeSharpness": "round", + "boundElementIds": [], + "startBinding": { + "elementId": "McdqQdNimFO5S_blWnGqe", + "gap": 1.3731343283582096, + "focus": -0.002036659877800407 + }, + "endBinding": { + "elementId": "lUtT4B0Dcbh1jZWTf-Pmx", + "gap": 1.3731343283582096, + "focus": -0.054989816700611 + }, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + -1.342798060683208, + -76.25373134328356 + ] + ] + }, + { + "type": "rectangle", + "version": 1777, + "versionNonce": 1018720786, + "isDeleted": false, + "id": "3brGgyPnliY43-VLkQbVO", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 362, + "y": 502, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 184.00000000000003, + "height": 137, + "seed": 1484302030, + "groupIds": [], + "strokeSharpness": "sharp", + "boundElementIds": [ + "qYtX-Dbe48WGy5NUs2SWN", + "bpFYMjIuLwNHKYoF5sP1m", + "zBZdXkTFmJO9KXJqxovCk", + "zDwPJzeKWLvw4E2gXDD7h", + "tF0J9QyFKzLm643LknUSp", + "2QB5PnOHSjpZxpfsPWK9N", + "d0bomk6ntAhDkzr1jutsJ", + "vjN8YKa1-pss_8nUBz9sE", + "KF9YfK1TYP8afMie5CvX_", + "80vcK5NnLsmPE0vh6TArx", + "f62E6s_57Je1ZPUXcCtX_", + "9CDIL9ezNU7U6jmKxIj6S", + "s0syZkTZO3RyvCl68ZEKu", + "UJjYgRCPkN8MK54EHuFBW" + ] + }, + { + "type": "text", + "version": 956, + "versionNonce": 1590489102, + "isDeleted": false, + "id": "inFwWqiXEHuINtuaBxfEa", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 374, + "y": 560, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 168, + "height": 20, + "seed": 1576359634, + "groupIds": [], + "strokeSharpness": "sharp", + "boundElementIds": [ + "-rW8RlMAg9krSeGI2WgDY", + "bpFYMjIuLwNHKYoF5sP1m" + ], + "fontSize": 16, + "fontFamily": 1, + "text": "@wordpress/edit-post", + "baseline": 14, + "textAlign": "center", + "verticalAlign": "middle" + }, + { + "type": "rectangle", + "version": 985, + "versionNonce": 680301970, + "isDeleted": false, + "id": "pDgrhb9xTc--X-w4JYTNs", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 977, + "y": 820, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 187.99999999999994, + "height": 144, + "seed": 1937873106, + "groupIds": [], + "strokeSharpness": "sharp", + "boundElementIds": [ + "zDwPJzeKWLvw4E2gXDD7h", + "tF0J9QyFKzLm643LknUSp", + "-Xd-x5jHExxdRzXBxuDgG" + ] + }, + { + "type": "text", + "version": 353, + "versionNonce": 133114699, + "isDeleted": false, + "id": "MkPnoYQafrQJx2pxVJ1Yj", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 627, + "y": 294, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 136, + "height": 20, + "seed": 476050446, + "groupIds": [], + "strokeSharpness": "sharp", + "boundElementIds": [], + "fontSize": 16, + "fontFamily": 1, + "text": "@wordpress/data", + "baseline": 14, + "textAlign": "center", + "verticalAlign": "middle" + }, + { + "type": "text", + "version": 235, + "versionNonce": 2104689358, + "isDeleted": false, + "id": "10Fa43h0aaCGAOmTuy4M9", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 511, + "y": 189, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 161, + "height": 20, + "seed": 928272786, + "groupIds": [], + "strokeSharpness": "sharp", + "boundElementIds": [], + "fontSize": 16, + "fontFamily": 1, + "text": "@wordpress/apiFetch", + "baseline": 14, + "textAlign": "left", + "verticalAlign": "top" + }, + { + "type": "text", + "version": 1035, + "versionNonce": 156096786, + "isDeleted": false, + "id": "dkdIk6pT4WXxVzPRoShAL", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 987, + "y": 885, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 171, + "height": 18.387096774193544, + "seed": 1424271950, + "groupIds": [], + "strokeSharpness": "sharp", + "boundElementIds": [], + "fontSize": 14.709677419354838, + "fontFamily": 1, + "text": "@wordpress/block-editor", + "baseline": 12.387096774193544, + "textAlign": "center", + "verticalAlign": "top" + }, + { + "type": "text", + "version": 546, + "versionNonce": 2036521554, + "isDeleted": false, + "id": "UvOX7k0ZNJko4MmagViJb", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 345, + "y": 244, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 69, + "height": 60, + "seed": 436020174, + "groupIds": [], + "strokeSharpness": "sharp", + "boundElementIds": [], + "fontSize": 16, + "fontFamily": 1, + "text": "retrieve \nedited\n post", + "baseline": 54, + "textAlign": "left", + "verticalAlign": "top" + }, + { + "type": "rectangle", + "version": 1778, + "versionNonce": 947372050, + "isDeleted": false, + "id": "HDpe1eQvOQBy-DQ-08BAe", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 256, + "y": 820.5, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 184.00000000000003, + "height": 137, + "seed": 780465742, + "groupIds": [], + "strokeSharpness": "sharp", + "boundElementIds": [ + "qYtX-Dbe48WGy5NUs2SWN", + "-rW8RlMAg9krSeGI2WgDY", + "bpFYMjIuLwNHKYoF5sP1m", + "9yv_MGYQYvx5-mDLvir57", + "losiO7bv6zt5N836LnRRO" + ] + }, + { + "type": "text", + "version": 1561, + "versionNonce": 2079425038, + "isDeleted": false, + "id": "m4OzW-ennijYUZh1fXM_Z", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 278, + "y": 876, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 142, + "height": 20, + "seed": 227576274, + "groupIds": [], + "strokeSharpness": "sharp", + "boundElementIds": [], + "fontSize": 16, + "fontFamily": 1, + "text": "@wordpress/blocks", + "baseline": 14, + "textAlign": "center", + "verticalAlign": "middle" + }, + { + "type": "text", + "version": 1216, + "versionNonce": 995124114, + "isDeleted": false, + "id": "HJmrroFIvv3NtW-04tPnv", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 453, + "y": 806, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 160, + "height": 40, + "seed": 353140818, + "groupIds": [], + "strokeSharpness": "sharp", + "boundElementIds": [ + "-rW8RlMAg9krSeGI2WgDY" + ], + "fontSize": 16, + "fontFamily": 1, + "text": "parse post content \nas blocks", + "baseline": 34, + "textAlign": "left", + "verticalAlign": "top" + }, + { + "type": "arrow", + "version": 997, + "versionNonce": 1664158478, + "isDeleted": false, + "id": "zDwPJzeKWLvw4E2gXDD7h", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 802.1288386374247, + "y": 873.2154561884701, + "strokeColor": "#000000", + "backgroundColor": "#15aabf", + "width": 162, + "height": 0.835325367748851, + "seed": 841320402, + "groupIds": [], + "strokeSharpness": "round", + "boundElementIds": [], + "startBinding": { + "elementId": "IbjzKI7nkNZcIk0emr1CM", + "focus": -0.2525685281587491, + "gap": 11.128838637424678 + }, + "endBinding": { + "elementId": "aQhRkVVPjCbceVVmXJAqr", + "focus": -1.3049652210649765, + "gap": 7.050781556218908 + }, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": "arrow", + "points": [ + [ + 0, + 0 + ], + [ + 162, + 0.835325367748851 + ] + ] + }, + { + "type": "text", + "version": 667, + "versionNonce": 1820554450, + "isDeleted": false, + "id": "aQhRkVVPjCbceVVmXJAqr", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 804, + "y": 827, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 160, + "height": 40, + "seed": 105484306, + "groupIds": [], + "strokeSharpness": "sharp", + "boundElementIds": [ + "zDwPJzeKWLvw4E2gXDD7h" + ], + "fontSize": 16, + "fontFamily": 1, + "text": "render block list to \nedit parsed content", + "baseline": 34, + "textAlign": "left", + "verticalAlign": "top" + }, + { + "type": "arrow", + "version": 744, + "versionNonce": 658012494, + "isDeleted": false, + "id": "tF0J9QyFKzLm643LknUSp", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 963.1446744404734, + "y": 917.3370618394133, + "strokeColor": "#000000", + "backgroundColor": "#15aabf", + "width": 164.4152128934861, + "height": 0.9076979169312835, + "seed": 2061209550, + "groupIds": [], + "strokeSharpness": "round", + "boundElementIds": [], + "startBinding": { + "elementId": "pDgrhb9xTc--X-w4JYTNs", + "gap": 13.855325559526591, + "focus": -0.34117533294611724 + }, + "endBinding": { + "elementId": "IbjzKI7nkNZcIk0emr1CM", + "gap": 7.729461546987295, + "focus": 0.38320132234349474 + }, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": "arrow", + "points": [ + [ + 0, + 0 + ], + [ + -164.4152128934861, + 0.9076979169312835 + ] + ] + }, + { + "type": "text", + "version": 732, + "versionNonce": 1637943954, + "isDeleted": false, + "id": "N6ghE6kmiiSIw-G2RF4cR", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 827, + "y": 926, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 102, + "height": 20, + "seed": 1489153550, + "groupIds": [], + "strokeSharpness": "sharp", + "boundElementIds": [], + "fontSize": 16, + "fontFamily": 1, + "text": "edited blocks", + "baseline": 14, + "textAlign": "left", + "verticalAlign": "top" + }, + { + "type": "rectangle", + "version": 530, + "versionNonce": 1287135118, + "isDeleted": false, + "id": "ABPY44UfhcdQ5WnUXgCE9", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 159.5, + "y": -24, + "strokeColor": "#000000", + "backgroundColor": "#15aabf", + "width": 296.99999999999994, + "height": 184, + "seed": 572741266, + "groupIds": [], + "strokeSharpness": "sharp", + "boundElementIds": [ + "QLn4BR5QLdchLbbb7y3fb", + "TzpUf-tsg6VY1wgAzUtsW", + "U0zFx--iW_PEJin8uKAPt", + "vjN8YKa1-pss_8nUBz9sE" + ] + }, + { + "type": "text", + "version": 354, + "versionNonce": 91470955, + "isDeleted": false, + "id": "q_7-RjILSxs3tZuaDB5jH", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 268, + "y": 69, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 78, + "height": 25, + "seed": 1690690322, + "groupIds": [], + "strokeSharpness": "sharp", + "boundElementIds": [], + "fontSize": 20, + "fontFamily": 1, + "text": "post.php", + "baseline": 18, + "textAlign": "left", + "verticalAlign": "top" + }, + { + "type": "text", + "version": 391, + "versionNonce": 629739026, + "isDeleted": false, + "id": "XDocQzG8AkRvf6lr9JtEc", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 263, + "y": 34, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 79, + "height": 25, + "seed": 1339152270, + "groupIds": [], + "strokeSharpness": "sharp", + "boundElementIds": [], + "fontSize": 20, + "fontFamily": 1, + "text": "WPAdmin", + "baseline": 18, + "textAlign": "left", + "verticalAlign": "top" + }, + { + "type": "arrow", + "version": 1564, + "versionNonce": 317075858, + "isDeleted": false, + "id": "vjN8YKa1-pss_8nUBz9sE", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 308.04521862589274, + "y": 162.98092819589388, + "strokeColor": "#000000", + "backgroundColor": "#15aabf", + "width": 38.95472061044086, + "height": 382.9225584793661, + "seed": 2041473806, + "groupIds": [], + "strokeSharpness": "sharp", + "boundElementIds": [], + "startBinding": { + "elementId": "ABPY44UfhcdQ5WnUXgCE9", + "focus": 0.011258377745750744, + "gap": 2.980928195893881 + }, + "endBinding": { + "elementId": "3brGgyPnliY43-VLkQbVO", + "focus": 0.44833062939390156, + "gap": 15.000060763666397 + }, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": "arrow", + "points": [ + [ + 0, + 0 + ], + [ + 6.998887874642407, + 382.9225584793661 + ], + [ + 38.95472061044086, + 380.9229339041349 + ] + ] + }, + { + "type": "text", + "version": 509, + "versionNonce": 452782222, + "isDeleted": false, + "id": "O_dGAVA2Ad4VVa0q-TO85", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 171, + "y": 317, + "strokeColor": "#000000", + "backgroundColor": "#15aabf", + "width": 122, + "height": 40, + "seed": 1077528850, + "groupIds": [], + "strokeSharpness": "sharp", + "boundElementIds": [], + "fontSize": 16, + "fontFamily": 1, + "text": "Initialize \nthe post editor", + "baseline": 34, + "textAlign": "left", + "verticalAlign": "top" + }, + { + "type": "rectangle", + "version": 1362, + "versionNonce": 1057227474, + "isDeleted": false, + "id": "IbjzKI7nkNZcIk0emr1CM", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 613, + "y": 819, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 178, + "height": 144, + "seed": 2127531218, + "groupIds": [], + "strokeSharpness": "sharp", + "boundElementIds": [ + "zDwPJzeKWLvw4E2gXDD7h", + "tF0J9QyFKzLm643LknUSp", + "9yv_MGYQYvx5-mDLvir57", + "losiO7bv6zt5N836LnRRO", + "f62E6s_57Je1ZPUXcCtX_", + "dLVJH9bDssWAtSq5-YWqQ", + "d1SMxBnX-sncsVzKwbEew" + ] + }, + { + "type": "text", + "version": 1401, + "versionNonce": 625986382, + "isDeleted": false, + "id": "t6Xj5NZnKRCvlxEmVINsR", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 634.5, + "y": 883, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 134, + "height": 19, + "seed": 1096409422, + "groupIds": [], + "strokeSharpness": "sharp", + "boundElementIds": [], + "fontSize": 14.709677419354838, + "fontFamily": 1, + "text": "@wordpress/editor", + "baseline": 13, + "textAlign": "center", + "verticalAlign": "top" + }, + { + "type": "arrow", + "version": 45, + "versionNonce": 1476421188, + "isDeleted": false, + "id": "9yv_MGYQYvx5-mDLvir57", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 613, + "y": 859, + "strokeColor": "#000000", + "backgroundColor": "#15aabf", + "width": 171, + "height": 0, + "seed": 2012384910, + "groupIds": [], + "strokeSharpness": "sharp", + "boundElementIds": [], + "startBinding": { + "elementId": "IbjzKI7nkNZcIk0emr1CM", + "focus": 0.4444444444444444, + "gap": 1 + }, + "endBinding": { + "elementId": "HDpe1eQvOQBy-DQ-08BAe", + "focus": -0.43795620437956206, + "gap": 2 + }, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": "arrow", + "points": [ + [ + 0, + 0 + ], + [ + -171, + 0 + ] + ] + }, + { + "type": "arrow", + "version": 109, + "versionNonce": 1934823420, + "isDeleted": false, + "id": "losiO7bv6zt5N836LnRRO", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 610.5, + "y": 912, + "strokeColor": "#000000", + "backgroundColor": "#15aabf", + "width": 165.88295575603843, + "height": 1.157299730926752, + "seed": 524900050, + "groupIds": [], + "strokeSharpness": "sharp", + "boundElementIds": [], + "startBinding": { + "elementId": "IbjzKI7nkNZcIk0emr1CM", + "focus": -0.2979631757573413, + "gap": 2.5 + }, + "endBinding": { + "elementId": "HDpe1eQvOQBy-DQ-08BAe", + "focus": 0.30616250689753877, + "gap": 4.617044243961573 + }, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": "arrow", + "points": [ + [ + 0, + 0 + ], + [ + -165.88295575603843, + -1.157299730926752 + ] + ] + }, + { + "type": "text", + "version": 1264, + "versionNonce": 742061650, + "isDeleted": false, + "id": "zG5RvICD4F3YcU6Gps0N3", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 456.5, + "y": 922, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 127, + "height": 40, + "seed": 759868622, + "groupIds": [], + "strokeSharpness": "sharp", + "boundElementIds": [ + "-rW8RlMAg9krSeGI2WgDY", + "bpFYMjIuLwNHKYoF5sP1m" + ], + "fontSize": 16, + "fontFamily": 1, + "text": "serialize blocks \nto post content", + "baseline": 34, + "textAlign": "left", + "verticalAlign": "top" + }, + { + "type": "arrow", + "version": 328, + "versionNonce": 300702738, + "isDeleted": false, + "id": "f62E6s_57Je1ZPUXcCtX_", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 553, + "y": 609.2283258680051, + "strokeColor": "#000000", + "backgroundColor": "#15aabf", + "width": 86.18601002588642, + "height": 208.08736954660247, + "seed": 51212302, + "groupIds": [], + "strokeSharpness": "sharp", + "boundElementIds": [], + "startBinding": { + "elementId": "3brGgyPnliY43-VLkQbVO", + "focus": 0.6038522340716648, + "gap": 7 + }, + "endBinding": { + "elementId": "IbjzKI7nkNZcIk0emr1CM", + "focus": -0.7057751682484675, + "gap": 6.912630453397469 + }, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": "arrow", + "points": [ + [ + 0, + 0 + ], + [ + 86.18601002588642, + -5.228325868005087 + ], + [ + 86.18601002588642, + 202.85904367859737 + ] + ] + }, + { + "type": "text", + "version": 154, + "versionNonce": 379889166, + "isDeleted": false, + "id": "QvLT5BI50g8KCKtxx1mb5", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 447, + "y": 689, + "strokeColor": "#000000", + "backgroundColor": "#15aabf", + "width": 184, + "height": 40, + "seed": 1480368786, + "groupIds": [], + "strokeSharpness": "sharp", + "boundElementIds": [], + "fontSize": 16, + "fontFamily": 1, + "text": "Render components \nto edit post properties", + "baseline": 34, + "textAlign": "left", + "verticalAlign": "top" + }, + { + "type": "arrow", + "version": 137, + "versionNonce": 267061714, + "isDeleted": false, + "id": "dLVJH9bDssWAtSq5-YWqQ", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 717.6920056946436, + "y": 812.0511368080635, + "strokeColor": "#000000", + "backgroundColor": "#15aabf", + "width": 0.6920056946436262, + "height": 436.05113680806346, + "seed": 465217294, + "groupIds": [], + "strokeSharpness": "sharp", + "boundElementIds": [], + "startBinding": { + "elementId": "IbjzKI7nkNZcIk0emr1CM", + "focus": 0.1774945524835427, + "gap": 6.948863191936539 + }, + "endBinding": { + "elementId": "McdqQdNimFO5S_blWnGqe", + "focus": -0.19743556130360193, + "gap": 3 + }, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": "arrow", + "points": [ + [ + 0, + 0 + ], + [ + -0.6920056946436262, + -436.05113680806346 + ] + ] + }, + { + "type": "text", + "version": 708, + "versionNonce": 2139141198, + "isDeleted": false, + "id": "6CR4zY_a8ShNCI3k8WOMF", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 741.5, + "y": 533, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 94, + "height": 40, + "seed": 1757731730, + "groupIds": [], + "strokeSharpness": "sharp", + "boundElementIds": [], + "fontSize": 16, + "fontFamily": 1, + "text": "make edits \nto the post", + "baseline": 34, + "textAlign": "left", + "verticalAlign": "top" + }, + { + "type": "arrow", + "version": 243, + "versionNonce": 1028521874, + "isDeleted": false, + "id": "9CDIL9ezNU7U6jmKxIj6S", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 408, + "y": 495, + "strokeColor": "#000000", + "backgroundColor": "#15aabf", + "width": 202, + "height": 220, + "seed": 1955539602, + "groupIds": [], + "strokeSharpness": "sharp", + "boundElementIds": [], + "startBinding": { + "elementId": "3brGgyPnliY43-VLkQbVO", + "focus": -0.48389621188036475, + "gap": 7 + }, + "endBinding": { + "elementId": "McdqQdNimFO5S_blWnGqe", + "focus": 0.41370761939482315, + "gap": 1.5 + }, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": "arrow", + "points": [ + [ + 0, + 0 + ], + [ + -3, + -220 + ], + [ + 199, + -218 + ] + ] + }, + { + "type": "arrow", + "version": 469, + "versionNonce": 104921742, + "isDeleted": false, + "id": "s0syZkTZO3RyvCl68ZEKu", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 475.0748849169114, + "y": 495.9067426418228, + "strokeColor": "#000000", + "backgroundColor": "#15aabf", + "width": 125.70072406543284, + "height": 170.74647458243362, + "seed": 1064223054, + "groupIds": [], + "strokeSharpness": "sharp", + "boundElementIds": [], + "startBinding": { + "elementId": "3brGgyPnliY43-VLkQbVO", + "focus": 0.23601826970336381, + "gap": 6.093257358177198 + }, + "endBinding": { + "elementId": "McdqQdNimFO5S_blWnGqe", + "focus": -0.32221509826026673, + "gap": 9.591233454271105 + }, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": "arrow", + "points": [ + [ + 0, + 0 + ], + [ + -1.866842436615339, + -170.74647458243362 + ], + [ + 123.8338816288175, + -169.1942339044115 + ] + ] + }, + { + "type": "text", + "version": 635, + "versionNonce": 910966098, + "isDeleted": false, + "id": "fH8Zs_ID5t56EDgibwfUm", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 438.5, + "y": 297, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 104, + "height": 20, + "seed": 1170144978, + "groupIds": [], + "strokeSharpness": "sharp", + "boundElementIds": [], + "fontSize": 16, + "fontFamily": 1, + "text": "save changes", + "baseline": 14, + "textAlign": "left", + "verticalAlign": "top" + }, + { + "type": "rectangle", + "version": 2131, + "versionNonce": 1369340110, + "isDeleted": false, + "id": "K7VFWYXQ24D2SHN5g-xlP", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 614, + "y": 1215.5, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 184.00000000000003, + "height": 137, + "seed": 1033269326, + "groupIds": [], + "strokeSharpness": "sharp", + "boundElementIds": [ + "qYtX-Dbe48WGy5NUs2SWN", + "-rW8RlMAg9krSeGI2WgDY", + "bpFYMjIuLwNHKYoF5sP1m", + "9yv_MGYQYvx5-mDLvir57", + "losiO7bv6zt5N836LnRRO", + "d1SMxBnX-sncsVzKwbEew", + "-Xd-x5jHExxdRzXBxuDgG", + "UJjYgRCPkN8MK54EHuFBW" + ] + }, + { + "type": "text", + "version": 1919, + "versionNonce": 2017248018, + "isDeleted": false, + "id": "cEJxDDVp37m8WC8FZxLBE", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 616, + "y": 1270, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 182, + "height": 20, + "seed": 1635910546, + "groupIds": [], + "strokeSharpness": "sharp", + "boundElementIds": [], + "fontSize": 16, + "fontFamily": 1, + "text": "@wordpress/components", + "baseline": 14, + "textAlign": "center", + "verticalAlign": "top" + }, + { + "type": "arrow", + "version": 180, + "versionNonce": 1797455300, + "isDeleted": false, + "id": "d1SMxBnX-sncsVzKwbEew", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 704, + "y": 966, + "strokeColor": "#000000", + "backgroundColor": "#15aabf", + "width": 2, + "height": 248, + "seed": 747964626, + "groupIds": [], + "strokeSharpness": "sharp", + "boundElementIds": [], + "startBinding": { + "elementId": "IbjzKI7nkNZcIk0emr1CM", + "focus": -0.02907814187972633, + "gap": 3 + }, + "endBinding": { + "elementId": "K7VFWYXQ24D2SHN5g-xlP", + "focus": -0.04931817191652507, + "gap": 1.5 + }, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": "arrow", + "points": [ + [ + 0, + 0 + ], + [ + -2, + 248 + ] + ] + }, + { + "type": "arrow", + "version": 307, + "versionNonce": 182956156, + "isDeleted": false, + "id": "-Xd-x5jHExxdRzXBxuDgG", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1072.3109902433837, + "y": 965, + "strokeColor": "#000000", + "backgroundColor": "#15aabf", + "width": 370.3109902433837, + "height": 248, + "seed": 202943310, + "groupIds": [], + "strokeSharpness": "sharp", + "boundElementIds": [], + "startBinding": { + "elementId": "pDgrhb9xTc--X-w4JYTNs", + "gap": 1, + "focus": -0.027278151905208426 + }, + "endBinding": { + "elementId": "K7VFWYXQ24D2SHN5g-xlP", + "focus": -0.04347826086956521, + "gap": 2.5 + }, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": "arrow", + "points": [ + [ + 0, + 0 + ], + [ + -2.310990243383685, + 131 + ], + [ + -370.3109902433837, + 131 + ], + [ + -370.3109902433837, + 248 + ] + ] + }, + { + "type": "arrow", + "version": 350, + "versionNonce": 1023845700, + "isDeleted": false, + "id": "UJjYgRCPkN8MK54EHuFBW", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 362, + "y": 608, + "strokeColor": "#000000", + "backgroundColor": "#15aabf", + "width": 539, + "height": 604, + "seed": 145563346, + "groupIds": [], + "strokeSharpness": "sharp", + "boundElementIds": [], + "startBinding": { + "elementId": "3brGgyPnliY43-VLkQbVO", + "focus": -0.5474452554744527, + "gap": 1 + }, + "endBinding": { + "elementId": "K7VFWYXQ24D2SHN5g-xlP", + "focus": 0.015938824396535836, + "gap": 3.5 + }, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": "arrow", + "points": [ + [ + 0, + 0 + ], + [ + -196, + 0 + ], + [ + -194, + 494 + ], + [ + 339, + 489 + ], + [ + 343, + 604 + ] + ] + }, + { + "type": "text", + "version": 429, + "versionNonce": 2009088910, + "isDeleted": false, + "id": "lSCQaBWN4J0WRPxYp2uoo", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 727, + "y": 1127, + "strokeColor": "#000000", + "backgroundColor": "#15aabf", + "width": 110, + "height": 60, + "seed": 1661316114, + "groupIds": [], + "strokeSharpness": "sharp", + "boundElementIds": [], + "fontSize": 16, + "fontFamily": 1, + "text": "Use reusable \ncomponents \nto render UI", + "baseline": 54, + "textAlign": "left", + "verticalAlign": "top" + }, + { + "type": "ellipse", + "version": 38, + "versionNonce": 369230930, + "isDeleted": false, + "id": "KeA708xNXTWu0pafgRJSL", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 254, + "y": 305, + "strokeColor": "#000000", + "backgroundColor": "#15aabf", + "width": 23, + "height": 22, + "seed": 853985042, + "groupIds": [], + "strokeSharpness": "sharp", + "boundElementIds": [] + }, + { + "type": "text", + "version": 48, + "versionNonce": 1179427278, + "isDeleted": false, + "id": "iWMDMo2gKB4X2rL4oaalg", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 260.5, + "y": 305, + "strokeColor": "#000000", + "backgroundColor": "#15aabf", + "width": 11, + "height": 23, + "seed": 1544659854, + "groupIds": [], + "strokeSharpness": "sharp", + "boundElementIds": [], + "fontSize": 20, + "fontFamily": 2, + "text": "1", + "baseline": 18, + "textAlign": "center", + "verticalAlign": "middle" + }, + { + "type": "ellipse", + "version": 296, + "versionNonce": 195144210, + "isDeleted": false, + "id": "5md7eYYWVibRGbMVbZvgC", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 359.5, + "y": 305.5, + "strokeColor": "#000000", + "backgroundColor": "#15aabf", + "width": 23, + "height": 22, + "seed": 54788622, + "groupIds": [], + "strokeSharpness": "sharp", + "boundElementIds": [] + }, + { + "type": "text", + "version": 307, + "versionNonce": 942880782, + "isDeleted": false, + "id": "54MK4zUZitJhJabfozstJ", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 366, + "y": 305.5, + "strokeColor": "#000000", + "backgroundColor": "#15aabf", + "width": 11, + "height": 23, + "seed": 1149201874, + "groupIds": [], + "strokeSharpness": "sharp", + "boundElementIds": [], + "fontSize": 20, + "fontFamily": 2, + "text": "2", + "baseline": 18, + "textAlign": "center", + "verticalAlign": "middle" + }, + { + "type": "ellipse", + "version": 471, + "versionNonce": 29792210, + "isDeleted": false, + "id": "QR0h4yeUaMYdz5Yq5lf2U", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 604.5, + "y": 680.5, + "strokeColor": "#000000", + "backgroundColor": "#15aabf", + "width": 23, + "height": 22, + "seed": 911510030, + "groupIds": [], + "strokeSharpness": "sharp", + "boundElementIds": [] + }, + { + "type": "text", + "version": 483, + "versionNonce": 1433208398, + "isDeleted": false, + "id": "apBf80kSLTGS_xKauF78Z", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 611, + "y": 680.5, + "strokeColor": "#000000", + "backgroundColor": "#15aabf", + "width": 11, + "height": 23, + "seed": 1855677906, + "groupIds": [], + "strokeSharpness": "sharp", + "boundElementIds": [], + "fontSize": 20, + "fontFamily": 2, + "text": "3", + "baseline": 18, + "textAlign": "center", + "verticalAlign": "middle" + }, + { + "type": "ellipse", + "version": 376, + "versionNonce": 1216859538, + "isDeleted": false, + "id": "cEjRyNgMRhHi00kI4ZdRE", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 539.5, + "y": 825.5, + "strokeColor": "#000000", + "backgroundColor": "#15aabf", + "width": 23, + "height": 22, + "seed": 416075090, + "groupIds": [], + "strokeSharpness": "sharp", + "boundElementIds": [] + }, + { + "type": "text", + "version": 388, + "versionNonce": 1054018702, + "isDeleted": false, + "id": "nb81f_gWryUebiJdeho5W", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 546, + "y": 825.5, + "strokeColor": "#000000", + "backgroundColor": "#15aabf", + "width": 11, + "height": 23, + "seed": 1093593294, + "groupIds": [], + "strokeSharpness": "sharp", + "boundElementIds": [], + "fontSize": 20, + "fontFamily": 2, + "text": "4", + "baseline": 18, + "textAlign": "center", + "verticalAlign": "middle" + }, + { + "type": "ellipse", + "version": 419, + "versionNonce": 1574637842, + "isDeleted": false, + "id": "edcEhW3EQaqK_OpY2xbM5", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 867.5, + "y": 797.5, + "strokeColor": "#000000", + "backgroundColor": "#15aabf", + "width": 23, + "height": 22, + "seed": 1463623118, + "groupIds": [], + "strokeSharpness": "sharp", + "boundElementIds": [] + }, + { + "type": "text", + "version": 431, + "versionNonce": 1307033870, + "isDeleted": false, + "id": "2O2KjKy9AGit_YaaggTba", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 874, + "y": 797.5, + "strokeColor": "#000000", + "backgroundColor": "#15aabf", + "width": 11, + "height": 23, + "seed": 486993426, + "groupIds": [], + "strokeSharpness": "sharp", + "boundElementIds": [], + "fontSize": 20, + "fontFamily": 2, + "text": "5", + "baseline": 18, + "textAlign": "center", + "verticalAlign": "middle" + }, + { + "type": "ellipse", + "version": 493, + "versionNonce": 909844178, + "isDeleted": false, + "id": "YdsRRRSZ_0Ri6wogNpp6-", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 932.5, + "y": 922.5, + "strokeColor": "#000000", + "backgroundColor": "#15aabf", + "width": 23, + "height": 22, + "seed": 763406418, + "groupIds": [], + "strokeSharpness": "sharp", + "boundElementIds": [] + }, + { + "type": "text", + "version": 505, + "versionNonce": 900230990, + "isDeleted": false, + "id": "PHeW962z041M37JQx14Yo", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 939, + "y": 922.5, + "strokeColor": "#000000", + "backgroundColor": "#15aabf", + "width": 11, + "height": 23, + "seed": 1719287246, + "groupIds": [], + "strokeSharpness": "sharp", + "boundElementIds": [], + "fontSize": 20, + "fontFamily": 2, + "text": "6", + "baseline": 18, + "textAlign": "center", + "verticalAlign": "middle" + }, + { + "type": "ellipse", + "version": 443, + "versionNonce": 718501010, + "isDeleted": false, + "id": "eDt_EPMgUD0nS3Sljoojc", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 581.5, + "y": 921.5, + "strokeColor": "#000000", + "backgroundColor": "#15aabf", + "width": 23, + "height": 22, + "seed": 1995643346, + "groupIds": [], + "strokeSharpness": "sharp", + "boundElementIds": [] + }, + { + "type": "text", + "version": 455, + "versionNonce": 901338510, + "isDeleted": false, + "id": "suGGbL7s9ttjRwCvt_UoN", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 588, + "y": 921.5, + "strokeColor": "#000000", + "backgroundColor": "#15aabf", + "width": 11, + "height": 23, + "seed": 2025956430, + "groupIds": [], + "strokeSharpness": "sharp", + "boundElementIds": [], + "fontSize": 20, + "fontFamily": 2, + "text": "7", + "baseline": 18, + "textAlign": "center", + "verticalAlign": "middle" + }, + { + "type": "ellipse", + "version": 397, + "versionNonce": 492507730, + "isDeleted": false, + "id": "c1IOrSA3B8mc_gXboWjfO", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 843.5, + "y": 539.5, + "strokeColor": "#000000", + "backgroundColor": "#15aabf", + "width": 23, + "height": 22, + "seed": 521446354, + "groupIds": [], + "strokeSharpness": "sharp", + "boundElementIds": [] + }, + { + "type": "text", + "version": 431, + "versionNonce": 1196651470, + "isDeleted": false, + "id": "0zWPhd_iaI4kAgKN8ya5p", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 850, + "y": 539.5, + "strokeColor": "#000000", + "backgroundColor": "#15aabf", + "width": 11, + "height": 23, + "seed": 81115726, + "groupIds": [], + "strokeSharpness": "sharp", + "boundElementIds": [], + "fontSize": 20, + "fontFamily": 2, + "text": "8", + "baseline": 18, + "textAlign": "center", + "verticalAlign": "middle" + }, + { + "type": "ellipse", + "version": 471, + "versionNonce": 1421092306, + "isDeleted": false, + "id": "GIG1H7lfyR6S7-AtVzGmH", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 441.5, + "y": 323.5, + "strokeColor": "#000000", + "backgroundColor": "#15aabf", + "width": 23, + "height": 22, + "seed": 1124068562, + "groupIds": [], + "strokeSharpness": "sharp", + "boundElementIds": [] + }, + { + "type": "text", + "version": 483, + "versionNonce": 476534862, + "isDeleted": false, + "id": "eF0Kvnyd8dffx8-q4yU8U", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 448, + "y": 323.5, + "strokeColor": "#000000", + "backgroundColor": "#15aabf", + "width": 11, + "height": 23, + "seed": 1591705934, + "groupIds": [], + "strokeSharpness": "sharp", + "boundElementIds": [], + "fontSize": 20, + "fontFamily": 2, + "text": "9", + "baseline": 18, + "textAlign": "center", + "verticalAlign": "middle" + } + ], + "appState": { + "gridSize": null, + "viewBackgroundColor": "#ffffff" + } +} \ No newline at end of file diff --git a/explanations/architecture/assets/modules.png b/explanations/architecture/assets/modules.png new file mode 100644 index 0000000..ef134f1 Binary files /dev/null and b/explanations/architecture/assets/modules.png differ diff --git a/explanations/architecture/automated-testing.md b/explanations/architecture/automated-testing.md new file mode 100644 index 0000000..ecbb463 --- /dev/null +++ b/explanations/architecture/automated-testing.md @@ -0,0 +1,20 @@ +# 自动化测试 + +## 为何选择Puppeteer作为端到端测试工具? + +当前存在丰富的网络端到端自动化测试工具生态。因此常有人提出疑问:"为何Gutenberg选用[Puppeteer](https://developers.google.com/web/tools/puppeteer/)而非([Cypress](https://cypress.io/)、[Selenium](https://www.selenium.dev/)、[Playwright](https://github.com/microsoft/playwright)等)?"考虑到端到端测试在构建结果方面存在历史性的不稳定性,在评估工具提供的价值是否超过维护成本时,这个问题显得尤为关键。虽然我们应始终保持重新评估早期决策的开放态度,但无论是过去还是现在,Puppeteer始终是端到端测试方案中的最佳折中选择。 + +其优势包括: + +- **与现有测试框架的互操作性**。Puppeteer"仅"是一个控制Chrome浏览器的工具,并不预设测试环境的集成方式。虽然这需要额外确保测试环境的可用性,但也使其能够灵活适配现有配置。Gutenberg得以在单元测试和端到端测试中持续使用Jest框架。这与Cypress等其他方案形成鲜明对比——后者提供自成体系的测试框架和断言库作为一体化解决方案。 +- **表达力强且可预测的API**。Puppeteer在底层浏览器行为访问与现代化JavaScript[异步/等待语法](https://developer.mozilla.org/zh-CN/docs/Learn/JavaScript/Asynchronous/Async_await)的命令发布/响应等待之间实现了精妙平衡。相较之下,其他方案要么不支持原生异步功能,要么未开放直接浏览器访问权限,要么采用自定义领域特定语言来表达浏览器命令和断言。尽管Puppeteer主要针对Chrome浏览器在跨浏览器覆盖方面存在局限,但有限的浏览器目标反而能提供更一致的测试结果,并确保代码在浏览器环境中获得更可靠的评估。 +- **暴露问题而非掩盖缺陷**。许多替代方案提供自动等待网络请求完成或页面元素异步加载的功能。虽然这为处理不可预知的延迟提供了便利,但也可能无意间掩盖真实存在的用户体验问题。例如,若某个元素仅在网络请求或计算完成后才会显示,开发者很容易忽略这些延迟可能导致用户遭遇不可预测的挫败体验([案例](https://github.com/WordPress/gutenberg/pull/11287))。鉴于开发者通常在高性能设备和稳定网络环境下测试,低端设备或不稳定网络连接下的韧性考量往往被忽视。Puppeteer通过显式的`waitFor*`表达式迫使开发者正视这些延迟,使测试更贴近真实用户的体验场景。 +- **调试功能**。当测试失败时,必须具备直观的问题诊断与解决手段。虽然相比竞品功能较为基础,但Puppeteer确实提供了"有头模式"(可见浏览器界面)和延迟操作等调试选项。结合其与原生语言/运行时功能(如调试器语句或断点)的良好互操作性,为开发者提供了充分的调试支持。 + +更多背景信息请参阅: + +- [测试概览:端到端测试](/docs/contributors/code/testing-overview.md#端到端测试) +- [测试:Puppeteer端到端测试实验](https://github.com/WordPress/gutenberg/pull/5618) + - 在早期迭代中,贡献团队曾选择Cypress进行端到端测试。该拉取请求阐述了该方案存在的问题,并提出了向Puppeteer迁移的初步建议。 +- [JavaScript聊天纪要:2020年1月28日](https://make.wordpress.org/core/2020/02/04/javascript-chat-summary-january-28-2020/) + - Playwright是由多位Puppeteer原始贡献者打造的新方案。它提供更广泛的浏览器覆盖和更可靠的测试表现。尽管撰写本文时该工具尚处早期开发阶段,但已引起将其作为未来端到端测试方案的评估兴趣。 \ No newline at end of file diff --git a/explanations/architecture/data-flow.md b/explanations/architecture/data-flow.md new file mode 100644 index 0000000..f0869f4 --- /dev/null +++ b/explanations/architecture/data-flow.md @@ -0,0 +1,125 @@ +### 分隔符与解析表达式语法 + +我们最终选择尝试寻找一种方法,在保留现有HTML语法规范性、明确性和无歧义性的基础上进行创新。在HTML体系内,我们拥有多种可行方案。 + +在这些方案中,有人提出了一种新颖思路:通过将数据存储在HTML注释中,既能确保不破坏文档中原有的HTML结构,又能保证浏览器会忽略这些内容,同时还能简化文档解析流程。 + +HTML注释的独特之处在于它们不会合法存在于模糊位置——比如像`data-id="14"`这样的HTML属性内部。注释语法还具有高度包容性:解析HTML属性需要复杂处理,而注释规则却极其简单——以``外的任意内容,直至遇到首个`-->`结束。这种简洁性与包容性意味着解析器可以通过多种方式实现,且无需深入理解HTML语法,我们还能在注释中自由使用更便捷的语法——仅需转义双连字符序列即可。我们在存储块属性时充分利用了这一特性:将JSON字面量直接嵌入注释。 + +经过解析器处理后,我们获得了可直接操作的标准对象,无需担心数据转义或反转义问题——序列化过程已自动处理这些细节。由于注释与其他HTML标签存在显著差异,加之我们可以通过首轮解析提取顶层块,实际上并不需要完全有效的HTML文档! + +这对解析器的简洁性与性能表现具有重大意义。这些明确的分界符还能防止单个块的损坏蔓延至其他块或污染整个文档。系统也得以在渲染前识别出未知块。 + +*注:* 块的核心特征在于其语义与提供的隔离机制,即其身份标识。而数据存储位置则具有更高灵活性。块不仅支持静态本地数据(通过HTML注释中的JSON字面量或块内HTML实现),未来还将支持更多机制(例如全局块或辅助存储于`WP_Post`对象)。详见[属性说明](/docs/reference-guides/block-api/block-attributes.md)。 + +### 序列化块的解剖结构 + +当块在编辑会话结束后保存至内容时,其属性会根据块特性被序列化为这些显式注释分隔符: + +```html + +
+ +``` + +对于需服务器预渲染的纯动态块,其形态可能如下: + +```html + +``` + +## 数据生命周期 + +总而言之,区块编辑器工作流通过词法分隔符辅助,将已保存文档解析为内存中的块树结构。编辑过程中的所有操作都在块树内部进行,最终通过将块序列化回`post_content`完成流程。 + +该工作流依赖序列化/解析器组合来实现文章持久化。理论上,文章数据结构既可通过插件存储,也可从远程JSON文件获取后转换为块树结构。 + +# 数据流与数据格式 + +## 格式规范 + +区块编辑器文章是具备区块感知能力的文章表征形式:由一系列语义一致的描述构成,阐明每个区块的定义及其核心数据。这种表征仅存在于内存中,如同排版工坊中的[活字追排](https://zh.wikipedia.org/wiki/%E6%8E%92%E7%89%88#%E6%B4%BB%E5%AD%97%E6%8E%92%E7%89%88),随着[字模](https://zh.wikipedia.org/wiki/%E5%AD%97%E6%A8%A1)的嵌入与重新定位而持续变化。 + +区块编辑器文章并非其最终产物——即`post_content`(文章内容)。后者如同印刷成品,为读者优化呈现的同时,仍保留着用于后续编辑的隐形标记。 + +区块编辑器的输入与输出采用当前格式的区块对象树: + +```js +const value = [ block1, block2, block3 ]; +``` + +### 区块对象 + +每个区块对象包含唯一标识符、属性集合及可能的子区块列表。 + +```js +const block = { + clientId, // 唯一字符串标识符 + type, // 区块类型(段落、图片等) + attributes, // 代表当前区块直接属性/内容的键值对集合 + innerBlocks, // 子区块或内部区块数组 +}; +``` + +需注意属性键名与类型、允许嵌套的区块均由区块类型定义。例如核心引用区块包含字符串类型的`cite`属性表示引用来源,而标题区块则包含数值型`level`属性表示标题层级(1至6级)。 + +在编辑器的区块生命周期中,区块对象可接收额外元数据: + +- `isValid`:布尔值,标识区块是否有效 +- `originalContent`:区块原始HTML序列化内容 + +**示例** + +```js +// 简单段落区块 +const paragraphBlock1 = { + clientId: '51828be1-5f0d-4a6b-8099-f4c6f897e0a3', + type: 'core/paragraph', + attributes: { + content: '这是段落区块的内容', + dropCap: true, + }, +}; + +// 分隔符区块 +const separatorBlock = { + clientId: '51828be1-5f0d-4a6b-8099-f4c6f897e0a4', + type: 'core/separator', + attributes: {}, +}; + +// 包含双栏段落区块的列区块 +const columnsBlock = { + clientId: '51828be1-5f0d-4a6b-8099-f4c6f897e0a7', + type: 'core/columns', + attributes: {}, + innerBlocks: [ + { + clientId: '51828be1-5f0d-4a6b-8099-f4c6f897e0a5', + type: 'core/column', + attributes: {}, + innerBlocks: [ paragraphBlock1 ], + }, + { + clientId: '51828be1-5f0d-4a6b-8099-f4c6f897e0a6', + type: 'core/column', + attributes: {}, + innerBlocks: [ paragraphBlock2 ], + }, + ], +}; +``` + +## 序列化与解析 + +![流程图](https://docs.google.com/drawings/d/1iuownt5etcih7rMMvPvh0Mny8zUA1Z28saxjxaWmfJ0/pub?w=1234&h=453) + +需要注意的是,这种数据模型仅在文章编辑过程中存在于内存中。页面最终渲染时对浏览者不可见,正如印刷成品不会显露印刷机上字母的组成结构。 + +鉴于整个WordPress生态在渲染或编辑文章时期望接收HTML格式,区块编辑器通过序列化将其数据转换为可保存至`post_content`的形式。这确保了内容存在单一可信源,且该源始终保持可读性并与当前所有WordPress内容交互工具兼容。若将对象树单独存储,我们将面临`post_content`与对象树失步的风险,以及数据在两地重复存储的问题。 + +因此,序列化过程使用HTML注释作为显式区块定界符(其中可包含非HTML格式的属性),将区块树转换为HTML。这一过程如同在印刷页面上留下隐形标记,为原始结构化意图保留痕迹。 + +这是流程的一端。另一端则关乎如何在下一次编辑时重建区块集合。正如通过基本规则定义如何将树转换为类HTML字符串,形式化语法定义了应如何加载区块编辑器文章的序列化表征。区块编辑器文章并非为手动编辑而设计,亦非作为HTML文档进行编辑——因为其本质并非HTML。 + +它们只是恰巧以无需传统系统转换即可直接查看的方式存储在`post_content`中。诚然,在没有相应机制的情况下将存储的HTML加载至浏览器可能会影响体验:若包含动态内容区块,动态元素可能无法加载,服务器生成的内容可能无法显示,交互内容可能保持静态。但至少这确保了在未启用区块功能的主题和环境中仍可查看区块编辑器文章,并提供了最便捷的内容访问方式。换言之,即使保存的HTML按原样渲染,文章内容仍能基本保持完整。 \ No newline at end of file diff --git a/explanations/architecture/entities.md b/explanations/architecture/entities.md new file mode 100644 index 0000000..eee8744 --- /dev/null +++ b/explanations/architecture/entities.md @@ -0,0 +1,70 @@ +# 实体与撤销/重做 + +无论是文章编辑器还是站点编辑器,WordPress 编辑器操作的都是我们称为实体记录的对象。这些对象代表着文章、页面、用户、分类项、模板等数据。它们既是存储在数据库中的数据,也是被编辑器操作的对象。每个编辑器都能同时获取、编辑和保存多个实体记录。 + +例如在站点编辑器中打开页面时: + +- 您可以编辑页面本身的属性(标题、内容等) +- 您可以编辑页面模板的属性(模板内容、设计等) +- 您可以编辑模板所使用的模板部件属性(页眉、页脚等) + +编辑器会追踪所有这些修改,并协调所有已修改记录的保存操作。这些功能都在 `@wordpress/core-data` 包中实现。 + +## 编辑实体 + +要编辑实体,首先需要获取并将其加载到 `core-data` 存储中。例如以下代码将 ID 为 1 的文章加载到存储中(实体是文章,文章 1 是实体记录)。 + +````js +wp.data.select( 'core' ).getEntityRecord( 'postType', 'post', 1 ); +```` + +实体加载后即可进行编辑。例如以下代码将文章标题设置为 "Hello World"。对于每个获取的实体记录,`core-data` 存储会追踪以下内容: + +- **“持久化”记录**:从后端获取时记录的最终状态 +- **“编辑”列表**:记录一个或多个属性的未保存本地修改 + +该程序包还提供了一系列操作来管理已获取的实体记录。 + +要编辑实体记录,可以调用 `editEntityRecord` 方法,该方法需要传入实体类型、实体 ID 和新实体记录作为参数。以下示例将 ID 为 1 的文章标题设置为 "Hello World"。 + +````js +wp.data.dispatch( 'core' ).editEntityRecord( 'postType', 'post', 1, { title: 'Hello World' } ); +```` + +编辑实体记录后即可进行保存。以下代码保存了 ID 为 1 的文章。 + +````js +wp.data.dispatch( 'core' ).saveEditedEntityRecord( 'postType', 'post', 1 ); +```` + +## 撤销/重做 + +由于 WordPress 编辑器允许同时编辑多个实体记录,`core-data` 包会在公共的撤销/重做堆栈中追踪所有已获取和编辑的实体记录。撤销/重做堆栈中的每个步骤都包含一个“编辑”列表,在调用 `undo` 或 `redo` 操作时需要同时执行这些编辑。 + +为了正确执行撤销和重做操作,每个编辑列表中的修改都包含以下信息: + +- **实体种类与名称**:core-data 中的每个实体都由_(种类, 名称)_对标识,对应被修改实体的标识符 +- **实体记录 ID**:被修改记录的 ID +- **属性**:被修改属性的名称 +- **原值**:属性的原始值(用于执行撤销操作) +- **新值**:属性的新值(用于执行重做操作) + +例如,假设用户先编辑了文章标题,接着修改了文章别名,然后又修改了文章中使用的可重用块标题。撤销/重做堆栈中将存储以下信息: + +- `[ { kind: 'postType', name: 'post', id: 1, property: 'title', from: '', to: 'Hello World' } ]` +- `[ { kind: 'postType', name: 'post', id: 1, property: 'slug', from: 'Previous slug', to: 'This is the slug of the hello world post' } ]` +- `[ { kind: 'postType', name: 'wp_block', id: 2, property: 'title', from: 'Reusable Block', to: 'Awesome Reusable Block' } ]` + +存储还会维护一个指向当前“撤销/重做”步骤的“指针”。默认情况下,指针始终指向堆栈中的最后一个项目。当用户执行撤销或重做操作时,该指针会相应更新。 + +### 缓存变更 + +撤销/重做的核心行为还支持所谓的“缓存修改”。这些修改不会立即存储在撤销/重做堆栈中。例如当用户在文本字段中输入时,字段值会在存储中修改,但该修改只有在用户移动到下一个单词或经过几毫秒后才会存入撤销/重做堆栈。这样做是为了避免为用户输入的每个字符都创建新的撤销/重做步骤。 + +缓存变更会被保留在撤销/重做堆栈之外的“修改缓存”中,只有当显式调用 `__unstableCreateUndoLevel` 或当下一个修改不是缓存修改时,这些修改才会存入撤销/重做堆栈。 + +默认情况下,所有对 `editEntityRecord` 的调用都被视为“非缓存”操作,除非传入 `isCached` 选项为 true。例如: + +```js +wp.data.dispatch( 'core' ).editEntityRecord( 'postType', 'post', 1, { title: 'Hello World' }, { isCached: true } ); +``` \ No newline at end of file diff --git a/explanations/architecture/full-site-editing-templates.md b/explanations/architecture/full-site-editing-templates.md new file mode 100644 index 0000000..e7f4813 --- /dev/null +++ b/explanations/architecture/full-site-editing-templates.md @@ -0,0 +1,44 @@ +# 站点编辑模板 + +## 模板与模板部件流程 + +本文档将阐述模板及模板部件在前端如何渲染、在后端如何编辑的内部机制。 + +## 存储机制 + +与常规模板类似,区块模板最初以文件形式存在于主题文件夹中,但主要区别在于用户可通过站点编辑器的界面编辑这些模板。 + +当用户编辑模板(或模板部件)时,初始的主题模板文件将保持原样,但模板的衍生版本会保存至`wp_template`自定义文章类型(模板部件则保存至`wp_template_part`)。 + +这意味着在任意时间点,网站前端渲染会同时使用主题文件中的模板文件与CPT模板(经编辑的模板)。 + +## 同步机制 + +为简化从两个不同位置编辑和渲染模板的算法,我们执行了名为"模板同步"的操作。 + +同步操作包括将主题模板复制到`wp_template`(及`wp_template_part`)自定义模板中并标记为`auto-draft`状态。当用户编辑这些模板时,状态将更新为`publish`。 + +这意味着: + +- 模板的渲染/获取只需考虑自定义文章类型模板,无需直接从主题文件夹获取模板文件。同步机制将确保这些模板被复制到CPT中 +- 未编辑的主题模板保持`auto-draft`状态 +- 经编辑的主题模板具有`publish`状态 + +同步机制对以下两个流程至关重要: + +- 当编辑模板和模板部件时,站点编辑器前端通过REST API获取已编辑和可用的模板。这意味着所有对`wp-templates`和`wp-template-parts`端点的`GET`API请求都需要进行同步 +- 当渲染模板(有时称为"解析模板")时:这是WordPress遵循的算法,用于遍历模板层级结构并为当前加载页面找到正确的渲染模板 +- 当导出区块主题时,我们需要将所有模板重新导出为文件。同步机制可简化此操作,仅导出CPT模板 + +## 主题切换 + +由于区块主题使用的模板可以相互引用,并且可以保存到自定义文章类型中,因此可能混合使用来自不同主题的模板和模板部件。例如: + +- 用户可能喜欢主题A的"页眉"模板部件,并希望在主题B中使用它 +- 用户可能喜欢主题A的"联系"模板,并希望在主题B中使用它 + +实现这些流程需要精心设计的界面和用户体验。在全站编辑的当前阶段,我们首先禁止这些可能性,确保模板和模板部件具有主题专属性。 + +尽管如此,记录模板和模板部件的初始来源(基于哪个主题)仍然非常重要。我们通过为每个模板和模板部件CPT条目保存包含主题标识符的`theme`文章元数据来实现这一点。 + +未来,我们可能会考虑允许用户混合使用具有不同`theme`文章元数据值的模板和模板部件。 \ No newline at end of file diff --git a/explanations/architecture/key-concepts.md b/explanations/architecture/key-concepts.md new file mode 100644 index 0000000..23a85f2 --- /dev/null +++ b/explanations/architecture/key-concepts.md @@ -0,0 +1,69 @@ +# 核心概念 + +## 区块 + +区块是用于构建和交互内容的抽象单元。当它们组合在一起时,就构成了网页的内容。从段落、视频到网站标题,所有内容都以区块形式呈现。 + +区块形式多样但提供统一的交互界面。它们可被插入、移动、重新排序、复制、克隆、转换、删除、拖拽和组合。区块还支持复用功能,可在不同文章和文章类型间共享,或在同一文章中多次使用。简单来说,可将区块视为更优雅的简码,配备丰富的格式工具供用户编排内容。 + +区块设置与内容主要在三个区域进行自定义:区块画布、区块工具栏和区块检查器。 + +### 组合性 + +区块支持多种组合方式。它们具有层级结构,允许区块嵌套于其他区块内。嵌套区块与其容器分别称为*子区块*和*父区块*。例如*多栏*区块可作为父区块,在其各栏中包含多个子区块。管理子区块使用的API命名为`InnerBlocks`。 + +### 数据与属性 + +区块将内容解析为属性,并可序列化为HTML。为此专门设计了新的区块语法规则:区块语法本质上是HTML注释,可以是自闭合标签或包含起始/结束标签。根据区块类型和用户自定义设置,主标签内可能包含JSON对象。这种原始区块形态称为序列化形态。 + +```html + +

欢迎来到区块世界。

+ +``` + +区块分为静态与动态两类。静态区块包含渲染内容和属性对象,可根据变更重新渲染。动态区块在生成文章内容时需要服务器端数据和渲染过程。 + +每个区块包含属性或配置设置,这些数据可从内容中的原始HTML、元数据或其他自定义来源获取。 + +详细了解[数据格式与数据流](/docs/explanations/architecture/data-flow.md)。 + +### 区块转换 + +区块具备转换为其他区块类型的能力。既可实现基础操作(如将段落转为标题),也能完成复杂转换(如多张图片转为相册)。区块转换适用于单个区块及多选区块组合,内部区块变体也可作为转换目标。 + +### 区块变体 + +区块变体是特定区块类型的预定义初始属性集合。该API支持创建基础区块,并衍生出多种配置方案。变体提供不同的交互界面,既可在库中显示为全新区块,也可作为插入新区块时的预设模板。详见[API文档](/docs/reference-guides/block-api/block-registration.md#variations-optional)。 + +**区块进阶指南** +- **[区块API](/docs/reference-guides/block-api/README.md)** +- **[教程:构建自定义区块](/docs/getting-started/devenv/get-started-with-create-block.md)** + +## 可复用区块 + +可复用区块是区块(或多个区块组合)的**实例**,可在多处插入和编辑,并保持全局同步。当在某处编辑可复用区块时,所有使用该区块的文章和页面都会同步更新。典型应用场景包括:具有特定内容和自定义颜色的标题区块(用于多个页面),以及侧边栏小组件(用于所有页面)。 + +对可复用区块的任何编辑都会自动同步到所有使用位置,避免在不同文章中重复修改。 + +技术上,可复用区块以隐藏文章类型(`wp_block`)存储,属于动态区块,通过"引用"方式关联`post_id`并返回对应区块的`post_content`。 + +## 区块模式 + +[区块模式](/docs/reference-guides/block-api/block-patterns.md)是由多个区块组合形成的设计范式。这些设计模式为快速构建高级页面和布局提供起点,无需逐个插入区块。区块模式小至单个区块,大至整页内容。与可复用区块不同,模式插入后不会与原始内容保持同步,其包含的区块需用户自行编辑定制。本质上,模式就是常规区块的组合。主题可通过注册模式,为用户提供符合其设计语言的快速入门方案。 + +## 模板 + +文章编辑器专注于内容创作,而[模板](/docs/reference-guides/block-api/block-templates.md)编辑器支持用区块声明和编辑整个站点(从页眉到页脚)。模板系统分为完整页面模板和模板部件(描述模板内的可复用区域,包括页眉、侧边栏、页脚等语义区域)。 + +这些模板和模板部件可由主题组合注册。用户也可通过区块编辑器完全自定义它们。在编辑模板时,与站点属性(如站点标题、描述、徽标、导航等)交互的区块集合尤为实用。自定义模板保存在`wp_template`文章类型中,包括静态页面和动态页面(如归档页、单篇文章页、首页、404页等)。 + +注意:自定义文章类型也可通过初始`post_content`模板初始化,请勿与上述主题模板系统混淆。 + +深入了解[全站编辑模板](/docs/explanations/architecture/full-site-editing-templates.md)。 + +## 样式 + +样式系统(代码中仍沿用旧称“全局样式”)既是用户通过编辑器访问的界面,也是通过[`theme.json`文件](/docs/how-to-guides/themes/global-settings-and-styles.md)实现的配置系统。该文件整合了通常分散在多个`add_theme_support`调用中的配置项,简化与编辑器的通信。其目标是改进以下功能的声明方式:应启用的设置、主题提供的特定工具(如自定义调色板)、可用的设计工具,以及协调WordPress、活跃主题和用户样式的基础架构。 + +了解更多关于[全局样式](/docs/explanations/architecture/styles.md#global-styles)的信息。 \ No newline at end of file diff --git a/explanations/architecture/modularity.md b/explanations/architecture/modularity.md new file mode 100644 index 0000000..8c7a643 --- /dev/null +++ b/explanations/architecture/modularity.md @@ -0,0 +1,103 @@ +## 进阶阅读 + +- [软件包参考](/docs/reference-guides/packages.md) + +# 模块化设计 + +WordPress 区块编辑器的核心理念在于通过组合独立区块来撰写文章或构建页面。区块之间能够相互调用与交互,这种设计赋予了系统高度的模块化特性与灵活性。 + +但区块编辑器的模块化不仅体现在功能与输出层面。古腾堡项目仓库本身也是由多个可复用独立模块(即程序包)构建而成,这些模块共同构成了我们熟悉的应用程序与交互界面。这些模块被称为 [WordPress 程序包](https://www.npmjs.com/org/wordpress),会定期发布并更新至 npm 包仓库。 + +这些程序包不仅为区块编辑器提供核心支持,也可用于增强 WordPress 管理后台或外部环境的任何页面。 + +## 设计理念 + +采用模块化架构为所有参与者带来多重优势: + +- 每个程序包都是独立单元,拥有明确定义的公共 API 用于与其他程序包及第三方代码交互。这使得**核心贡献者**能更清晰地理解代码库,可专注于单个程序包的开发维护,并在明确知晓变更影响范围的前提下进行更新 +- 模块化架构对**终端用户**同样有益。通过在不同管理页面选择性加载脚本,有效控制资源包体积。例如当使用组件包构建插件设置页面时,就无需额外加载区块编辑器包 +- 这种架构允许**第三方开发者**通过 npm 或 WordPress 脚本依赖的方式,在 WordPress 内外环境中复用这些程序包 + +## 程序包分类 + +古腾堡仓库中的几乎所有功能都被构建为程序包。这些程序包可分为两大类型: + +### 生产环境包 + +这类程序包以 JavaScript 脚本形式随 WordPress 核心发布,构成在浏览器中运行的实际生产代码。例如: +- `components` 程序包提供可复用的 React 组件集合,用于快速原型设计与界面构建 +- `api-fetch` 程序包可用于调用 WordPress REST API + +第三方开发者可通过两种方式使用这些生产环境包: + +- 若在 WordPress 环境外构建 JavaScript 应用、网站或页面,可像使用常规 npm 包那样调用: + +``` +npm install @wordpress/components +``` + +```js +import { Button } from '@wordpress/components'; + +function MyApp() { + return ; +} +``` + +- 若开发 WordPress 插件,建议直接调用 WordPress 内置程序包。这样可实现多插件共享同一程序包,避免代码重复。在 WordPress 中,这些程序包以后缀为 `wp-包名称`(如 `wp-components`)的脚本句柄形式提供。只需将脚本添加到插件依赖中,即可通过全局变量 `wp` 调用: + +```php +// myplugin.php +// 注册依赖 "components" 和 "element" 程序包的示例 +wp_register_script( 'myscript', 'pathtomyscript.js', array ('wp-components', "react" ) ); +``` + +```js +// 在脚本中使用程序包 +const { Button } = wp.components; + +function MyApp() { + return ; +} +``` + +手动定义脚本依赖对开发者而言可能繁琐易错。若需了解如何自动化此流程,请参阅 [@wordpress/scripts](https://developer.wordpress.org/block-editor/packages/packages-scripts/#build) 与 [@wordpress/dependency-extraction-webpack-plugin](https://developer.wordpress.org/block-editor/packages/packages-dependency-extraction-webpack-plugin/) 文档。 + +#### 包含样式文件的功能包 + +部分生产环境功能包需依赖样式文件才能正常运行。 + +- 若通过 npm 依赖形式使用功能包,样式文件将存放于功能包的 `build-style` 目录。请确保在应用中加载该样式文件。 +- 若在 WordPress 环境中使用,需通过 wp_enqueue_style 函数加载样式文件,或将其添加至现有样式依赖中。样式文件句柄命名规则与脚本句柄保持一致。 + +在现有 WordPress 页面环境中,若未正确定义脚本或样式依赖,当这些资源已被 WordPress 或其他插件加载时,您的插件仍可能正常运行。但为规避未来版本可能出现的兼容性问题,强烈建议完整定义所有依赖项。 + +#### 包含数据存储的功能包 + +部分 WordPress 生产环境功能包通过数据存储机制管理状态。这些存储空间也可被第三方插件和主题调用,用于数据获取与操作。数据存储命名遵循 `core/功能包名称` 的标准化格式(例如 `@wordpress/block-editor` 功能包定义并使用 `core/block-editor` 数据存储)。 + +若在插件中通过此类存储空间访问或操作 WordPress 数据,请务必将对应的 WordPress 脚本添加至插件脚本依赖中以确保正常运行(例如:若从 `core/block-editor` 存储获取数据,应按前文示例将 `wp-block-editor` 包添加至脚本依赖)。 + +### 开发环境功能包 + +此类功能包用于开发模式,协助开发者完成 JavaScript 应用、WordPress 插件及主题的开发、构建和发布等日常任务。包含代码规范检查、项目构建、测试验证等开发工具。 + +## 编辑器功能包 + +![文章编辑器模块架构](https://raw.githubusercontent.com/WordPress/gutenberg/HEAD/docs/explanations/architecture/assets/modules.png) + +### 不同编辑器功能包有何区别?各自承担什么职责? + +新贡献者往往会对文章编辑器由三个独立功能包(`@wordpress/edit-post`、`@wordpress/editor` 和 `@wordpress/block-editor`)分层构建的架构感到惊讶。 + +前文[设计初衷](#why)章节已说明单个功能包如何满足特定需求的设计理念,这同样适用于以下功能包: + +- `@wordpress/block-editor` 提供用于实现区块编辑器的组件,其操作对象为区块对象数组的基础数据。该包不预设数据保存方式,且无需感知(或依赖)WordPress 环境。 +- `@wordpress/editor` 是 WordPress 文章专用的增强版区块编辑器。它在 `@wordpress/block-editor` 组件基础上构建,具备 WordPress 文章概念感知能力,将表征区块的数据加载保存机制与文章及其内容相关联。同时提供在编辑器环境中处理文章对象的相关组件(如文章标题输入组件)。该功能包支持编辑任何文章类型的文章,且不限定渲染环境必须位于特定 WordPress 界面或布局中。 +- `@wordpress/edit-post` 是 WordPress 后台“新建文章”(“编辑文章”)界面的具体实现。它负责对 `@wordpress/editor` 和 `@wordpress/block-editor` 提供的各类组件进行布局编排,完全掌控其在 WordPress 管理后台特定界面中的呈现方式。 + +通过这样的结构设计,这些功能包可在“新建文章”界面之外实现多样化组合应用: + +- `@wordpress/edit-site` 或 `@wordpress/edit-widgets` 功能包可分别实现“站点编辑器”或“小工具编辑器”,其实现方式与 `@wordpress/edit-post` 高度相似。 +- `@wordpress/editor` 可应用于“可重用区块”功能的实现,因其本质上是与 `wp_block` 文章类型关联的嵌套区块编辑器。 +- `@wordpress/block-editor` 可独立于 WordPress 环境使用,或采用完全不同的保存机制。例如:可用于构建网站文章的评论区编辑器。 \ No newline at end of file diff --git a/explanations/architecture/performance.md b/explanations/architecture/performance.md new file mode 100644 index 0000000..5851192 --- /dev/null +++ b/explanations/architecture/performance.md @@ -0,0 +1,97 @@ +# 性能表现 + +性能是编辑器应用的关键特性,块编辑器也不例外。 + +## 指标衡量 + +为确保块编辑器在版本迭代和开发过程中始终保持高性能,我们通过[性能基准测试任务](#性能基准测试任务)监控以下核心指标: + +部分关键指标包括: + +- **加载时间:** 编辑器页面加载所需时长。涵盖服务器响应时间、首次绘制时间、首次内容绘制时间、DOM内容加载完成时间、页面完全加载时间以及首区块渲染时间(文章与站点编辑场景均包含)。 +- **输入响应时间:** 在编辑器中输入时浏览器作出响应所需时长。 +- **区块选择时间:** 用户选择区块后浏览器作出响应所需时长(插入区块等效于选择区块,监控选择操作即可同步覆盖这两项指标)。 + +## 关键性能决策与解决方案 + +**数据模块异步模式** + +WordPress组件库与块编辑器的数据模块基于Redux构建。这意味着状态全局存储,当状态发生变化时,依赖该状态的组件(UI)会相应更新。 + +随着渲染组件数量增加(例如长文编辑时),由于全局状态会向所有组件分发事件,性能将受到影响。这是Redux应用的常见问题,Gutenberg通过数据模块异步模式解决了这一难题。 + +异步模式的核心在于:开发者可自主决定以同步或异步方式刷新/重渲染React组件树的特定部分。 + +在此语境下,异步渲染意味着当全局状态触发变更时,订阅者(组件)不会立即被同步调用,而是等待浏览器进入空闲状态后再执行React树更新。 + +基于**编辑特定区块时,该区块的更新极少影响内容其他部分**这一理念,块编辑器画布仅以同步模式渲染当前选中区块,其余所有区块均采用异步渲染。这确保了随着文章内容增长,编辑器仍能保持流畅响应。 + +## 性能基准测试任务 + +我们提供了跨分支/标签/提交进行性能对比的工具。可通过以下方式本地运行:`./bin/plugin/cli.js perf [分支名]`,例如: + +``` +./bin/plugin/cli.js perf trunk v8.1.0 v8.0.0 +``` + +为获得最精确结果,运行测试时必须确保测试版本与环境(主题等)完全一致,各分支间唯一差异应为Gutenberg插件版本(或用于构建插件的分支)。 + +为实现该目标,指令会先创建以下目录结构: + + │ + ├── tests/packages/e2e-tests/specs/performance/* + │ 待运行的实际性能测试 + │ + ├── tests/test/emptytheme + │ 测试环境所用主题(站点编辑器) + │ + │── envs/branch1/.wp-env.json + │ branch1的wp-env配置文件(除插件目录外与其他分支配置相同) + │── envs/branch1/plugin + │ branch1的Gutenberg插件构建副本(通过git checkout branch1获取) + │ + └── envs/branchX + 所有其他分支均复制perf-envs/branch1的目录结构 + +完成目录准备后,性能测试指令将循环执行各性能测试套件(文章编辑器与站点编辑器),并执行以下操作: + +1. 启动`branch1`对应环境 +2. 运行当前套件的性能测试 +3. 停止`branch1`对应环境 +4. 对其余所有分支重复前三步操作 +5. 计算当前套件所有性能指标的中位数值 + +所有测试套件执行完毕后,将生成汇总报告。 + +## 通过CodeVitals追踪性能 + +每次提交的性能结果将推送至codevitals,可在[Gutenberg数据看板](https://www.codevitals.run/project/gutenberg)查看。通过趋势图可追踪特定指标随时间的变化情况。 + +因此确保所计算指标的稳定性至关重要。这意味着在代码与环境相同的情况下,重复运行测试应获得相近结果。 + +由于性能任务运行于GitHub CI环境,我们不能完全相信两次相似任务运行获得的数值一致性。例如GitHub CI可能随时间推移分配不同的CPU与内存资源。为缓解此问题,每次在trunk分支运行性能任务时,我们会将当前提交的性能与固定的参考提交哈希进行对比,这样无论环境如何变化,都能持续追踪当前提交与参考提交间的相对差异。 + +### 更新参考提交 + +Gutenberg仅支持两个WP版本,这对性能任务产生两方面影响: + + - 当Gutenberg支持的最低版本变更时,用于运行性能任务的基础WP版本需同步更新。为此,我们依赖插件`readme.txt`文件中的`Tested up to`标识。每次该标识变更时,性能任务所用版本也会相应调整。 + + - 更新性能任务使用的WP版本意味着,用于保证性能测试稳定性的参考提交很可能与当前WP版本不兼容。因此每次`readme.txt`中的`Tested up to`标识更新时,必须同步更新`.github/workflows/performance.yml`中使用的参考提交。 + +新选的参考提交哈希需满足以下要求: + + - 与`Tested up to`标识使用的新WP版本兼容 + - 已在codevitals.run平台追踪所有现有指标 + +当发布涉及最低WordPress版本要求变更的插件更新时,需更新Core SVN中失去支持分支的端到端测试GitHub Action工作流,否则该分支在发布后的首次工作流运行将会失败。 + +可通过在测试矩阵中添加`gutenberg-version`输入来固定工作流中使用的插件版本。[Core-59221](https://core.trac.wordpress.org/changeset/59221)展示了6.4分支的此类变更示例。 + +**注意:** 请始终使用包含错误修复的最终发行版(例如`x.y.2`或`x.y.3`)。若最终发行版尚未确定,请创建[Trac工单](https://core.trac.wordpress.org/ticket/62488)以免遗忘。 + +**选择提交的简易方法是选取trunk分支上最近一次通过性能测试的提交。** + +## 拓展阅读 + +- [高性能编辑器的演进之路](https://riad.blog/2020/02/14/a-journey-towards-a-performant-web-editor/) \ No newline at end of file diff --git a/explanations/architecture/styles.md b/explanations/architecture/styles.md new file mode 100644 index 0000000..c73dbe6 --- /dev/null +++ b/explanations/architecture/styles.md @@ -0,0 +1,549 @@ +#### 语义化类名 + +当前正在扩展布局区块支持输出的稳定语义化类名,相关讨论可在[此议题](https://github.com/WordPress/gutenberg/issues/38719)中查看。 + +目前通过布局区块支持可输出的语义化类名包括: + +- `is-layout-flow`:采用默认/流式布局类型的区块 +- `is-layout-constrained`:采用约束布局类型的区块 +- `is-layout-flex`:采用弹性布局类型的区块 +- `is-layout-grid`:采用网格布局类型的区块 +- `wp-container-$id`:其中`$id`为半随机数。该容器类仅当区块包含非默认布局值时存在,请勿直接将其用于CSS选择器,因其可能动态变化 +- `is-horizontal`:当区块显式设置`orientation`为`horizontal`时 +- `is-vertical`:当区块显式设置`orientation`为`vertical`时 +- `is-content-justification-left`:当区块显式设置`justifyContent`为`left`时 +- `is-content-justification-center`:当区块显式设置`justifyContent`为`center`时 +- `is-content-justification-right`:当区块显式设置`justifyContent`为`right`时 +- `is-content-justification-space-between`:当区块显式设置`justifyContent`为`space-between`时 +- `is-nowrap`:当区块显式设置`flexWrap`为`nowrap`时 + +### 禁用自动生成的布局样式 + +由于核心结构区块需要依赖布局样式,系统默认开启布局样式输出。但主题可通过使用`disable-layout-styles`区块支持来禁用自动生成的区块布局样式,同时保留语义化类名输出。选择此方案的主题需自行提供完整的布局样式支持,具体请参阅[主题支持文档](https://developer.wordpress.org/block-editor/how-to-guides/themes/theme-support/#disabling-base-layout-styles)中的相关说明。 + +### 基础布局样式 + +基础布局样式是指那些选择特定布局类型的所有区块共通的样式。常见的基础布局样式示例包括:为采用弹性布局类型的区块(例如按钮区块和社交图标区块)设置 `display: flex`,以及为受限布局提供默认的最大宽度。 + +基础布局样式通过[处理全局样式的主要 PHP 类](https://github.com/WordPress/wordpress-develop/blob/trunk/src/wp-includes/class-wp-theme-json.php)输出,并构成全局样式表的一部分。为了在经典主题中支持核心区块,无论主题是否提供自身的 `theme.json` 文件,这些样式始终会被输出。 + +通用布局定义存储在[核心布局区块支持文件](https://github.com/WordPress/wordpress-develop/blob/trunk/src/wp-includes/block-supports/layout.php)中。 + +### 独立布局样式 + +当选择支持布局的区块被渲染时,会通过 [`layout.php`](https://github.com/WordPress/wordpress-develop/blob/trunk/src/wp-includes/block-supports/layout.php) 处理并将以下两项内容添加到输出中: + +- 语义类名会被添加到区块标记中,以指示正在使用的布局设置。例如,`is-layout-flow` 用于使用默认/流式布局的区块(如群组区块),而 `is-content-justification-right` 会在用户将区块设置为右对齐时添加。 +- 为正在渲染的单个区块上设置的非默认布局值生成独立样式。这些样式通过容器类名附加到区块上,类名格式为 `wp-container-$id`,其中 `$id` 是一个[唯一编号](https://developer.wordpress.org/reference/functions/wp_unique_id/)。 + +### 可用的布局类型 + +目前有四种布局类型在使用: + +- 默认/流式布局:项目垂直堆叠。父容器区块的显示值未指定,因此可以使用该 HTML 元素的默认值。对于大多数元素,这通常是 `block`。子元素之间的间距通过垂直边距处理。 +- 受限布局:项目垂直堆叠,使用与流式布局相同的间距逻辑。具有子内容宽度的限制,为标准内容尺寸和宽尺寸输出宽度。默认使用 `theme.json` 中 `settings.layout` 设置的全局 `contentSize` 和 `wideSize` 值。 +- 弹性布局:项目使用弹性盒子布局显示。默认为水平方向。子元素之间的间距通过 `gap` CSS 属性处理。 +- 网格布局:项目使用网格布局显示。默认为 `auto-fill` 方式生成列,但也可以设置为固定列数。子元素之间的间距通过 `gap` CSS 属性处理。 + +关于控制区块之间的间距以及启用区块间距控制,请参阅:[什么是 blockGap,我该如何使用它?](https://developer.wordpress.org/block-editor/how-to-guides/themes/global-settings-and-styles/#what-is-blockgap-and-how-can-i-use-it) + +### 从主题中定位布局或容器区块 + +布局区块支持旨在通过区块和站点编辑器实现对布局功能的控制。在可能的情况下,尽量使用区块的功能来确定特定的布局需求,而不是依赖额外的样式表。 + +对于希望定位容器区块以添加或调整特定样式的主题,区块的类名通常是最佳选择。例如 `wp-block-group` 或 `wp-block-columns` 这样的类名通常是定位特定区块的可靠类名。除了区块和布局类名外,还有一个由区块和布局组合而成的类名:例如,对于具有受限布局的群组区块,类名将是 `wp-block-group-is-layout-constrained`。 + +对于使用特定布局类型的区块进行定位,请避免定位 `wp-container-`,因为容器类可能不会始终出现在渲染的标记中。 + +### 从界面控件到HTML标记 + +若您遵循[区块教程](https://developer.wordpress.org/block-editor/how-to-guides/block-tutorial/),可深入了解[区块API](https://developer.wordpress.org/block-editor/reference-guides/block-api/)的各个组成部分,并构建专属区块。本文将介绍区块如何让用户编辑其状态的核心概念。 + +要构建上述交互体验,区块开发者需具备以下要素: + +1. **界面控件**:向用户提供选项交互,例如调整区块字体大小。该控件负责读取区块数据(当前是否已设定字体大小?)及其他必要信息(本区块允许使用哪些字号?)。可查阅现有[组件库](https://developer.wordpress.org/block-editor/reference-guides/components/)。 +2. **区块属性**:区块需存储数据以记录修改状态,例如是否已设定字号。了解区块如何定义[属性](https://developer.wordpress.org/block-editor/reference-guides/block-api/block-attributes/)。 +3. **样式数据接入**:控件可能需要获取区块可用样式的外部信息,如色彩列表或字号列表。这类预设样式通常由主题定义(WordPress也提供默认配置)。查看主题可向编辑器提供的[数据列表](https://developer.wordpress.org/block-editor/reference-guides/theme-json-reference/theme-json-living/#settings),以及开发者如何通过[useSetting](https://developer.wordpress.org/block-editor/reference-guides/packages/packages-block-editor/#usesetting)获取这些数据。 +4. **将用户样式序列化为HTML标记**:用户操作后,需相应更新区块HTML标记(添加对应类或行内样式)。此序列化过程由[编辑/保存](https://developer.wordpress.org/block-editor/reference-guides/block-api/block-edit-save/)函数与[render_callback](https://developer.wordpress.org/block-editor/how-to-guides/block-tutorial/creating-dynamic-blocks/)函数实现,它们将区块数据转换为HTML代码。 + +本质上,这些是区块开发者实现用户样式定制需关注的核心机制。虽然完全手动可实现,但针对通用样式需求,可通过自动化API——区块支持功能来简化流程。 + +### 区块支持API + +[区块支持](https://developer.wordpress.org/block-editor/reference-guides/block-api/block-supports/)API允许区块声明其支持的功能。通过在[block.json文件](https://developer.wordpress.org/block-editor/reference-guides/block-api/block-metadata/)中添加配置信息,区块可向系统声明允许用户执行的操作类型。 + +例如: + +```json +{ + "name": "core/paragraph", + "...": "...", + "supports": { + "typography": { + "fontSize": true + } + } +} +``` + +段落区块在其`block.json`中声明支持字号调整。这意味着区块将显示字号调节控件(除非被主题禁用,详见[主题配置参考](https://developer.wordpress.org/block-editor/reference-guides/theme-json-reference/))。系统会自动配置控件数据(当前字号、可用字号列表),并在用户操作时将区块数据序列化为HTML标记(自动附加类与行内样式)。 + +通过`block.json`使用区块支持机制,开发者仅需数行代码即可实现完整交互体验。查看[区块支持API文档](/docs/reference-guides/block-api/block-supports.md)了解如何为静态或动态区块添加支持功能。 + +除简化开发流程外,该机制还有以下优势: + +- 区块样式信息可被原生移动应用和服务端调用 +- 区块对同类样式使用统一控件,确保用户体验一致性 +- 所用界面控件将随系统升级自动优化,无需开发者干预 + +# 编辑器中的样式 + +本文档将介绍影响区块编辑器用户内容的相关样式核心概念,同时提供对应的参考指南和教程链接,方便读者深入探究每个主题。本文主要面向区块创作者和参与区块编辑器项目的开发人员。 + +## HTML 与 CSS + +用户在区块编辑器中创建内容时,实际上在生成一系列产物:一个 HTML 文档和若干 CSS 样式表(可内嵌于文档或作为外部文件)。 + +最终生成的 HTML 文档由以下要素共同构成: + +- 主题提供的 [WordPress 模板](https://developer.wordpress.org/themes/basics/template-files/)(通过 PHP 实现的经典主题或通过 HTML 模板实现的区块主题)([详细了解](https://developer.wordpress.org/themes/block-themes/#differences-and-similarities-between-classic-themes-and-block-themes)二者差异) +- 具有预定义结构(HTML 标记)的[区块](https://developer.wordpress.org/block-editor/reference-guides/core-blocks/)与版式 +- 用户对内容的修改:添加内容、转换现有内容(如将段落转换为标题)或调整内容(为区块添加类或内联样式) + +前端加载的样式表包含: + +- **区块样式**:区块自带的样式表。在前端,您可能会看到 WordPress 定义的所有区块样式合并为单一样式表(`wp-block-library-*`),也可能是每个使用中的区块拥有独立样式表(如 `wp-block-group-*`、`wp-block-columns-*` 等)。完整说明请参阅[此说明文档](https://make.wordpress.org/core/2021/07/01/block-styles-loading-enhancements-in-wordpress-5-8/)。 +- **全局样式**:这些样式通过 theme.json 文件的数据动态生成(参见[说明文档](https://make.wordpress.org/core/2021/06/25/introducing-theme-json-in-wordpress-5-8/)、[参考指南](https://developer.wordpress.org/block-editor/reference-guides/theme-json-reference/)和[操作指南](https://developer.wordpress.org/block-editor/how-to-guides/themes/global-settings-and-styles/))。具体而言,它会整合来自 WordPress 的 theme.json、主题自带的 theme.json(如有)以及用户通过站点编辑器的全局样式侧边栏提供的数据,最终生成一个 ID 为 `global-styles-inline-css` 的内嵌样式表。 +- **主题样式**:传统上主题会自行注册样式表,其 ID 基于主题名称(如 `twentytwentytwo-style-css`)。如今除了自有样式表外,主题还可声明包含样式的 theme.json 文件,这些样式将成为全局样式生成样式表的一部分。 +- **用户样式**:用户在编辑器中的某些操作会生成样式内容,例如双色调、布局或链接颜色等功能。 +- **其他样式**:WordPress 核心和插件也可注册样式表。 + +## 区块样式 + +自 WordPress 5.0 引入区块编辑器以来,一直提供为用户“添加样式”到特定区块的工具。通过这些工具,用户可为区块附加新类或内联样式,从而改变其视觉呈现。 + +默认情况下,区块具有预设的 HTML 标记。以段落区块为例: + +```html +

+``` + +在最简形式下,任何针对 `p` 选择器的样式规则都会作用于该区块,无论这些规则来自区块、主题或其他来源。 + +用户可通过应用不同样式来改变区块状态:文本对齐方式、颜色、字体大小、行高等。这些状态通过 HTML 属性(主要是 `class` 或 `style` 属性,也可以是区块作者认为合适的任何其他属性)反映在区块的 HTML 标记中。 + +经过用户修改后,初始标记可能变为: + +```html +

+``` + +这就是我们所说的“用户提供的区块样式”,也称为“本地样式”或“序列化样式”。本质上,每个工具(字体大小、颜色等)最终都会向区块标记添加若干类和/或内联样式。这些类对应的 CSS 样式属于区块、全局或主题样式表的一部分。 + +修改区块状态的能力,加上区块可嵌套于其他区块的特性(例如段落区块位于编组区块内),创造了海量的潜在状态和样式组合可能。 + +#### 设置项到CSS规则的转换 + +从 `settings` 部分中,所有预设值都将转换为遵循此命名结构的CSS自定义属性:`--wp--preset--<类别>-<别名>`。选择器遵循上述样式部分描述的相同规则。 + +例如,以下 theme.json 配置: + +```json +{ + "settings": { + "color": { + "palette": { + "default": [ + { + "slug": "vivid-red", + "value": "#cf2e2e", + "name": "Vivid Red" + } + ], + "theme": [ + { + "slug": "foreground", + "value": "#000", + "name": "Foreground" + } + ] + } + }, + "blocks": { + "core/site-title": { + "color": { + "palette": { + "theme": [ + { + "slug": "foreground", + "value": "#1a4548", + "name": "Foreground" + } + ] + } + } + } + } + } +} +``` + +将被转换为以下CSS样式规则: + +```CSS +body { + --wp--preset--color--vivid-red: #cf2e2e; + --wp--preset--color--foreground: #000; +} + +.wp-block-site-title { + --wp--preset--color--foreground: #1a4548; +} +``` + +除了CSS自定义属性外,除双色调外的所有预设都会为每个值生成CSS类。上例还会生成以下CSS类: + +```CSS +/* vivid-red */ +.has-vivid-red-color { color: var(--wp--preset--color--vivid-red) !important; } +.has-vivid-red-background-color { background-color: var(--wp--preset--color--vivid-red) !important; } +.has-vivid-red-border-color { border-color: var(--wp--preset--color--vivid-red) !important; } + +/* foreground */ +.has-foreground-color { color: var(--wp--preset--color--foreground) !important; } +.has-foreground-background-color { background-color: var(--wp--preset--color--foreground) !important; } +.has-foreground-border-color { border-color: var(--wp--preset--color--foreground) !important; } + +/* 站点标题内的foreground */ +.wp-block-site-title .has-foreground-color { color: var(--wp--preset--color--foreground) !important; } +.wp-block-site-title .has-foreground-background-color { background-color: var(--wp--preset--color--foreground) !important; } +.wp-block-site-title .has-foreground-border-color { border-color: var(--wp--preset--color--foreground) !important; } +``` + +### 全局样式API的当前限制 + +#### 1. **为区块设置不同CSS选择器需服务端注册** + +默认情况下,分配给区块的选择器是 `.wp-block-<区块名称>`。但区块可以根据需要更改此设置,可以通过其 `block.json` 中的 `__experimentalSelector` 属性提供CSS选择器。 + +如果区块这样做,需要在服务端使用 `block.json` 进行注册,否则全局样式代码无法访问该信息,将使用区块的默认CSS选择器。 + +#### 2. **无法为不同样式定位不同HTML节点** + +每个样式块只能使用单个选择器。 + +当区块使用 `__experimentalSkipSerialization` 将不同样式属性序列化到包装器以外的不同节点时,这一点尤为重要。详见"区块支持的当前限制"。 + +#### 3. **每个区块仅支持单一属性** + +与区块支持类似,区块只能使用任何样式的单个实例。例如,区块只能拥有单一字体大小。详见相关"区块支持的当前限制"。 + +#### 4. **仅使用区块支持的区块会显示在全局样式UI中** + +站点编辑器中的全局样式UI设有按区块样式的界面。区块列表是使用来自区块 `block.json` 的区块支持动态生成的。如果区块希望被列出,需要使用区块支持机制。 + +## 布局样式 + +除了单个区块级别和全局样式中的样式外,还存在为基于区块的主题和经典主题输出的布局样式概念。 + +布局区块支持输出用于创建布局的区块之间共享的通用布局样式。布局样式对于为作为其他区块容器的任何区块提供通用样式非常有用。依赖这些布局样式的区块示例包括群组、行、列、按钮和社交图标。核心区块通过其 `block.json` 文件中 `supports` 下的 `layout` 设置启用此功能。 + +布局样式主要输出在两个位置: + +### 区块支持API的当前限制 + +尽管区块支持API具有实用价值,但区块开发者仍需注意其存在的一些限制。为了更好地理解这些限制,我们以表格区块为例进行说明: + +```html + + + + + + + + + + + + + + + + + + + +
标题
第一行
第二行
页脚
+``` + +1. **每个区块仅支持单一样式类型** + +在[所有可用样式](https://developer.wordpress.org/block-editor/reference-guides/block-api/block-supports/)中,区块只能使用每种样式的一个实例。以表格区块为例,它只能设置单一字体大小。如果区块开发者希望为表头、主体和页脚设置三种不同字体大小,当前区块支持API无法实现。更多详细信息及解决方案请参阅[此议题](https://github.com/WordPress/gutenberg/issues/33255)。 + +2. **样式仅序列化至区块最外层HTML节点(包装器)** + +区块支持API仅将字体大小值序列化至包装器,生成的HTML为``。当前API无法将该值序列化至其他节点(例如``)。 + +这项工作正处于积极开发阶段,您可以通过[跟踪议题](https://github.com/WordPress/gutenberg/issues/38167)了解进展。相关提案正在探索不同的用户更改序列化方案:不再由每个区块支持单独序列化数据(例如生成`has-small-font-size`、`has-green-color`等类),而是让区块获得单个类名(例如`wp-style-UUID`),并由WordPress在服务器端生成该类的CSS样式。 + +在该提案持续推进的同时,区块开发者可以使用实验性选项作为解决方案。任何区块支持都可以通过`__experimentalSkipSerialization`跳过HTML标记的序列化。例如: + +```json +{ + "name": "core/paragraph", + "...": "...", + "supports": { + "typography": { + "fontSize": true, + "__experimentalSkipSerialization": true + } + } +} +``` + +这意味着排版区块支持将执行所有操作(创建UI控件、将区块属性绑定到控件等),但不会将用户值序列化到HTML标记中。类和内联样式不会自动应用于包装器,区块开发者需要在`edit`、`save`和`render_callback`函数中实现此功能。具体实现示例请参阅[此议题](https://github.com/WordPress/gutenberg/issues/28913)。 + +请注意,如果为组(排版、颜色、间距)启用`__experimentalSkipSerialization`,将影响该组内_所有_区块支持。在上例中,`typography`组内的_所有_属性(如`fontSize`、`lineHeight`、`fontFamily`等)都会受到影响。 + +若仅针对_单个_属性启用,可以使用数组声明要跳过的属性。下例中仅`fontSize`会跳过序列化,`typography`组内的其他项目(如`lineHeight`、`fontFamily`等)不受影响: + +```json +{ + "name": "core/paragraph", + "...": "...", + "supports": { + "typography": { + "fontSize": true, + "lineHeight": true, + "__experimentalSkipSerialization": [ "fontSize" ] + } + } +} +``` + +该功能支持已[通过此PR添加](https://github.com/WordPress/gutenberg/pull/36293)。 + +## 全局样式 + +全局样式是指生成全站样式的一种机制。与前一节所述的区块样式不同,这些样式不会序列化到文章内容中,也不会附加到区块HTML。该系统的输出是一个ID为`global-styles-inline-css`的新样式表。 + +该机制[于WordPress 5.8引入](https://make.wordpress.org/core/2021/06/25/introducing-theme-json-in-wordpress-5-8/)。当时仅从WordPress和启用主题获取数据。WordPress 5.9将该系统扩展为同时接收来自用户的样式数据。 + +基本数据流如下: + +![全局样式数据流](https://raw.githubusercontent.com/WordPress/gutenberg/HEAD/docs/explanations/architecture/assets/global-styles-input-output.png) + +样式表的生成过程主要包含三个步骤: + +1. 收集数据:[WordPress内置](https://github.com/WordPress/wordpress-develop/blob/trunk/src/wp-includes/theme.json)的`theme.json`文件、启用主题的`theme.json`文件(如果存在),以及用户通过站点编辑器中全局样式UI提供的样式 +2. 整合数据:对来自不同来源(WordPress默认值、主题和用户)的结构化信息进行标准化处理,并合并为统一结构 +3. 将数据转换为样式表:将内部表示转换为CSS样式规则,并将其加入样式表队列 + +### 收集数据 + +数据可能来自三个不同来源:WordPress 默认配置、当前启用主题或用户设置。这三类数据均采用统一的 [`theme.json` 格式](https://developer.wordpress.org/block-editor/reference-guides/theme-json-reference/)。 + +WordPress 和当前主题的数据从对应的 `theme.json` 文件中获取。用户数据则从数据库中提取,这些数据是用户通过站点编辑器的全局样式侧边栏保存修改后存储的。 + +### 整合数据 + +此阶段的目标是构建统一的数据结构。 + +该阶段包含两个重要处理流程:首先,系统需要对所有输入数据进行标准化处理,因为不同来源可能使用不同版本的 `theme.json`格式(例如某个主题可能使用 [v1 版本](https://developer.wordpress.org/block-editor/reference-guides/theme-json-reference/theme-json-v1/),而 WordPress 基础框架使用[最新版本](https://developer.wordpress.org/block-editor/reference-guides/theme-json-reference/theme-json-living/))。其次,系统需要确定如何将输入数据合并为统一结构,这将是后续章节的重点内容。 + +#### 样式处理 + +对于 `theme.json` 结构中的不同部分,系统会采用差异化处理方式。`styles` 章节中的数据遵循以下逻辑进行融合:用户数据会覆盖主题数据,主题数据会覆盖 WordPress 数据。 + +例如,假设我们分别从 WordPress、主题和用户获取到以下三个 `theme.json` 结构: + +```json +{ + "styles": { + "color": { + "background": "" + }, + "typography": { + "fontSize": "" + } + } +} +``` + +```json +{ + "styles": { + "typography": { + "fontSize": "<主题值>", + "lineHeight": "<主题值>" + } + } +} +``` + +```json +{ + "styles": { + "typography": { + "lineHeight": "<用户值>" + } + } +} +``` + +整合后的结果将是: + +```json +{ + "styles": { + "color": { + "background": "" + }, + "typography": { + "fontSize": "<主题值>", + "lineHeight": "<用户值>" + } + } +} +``` + +#### 设置处理 + +`settings` 章节的处理方式与样式不同。大部分设置仅用于编辑器配置,不会影响全局样式。其中仅预设值会最终影响样式表输出。 + +预设值是指在界面不同区域向用户展示的预定义样式,例如调色板或字体尺寸。包含以下设置项:`color.duotone`、`color.gradients`、`color.palette`、`typography.fontFamilies`、`typography.fontSizes`。与 `styles` 不同,来自不同来源的预设值不会相互覆盖,而是全部保存在整合后的结构中。 + +例如,假设我们分别从 WordPress、主题和用户获取到以下三个 `theme.json` 结构: + +```json +{ + "settings": { + "color": { + "palette": [ "" ], + "gradients": [ "" ] + } + } +} +``` + +```json +{ + "settings": { + "color": { + "palette": [ "<主题值>" ] + }, + "typography": { + "fontFamilies": [ "<主题值>" ] + } + } +} +``` + +```json +{ + "settings": { + "color": { + "palette": [ "<用户值>" ] + } + } +} +``` + +整合后的结果将是: + +```json +{ + "settings": { + "color": { + "palette": { + "default": [ "" ], + "theme": [ "<主题值>" ], + "user": [ "<用户值>" ] + }, + "gradients": { + "default": [ "" ] + } + }, + "typography": { + "fontFamilies": { + "theme": [ "<主题值>" ] + } + } + } +} +``` + +### 从数据到样式 + +生成样式表的最后阶段是将整合数据转换为 CSS 样式规则。 + +#### 样式转 CSS 规则 + +可以将 `styles` 章节视为 CSS 规则的结构化表示,每个数据块对应一条 CSS 规则: + +- theme.json 中的键值对映射为 CSS 声明(`property: value`) +- 每个数据块的 CSS 选择器根据其语义生成: + - 顶层章节使用 `body` 选择器 + - 顶层元素使用与其表示的 HTML 元素匹配的 ID 选择器(例如 `h1` 或 `a`) + - 区块使用默认生成的类名(`core/group` 转换为 `.wp-block-group`),除非通过 `block.json` 明确设置不同选择器(`core/paragraph` 转换为 `p`)。详见“当前限制”章节说明 + - 区块内的元素使用区块选择器与元素选择器的组合 + +例如,以下 `theme.json` 结构: + +```json +{ + "styles": { + "typography": { + "fontSize": "<顶层值>" + }, + "elements": { + "h1": { + "typography": { + "fontSize": "

" + } + } + }, + "blocks": { + "core/paragraph": { + "color": { + "text": "<段落值>" + } + }, + "core/group": { + "color": { + "text": "<群组值>" + }, + "elements": { + "h1": { + "color": { + "text": "<群组内 h1 值>" + } + } + } + } + } + } +} +``` + +将被转换为以下 CSS: + +```css +body { + font-size: <顶层值>; +} +h1 { + font-size:

; +} +p { + color: <段落值>; +} +.wp-block-group { + color: <群组值>; +} +.wp-block-group h1 { + color: <群组内 h1 值>; +} +``` \ No newline at end of file diff --git a/explanations/history.md b/explanations/history.md new file mode 100644 index 0000000..e545cde --- /dev/null +++ b/explanations/history.md @@ -0,0 +1,35 @@ +# 历史沿革 + +本部分收录了与古腾堡项目历史相关的链接及资源,涵盖项目起源、灵感来源及初期构想。 + +## 灵感溯源 + +2017年初开展的社区[编辑器体验调查](https://make.wordpress.org/core/2017/04/07/editor-experience-survey-results/)结果显示,WordPress平台亟需打造全新的编辑体验。 + +以下是对古腾堡项目及区块编辑器创建产生深远影响的历史文献与产品清单: +- [Parrot:面向WordPress的集成化站点构建器与编辑器概念](https://intenseminimalism.com/2017/parrot-an-integrated-site-builder-and-editor-concept-for-wordpress/) +- LivingDocs +- Apple Keynote +- Slack +- Google Sites v2 + +### 古腾堡更新与功能纵览 + +- [未来主题构想](https://wordpress.tv/2021/01/21/eileen-violini-themes-of-the-future-the-new-frontier-of-gutenberg-block-based-themes-and-theme-development/),艾琳·维奥里尼(2021年1月) +- [进展检视:站点编辑与定制功能](https://make.wordpress.org/core/2020/12/10/status-check-site-editing-and-customization/),马蒂亚斯·本图拉·鲍塞罗(2020年12月) +- [2020年度WordPress现状全站编辑演示](https://youtu.be/QI3qCoiuG3w?t=1279),马特·穆伦维格(2020年12月) +- [拥抱模块化设计理念](https://riad.blog/2020/01/28/embrace-the-modularity/),里亚德·本盖拉(2020年1月) +- [2019年度WordPress现状古腾堡演示](https://www.youtube.com/watch?v=LezbkeV059Q),马特·穆伦维格(2019年12月) +- [通过WordPress提升JavaScript技能](https://gziolo.pl/2019/07/15/growing-javascript-skills-with-wordpress/) - WordPress JavaScript技术大会(2019年7月) +- [古腾堡的未来展望](https://wordpress.tv/2018/07/09/matias-ventura-beyond-gutenberg/),马蒂亚斯·本图拉·鲍塞罗(2018年7月) +- [区块解析:古腾堡设计模式探微](https://wordpress.tv/2018/07/08/tammie-lister-anatomy-of-a-block-gutenberg-design-patterns/),塔米·李斯特(2018年7月) +- [古腾堡的设计语言体系](https://lamda.blog/2018/04/22/the-language-of-gutenberg/),米格尔·丰塞卡(2018年4月) +- [2017年度WordPress现状古腾堡演示](https://youtu.be/XOY3ZUO6P0k?t=2100),马特·穆伦维格携马蒂亚斯·本图拉·鲍塞罗联合演示(2017年12月) +- [古腾堡:忒修斯之船哲学思辨](https://matiasventura.com/post/gutenberg-or-the-ship-of-theseus/),马蒂亚斯·本图拉·鲍塞罗(2017年10月) +- [我们以古腾堡命名的深意](https://ma.tt/2017/08/we-called-it-gutenberg-for-a-reason/),马特·穆伦维格(2017年8月) +- [古腾堡如何重塑WordPress开发模式](https://riad.blog/2017/10/06/how-gutenberg-is-changing-wordpress-development/),里亚德·本盖拉(2017年10月) +- [古腾堡与定制功能路线图修订建议](https://make.wordpress.org/core/2017/08/11/revised-suggested-roadmap-for-gutenberg-and-customization/),塔米·李斯特(2017年8月) +- [探索古腾堡与后续规划](https://make.wordpress.org/core/2017/08/08/discovering-gutenberg-and-next-steps/),塔米·李斯特(2017年8月) +- [古腾堡将如何引领WordPress未来变革](https://www.linkedin.com/pulse/gutenberg-morten-rand-hendriksen/),莫滕·兰德-亨里克森(2017年8月) + +您还可以查阅[古腾堡相关文章索引](https://make.wordpress.org/core/handbook/references/keeping-up-with-gutenberg-index/)获取更多资源。 \ No newline at end of file diff --git a/explanations/user-interface/README.md b/explanations/user-interface/README.md new file mode 100644 index 0000000..50e7c2c --- /dev/null +++ b/explanations/user-interface/README.md @@ -0,0 +1,63 @@ +# 用户界面 + +## 区块编辑器 + +区块编辑器的整体布局采用顶部工具栏+内容区域的设计。 + +![编辑器界面](https://cldup.com/VWA_jMcIRw-3000x3000.png) + +**工具栏**包含文档级操作:编辑/选择模式、保存状态、撤销/重做/插入等全局功能、设置开关以及发布选项。 + +**内容区域**承载文档主体内容。 + +**设置侧边栏**包含文档相关设置(标签、分类、发布时间等)以及"区块"标签页中的区块设置。工具栏中的齿轮按钮可隐藏设置侧边栏,让用户获得更专注的写作体验。在小尺寸屏幕上,侧边栏默认处于隐藏状态。 + +## 区块单元 + +区块是编辑器最基础的构成单元。广义而言,所有内容都是区块。用户通过区块构建文章与页面,这种模式完美复现了底层HTML标记的垂直流式布局。 + +通过将文档每个部分呈现为可操作的独立区块,我们实现了区块特定功能的场景化调用。这一设计灵感源自桌面应用惯例,既能支持丰富的高级功能,又不会造成界面臃肿。 + +被选中的区块会显示多项场景化操作: + +![区块界面](https://cldup.com/3tQqIncKPB-3000x3000.png) + +区块界面提供基础操作功能。区块编辑器致力于提供优质通用的默认设置,用户无需依赖设置侧边栏的高级功能即可完成完整文档创作。 + +**区块工具栏**突出显示常用功能。**区块图标**位于区块工具栏内,包含针对当前选中区块的顶层控制,主要支持用户将区块转换为其他兼容类型。部分区块还会通过区块图标提供替代样式选择。 + +**区块格式化**选项用于调整区块层级设置,**行内格式化**选项则用于修饰区块内部元素。当区块内容较长时,随着用户向下滚动页面,区块工具栏会自动固定在屏幕顶部。 + +通过**区块移动器**图标可实现区块的上下移动。点击省略号菜单可唤出附加功能:删除与复制区块,以及"以HTML编辑"、"转换为可复用区块"等**高级操作**。 + +未选中的区块不会显示区块工具栏或任何场景化控件,此时区块仅呈现内容预览效果: + +![未选中区块](https://cldup.com/DH9HZnEgwH-3000x3000.png) + +请注意选择状态与焦点状态可独立存在。例如图片区块被选中时,焦点可能位于标题输入框。 + +## 设置侧边栏 + +![设置侧边栏](https://cldup.com/iAqrn6Gc8o-3000x3000.png) + +侧边栏包含"文档"与"区块"两个标签页: + +- **文档标签页**显示正在编辑的文章或页面的元数据及设置 +- **区块标签页**显示当前选中区块的元数据及设置 + +每个标签页均包含可展开/折叠的**侧边栏分区**编辑字段组。 + +若区块需要高级配置,相关设置应置于设置侧边栏中。但请注意:区块基础运行所需的设置不应放在侧边栏区块标签页——用户可能为获得沉浸式写作体验而关闭侧边栏。建议设置合理的默认值,并将重要操作置于区块工具栏。 + +适合放入侧边栏区块标签页的功能包括: + +- 文本区块的首字下沉 +- 图库的列数设置 +- "最新文章"区块的文章数量或分类筛选 +- 所有非基础操作必需的配置项 + +## 区块库 + +![区块库](https://cldup.com/7QoQIoLk-A-3000x3000.png) + +当用户通过工具栏或内容区场景化菜单插入区块时,**区块库**界面将会启动。库内区块按可展开的分组进行组织,搜索栏会随用户输入实时筛选区块列表。用户可通过选择**区块按钮**或**区块名称**来选用特定区块。 \ No newline at end of file diff --git a/explanations/user-interface/animation.md b/explanations/user-interface/animation.md new file mode 100644 index 0000000..7512b72 --- /dev/null +++ b/explanations/user-interface/animation.md @@ -0,0 +1,40 @@ +# 动画设计 + +动画能够有效强化界面层级感和空间方位感。本文档将阐述添加动画时应遵循的设计原则。 + +## 核心原则 + +### 起始原点 + +- 动画可锚定界面元素,例如菜单可从触发按钮位置缩放展开 +- 动画可构建空间感知,例如侧边栏从屏幕边缘滑入,暗示其始终隐藏于屏幕之外 +- 设计动画时需遵循真实物理规律。想象界面元素由实体材料构成——当它们不在屏幕上时位于何处?用动画来呈现这种空间关系 + +### 响应速度 + +- 动画绝不应阻碍用户交互,必须保持快速响应,通常在0.2秒内完成 +- 用户无需等待动画完成即可进行交互操作 +- 确保动画性能优化。优先使用CSS `transform`属性,通过GPU渲染实现流畅效果 +- 若无法实现快速高性能的动画效果,宁可不使用动画 + +### 简洁克制 + +- 非弹性材料不做弹跳动画 +- 避免旋转、折叠或曲线路径动画,保持简洁直观 + +### 统一规范 + +建立统一的动画物理规则,使所有元素的动效保持协调一致、符合预期。动画应当匹配用户心理预期,否则可能不适配当前场景。 + +若已有现成动画效果,请直接复用。 + +## 无障碍考量 + +- 动画需保持克制,注意可能引发前庭功能障碍用户不适的情况(详见[运动触发前庭障碍研究](https://www.ncbi.nlm.nih.gov/pubmed/29017000)) +- 避免对正在向辅助技术报告内容的区域(如正在接收更新的`aria-live`区域)添加动画,这可能导致技术解析动态变化区域时产生混乱 +- 杜绝非用户直接触发的动画 +- 确保动画遵循操作系统层级的"减弱动态效果"设置,可通过[`prefers-reduced-motion`](https://developer.mozilla.org/zh-CN/docs/Web/CSS/@media/prefers-reduced-motion)媒体查询实现。Gutenberg提供`@reduce-motion`混合宏,需与含CSS`animate`属性的规则配合使用 + +## 复用动画库 + +通用`Animate`组件用于驱动界面各区域的动画效果。具体可用动画请参阅[组件文档](/packages/components/src/animate/README.md)。 \ No newline at end of file diff --git a/explanations/user-interface/block-design.md b/explanations/user-interface/block-design.md new file mode 100644 index 0000000..cddb23d --- /dev/null +++ b/explanations/user-interface/block-design.md @@ -0,0 +1,203 @@ +### 高级区块设置 + +设置侧边栏中的“区块”标签页可包含额外的区块选项与配置项。请注意用户可能关闭侧边栏且永不使用,不应将关键选项置于侧边栏内。 + +![段落区块在侧边栏中的高级设置截图](https://developer.wordpress.org/files/2022/01/advanced-settings-do.png) +**正确做法:** +首字下沉功能并非区块基础运行的必要功能,可将其作为可选配置置于区块标签页中。 + +### 移动端适配 + +尽可能在多设备、多屏幕尺寸下检验区块的视觉呈现、交互体验与功能表现。 + +### 支持古腾堡深色背景编辑模式 + +检验区块在编辑器[深色背景](/docs/how-to-guides/themes/theme-support.md#dark-backgrounds)下的视觉呈现效果。 + +## 示例演示 + +以下通过古腾堡默认区块的标注示例展示部分实践方案: + +### 段落区块 + +编辑器最基础的构成单元。段落区块即简易输入框。 + +![段落区块](https://cldup.com/HVJe5bGZ8H-3000x3000.png) + +#### 占位状态 +- 简洁提示语“输入/选择区块”,选中区块后提示消失 + +#### 选中状态 +- 区块工具栏:配备转换器(可切换为标题等) +- 区块工具栏:提供基础文本对齐功能 +- 区块工具栏:支持行内格式设置(加粗、斜体、删除线及链接) + +### 图片区块 + +基础图片区块。 + +![图片区块占位状态](https://cldup.com/w6FNywNsj1-3000x3000.png) + +#### 占位状态 +- 通用灰色占位区块,支持上传图片、直接拖放图片或从媒体库选取 + +#### 选中状态 +- 区块工具栏:对齐选项(若主题支持则包含宽幅与全宽对齐) +- 区块工具栏:编辑图片(打开媒体库) +- 区块工具栏:链接按钮 +- 图片上传后,下方出现标注输入框及“添加标注…”提示语: + +![图片区块](https://cldup.com/6YYXstl_xX-3000x3000.png) + +#### 区块设置 +- 描述文本:“一图胜千言!插入单张图片” +- 提供修改替代文本、添加自定义CSS类等功能 + +*图片区块的未来改进方向包括取消媒体弹窗,允许用户直接在占位区选取图片。总体而言应尽量避免使用弹窗。* + +### 最新文章区块 + +![最新文章区块](https://cldup.com/8lyAByDpy_-3000x3000.png) + +#### 占位状态 +插入即生效,无占位状态。默认显示最近5篇文章。 + +#### 选中状态 +- 区块工具栏:对齐选项 +- 区块工具栏:列表视图/网格视图切换功能 + +*注意:因无可切换的相似区块,此时区块工具栏不包含区块标识符* + +#### 区块设置 +- 描述文本:“展示最新文章列表” +- 提供文章排序、按分类筛选、默认显示篇数设置、发布日期显示等选项 + +*最新文章区块凭借完善的默认配置,插入后即可完整运作。* + +## 注意事项 + +### 区块工具栏 + +按逻辑分组组织工具栏控件,避免为每个控件单独设置分组。 + +![对比区块工具栏合理与不合理分组的截图](https://make.wordpress.org/design/files/2021/03/docs__block-toolbar-do-dont.png) + +### 区块标识 + +区块名称应简洁明了,方便用户在区块库中快速定位。例如命名为“YouTube”的区块直观易寻,而命名为“嵌入式视频(YouTube)”则显得冗长难觅。 + +在文档或界面中提及区块时,区块标题采用首字母大写格式,“区块”描述词保持小写。例如: +- 段落区块 +- 最新文章区块 +- 媒体与文本区块 + +区块应配备辨识性图标,建议使用单色设计并避免与现有区块图标重复。核心区块图标基于[Material Design图标集](https://fonts.google.com/icons),可参考该系列或[Dashicons](https://developer.wordpress.org/resource/dashicons/)获取设计灵感。 + +![展示简洁区块名称的区块库截图](https://developer.wordpress.org/files/2022/01/blocks-do.png) +**建议:** +使用简练的区块名称 + +![展示多行长名称的区块库截图](https://developer.wordpress.org/files/2022/01/blocks-dont.png) +**避免:** +使用冗长的多行区块名称 + +### 区块描述 + +每个区块需包含功能说明,该描述将显示在设置侧边栏中。 + +可通过[registerBlockType函数](/docs/reference-guides/block-api/block-registration.md)的description属性添加描述。 + +采用“动词+主语”结构的祈使句,例如: +- 作为所有叙事的基础构建单元 +- 通过内容分节帮助访客(及搜索引擎)理解内容结构 +- 创建项目符号列表或编号列表 + +![简短区块描述示例](https://developer.wordpress.org/files/2022/01/block-descriptions-do.png) +**建议:** +使用简短明晰的区块描述 + +![包含品牌信息的冗长描述示例](https://developer.wordpress.org/files/2022/01/block-descriptions-dont.png) +**避免:** +使用冗长描述及品牌信息 + +### 占位符 + +若区块需用户配置后方可显示,应提供指导性占位状态。 + +![图库区块占位符示例](https://developer.wordpress.org/files/2022/01/placeholder-do.png) +**建议:** +提供具指导意义的占位状态 + +![无说明信息的炫目占位符示例](https://developer.wordpress.org/files/2022/01/placeholder-dont.png) +**避免:** +使用品牌元素且仅依赖标题传递指引 + +### 选中与未选中状态 + +未选中时,区块预览应最大程度接近前端显示效果。 + +选中状态下,可呈现输入框或按钮等配置选项,特别是基础操作必需的功能。 + +![包含行内必控功能的谷歌地图区块](https://developer.wordpress.org/files/2022/01/block-controls-do.png) +**建议:** +区块运行必需的控制元件应直接内嵌于编辑视图 + +![必控元件移至侧边栏的谷歌地图区块](https://developer.wordpress.org/files/2022/01/block-controls-dont.png) +**避免:** +将核心控制元件置于侧边栏,否则移动端用户或关闭侧边栏的桌面用户将无法正常操作区块 + +# 区块设计 + +以下是设计新区块时的最佳实践,包含相关建议及现有区块的详细说明,以展示我们创建区块的方法。 + +## 最佳实践 + +### 区块内容区域是主要交互界面 + +由于区块本身代表实际显示在网站上的内容,此处的交互最符合直接操作原则,对用户也最为直观。这里应被视为添加和操作内容、调整显示效果的主要界面。交互方式有两种: + +1. 区块内容区域中的占位内容可视为引导界面,帮助用户遵循指令或“填空”。例如,嵌入第三方服务内容的区块可能在占位区域包含该服务的登录控件。 +2. 用户添加内容后,选中区块可显示用于调整或编辑内容的附加控件。例如,订阅区块可能显示隐藏/显示订阅者数量的控件。但这类操作应保持最小化,避免用户选中区块时引发区块尺寸与显示的剧烈变化(这可能造成困惑或困扰)。 + +### 区块工具栏作为次要选项设置区域 + +基础区块设置并非始终适合放在占位内容界面中。作为次要选择,对区块功能至关重要的选项可置于区块工具栏。区块工具栏仍保持高度情境化,且在所有屏幕尺寸下可见。需注意其图标化界面的特性,因此工具栏中的控件必须能通过图标有效传达信息。 + +### 按关联性分组工具栏控件 + +区块工具栏采用分层分段式控件分组:首段包含区块类型控件(如区块切换器、拖拽柄和移动控制器);次段包含影响整个区块的通用与特定工具,随后是行内格式工具与“更多”菜单;可选设置的“元数据”或“其他”组可将部分工具独立分段。 + +![区块工具栏分段示意图](https://make.wordpress.org/design/files/2021/03/docs_block-toolbar-structure.png) + +### 设置侧边栏仅用于高级三级控件 + +设置侧边栏在小屏/移动设备上默认隐藏,在桌面视图也可能被折叠。因此不应将区块基础操作所必需的功能置于此处。建议选择合理的默认值,将重要操作置于区块工具栏,并将设置侧边栏视为多数用户无需开启的辅助区域。 + +若选项超过五项,应使用分区标题帮助用户快速浏览和理解可用选项。 + +默认情况下每个设置侧边栏都包含“高级”分区,该区域设有“附加CSS类”字段,可用于放置高级用户控件。 + +## 设置状态与实时预览状态 + +设置状态(常称为“占位界面”)用于在展示区块实时预览前引导用户完成初始化流程。该过程收集渲染区块所需的用户信息,并通过灰色背景明确标识设置状态。并非所有区块都需要设置状态(如段落区块)。 + +![图片区块设置状态示例](https://make.wordpress.org/design/files/2021/03/docs__gallery-setup-state.png) + +**无需**设置状态的情况: +- 能提供满足多数需求的优质默认内容 +- 默认内容易于编辑和自定义 + +**需要**设置状态的情况: +- 缺乏适用于多数用户的明确默认状态 +- 需收集与区块实时预览无直接关联的用户输入(如要求输入API密钥来渲染内容) +- 需要更多用户信息才能生成有效的默认内容 + +对于具有设置状态的区块,用户完成设置流程后,占位界面会被该区块的实时预览状态替代。 + +![图库实时预览状态示例](https://make.wordpress.org/design/files/2018/12/gallery-live-preview.png) + +选中区块时可能显示附加控件用于自定义内容。例如选中图片库时,会显示删除或添加图片的控件。 + +![选中区块时显示附加控件的示例](https://make.wordpress.org/design/files/2018/12/gallery-additional-controls.png) + +多数情况下区块设置状态仅显示一次,后续通过实时预览状态进行优化。但某些场景下需允许用户返回设置状态,例如区块内容被全部删除时,或通过区块工具栏/侧边栏的链接进入。 \ No newline at end of file diff --git a/explanations/user-interface/design-resources.md b/explanations/user-interface/design-resources.md new file mode 100644 index 0000000..26b4ce6 --- /dev/null +++ b/explanations/user-interface/design-resources.md @@ -0,0 +1,55 @@ +# 资源 + +## Figma + +[WordPress设计团队](https://make.wordpress.org/design/)使用[Figma](https://www.figma.com/)进行协作与成果共享。若您希望参与贡献,可通过[WordPress Figma设计资源库](https://make.wordpress.org/design/handbook/get-involved/tools-figma/)制作设计原型。您也可以加入[Slack](https://make.wordpress.org/chat/)的[#design频道](https://app.slack.com/client/T024MFP4J/C02S78ZAL)进行咨询交流。Figma提供免费账户,通过该账户您可以使用共享资源库中的组件,或通过复制文件至草稿箱进行编辑。对WordPress资源库的完整编辑权限为付费功能,仅限设计团队使用。 + +### 如何参与贡献 + +### Figma使用学习资源 + +[Figma入门指南](https://help.figma.com/category/9-getting-started) + +[UI/UX设计必备:Figma在线教程精选](https://medium.com/quick-design/top-online-tutorials-to-learn-figma-for-ui-ux-design-4e9c6721a72d) + +[Figma功能导览](https://help.figma.com/article/12-getting-familiar-with-figma) + +### 文件与项目使用指南 + +[Figma文件与项目入门](https://help.figma.com/article/298-getting-started-with-files-and-projects) + +[什么是文件?](https://help.figma.com/article/298-getting-started-with-files-and-projects#files) + +[什么是项目?](https://help.figma.com/article/298-getting-started-with-files-and-projects#projects) + +[视频教程](https://www.youtube.com/watch?v=c5HS6smhq2E) + +[常见问题解答](https://help.figma.com/article/298-getting-started-with-files-and-projects#faq) + +### 组件使用指南 + +[组件功能入门](https://help.figma.com/article/66-components) + +[什么是组件?](https://help.figma.com/article/66-components#components) + +[视频教程](https://help.figma.com/article/66-components#videos) + +### WordPress Figma资源库使用指南 + +**如何在Figma中启用WordPress组件库** + +![启用Figma资源库动态演示](https://wordpress.org/gutenberg/files/2019/08/figma-howtoturnonlibraries.gif) + +1. 点击**资源面板**中的**团队资源库**图标: + +![悬停显示团队资源库图标](https://wordpress.org/gutenberg/files/2019/08/figma-turn-on-libraries-e1564770916643.png) + +2. 开启**资源库**弹窗后,您可查看可用资源库列表。通过切换按钮_启用_或_禁用_特定资源库: + +![在Figma中启用WordPress组件库](https://wordpress.org/gutenberg/files/2019/08/figma-libraries-e1564770879415.png) + +**如何优化或贡献WordPress组件React库(即将推出)** + +Figma中的WordPress组件与实时React组件保持同步。关于如何优化或贡献React版WordPress组件的文档即将发布。 + +如有疑问,欢迎随时在WordPress社区Slack的#design频道中提出。 \ No newline at end of file diff --git a/getting-started/README.md b/getting-started/README.md new file mode 100644 index 0000000..7f4913b --- /dev/null +++ b/getting-started/README.md @@ -0,0 +1,41 @@ +# 入门指南 + +欢迎阅读入门指南文档。从搭建开发环境到构建第一个区块,再到理解基础概念,本部分内容都是绝佳的起点——无论您是区块开发新手,还是希望提升技能的开发者。 + +## 本章导航 + +通过以下链接快速定位本章节内容。若从未构建过区块,建议按所列顺序阅读文档。 + +- **[区块开发环境](https://developer.wordpress.org/block-editor/getting-started/devenv/)**: 配置专属开发环境,认识区块开发基础工具:[`wp-env`](https://developer.wordpress.org/block-editor/getting-started/devenv/get-started-with-wp-env/)、[`create-block`](https://developer.wordpress.org/block-editor/getting-started/devenv/get-started-with-create-block/) 与 [`wp-scripts`](https://developer.wordpress.org/block-editor/getting-started/devenv/get-started-with-create-block/) +- **[快速入门指南](https://developer.wordpress.org/block-editor/getting-started/quick-start-guide/)**: 一分钟内快速创建可运行的自定义区块 +- **[教程:构建你的第一个区块](https://developer.wordpress.org/block-editor/getting-started/tutorial/)**: 从零开始构建功能完整的自定义区块 +- **[区块开发基础](https://developer.wordpress.org/block-editor/getting-started/fundamentals/)**: 掌握区块开发核心概念 +- **[术语表](https://developer.wordpress.org/block-editor/getting-started/glossary/)**: 区块编辑器常用术语释义 +- **[常见问题解答](https://developer.wordpress.org/block-editor/getting-started/faq/)**: 古腾堡项目近年来的高频问答合集 + +## 跟进WordPress项目动态 + +完成本章学习后,您将对区块开发形成系统认知。接下来该关注什么? + +WordPress项目(特别是古腾堡)迭代迅速。为帮助您持续跟进,以下推荐核心开发者资源。请根据自身需求选择合适的信息渠道: + +- **[WordPress发展路线图](https://wordpress.org/about/roadmap/)**: 项目高层级发展规划 +- **[核心开发博客](https://make.wordpress.org/core/)**: 获取WordPress核心更新的主阵地 +- **[WordPress Slack社区](https://make.wordpress.org/chat/)**: 官方协作平台,建议加入`#core`与`#core-editor`频道 +- **[古腾堡GitHub仓库](https://github.com/WordPress/gutenberg/)**: 区块编辑器开发实况窗口 +- **[古腾堡动态追踪](https://make.wordpress.org/core/handbook/references/keeping-up-with-gutenberg-index/)**: 跨团队动态合集 +- **[古腾堡新功能速递](https://make.wordpress.org/core/tag/gutenberg-new/)**: 双周版本更新详解 +- **[开发者月报](https://developer.wordpress.org/news/)**: 月度重要开发变更汇总 + +## 扩展资源 + +如需更多区块开发与编辑器扩展资源,请查阅区块编辑器手册的其他章节。[block-development-examples](https://github.com/WordPress/block-development-examples)代码库中还有更多实践案例。 + +若想系统学习,欢迎访问[Learn WordPress](https://learn.wordpress.org/)平台,这里提供精选教程与课程: + +- [区块开发入门:构建你的第一个自定义区块](https://learn.wordpress.org/course/introduction-to-block-development-build-your-first-custom-block/) +- [短代码转区块开发实战](https://learn.wordpress.org/course/converting-a-shortcode-to-a-block/) +- [WordPress数据层应用详解](https://learn.wordpress.org/course/using-the-wordpress-data-layer/) +- [区块模式注册指南](https://learn.wordpress.org/workshop/registering-block-patterns/) +- [古腾堡区块开发入门](https://learn.wordpress.org/workshop/intro-to-gutenberg-block-development/) +- [区块编辑器发布流程详解](https://learn.wordpress.org/workshop/intro-to-publishing-with-the-block-editor/) \ No newline at end of file diff --git a/getting-started/devenv/README.md b/getting-started/devenv/README.md new file mode 100644 index 0000000..9149a79 --- /dev/null +++ b/getting-started/devenv/README.md @@ -0,0 +1,61 @@ +# 区块开发环境 + +本指南将帮助您搭建合适的开发环境,用于创建扩展和修改WordPress区块编辑器的区块及其他插件。 + +区块开发环境包含在计算机上成功进行区块编辑器开发所需的工具。以下三项是基本要求: + +- [区块开发环境](#区块开发环境) + - [代码编辑器](#代码编辑器) + - [Node.js开发工具](#nodejs开发工具) + - [本地WordPress环境](#本地wordpress环境) + +
+ 若想为Gutenberg项目本身做贡献,请参阅代码贡献指南中的补充文档。 +
+ +## 代码编辑器 + +代码编辑器用于编写代码。您可以使用任何熟悉的编辑器,关键是要具备打开、编辑和保存文本文件的功能。 + +如果您还没有偏好的代码编辑器,[Visual Studio Code](https://code.visualstudio.com/)(VS Code)是核心贡献者中进行JavaScript开发的流行选择。它完美兼容三大主流平台(Windows、Linux和Mac),是微软积极维护的开源软件。VS Code还拥有活跃的社区提供插件和扩展支持,其中包含许多适用于WordPress开发的工具。 + +## Node.js开发工具 + +Node.js(`node`)是一个开源运行时环境,允许您在网络浏览器之外执行JavaScript。虽然并非所有WordPress JavaScript开发都需要Node.js,但在使用现代JavaScript工具和进行区块编辑器开发时它必不可少。 + +Node.js及其配套开发工具使您能够: + +- 安装并运行区块编辑器开发所需的WordPress包,例如`wp-scripts` +- 使用`wp-env`和`@wp-playground/cli`搭建本地WordPress环境 +- 使用最新ECMAScript特性并以ESNext规范编写代码 +- 对JavaScript代码进行格式检查、格式化与测试 +- 使用`create-block`包快速创建自定义区块 + +其功能远不止于此。虽然现代JavaScript开发可能具有挑战性,但WordPress提供了[`wp-scripts`](/docs/getting-started/devenv/get-started-with-wp-scripts.md)和[`create-block`](/docs/getting-started/devenv/get-started-with-create-block.md)等工具来简化流程,这些都得益于Node.js开发工具的支持。 + +**区块开发推荐使用Node.js的[Active LTS](https://nodejs.org/en/about/previous-releases)(长期支持)版本**。但有时您可能需要使用不同版本。强烈推荐使用`nvm`等Node.js版本管理工具,它允许您在需要时切换`node`版本。您还需要Node包管理器(`npm`)和Node包执行工具(`npx`)来处理某些WordPress包,这两者会随Node.js自动安装。 + +为了使用Node.js工具和[WordPress提供的开发包](https://github.com/WordPress/gutenberg/tree/trunk/packages)进行区块开发,您需要在计算机上配置合适的Node.js运行时环境。具体设置方法请参考以下链接: + +- [在Mac和Linux上安装Node.js](/docs/getting-started/devenv/nodejs-development-environment.md#node-js-installation-on-mac-and-linux-with-nvm) +- [在Windows上安装Node.js](/docs/getting-started/devenv/nodejs-development-environment.md#node-js-installation-on-windows-and-others) + +## 本地WordPress环境 + +本地WordPress环境(站点)为开发提供了受控、高效且安全的空间,让您可以在部署到生产站点之前构建和测试代码。WordPress的相同[运行要求](https://en-gb.wordpress.org/about/requirements/)也适用于本地站点。 + +在更广泛的WordPress社区中,有许多工具可用于在计算机上搭建本地WordPress环境。本《区块编辑器手册》重点介绍由WordPress项目自身维护的开源工具`wp-env`,它也是Gutenberg开发的推荐工具。 + +具体设置说明请参阅[`wp-env`入门指南](/docs/getting-started/devenv/get-started-with-wp-env.md)。 + +
+ 在本手册中,您可能还会看到对@wp-playground/cli的引用。这是一个由WordPress Playground驱动的轻量级工具,可简化本地WordPress环境的搭建。虽然仍处于实验阶段,但该工具非常适合快速测试WordPress版本、插件和主题。 +
+ +以下列举了部分备选方案(若您不想使用`wp-env`): + +- [WordPress Studio](https://developer.wordpress.com/studio/) +- [Local](https://localwp.com/) +- [XAMPP](https://www.apachefriends.org/) +- [MAMP](https://www.mamp.info/en/mamp/mac/) +- [Varying Vagrant Vagrants](https://varyingvagrantvagrants.org/)(VVV) \ No newline at end of file diff --git a/getting-started/devenv/get-started-with-create-block.md b/getting-started/devenv/get-started-with-create-block.md new file mode 100644 index 0000000..646afcc --- /dev/null +++ b/getting-started/devenv/get-started-with-create-block.md @@ -0,0 +1,95 @@ +# 使用 create-block 快速入门 + +WordPress 区块编辑器的自定义区块通常通过插件注册,并由特定文件集合定义。[`@wordpress/create-block`](https://developer.wordpress.org/block-editor/reference-guides/packages/packages-create-block/) 是官方支持的脚手架工具,可快速生成创建和注册区块所需的文件结构。它能生成项目初始代码,并集成现代化的 JavaScript 构建环境(使用 [`wp-scripts`](https://developer.wordpress.org/block-editor/getting-started/devenv/get-started-with-wp-scripts/)),无需额外配置。 + +该工具包旨在帮助开发者遵循 WordPress 最佳实践快速搭建区块开发环境。 + +## 快速开始 + +### 安装配置 + +请先确保计算机已安装 Node.js 和 `npm`。若未安装,请查阅 [Node.js 开发环境](https://developer.wordpress.org/block-editor/getting-started/devenv/nodejs-development-environment/)指南。 + +您可以在任意目录使用 `create-block` 创建区块脚手架,然后在生成的插件文件夹内[使用 `wp-env`](https://developer.wordpress.org/block-editor/getting-started/devenv/get-started-with-wp-env/) 创建本地 WordPress 开发环境,该环境会自动安装并激活您的新区块插件。 + +若已搭建[本地 WordPress 开发环境](https://developer.wordpress.org/block-editor/getting-started/devenv/#local-wordpress-environment),请通过终端进入 `plugins/` 目录。 + +执行以下命令创建示例区块插件: + +```bash +npx @wordpress/create-block@latest todo-list +cd todo-list +``` + +其中提供的 `slug` 参数(`todo-list`)将作为插件文件夹名称和内部区块标识。 + +进入本地 WordPress 的插件管理页面,激活 "Todo List" 插件后,即可在编辑器中找到该示例区块。 + +### 基础使用 + +`create-block` 默认采用现代 JavaScript(ESNext 与 JSX)构建区块,这需要构建步骤将代码编译为浏览器可识别的格式。幸运的是,`wp-scripts` 工具包已自动处理此过程。关于该工具的详细介绍请参阅 [wp-scripts 入门指南](https://developer.wordpress.org/block-editor/getting-started/devenv/get-started-with-wp-scripts)。 + +当 `create-block` 生成区块脚手架时,会自动安装 `wp-scripts` 并将常用脚本添加到区块的 `package.json` 文件中,默认包含: + +```json +{ + "scripts": { + "build": "wp-scripts build", + "format": "wp-scripts format", + "lint:css": "wp-scripts lint-style", + "lint:js": "wp-scripts lint-js", + "packages-update": "wp-scripts packages-update", + "plugin-zip": "wp-scripts plugin-zip", + "start": "wp-scripts start" + } +} +``` + +这些脚本可通过 `npm run {脚本名称}` 命令运行。最常使用的两个脚本是 `start` 和 `build`,它们负责构建流程。 + +开发区块时请运行 `npm run start` 命令,这将启动开发服务器并在检测到代码变更时自动重新构建。 + +准备部署区块时请运行 `npm run build` 命令,该命令会对代码进行优化并生成生产环境版本。 + +各脚本的详细说明请参阅 `wp-scripts` 的[工具包文档](https://developer.wordpress.org/block-editor/packages/packages-scripts/)。 + +## 其他实现方式 + +### 交互模式 + +为偏好引导式操作的开发者,`create-block` 提供了交互模式。此模式会逐步提示输入配置参数,无需像前文示例那样手动预先指定 `slug` 等所有选项。 + +运行以下命令启用此模式: + +```bash +npx @wordpress/create-block@latest +``` + +根据提示逐步完成区块设置即可。 + +### 快速启动模式(选项参数) + +若您已熟悉 `create-block` 的[选项参数](https://developer.wordpress.org/block-editor/reference-guides/packages/packages-create-block/#options)并希望快速完成配置,可使用快速启动模式。通过命令行直接传递特定参数,省去交互提示步骤。 + +例如,要快速创建命名空间为 "my-plugin"、区块标识为 "my-block" 的动态区块,可使用: + +```bash +npx @wordpress/create-block@latest --namespace="my-plugin" --slug="my-block" --variant="dynamic" +``` + +### 使用模板 + +`create-block` 支持模板功能,允许基于预定义配置和结构创建区块。当您有特定区块结构偏好,或需构建多个相似配置的区块时,此功能尤为实用。 + +使用模板时,需通过 `--template` 选项指定模板名称或路径: +```bash +npx @wordpress/create-block --template="my-custom-template" +``` + +模板需预先设置以确保 `create-block` 能正确识别。更多细节请参阅 `create-block` 的[模板文档](https://developer.wordpress.org/block-editor/reference-guides/packages/packages-create-block/#template),并查阅[外部项目模板指南](https://developer.wordpress.org/block-editor/reference-guides/packages/packages-create-block/packages-create-block-external-template/)。 + +## 扩展资源 + +- [使用 create-block 工具](https://learn.wordpress.org/tutorial/using-the-create-block-tool/)(Learn WordPress 教程) +- [@wordpress/create-block](https://developer.wordpress.org/block-editor/reference-guides/packages/packages-create-block/)(官方文档) +- [@wordpress/scripts](https://developer.wordpress.org/block-editor/reference-guides/packages/packages-scripts/)(官方文档) \ No newline at end of file diff --git a/getting-started/devenv/get-started-with-wp-env.md b/getting-started/devenv/get-started-with-wp-env.md new file mode 100644 index 0000000..a537eb5 --- /dev/null +++ b/getting-started/devenv/get-started-with-wp-env.md @@ -0,0 +1,144 @@ +# 开始使用 wp-env + +[@wordpress/env](https://www.npmjs.com/package/@wordpress/env) 包(简称 `wp-env`)可帮助您快速搭建本地 WordPress 环境(站点),用于插件和主题的开发测试,无需任何额外配置。 + +在按照本指南操作前,请先确保已安装 [Node.js 开发工具](/docs/getting-started/devenv#node-js-development-tools)。 + +![wp-env 基础架构图](https://developer.wordpress.org/files/2023/10/wp-env-diagram.png) + +## 快速开始 + +1. 根据操作系统说明下载、安装并启动 [Docker Desktop](https://www.docker.com/products/docker-desktop) +2. 在终端中运行 `npm -g install @wordpress/env` 全局安装 `wp-env` +3. 在终端中进入现有插件目录、主题目录或新建工作目录 +4. 运行 `wp-env start` 启动本地 WordPress 环境 +5. 脚本运行完成后,访问 http://localhost:8888/wp-admin,使用用户名 `admin` 和密码 `password` 登录 WordPress 管理后台 + +## 配置 Docker Desktop + +`wp-env` 工具使用 [Docker](https://www.docker.com/) 创建运行本地 WordPress 站点的虚拟机。Docker Desktop 应用程序对小型企业、个人用户、教育机构和非商业开源项目免费。详见官方 [FAQ](https://docs.docker.com/desktop/faqs/general/#do-i-need-to-pay-to-use-docker-desktop)。 + +点击对应链接下载并安装适用于您操作系统的 Docker Desktop: + +- [Mac 版 Docker Desktop](https://docs.docker.com/desktop/install/mac-install/) +- [Windows 版 Docker Desktop](https://docs.docker.com/desktop/install/windows-install/) +- [Linux 版 Docker Desktop](https://docs.docker.com/desktop/install/linux-install/) + +若使用早于 20.04.1 的 Ubuntu 版本,请参阅下方的[故障排除说明](#ubuntu-docker-setup)。 + +成功安装后启动 Docker Desktop 应用程序,按提示完成设置。通常使用推荐(默认)设置即可,创建 Docker 账户为可选步骤。 + +## 安装并运行 `wp-env` + +`wp-env` 工具用于通过 Docker 创建本地 WordPress 环境。配置好 Docker Desktop 后,在终端中运行以下命令安装 `wp-env`: + +```sh +npm -g install @wordpress/env +``` + +此命令将全局安装 `wp-env`,使该工具可在任意目录运行。可通过运行 `wp-env --version` 验证安装,成功会显示版本号。 + +接着在终端中进入现有插件目录、主题目录或新建工作目录,运行: + +```sh +wp-env start +``` + +脚本执行完毕后,即可通过 http://localhost:8888 访问本地环境。使用用户名 `admin` 和密码 `password` 登录 WordPress 管理后台。 + +
+ 部分项目(如 Gutenberg)包含特定的 wp-env 配置,文档可能会要求您运行 npm run wp-env start 命令 +
+ +有关控制 Docker 环境的更多信息,请参阅 [@wordpress/env 包](/packages/env/README.md) 说明文档。 + +### 运行环境配置 + +`wp-env` 工具几乎可在任意目录运行。在开发单个插件时,在插件所在目录执行 `wp-env start` 可自动挂载并激活该插件。此规则同样适用于主题开发目录。 + +若在非插件/主题目录运行 `wp-env start`,将创建通用 WordPress 环境。此时脚本会显示以下警告,若属预期行为可忽略: + +``` +!! 警告:未找到 .wp-env.json 配置文件,且无法判定“DIR”是否为 WordPress 安装目录、插件或主题 +``` + +您还可以通过 `.wp-env.json` 配置文件创建支持多插件和/或主题的环境。详细配置说明请参阅 [@wordpress/env 包](/packages/env/README.md#wp-envjson) 文档。 + +### 卸载或重置 `wp-env` + +以下是一些操作指南,适用于需要重新开始或希望移除已安装内容的情况。 + +- 如果只需重置并清理 WordPress 数据库,请运行 `wp-env clean all` +- 若要完全移除特定项目的本地环境,请运行 `wp-env destroy` +- 若要全局卸载 `wp-env` 工具,请运行 `npm -g uninstall @wordpress/env` + +## 故障排除 + +### 常见错误 + +使用 `wp-env` 时,常见错误提示:`执行 docker-compose 命令时出错` + +- 请检查 Docker Desktop 是否已启动并正在运行。 +- 查看 Docker Desktop 控制面板中的日志,重启或移除现有的虚拟机。 +- 然后尝试重新运行 `wp-env start`。 + +如果出现错误:`主机已被其他容器占用` + +- 您尝试启动的容器已在运行,或者存在其他容器占用。您可以通过在启动该容器的目录中运行 `wp-env stop` 来停止现有容器。 +- 如果不记得启动 `wp-env` 的目录,可以通过运行 `docker stop $(docker ps -q)` 停止所有容器。此操作会停止所有 Docker 容器,请谨慎使用。 +- 然后尝试重新运行 `wp-env start`。 + +### Ubuntu Docker 设置 + +如果您使用的 Ubuntu 版本早于 20.04.1,在使用 `wp-env` 设置本地 WordPress 环境时可能会遇到错误。 + +要解决此问题,请先按照 Docker 的[安装指南](https://docs.docker.com/install/linux/docker-ce/ubuntu/)进行操作。同时需要安装 `docker-compose`,您可能需要单独安装。请参阅 [Docker compose 文档](https://docs.docker.com/compose/install/)。 + +安装 Docker 和 `wp-env` 后,假设 `wp-env` 已全局配置,尝试在某个目录中运行 `wp-env start`。如果遇到以下错误: + +``` +错误:无法在 http+docker://localhost 连接到 Docker 守护进程——它是否正在运行? + +如果它位于非标准位置,请使用 DOCKER_HOST 环境变量指定 URL。 +``` + +首先,请确保 Docker 正在运行。可以通过运行 `ps -ef | grep docker` 来检查,应返回类似以下内容: + +``` +/usr/bin/dockerd -H fd:// --containerd=/run/containerd/containerd.sock +``` + +如果 Docker 未运行,请尝试通过运行 `sudo systemctl start docker.service` 启动服务。 + +如果 Docker 正在运行,但未监听 WordPress 环境的通信方式。请尝试添加以下服务覆盖文件以包含对 `tcp` 的监听。有关如何配置 Docker 守护进程的远程访问,请参阅 [Docker 文档](https://docs.docker.com/config/daemon/remote-access/)。 + +``` +# /etc/systemd/system/docker.service.d/override.conf +[Service] +ExecStart= +ExecStart=/usr/bin/dockerd -H fd:// -H tcp://0.0.0.0:2376 +``` + +从命令行重启服务: + +``` +sudo systemctl daemon-reload +sudo systemctl restart docker.service +``` + +重启服务后,设置环境变量 `DOCKER_HOST` 并尝试启动 `wp-env`: + +``` +export DOCKER_HOST=tcp://127.0.0.1:2376 +wp-env start +``` + +现在您的环境应设置在 http://localhost:8888。 + +## 其他资源 + +- [@wordpress/env](https://www.npmjs.com/package/@wordpress/env)(官方文档) +- [Docker Desktop](https://docs.docker.com/desktop)(官方文档) +- [使用 wp-env 快速轻松地进行本地 WordPress 开发](https://developer.wordpress.org/news/2023/03/quick-and-easy-local-wordpress-development-with-wp-env/)(WordPress 开发者博客) +- [wp-env:简单的 WordPress 本地环境](https://make.wordpress.org/core/2020/03/03/wp-env-simple-local-environments-for-wordpress/)(Make WordPress Core 博客) +- [`wp-env` 基础示意图](https://excalidraw.com/#json=8Tp55B-R6Z6-pNGtmenU6,_DeBR1IBxuHNIKPTVEaseA)(Excalidraw) \ No newline at end of file diff --git a/getting-started/devenv/get-started-with-wp-scripts.md b/getting-started/devenv/get-started-with-wp-scripts.md new file mode 100644 index 0000000..f255b73 --- /dev/null +++ b/getting-started/devenv/get-started-with-wp-scripts.md @@ -0,0 +1,150 @@ +### 高级配置 + +虽然 `wp-scripts` 提供了可靠的默认配置,但在某些情况下可能需要更专业的设置。好消息是 `wp-scripts` 具有高度适应性。例如,您可以扩展和覆盖默认的 webpack 配置,从而能够添加加载器和插件,或修改构建流程的几乎所有环节。这种灵活性确保当项目规模扩大或需求变化时,`wp-scripts` 能够根据您不断变化的需求进行定制。 + +有关所有配置选项,请参阅 `wp-scripts` 的[包文档](https://developer.wordpress.org/block-editor/packages/packages-scripts/)。 + +## 扩展资源 + +- [@wordpress/scripts](https://developer.wordpress.org/block-editor/reference-guides/packages/packages-scripts/)(官方文档) +- [webpack 与 WordPress 包的交互机制](https://developer.wordpress.org/news/2023/04/how-webpack-and-wordpress-packages-interact/)(WordPress 开发者博客) + +# 开始使用 wp-scripts + +[`@wordpress/scripts`](https://developer.wordpress.org/block-editor/reference-guides/packages/packages-scripts/) 包(通常简称为 `wp-scripts`)是一组配置文件和脚本,主要旨在标准化和简化需要 JavaScript 构建步骤的 WordPress 项目的开发流程。 + +JavaScript 构建步骤指的是将 JavaScript 源代码和相关资源转换、打包并优化为适合生产环境格式的过程。这些构建步骤通常会将现代 JavaScript(ESNext 和 JSX)转换为与大多数浏览器兼容的版本,还可以将多个文件打包成一个,通过代码压缩减小文件大小,并执行各种其他任务来优化代码。 + +在为区块编辑器开发时,您通常会使用 ESNext 和 JSX,区块编辑器手册中的所有示例都采用这些语法。学习如何设置构建步骤至关重要,但配置 [webpack](https://webpack.js.org/)、[Babel](https://babeljs.io/) 和 [ESLint](https://eslint.org/) 等必要工具可能变得复杂。这正是 `wp-scripts` 的用武之地。 + +以下是 `wp-scripts` 能够实现的功能: + +- **代码编译:** 使用 Babel 将现代 JavaScript(ESNext 和 JSX)转换为与大多数浏览器兼容的代码 +- **资源打包:** 使用 webpack 将多个 JavaScript 文件合并为单一包以提升性能 +- **代码检查:** 提供 ESLint 配置,帮助确保代码质量并符合编码规范 +- **代码格式化:** 集成 Prettier 实现自动化代码风格统一,保持项目间代码格式一致性 +- **Sass 编译:** 将 Sass(.scss 或 .sass)文件转换为标准 CSS +- **代码压缩:** 为生产环境缩减 JavaScript 代码体积,确保更快的页面加载速度 + +该软件包封装了现代 WordPress JavaScript 开发中大量初始设置、配置和样板代码,让您可以专注于构建区块和区块编辑器扩展功能。 + +## 快速开始 + +
+ 如果您使用 @wordpress/create-block 包来搭建创建和注册区块所需的文件结构,您将同时获得现代化的 JavaScript 构建设置(使用 wp-scripts)且无需任何配置,因此无需担心安装 wp-scripts 或注册资源。详细信息请参阅开始使用 create-block。 +
+ +### 安装步骤 + +请确保计算机已安装 Node.js 和 `npm`。若未安装,请查阅 [Node.js 开发环境](https://developer.wordpress.org/block-editor/getting-started/devenv/nodejs-development-environment/)指南。 + +接着创建项目文件夹,确保其中包含 `package.json` 文件、`build` 文件夹和 `src` 文件夹。`src` 文件夹内还需包含 `index.js` 文件。 + +如果尚未创建 `package.json` 文件,请在终端中进入项目文件夹并运行 `npm init` 命令。交互式提示将引导您完成设置步骤。您可以按需配置,但当询问 "entry point"(入口点)时,请输入 `build/index.js`。 + +当然,使用 `wp-scripts` 设置项目有多种方式,但这是区块编辑器手册全程推荐的实践方案。 + +最后,通过以下命令将 `wp-scripts` 包安装为开发依赖项: + +```bash +npm install @wordpress/scripts --save-dev +``` + +安装完成后,您的项目文件夹结构应如下所示: + +```bash +示例项目文件夹/ +├── build/ +├── node_modules/ (自动生成) +├── src/ +│ └── index.js +├── package-lock.json (自动生成) +└── package.json +``` + +### 基础用法 + +安装完成后,您可以通过在 `package.json` 文件的脚本部分引用 `wp-scripts` 提供的预定义脚本来运行它们。以下是一个示例: + +```json +{ + "scripts": { + "start": "wp-scripts start", + "build": "wp-scripts build" + } +} +``` + +随后可以使用 `npm run {脚本名称}` 命令来运行这些脚本。 + +### 使用 `wp-scripts` 的构建流程 + +您最常使用的两个脚本是 `start` 和 `build`,因为它们负责构建步骤。有关所有选项,请参阅[包文档](https://developer.wordpress.org/block-editor/packages/packages-scripts/)。 + +在开发项目时,使用 `npm run start` 命令。这将启动开发服务器,并在检测到任何更改时自动重新构建项目。请注意,`build/index.js` 中的编译代码不会被优化。 + +当您准备部署项目时,使用 `npm run build` 命令。这会优化您的代码,使其适合生产环境。 + +构建完成后,您将看到在 `build/index.js` 中创建的编译后的 JavaScript 文件。 + +构建过程中还会生成一个 `build/index.asset.php` 文件,其中包含依赖项数组和一个版本号(用于缓存清除)。请注意,如果没有使用 `wp-scripts` 构建流程注册块,您需要手动创建 `*.asset.php` 依赖文件(参见[示例](https://github.com/WordPress/block-development-examples/tree/trunk/plugins/minimal-block-no-build-e621a6))。 + +### 加载资源 + +如果您通过 `register_block_type` 注册块,`block.json` 中定义的脚本将自动加载(参见[示例](https://github.com/WordPress/block-development-examples/tree/trunk/plugins/minimal-block-ca6eda))。 + +要在编辑器或其他上下文中手动加载文件,可以参考[在编辑器中加载资源](https://developer.wordpress.org/block-editor/how-to-guides/enqueueing-assets-in-the-editor/)指南获取更多信息。以下是一个典型的实现示例: + +```php +/** + * 加载编辑器资源。 + */ +function example_project_enqueue_editor_assets() { + $asset_file = include( plugin_dir_path( __FILE__ ) . 'build/index.asset.php'); + + wp_enqueue_script( + 'example-editor-scripts', + plugins_url( 'build/index.js', __FILE__ ), + $asset_file['dependencies'], + $asset_file['version'] + ); +} +add_action( 'enqueue_block_editor_assets', 'example_project_enqueue_editor_assets' ); +``` + +这里有一个在编辑器中手动加载文件的[示例](https://github.com/WordPress/block-development-examples/tree/trunk/plugins/data-basics-59c8f8)。 + +## 后续步骤 + +虽然 `start` 和 `build` 是两个最常用的脚本,但 `wp-scripts` 还提供了其他几个有用的工具,值得探索。以下是一些示例。 + +### 保持代码质量 + +为了帮助开发者提高代码质量,`wp-scripts` 预配置了 ESLint 和 Prettier 等工具。ESLint 确保您的 JavaScript 遵循最佳实践和 [WordPress 编码标准](https://developer.wordpress.org/coding-standards/wordpress-coding-standards/),而 Prettier 会自动格式化您的代码。可用的脚本包括: + +```json +{ + "scripts": { + "format": "wp-scripts format", + "lint:css": "wp-scripts lint-style", + "lint:js": "wp-scripts lint-js", + } +} +``` + +定期对代码进行格式化和检查可以确保代码功能正常、清晰且易于维护,无论是您自己还是其他开发者都能受益。 + +### 运行测试 + +除了编写代码,验证其功能也至关重要。`wp-scripts` 包含了 [Jest](https://jestjs.io/)(一个 JavaScript 测试框架)以及端到端测试和单元测试脚本: + +```json +{ + "scripts": { + "test:e2e": "wp-scripts test-e2e", + "test:unit": "wp-scripts test-unit-js" + } +} +``` + +单元测试验证代码的独立单元(例如函数)是否按预期工作,而端到端测试通过模拟真实用户场景来评估整个项目,确保系统的所有部分无缝协作。 \ No newline at end of file diff --git a/getting-started/devenv/nodejs-development-environment.md b/getting-started/devenv/nodejs-development-environment.md new file mode 100644 index 0000000..1619130 --- /dev/null +++ b/getting-started/devenv/nodejs-development-environment.md @@ -0,0 +1,49 @@ +# Node.js 开发环境 + +进行区块编辑器开发时,您需要准备 [Node.js](https://nodejs.org/en) 开发工具、代码编辑器以及本地 WordPress 环境(详见[区块开发环境](/docs/getting-started/devenv/README.md))。Node.js(`node`)是一个开源运行时环境,可让您通过终端(也称为命令行界面 CLI 或 Shell)执行 JavaScript 代码。 + +安装 `node` 将自动包含 Node 包管理器(`npm`)和 Node 包执行工具(`npx`),这两个工具在区块和插件开发中会频繁使用。 + +Node 包管理器 ([`npm`](https://docs.npmjs.com/cli/v10/commands/npm)) 具有依赖管理和脚本执行等多重功能,是官方推荐的包管理工具,所有文档中均大量涉及其使用方法。 + +Node 包执行工具 ([`npx`](https://docs.npmjs.com/cli/v10/commands/npx)) 用于运行未全局安装的软件包命令,在使用 [`create-block`](https://developer.wordpress.org/block-editor/reference-guides/packages/packages-create-block/) 包搭建区块时尤为常用。 + +## Mac 和 Linux 系统安装 Node.js(通过 `nvm`) + +推荐使用 [Node 版本管理器](https://github.com/nvm-sh/nvm)(`nvm`)安装 Node.js。通过该工具可安装并管理特定版本的 `node`,所有版本将本地化安装在用户目录中,有效避免全局权限问题。 + +以下是通过 `nvm` 安装 `node` 并设置区块开发推荐版本的快速指南,详见[完整安装指南](https://github.com/nvm-sh/nvm#installing-and-updating)。 + +1. 打开终端执行以下命令安装 `nvm`。macOS 系统默认未安装开发者工具,若出现提示请按指引安装。 + +```sh +curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.5/install.sh | bash +``` + +2. 关闭并重启终端 +3. 在终端运行 `nvm install --lts` 安装最新的 [LTS](https://nodejs.org/en/about/previous-releases)(长期支持)版 Node.js +4. 在终端运行 `node -v` 和 `npm -v` 验证安装的 `node` 与 `npm` 版本 + +如需安装特定版本 `node`,可运行 `nvm install 18` 安装 18 版本,通过 `nvm use [版本号]` 切换不同版本。更多操作详见 `nvm` [使用指南](https://github.com/nvm-sh/nvm#usage)。 + +部分项目(如 Gutenberg)包含 [`.nvmrc`](https://github.com/WordPress/gutenberg/blob/trunk/.nvmrc) 文件来指定所需 `node` 版本。此时运行 `nvm use` 将自动选择对应版本。若该版本未安装,系统会提示需要安装的版本号,请依次运行 `nvm install [版本号]` 和 `nvm use`。 + +## Windows 及其他系统安装 Node.js + +您可直接从 [Node.js 官网下载安装包](https://nodejs.org/en/download/),推荐选择最新版本。系统提供 Windows 和 Mac 安装程序,以及 Linux 二进制文件。 + +微软官方还提供了 [详细指南](https://learn.microsoft.com/en-us/windows/dev-environment/javascript/nodejs-on-windows#install-nvm-windows-nodejs-and-npm),说明如何在 Windows 和 WSL 中安装 `nvm` 与 Node.js。 + +## 故障排除 + +若安装 `node` 时出现 `zsh: command not found: nvm` 错误,可能需要创建默认配置文件。 + +macOS 系统默认 Shell 为 `zsh`,请在终端运行 `touch ~/.zshrc` 创建配置文件(若文件已存在亦可直接运行)。Ubuntu 系统(含 WSL)默认使用 `bash`,请改用 `touch ~/.bashrc` 命令。完成后重复步骤 2-4。 + +最新版 `node` 适用于多数开发项目,但请注意某些软件包和工具可能存在特定版本要求。若遇到问题,可能需要安装并使用旧版 `node`。同时请确认项目是否包含 `.nvmrc` 文件,并使用其中指定的 `node` 版本。 + +## 扩展资源 + +- [Node.js](https://nodejs.org/en)(官方文档) +- [Node 版本管理器](https://github.com/nvm-sh/nvm)(官方文档) +- [为本地 WordPress 开发安装 Node.js 和 npm](https://learn.wordpress.org/tutorial/installing-node-js-and-npm-for-local-wordpress-development/)(Learn WordPress 教程) \ No newline at end of file diff --git a/getting-started/faq.md b/getting-started/faq.md new file mode 100644 index 0000000..8b4a943 --- /dev/null +++ b/getting-started/faq.md @@ -0,0 +1,214 @@ +### 能否为我的站点停用古腾堡编辑器? + +存在一种“经典”区块,其功能与当前编辑器几乎完全相同,只是以区块形式呈现。 + +另外还有[经典编辑器插件](https://wordpress.org/plugins/classic-editor/),可恢复旧版编辑器,详情请参阅该插件说明。WordPress核心团队已承诺[在2021年12月之前](https://make.wordpress.org/core/2018/11/07/classic-editor-plugin-support-window/)持续支持经典编辑器插件。 + +### 自定义TinyMCE按钮在古腾堡中如何运作? + +自定义TinyMCE按钮在“经典”区块中仍然有效,该区块是当前经典编辑器的区块化版本。 + +古腾堡配备了全新的通用插入工具,可让您访问所有可用区块,支持搜索功能,并按最近使用时间和分类排序。这一插入工具为每个向编辑器添加内容的插件提供了公平的竞争环境,并提供了统一的操作界面供用户学习使用。 + +### 短代码在古腾堡中如何运作? + +短代码仍保持现有功能不变。 + +但我们认为区块是`[shortcode]`的进化形态。您无需手动输入代码,只需通过通用插入面板选择区块,即可获得更丰富的配置界面和预览效果。我们建议用户最终将短代码升级为区块。 + +### 是否应将短代码转换为内容区块? + +我们认为基于以下多重因素(包括但不限于)应该转换: + +- 区块内置可视化编辑功能,为网站建设提供更丰富、更动态的体验 +- 区块本质上是HTML格式,不会在前端保留浏览器无法识别的内容。相比之下,如果您停用了支持短代码的插件,前端会显示异常视觉效果(通常以纯文本形式显示短代码) +- 随着区块目录的推出,区块将比短代码更易被发现,让更多用户获得更丰富的功能 + +最终,区块设计旨在直观呈现最终视觉效果,随着5.5版本区块目录的推出,这将成为用户在WordPress中发现和插入内容的预期标准方式。 + +## 其他事项 + +### 古腾堡是否充分考虑无障碍访问? + +无障碍设计并非事后补充。目前古腾堡并非所有功能都实现完全无障碍。您可在此处查看已记录的问题。我们深知WordPress服务所有用户,无障碍设计关乎包容性,这是我们的核心价值。 + +如果您愿意为古腾堡的无障碍改进贡献力量,我们始终欢迎更多测试者和贡献者。 + +### 数据如何存储?我注意到HTML注释,其作用是什么? + +如技术概述介绍中所述,我们的方法是在不破坏WordPress十五年来内容数据结构的前提下,对现有数据格式进行增强。换言之,这种格式优化优先考虑人类可读性(网页HTML文档)和跨平台渲染便利性,而非主要服务于编辑场景的机器便捷文件(文章元数据中的JSON格式)。 + +这种设计也赋予我们灵活性,可将那些本质上独立于内容流的区块(如可重用小组件或文章类型元素)存储在其他位置,仅保留其位置的标记引用。 + +建议您查阅古腾堡核心概念文档,深入了解该功能的运作机制。 + +### 如何通过PHP或JS将文章内容解析回区块? + +JS方法: +```js +var blocks = wp.blocks.parse( postContent ); +``` + +PHP方法: +```php +$blocks = parse_blocks( $post_content ); +``` + +## 编辑体验 + +### 什么是“区块”?为何要使用它们? + +经典WordPress编辑器是一个开放的文本窗口——它始终是绝佳的写作空白画布,但在构建包含图片、多媒体、社交媒体嵌入内容、投票及其他元素的文章与页面时,往往需要混合使用多种并不直观的操作方式: + +- 通过媒体库/HTML处理图片、多媒体和授权文件 +- 使用粘贴链接实现内容嵌入 +- 通过短代码调用插件的特殊资源 +- 使用特色图像设置文章/页面顶部图片 +- 通过摘要字段设置副标题 +- 通过小工具管理页面侧边内容 + +在思考这些使用场景并寻求直观统一的解决方案时,我们开始采用“区块”概念。上述所有元素均可转化为区块:易于检索理解,并能动态调整页面布局。区块概念极具潜力,经过精心设计后,能提供卓越的编辑发布体验。区块的终极目标是建立WordPress全新通用语言,构建连接用户与插件的新范式,取代短代码、小工具等传统内容类型——这些旧模式通常需要用户精通WordPress特性才能掌握。 + +### 写作体验如何? + +Gutenberg的目标不仅是打造无缝的文章与页面构建体验,更致力于提供流畅的写作环境。欢迎访问[演示页面亲自体验](https://wordpress.org/gutenberg/)! + +### Gutenberg基于TinyMCE开发吗? + +并非如此。[TinyMCE](https://www.tinymce.com/)仅用于“经典”区块。 + +### Gutenberg支持键盘快捷键吗? + +支持。快捷键非常丰富!可通过帮助模态窗查看所有可用快捷键。 + +具体操作:点击新编辑器右上角菜单中的“键盘快捷键”(或使用快捷键Shift+Alt+H(Linux/Windows系统)/H(macOS系统))。 + +以下动画演示如何查找和使用键盘快捷键: + +![展示如何访问键盘快捷键的GIF](https://make.wordpress.org/core/files/2020/07/keyboard-shortcuts.gif) + +### Gutenberg支持分栏功能吗? + +支持,Gutenberg提供分栏区块。 + +### Gutenberg支持嵌套区块吗? + +支持多层嵌套——区块内可包含子区块。详见[嵌套区块教程](https://developer.wordpress.org/block-editor/tutorials/block-tutorial/nested-blocks-inner-blocks/)。 + +### 能否通过拖放调整区块顺序? + +支持拖放区块进行重新排序。 + +## 开发体验 + +### 如何创建自定义区块? + +建议从[创建区块教程](https://developer.wordpress.org/block-editor/getting-started/create-block/)开始学习。 + +### Gutenberg支持前端编辑文章/页面吗? + +不支持。Gutenberg主要设计为替代传统文章/页面编辑界面。需注意的是,前端编辑常被误解为编辑器界面与前端完全一致。Gutenberg允许主题通过自定义区块并为编辑器提供对应样式来实现类似效果。由于内容需要适配多种场景(从桌面端、移动端到全文订阅和联合发布平台),我们认为仅基于单一前端体验进行内容创作并非理想方案。 + +### 鉴于Gutenberg基于JavaScript开发,传统元框(PHP)如何兼容? + +请参阅[元框教程](https://developer.wordpress.org/block-editor/how-to-guides/metabox/)了解如何在新版区块编辑器中兼容元框。 + +# 常见问题解答 + +以下是古腾堡项目过去几年开发过程中积累的一系列问题。如果您有任何希望得到解答并纳入本文档的问题,[请直接在GitHub上提交问题](https://github.com/WordPress/gutenberg/issues)。我们非常乐意解答那些未曾考虑到的问题。如需回顾历史背景,请查阅Matt于2018年11月发布的文章[《WordPress 5.0:古腾堡常见问题解答》](https://ma.tt/2018/11/a-gutenberg-faq/)。 + +## 古腾堡项目 + +### 什么是古腾堡? + +“古腾堡”是为WordPress创建全新编辑器体验的项目代号——贡献者自2017年1月启动该项目,这是WordPress近年来最具颠覆性的变革之一。其核心理念是采用“区块”模式来编写和设计文章与页面。这将成为WordPress未来升级的基石,包括将区块功能从内容设计延伸至全站设计。总体目标是简化WordPress新用户的操作体验——涵盖内容创作、编辑、发布及网页设计的全流程。该编辑器致力于让用户更直观地预览发布后的内容呈现效果。项目启动时的核心目标如下: + +> 编辑器将致力于打造全新的页面构建体验,让富媒体内容创作变得轻松自如。通过“区块”功能,实现目前需要短代码、自定义HTML或“神秘拼盘”式嵌入操作才能完成的效果。 + +关键要点包括: + +- 布局精美的内容创作是WordPress的核心优势 +- 采用区块交互模式,可将多重操作界面统一整合。用户无需掌握短代码和自定义HTML的编写,也无需通过粘贴URL来嵌入媒体,通过统一可靠的操作流程即可插入各类内容 +- “神秘拼盘”指软件中需要用户自行探索的隐藏功能。WordPress已支持大量区块和30多种嵌入内容,现在要让这些功能浮出水面 + +古腾堡项目在WordPress组织下的[GitHub平台](https://github.com/WordPress/gutenberg)进行开发。自WordPress 5.0起,区块编辑器已集成至核心程序。若想体验古腾堡的新功能,可[在插件库获取测试版](https://wordpress.org/plugins/gutenberg/)。 + +### 长期发展规划是什么? + +根据[官方路线图](https://wordpress.org/about/roadmap/),古腾堡分为四个发展阶段(截至本文撰写时正处于第二阶段): + +1. 更便捷的编辑——自WordPress 5.0起已实现,持续优化中 +2. 全面定制——全站编辑、区块模式、区块目录、区块主题 +3. 协同创作——更直观的多用户协作体验 +4. 多语言支持——原生多语言站点解决方案 + +### 项目启动于何时? + +编辑器专项于2017年初启动,前三个月主要进行设计规划、原型构建与测试验证,为项目实施奠定基础。首个测试插件于2017年6月欧洲WordCamp大会期间发布。 + +### 何时并入WordPress核心? + +古腾堡于2018年12月首次并入[WordPress 5.0](https://wordpress.org/news/2018/12/bebo/)。完整版本对应关系请参阅[WordPress中的版本记录](https://developer.wordpress.org/block-editor/principles/versions-in-wordpress/)。 + +### WordPress已是全球最流行的发布平台,为何还要改造编辑器? + +编辑器是WordPress日常使用最频繁的核心模块,在封闭环境中打磨区块体验能获得最佳效果。作为开源项目,我们坚信WordPress必须持续创新,让核心体验始终直观易用。古腾堡作为社区项目正承载着这一使命,我们期待共同实现这个目标。如果您愿意参与测试、贡献代码或反馈建议,欢迎[在GitHub分享您的发现](https://github.com/WordPress/gutenberg/issues)。 + +### 插件如何扩展古腾堡界面? + +我们想要强调的主要扩展点是创建新块。通过插件可以将区块添加到区块编辑器中,请参阅[构建你的第一个区块教程](https://developer.wordpress.org/block-editor/getting-started/tutorial/)开始学习。 + +### 自定义文章类型是否仍受支持? + +是的。自定义文章类型可以通过多种方式利用古腾堡。计划允许它们指定支持的区块,并为文章类型定义默认区块。目前尚未实现,但如果文章类型禁用了内容字段,页面底部的“高级”部分将填充页面。 + +## 样式 + +### 主题能否为区块设置样式? + +可以。区块可以提供自己的样式,主题可以在此基础上添加或覆盖样式,或者它们完全不提供样式,完全依赖主题提供的样式。 + +### 区块样式在前端和后端如何工作? + +区块能够提供基础的结构性CSS样式,主题可以在此基础上添加样式。一些区块,如分隔符(`
`),可能不需要任何前端样式,而其他区块,如图库,则需要一些样式。 + +其他功能,如新的宽幅和全宽对齐选项,仅仅是应用于支持此对齐方式的区块的CSS类。我们正在研究主题如何选择加入此功能,例如使用`add_theme_support`。 + +这目前仍在开发中,我们建议查阅[基于区块的主题文档](https://developer.wordpress.org/themes/block-themes/)以了解更多信息。 + +### 什么是区块变体?它们与区块样式相同吗? + +不,[区块变体](/docs/reference-guides/block-api/block-variations.md)是单个基础区块的不同版本,共享相似的功能,但在实现或设置(属性、内部区块等)上略有不同。区块变体对用户是透明的,一旦注册了区块变体,它将显示为一个新块。例如,`embed`区块注册了不同的区块变体,以嵌入来自特定提供商的内容。 + +同时,[区块样式](/docs/reference-guides/filters/block-filters.md#block-style-variations)允许您为现有区块提供替代样式,它们通过向区块包装器添加`className`来工作。一旦区块注册了区块样式,区块样式选择器将出现在其侧边栏中,用户可以在不同的注册样式之间进行选择。 + +### 编辑器样式如何工作? + +常规的编辑器样式是选择加入的,在大多数情况下按原样工作。主题还可以通过以下钩子加载额外的样式表: + +```php +function gutenbergtheme_editor_styles() { + wp_enqueue_style( 'gutenbergtheme-blocks-style', get_template_directory_uri() . '/blocks.css'); +} +add_action( 'enqueue_block_editor_assets', 'gutenbergtheme_editor_styles' ); +``` + +_参见:_[编辑器样式](/docs/how-to-guides/themes/theme-support.md#editor-styles) + +## 兼容性 + +### 古腾堡支持哪些浏览器? + +古腾堡在现代浏览器中运行。 + +[支持的浏览器列表可以在Make WordPress手册中找到](https://make.wordpress.org/core/handbook/best-practices/browser-support/)。术语“现代浏览器”通常指的是每个主要浏览器的_当前版本及前两个版本_。 + +自WordPress 5.8起,古腾堡不再支持任何版本的Internet Explorer。 + +### 我需要担心古腾堡会让我的插件过时吗? + +古腾堡的目标不是让任何人失业,而是推动WordPress的发展,以便未来为每个人带来更多的商业机会。 + +除了实现丰富的文章和页面构建体验外,一个元目标是_推动WordForward作为一个平台向前发展_,不仅通过现代化用户界面,还通过现代化基础架构。 + +我们意识到这是一个巨大的变化。我们也认为插件将会有许多新的机会。WordPress可能会附带一系列基础区块,但高度定制化的高级插件仍有充足的空间来增强现有区块或添加新块。 \ No newline at end of file diff --git a/getting-started/fundamentals/README.md b/getting-started/fundamentals/README.md new file mode 100644 index 0000000..89b49bf --- /dev/null +++ b/getting-started/fundamentals/README.md @@ -0,0 +1,12 @@ +# 区块开发基础 + +本节将介绍区块开发中最核心的相关概念。通过以下链接深入了解: + +1. **[区块的文件结构](https://developer.wordpress.org/block-editor/getting-started/fundamentals/file-structure-of-a-block):** 解析构成区块插件的每个文件用途、文件间的关联关系及其在区块输出中的作用 +1. **[`block.json`配置文件](https://developer.wordpress.org/block-editor/getting-started/fundamentals/block-json):** 如何通过`block.json`元数据定义区块,并详解该文件的关键属性(如`attributes`和`supports`) +1. **[区块注册机制](https://developer.wordpress.org/block-editor/getting-started/fundamentals/registration-of-a-block):** 如何在服务端与客户端完成区块注册 +1. **[区块包装器](https://developer.wordpress.org/block-editor/getting-started/fundamentals/block-wrapper):** 如何为区块标记包装器配置正确的属性参数 +1. **[编辑器中的区块](https://developer.wordpress.org/block-editor/getting-started/fundamentals/block-in-the-editor):** 区块作为React组件在区块编辑器中的加载原理与结构概述 +1. **[区块的标记表示](https://developer.wordpress.org/block-editor/getting-started/fundamentals/markup-representation-block):** 区块在数据库、主题模板和模式中的呈现方式 +1. **[区块的静态与动态渲染](https://developer.wordpress.org/block-editor/getting-started/fundamentals/static-dynamic-rendering):** 区块如何通过动态或静态方式生成前端输出 +1. **[区块编辑器中的JavaScript](https://developer.wordpress.org/block-editor/getting-started/fundamentals/javascript-in-the-block-editor):** 为区块编辑器开发时如何运用现代JavaScript技术 \ No newline at end of file diff --git a/getting-started/fundamentals/block-in-the-editor.md b/getting-started/fundamentals/block-in-the-editor.md new file mode 100644 index 0000000..d43da06 --- /dev/null +++ b/getting-started/fundamentals/block-in-the-editor.md @@ -0,0 +1,169 @@ +## 补充资源 + +- [WordPress 组件故事书](https://wordpress.github.io/gutenberg/?path=/docs/docs-introduction--page) +- [@wordpress/block-editor](https://developer.wordpress.org/block-editor/reference-guides/packages/packages-block-editor/) +- [@wordpress/components](https://developer.wordpress.org/block-editor/reference-guides/packages/packages-components/) +- [`InspectorControls`](https://github.com/WordPress/gutenberg/blob/HEAD/packages/block-editor/src/components/inspector-controls/README.md) +- [`BlockControls`](https://github.com/WordPress/gutenberg/tree/trunk/packages/block-editor/src/components/block-controls) + +# 编辑器中的区块 + +区块编辑器是一个React单页应用程序(SPA)。编辑器中的每个区块都是通过React组件显示的,该组件定义在客户端[注册区块](https://developer.wordpress.org/block-editor/getting-started/fundamentals/registration-of-a-block/#registering-a-block-with-javascript-client-side)时所用设置对象的`edit`属性中。 + +区块的`Edit` React组件接收到的`props`对象包含: + +- **[`attributes`](https://developer.wordpress.org/block-editor/reference-guides/block-api/block-edit-save/#attributes):** 包含区块所有属性的对象 +- **[`setAttributes`](https://developer.wordpress.org/block-editor/reference-guides/block-api/block-edit-save/#setattributes):** 用于更新属性对象的方法 +- **[`isSelected`](https://developer.wordpress.org/block-editor/reference-guides/block-api/block-edit-save/#isselected):** 表示区块当前是否被选中的布尔值 + +WordPress提供了许多内置标准组件,可用于在编辑器中定义区块界面。这些内置组件可通过以下软件包获取:[`@wordpress/components`](https://developer.wordpress.org/block-editor/reference-guides/packages/packages-components/) 和 [`@wordpress/block-editor`](https://developer.wordpress.org/block-editor/reference-guides/packages/packages-block-editor/)。 + +
+ WordPress Gutenberg项目使用 Storybook 来记录WordPress软件包中可用的用户界面组件。 +
+ +区块工具栏或设置侧边栏中的自定义设置控件也可以通过这个`Edit` React组件使用内置组件来定义,例如: + +- [`InspectorControls`](https://github.com/WordPress/gutenberg/blob/HEAD/packages/block-editor/src/components/inspector-controls/README.md) +- [`BlockControls`](https://github.com/WordPress/gutenberg/tree/trunk/packages/block-editor/src/components/block-controls) + +## 内置组件 + +[`@wordpress/components`](https://developer.wordpress.org/block-editor/reference-guides/packages/packages-components/) 软件包包含一个通用WordPress组件库,用于为区块编辑器和WordPress仪表盘创建常见的UI元素。该软件包中最常用的一些组件包括: + +- [`TextControl`](https://wordpress.github.io/gutenberg/?path=/docs/components-textcontrol--docs) +- [`Panel`](https://wordpress.github.io/gutenberg/?path=/docs/components-panel--docs) +- [`ToggleControl`](https://wordpress.github.io/gutenberg/?path=/docs/components-togglecontrol--docs) +- [`ExternalLink`](https://wordpress.github.io/gutenberg/?path=/docs/components-externallink--docs) + +[`@wordpress/block-editor`](https://developer.wordpress.org/block-editor/reference-guides/packages/packages-block-editor/) 软件包包含一个用于区块编辑器的组件和钩子库,包括用于定义区块自定义设置控件的组件。该软件包中最常用的一些组件包括: + +- [`RichText`](https://github.com/WordPress/gutenberg/blob/HEAD/packages/block-editor/src/components/rich-text/README.md) +- [`BlockControls`](https://github.com/WordPress/gutenberg/tree/trunk/packages/block-editor/src/components/block-controls) +- [`InspectorControls`](https://github.com/WordPress/gutenberg/blob/HEAD/packages/block-editor/src/components/inspector-controls/README.md) +- [`InnerBlocks`](https://github.com/WordPress/gutenberg/blob/HEAD/packages/block-editor/src/components/inner-blocks/README.md) + +
+ @wordpress/block-editor 软件包还提供了创建和使用独立区块编辑器的工具。 +
+ +在区块编辑器中使用组件时,一个良好的工作流程是: + +- 从WordPress软件包中导入组件 +- 以JSX格式将组件的对应代码添加到项目中 +- 大多数内置组件将用于设置[区块属性](https://developer.wordpress.org/block-editor/getting-started/fundamentals/block-json/#using-attributes-to-store-block-data),因此在`block.json`中定义必要的属性,并在组件中创建事件处理程序以使用`setAttributes`更新这些属性 +- 根据需要调整代码以进行序列化并存储在数据库中 + +## 区块控制项:区块工具栏与设置侧边栏 + +为简化区块定制并确保一致的用户体验,系统内置了多种UI模式来辅助生成区块的编辑器预览界面。 + +下图详细展示了选中段落区块时的区块工具栏与设置侧边栏。 + +![展示选中段落区块时的区块工具栏与设置侧边栏示意图](https://developer.wordpress.org/files/2023/12/block-toolbar-settings-sidebar.png) + +### 区块工具栏 + +当用户选中区块时,选定区块上方会显示包含多个控制按钮的工具栏。部分区块级控件会自动包含其中,您也可以自定义工具栏以添加针对特定区块类型的控件。若区块类型`Edit`函数的返回值包含`BlockControls`元素,这些控件将显示在选定区块的工具栏中。 + +```jsx +export default function Edit( { className, attributes: attr, setAttributes } ) { + + const onChangeContent = ( newContent ) => { + setAttributes( { content: newContent } ); + }; + + const onChangeAlignment = ( newAlignment ) => { + setAttributes( { + alignment: newAlignment === undefined ? 'none' : newAlignment, + } ); + }; + + return ( +
+ + + + + + + +
+ ); +} +``` + +_查看[上述代码](https://github.com/WordPress/block-development-examples/blob/trunk/plugins/block-toolbar-ab967f/src/edit.js)的[完整区块示例](https://github.com/WordPress/block-development-examples/tree/trunk/plugins/block-toolbar-ab967f)。_ + +请注意:`BlockControls`仅在区块被选中且处于可视化编辑模式时可见。在HTML编辑模式下编辑区块时不会显示`BlockControls`。 + +### 设置侧边栏 + +设置侧边栏用于显示使用频率较低的设置项或需要更多屏幕空间的设置项。该区域**仅应用于区块级设置**,并在选中区块时显示。 + +若设置项仅影响区块内选定的内容(如文本"加粗"功能),**请勿将其置于设置侧边栏中**,而应使用工具栏。设置侧边栏在HTML编辑模式下仍会显示,因此应仅包含区块级设置项。 + +与渲染工具栏类似,若在区块类型`Edit`函数的`return`值中包含`InspectorControls`组件,这些控件将显示在设置侧边栏区域。 + +```jsx +export default function Edit( { attributes, setAttributes } ) { + const onChangeBGColor = ( hexColor ) => { + setAttributes( { bg_color: hexColor } ); + }; + + const onChangeTextColor = ( hexColor ) => { + setAttributes( { text_color: hexColor } ); + }; + + return ( +
+ +
+
+ + { __( '背景颜色', 'block-development-examples' ) } + + +
+
+ + { __( '文字颜色', 'block-development-examples' ) } + + +
+
+
+ setAttributes( { message: val } ) } + style={ { + backgroundColor: attributes.bg_color, + color: attributes.text_color, + } } + /> +
+ ); +} +``` +_查看[上述代码](https://github.com/WordPress/block-development-examples/blob/trunk/plugins/settings-sidebar-82c525/src/edit.js)的[完整区块示例](https://github.com/WordPress/block-development-examples/tree/trunk/plugins/settings-sidebar-82c525)。_ + +当选中多个同类型区块时,工具栏和侧边栏中渲染的区块控制项将同时生效。 + +
+ 对于常见定制设置(包括颜色、边框、间距等),您可依赖区块支持功能而非自定义方案。区块支持功能提供与其他核心区块功能一致的标准化UI界面。 +
\ No newline at end of file diff --git a/getting-started/fundamentals/block-json.md b/getting-started/fundamentals/block-json.md new file mode 100644 index 0000000..e0c2961 --- /dev/null +++ b/getting-started/fundamentals/block-json.md @@ -0,0 +1,134 @@ +## 补充资源 + +- [block.json 结构图](https://excalidraw.com/#json=v1GrIkGsYGKv8P14irBy6,Yy0vl8q7DTTL2VsH5Ww27A) +- [属性关系图](https://excalidraw.com/#json=pSgCZy8q9GbH7r0oz2fL1,MFCLd6ddQHqi_UqNp5ZSgg) + +# block.json + +`block.json` 文件通过使用相同的 JSON 格式区块定义,在服务端和客户端(区块编辑器)上注册区块,从而简化了定义和注册区块的过程。 + +下图详细说明了 `block.json` 文件的基本结构。 + +[![打开 block.json 图示](https://developer.wordpress.org/files/2023/11/block-json.png)](https://developer.wordpress.org/files/2023/11/block-json.png "打开 block.json 图示") + +
+ 要查看完整的区块示例及其相关的 block.json 文件,请访问 区块开发示例 GitHub 仓库。 +
+ +除了简化区块注册外,使用 `block.json` 还有[诸多优势](https://developer.wordpress.org/block-editor/reference-guides/block-api/block-metadata/#benefits-using-the-metadata-file),包括性能提升。 + +[block.json 中的元数据](https://developer.wordpress.org/block-editor/reference-guides/block-api/block-metadata/)文档提供了关于可在区块的 `block.json` 文件中使用的所有属性的完整指南。本文将介绍最常用的选项,这些选项允许你指定: + +- 区块的基本元数据 +- 控制区块功能、外观和输出的文件 +- 数据在区块内的存储方式 +- 用户界面中区块的设置面板 + +## 区块的基本元数据 + +使用 `block.json` 属性,你可以定义区块的唯一标识方式以及在区块编辑器中显示的信息。这些属性包括: + +- **[`apiVersion`](https://developer.wordpress.org/block-editor/reference-guides/block-api/block-metadata/#api-version):** 指定区块使用的 [API](https://developer.wordpress.org/block-editor/reference-guides/block-api/block-api-versions/) 版本。除非有特定要求,否则请使用最新版本。 +- **[`name`](https://developer.wordpress.org/block-editor/reference-guides/block-api/block-metadata/#name):** 区块的唯一名称,包括命名空间(例如 `my-plugin/my-custom-block`)。 +- **[`title`](https://developer.wordpress.org/block-editor/reference-guides/block-api/block-metadata/#title):** 区块的显示标题,在插入器中显示。 +- **[`category`](https://developer.wordpress.org/block-editor/reference-guides/block-api/block-metadata/#category):** 区块在插入器中出现的分类。常见分类包括 `text`、`media`、`design`、`widgets` 和 `theme`。 +- **[`icon`](https://developer.wordpress.org/block-editor/reference-guides/block-api/block-metadata/#icon):** 在插入器中代表区块的图标。可以是 [Dashicon](https://developer.wordpress.org/resource/dashicons) 标识或自定义 SVG 图标。 +- **[`description`](https://developer.wordpress.org/block-editor/reference-guides/block-api/block-metadata/#description):** 区块的简短描述,提供比标题更多的上下文信息。 +- **[`keywords`](https://developer.wordpress.org/block-editor/reference-guides/block-api/block-metadata/#keywords):** 关键词数组,帮助用户在搜索时找到区块。 +- **[`textdomain`](https://developer.wordpress.org/block-editor/reference-guides/block-api/block-metadata/#text-domain):** 区块的文本域,用于国际化。 + +## 控制区块行为、输出或样式的文件 + +`block.json` 文件还允许你指定区块功能所需的关键文件: + +- **[`editorScript`](https://developer.wordpress.org/block-editor/reference-guides/block-api/block-metadata/#editor-script):** 仅用于区块编辑器的 JavaScript 文件。 +- **[`editorStyle`](https://developer.wordpress.org/block-editor/reference-guides/block-api/block-metadata/#editor-style):** 用于区块编辑器内样式设置的 CSS 文件。 +- **[`script`](https://developer.wordpress.org/block-editor/reference-guides/block-api/block-metadata/#script):** 在区块编辑器和前端均可加载的 JavaScript 文件。 +- **[`style`](https://developer.wordpress.org/block-editor/reference-guides/block-api/block-metadata/#style):** 在区块编辑器和前端均可应用的 CSS 文件。 +- **[`viewScript`](https://developer.wordpress.org/block-editor/reference-guides/block-api/block-metadata/#view-script):** 仅用于前端的 JavaScript 文件。 + +对于所有这些属性,你可以提供[文件路径](https://developer.wordpress.org/block-editor/reference-guides/block-api/block-metadata/#wpdefinedpath)(以 `file:` 开头)、使用 `wp_register_script` 或 `wp_register_style` 注册的[句柄](https://developer.wordpress.org/block-editor/reference-guides/block-api/block-metadata/#wpdefinedasset),或结合这两种选项的数组。 + +此外,[`render`](https://developer.wordpress.org/block-editor/reference-guides/block-api/block-metadata/#render) 属性([在 WordPress 6.1 中引入](https://make.wordpress.org/core/2022/10/12/block-api-changes-in-wordpress-6-1/))指定了一个 PHP 模板文件的路径,该文件负责生成[动态渲染](/docs/getting-started/fundamentals/static-dynamic-rendering.md)区块的前端标记。如果未向 `register_block_type()` 函数提供 `$render_callback` 函数,则使用此方法。 + +## 使用区块 `attributes` 存储数据 + +区块[属性](https://developer.wordpress.org/block-editor/reference-guides/block-api/block-metadata/#attributes)是分配给区块的设置或数据。它们可以决定区块的各个方面,例如其内容、布局、样式以及需要随区块结构存储的任何其他特定信息。如果用户更改了区块(例如修改字体大小),您需要一种方法来保留这些更改。属性正是解决方案。 + +在注册新的区块类型时,`block.json` 的 `attributes` 属性描述了区块所需的自定义数据以及这些数据在数据库中的存储方式。这使得区块编辑器能够正确解析这些值,并将 `attributes` 传递给区块的 `Edit` 组件和 `save` 函数。 + +以下是在 `block.json` 中定义的三个属性的示例: + +```json +"attributes": { + "fallbackCurrentYear": { + "type": "string" + }, + "showStartingYear": { + "type": "boolean" + }, + "startingYear": { + "type": "string" + } +}, +``` + +区块使用包含类似 JSON 特定属性的 HTML 样式注释标签进行“界定”。这些界定符使得在渲染文章内容或在区块编辑器中编辑文章时能够识别区块边界并解析区块属性。 + +以下代码示例展示了区块界定符中定义的属性。 + +```html + + + +``` + +默认情况下,所有属性都会被序列化并存储在区块的界定符中,但这可以根据您的需求进行配置。查看[理解区块属性](https://developer.wordpress.org/news/2023/09/understanding-block-attributes/)文章以了解更多信息。 + +### 读取和更新属性 + +这些[属性](https://developer.wordpress.org/block-editor/reference-guides/block-api/block-edit-save/#attributes)会传递给区块的 `Edit` React 组件以在区块编辑器中显示,传递给 `save` 函数以生成存储在数据库中的标记,并传递给区块的任何服务器端渲染定义。 + +`Edit` 组件独特地具有通过 [`setAttributes`](https://developer.wordpress.org/block-editor/reference-guides/block-api/block-edit-save/#setattributes) 函数修改这些属性的能力。 + +下图详细说明了在典型区块中属性是如何存储、读取和更新的。 + +[![打开属性图表图像](https://developer.wordpress.org/files/2023/11/attributes.png)](https://developer.wordpress.org/files/2023/11/attributes.png "打开属性图表图像") + +_在此[完整区块示例](https://github.com/WordPress/block-development-examples/tree/trunk/plugins/copyright-date-block-09aac3)中查看属性如何传递给 [`Edit`](https://github.com/WordPress/block-development-examples/blob/trunk/plugins/copyright-date-block-09aac3/src/edit.js) 组件、[`save`](https://github.com/WordPress/block-development-examples/blob/trunk/plugins/copyright-date-block-09aac3/src/save.js) 函数和 [`render.php`](https://github.com/WordPress/block-development-examples/blob/trunk/plugins/copyright-date-block-09aac3/src/render.php)。_ + +有关属性以及如何在自定义区块中使用它们的更多信息,请访问[属性 API](https://developer.wordpress.org/block-editor/reference-guides/block-api/block-attributes/) 参考页面。 + +## 使用区块支持启用设置和样式 + +许多区块(包括核心区块)提供类似的定制选项,例如背景颜色、文本颜色和内边距调整。 + +`block.json` 中的 [`supports`](https://developer.wordpress.org/block-editor/reference-guides/block-api/block-metadata/#supports) 属性允许区块声明支持一组这些常见的定制选项。启用后,区块用户可以直接从设置侧边栏调整颜色或内边距等内容。 + +利用这些预定义的区块支持有助于确保您的区块与核心区块行为一致,无需从头重新创建类似功能。 + +以下是在 `block.json` 中定义颜色支持的示例: + +```json +"supports": { + "color": { + "text": true, + "link": true, + "background": true + } +} +``` + +使用区块支持会生成一组需要手动添加到[区块包装元素](https://developer.wordpress.org/block-editor/getting-started/fundamentals/block-wrapper/)的属性。这确保它们作为区块数据的一部分被正确存储,并在生成将交付给前端的区块标记时被考虑在内。 + +以下代码演示了通过启用区块支持生成的属性和 CSS 类如何存储在区块的标记表示中。 + +```html + +

Hello World

+ +``` + +_查看[上述代码](https://github.com/WordPress/block-development-examples/blob/trunk/plugins/block-supports-6aa4dd/src/block.json)的[完整区块示例](https://github.com/WordPress/block-development-examples/tree/trunk/plugins/block-supports-6aa4dd)。_ + +有关支持以及如何在自定义区块中使用它们的更多信息,请访问[支持 API](https://developer.wordpress.org/block-editor/reference-guides/block-api/block-supports/) 参考页面。 \ No newline at end of file diff --git a/getting-started/fundamentals/block-wrapper.md b/getting-started/fundamentals/block-wrapper.md new file mode 100644 index 0000000..c55b6d4 --- /dev/null +++ b/getting-started/fundamentals/block-wrapper.md @@ -0,0 +1,111 @@ +# 区块包装器 + +区块编辑器中的每个区块都包含在HTML包装器中,该包装器必须具备特定属性才能在编辑器和前端正常运作。作为开发者,我们可以直接操作这些标记,WordPress提供了诸如`useBlockProps()`等工具来修改添加到区块包装器的属性。 + +在使用自定义样式或[区块支持](https://developer.wordpress.org/block-editor/reference-guides/block-api/block-supports/)等功能时,确保区块包装器具有正确的属性尤为重要。 + +WordPress中的区块可以通过三种不同类型的标记来定义,每种标记都有其独特作用: + +- **编辑器标记**:这是区块在区块编辑器中的可视化呈现。当通过[`registerBlockType`](https://developer.wordpress.org/block-editor/reference-guides/block-api/block-registration/#registerblocktype)在客户端注册区块时,使用React的`Edit`组件来定义。 +- **保存标记**:这是保存区块内容时存入数据库的标记。通过提供给`registerBlockType`的`save`函数来指定。如果区块不使用动态渲染,前端将显示此保存的标记。 +- **动态渲染标记**:当区块内容需要动态生成时,将使用此标记。它在服务端定义,可以通过[`register_block_type`](https://developer.wordpress.org/reference/functions/register_block_type/)中的`render_callback`函数,或`block.json`中指定的[`render.php`](https://developer.wordpress.org/block-editor/reference-guides/block-api/block-metadata/#render)文件来定义。如果存在,此标记将覆盖任何已保存的标记,并用于区块的前端显示。 + +对于[`Edit`组件和`save`函数](https://developer.wordpress.org/block-editor/reference-guides/block-api/block-edit-save/),重要的是使用标准DOM元素(如`
`)或将所有附加属性传递给原生DOM元素的React组件作为包装器元素。使用React片段(``)或``组件不适用于这些包装器。 + +## 编辑器标记 + +[`@wordpress/block-editor`](https://developer.wordpress.org/block-editor/reference-guides/packages/packages-block-editor)包提供的[`useBlockProps()`](https://developer.wordpress.org/block-editor/reference-guides/packages/packages-block-editor/#useblockprops)钩子,用于在`Edit`组件中定义区块的外部标记。 + +此钩子简化了多项任务,包括: + +- 为区块的HTML结构分配唯一的`id` +- 添加各种可访问性和`data-`属性以增强功能和信息 +- 包含反映区块自定义设置的类和内联样式。默认情况下包括: + - 用于通用区块样式的`wp-block`类 + - 结合区块命名空间和名称的特定区块类,确保唯一且有目标的样式能力 + +在以下示例中,区块的编辑器标记在`Edit`组件中使用`useBlockProps()`钩子定义。 + +```js +const Edit = () =>

Hello World - 区块编辑器

; + +registerBlockType( ..., { + edit: Edit +} ); +``` + +_查看[上述代码](https://github.com/WordPress/block-development-examples/blob/trunk/plugins/minimal-block-ca6eda/src/index.js)的[完整区块示例](https://github.com/WordPress/block-development-examples/tree/trunk/plugins/minimal-block-ca6eda)。_ + +区块在区块编辑器中的标记可能如下所示,其中类和属性会自动应用: + +```html +

Hello World - 区块编辑器

+``` + +在区块的`Edit`组件中,使用`useBlockProps()`钩子并通过传递参数来包含额外的类和属性。(参见[示例](https://github.com/WordPress/block-development-examples/blob/trunk/plugins/stylesheets-79a4c3/src/edit.js)) + +当使用`supports`属性启用功能时,任何相应的类或属性都会自动包含在`useBlockProps`返回的对象中。 + +## 保存标记 + +在数据库中保存标记时,重要的是将`useBlockProps.save()`返回的属性添加到区块的包装元素中。`useBlockProps.save()`确保区块类名正确渲染,同时还包括区块支持API注入的任何HTML属性。 + +考虑以下在客户端注册区块的代码。注意它如何定义编辑区块和将区块保存到数据库时应使用的标记。 + +```js +const Edit = () =>

Hello World - 区块编辑器

; +const save = () =>

Hello World - 前端

; + +registerBlockType( ..., { + edit: Edit, + save, +} ); +``` + +_查看[上述代码](https://github.com/WordPress/block-development-examples/blob/trunk/plugins/minimal-block-ca6eda/src/index.js)的[完整区块示例](https://github.com/WordPress/block-development-examples/tree/trunk/plugins/minimal-block-ca6eda)。_ + +区块在前端的标记可能如下所示,其中类会自动应用: + +```html +

Hello World – 前端

+``` + +如果要在区块的`save`函数中添加任何额外的类或属性,应将它们作为`useBlockProps.save()`的参数传递。(参见[示例](https://github.com/WordPress/block-development-examples/blob/trunk/plugins/stylesheets-79a4c3/src/save.js)) + +当为任何功能添加`supports`时,适当的类会添加到`useBlockProps.save()`钩子返回的对象中。在下面的示例中,文本和背景颜色类已添加到段落区块中。 + +```html +

Hello World

+``` + +生成此HTML的[示例区块](https://github.com/WordPress/block-development-examples/tree/trunk/plugins/block-supports-6aa4dd)可在[区块开发示例](https://github.com/WordPress/block-development-examples)存储库中找到。 + +## 动态渲染标记 + +在动态区块中,前端标记在服务端渲染,您可以使用[`get_block_wrapper_attributes()`](https://developer.wordpress.org/reference/functions/get_block_wrapper_attributes/)函数输出必要的类和属性,就像在`save`函数中使用`useBlockProps.save()`一样。(参见[示例](https://github.com/WordPress/block-development-examples/blob/f68640f42d993f0866d1879f67c73910285ca114/plugins/block-dynamic-rendering-64756b/src/render.php#L11)) + +```php +

> + +

+``` \ No newline at end of file diff --git a/getting-started/fundamentals/file-structure-of-a-block.md b/getting-started/fundamentals/file-structure-of-a-block.md new file mode 100644 index 0000000..00d86c6 --- /dev/null +++ b/getting-started/fundamentals/file-structure-of-a-block.md @@ -0,0 +1,95 @@ +## 附加资源 + +- [展示区块文件结构的示意图](https://excalidraw.com/#json=YYpeR-kY1ZMhFKVZxGhMi,mVZewfwNAh_oL-7bj4gmdw) + +### `index.js` + +`index.js`文件(或`block.json`中`editorScript`属性定义的任何其他文件)是仅在块编辑器中加载的JavaScript入口文件。它负责调用`registerBlockType`函数在客户端注册区块,通常通过导入`edit.js`和`save.js`文件来获取区块注册所需的函数。 + +### `edit.js` + +`edit.js`文件包含负责渲染区块编辑界面的React组件,允许用户在块编辑器中与区块内容及设置进行交互。该组件会被传入`index.js`文件内`registerBlockType`函数的[`edit`](https://developer.wordpress.org/block-editor/reference-guides/block-api/block-edit-save/#edit)属性。 + +### `save.js` + +`save.js`文件导出的函数用于返回将被保存到WordPress数据库的静态HTML标记。该函数会被传入`index.js`文件内`registerBlockType`函数的[`save`](https://developer.wordpress.org/block-editor/reference-guides/block-api/block-edit-save/#save)属性。 + +### `style.(css|scss|sass)` + +扩展名为`.css`、`.scss`或`.sass`的`style`文件包含将在块编辑器和前端同时加载的区块样式。在构建过程中,该文件会被转换为`style-index.css`,通常通过`block.json`中的[`style`](https://developer.wordpress.org/block-editor/reference-guides/block-api/block-metadata/#style)属性进行定义。 + +
+ wp-scripts内部使用的webpack配置包含串联的 + css-loader、 + postcss-loader和 + sass-loader, + 使其能够处理CSS、SASS或SCSS文件。更多信息请参阅 + 默认webpack配置。 +
+ +### `editor.(css|scss|sass)` + +扩展名为`.css`、`.scss`或`.sass`的`editor`文件包含在块编辑器中应用于区块的附加样式。此文件通常用于定义区块用户界面特有的样式。在构建过程中该文件会被转换为`index.css`,一般通过`block.json`中的`editorStyle`属性进行定义。 + +### `render.php` + +`render.php`文件(或`block.json`的[`render`](https://developer.wordpress.org/block-editor/reference-guides/block-api/block-metadata/#render)属性定义的任何其他文件)定义了服务端处理逻辑,用于在前端请求时返回区块标记。若已定义,此文件将优先于其他前端区块渲染方式。 + +### `view.js` + +`view.js`文件(或`block.json`的[`viewScript`](https://developer.wordpress.org/block-editor/reference-guides/block-api/block-metadata/#view-script)属性定义的任何其他文件)会在区块展示时于前端加载。 + +## `build`文件夹 + +`build`文件夹包含从`src`文件夹编译优化后的代码版本。这些文件由[构建过程](https://developer.wordpress.org/block-editor/getting-started/devenv/get-started-with-wp-scripts/#the-build-process-with-wp-scripts)生成,该过程通过`wp-scripts`的`build`或`start`命令触发。 + +此转换过程包括代码压缩、将现代JavaScript转译为兼容更广泛浏览器的版本,以及资源打包以实现高效加载。WordPress最终会调用并使用`build`文件夹中的内容在块编辑器和前端渲染区块。 + +
+ 您可以使用wp-scripts构建命令的webpack-src-diroutput-path选项来 + 自定义入口和输出路径。 +
+ +# 区块的文件结构 + +为WordPress开发自定义区块时,最佳实践是在插件中注册它们而非主题中。这种策略能确保即使用户切换主题,您的区块仍可正常使用。虽然在某些情况下将区块直接嵌入主题可能更为合适,但本指南主要关注插件中的区块结构。具体来说,将详细说明通过[`create-block`](https://developer.wordpress.org/block-editor/getting-started/devenv/get-started-with-create-block/)工具生成的文件结构。 + +遵循`create-block`工具的结构并非强制要求,但它提供了一个可靠的参考标准。该工具生成的文件包含了区块定义和注册所需的所有内容。采用这种结构有助于保持一致性,并确保区块组织有序、易于维护。 + +[![点击查看区块文件结构示意图](https://developer.wordpress.org/files/2023/11/file-structure-block.png)](https://developer.wordpress.org/files/2023/11/file-structure-block.png "点击查看区块文件结构示意图") + +## `<插件文件>.php` + +在WordPress插件中创建区块时,通常需要在插件的主PHP文件中通过服务器端注册区块。这可以通过使用[`register_block_type()`](https://developer.wordpress.org/reference/functions/register_block_type/)函数来实现。 + +
+ 有关创建WordPress插件的更多信息,请参阅插件基础文档以及主PHP文件的头部要求。 +
+ +## `package.json` + +`package.json`文件用于配置Node.js项目(从技术角度说,区块插件就是Node.js项目)。在此文件中,您可以定义区块的`npm`依赖项以及用于本地开发的脚本。 + +## `src`文件夹 + +在标准项目中,`src`(源码)文件夹包含原始的未编译代码,包括开发区块所需的JavaScript、CSS和其他资源。这是您编写和编辑区块源代码的地方,可以利用现代JavaScript特性和JSX来创建React组件。 + +通过`wp-scripts`提供的[构建过程](docs/block-editor/getting-started/fundamentals/javascript-in-the-block-editor/#javascript-build-process.md)会从此文件夹中获取文件,并在项目的`build`文件夹中生成可用于生产环境的文件。 + +### `block.json` + +`block.json`文件包含[区块的元数据](docs/block-editor/reference-guides/block-api/block-metadata/),可简化其在客户端和服务器端环境中的定义和注册过程。 + +该文件包括区块名称、描述、[属性](docs/block-editor/reference-guides/block-api/block-attributes/)、[支持特性](docs/block-editor/reference-guides/block-api/block-supports/)等内容,以及负责区块功能、外观和样式的关键文件位置。 + +当应用构建过程时,`block.json`文件和其他生成的文件会被移动到指定文件夹(通常是`build`文件夹)。因此,`block.json`中指定的文件路径指向这些经过处理、打包后的文件版本。 + +在`block.json`中可以定义的一些最重要属性包括: + +- **[`editorScript`](https://developer.wordpress.org/block-editor/reference-guides/block-api/block-metadata/#editor-script):** 通常设置为从`src/index.js`构建的打包`index.js`文件路径。 +- **[`style`](https://developer.wordpress.org/block-editor/reference-guides/block-api/block-metadata/#style):** 通常设置为从`src/style.(css|scss|sass)`构建的打包`style-index.css`文件路径。 +- **[`editorStyle`](https://developer.wordpress.org/block-editor/reference-guides/block-api/block-metadata/#editor-style):** 通常设置为从`src/editor.(css|scss|sass)`构建的打包`index.css`文件路径。 +- **[`render`](https://developer.wordpress.org/block-editor/reference-guides/block-api/block-metadata/#render):** 通常设置为从`src/render.php`复制而来的打包`render.php`文件路径。 +- **[`viewScript`](https://developer.wordpress.org/block-editor/reference-guides/block-api/block-metadata/#view-script):** 通常设置为从`src/view.js`构建的打包`view.js`文件路径。 + +[![在excalidraw中打开构建输出示意图](https://developer.wordpress.org/files/2023/11/file-structure-build-output.png)](https://excalidraw.com/#json=c22LROgcG4JkD-7SkuE-N,rQW_ViJBq0Yk3qhCgqD6zQ "在excalidraw中打开构建输出示意图") \ No newline at end of file diff --git a/getting-started/fundamentals/javascript-in-the-block-editor.md b/getting-started/fundamentals/javascript-in-the-block-editor.md new file mode 100644 index 0000000..b2317fd --- /dev/null +++ b/getting-started/fundamentals/javascript-in-the-block-editor.md @@ -0,0 +1,94 @@ +# 区块编辑器中的JavaScript开发 + +为区块编辑器开发区块通常涉及使用现代JavaScript(ESNext和JSX),本手册中的大多数示例都采用这些语法编写。 + +然而,这种形式的JavaScript必须转换为浏览器兼容的格式,因此需要构建步骤。这个过程会将JavaScript源代码及相关资源进行转换、打包和优化,最终生成适用于生产环境的格式。 + +## 使用构建流程的JavaScript开发 + +采用构建流程进行区块开发能充分发挥现代JavaScript的潜力,便于使用ESNext和JSX。 + +[ESNext](https://developer.mozilla.org/en-US/docs/Web/JavaScript/JavaScript_technologies_overview#standardization_process) 指的是JavaScript的最新语法和特性。[JSX](https://react.dev/learn/writing-markup-with-jsx) 是React项目开发的语法扩展,允许您编写类似HTML的JavaScript代码。 + +由于浏览器无法直接执行ESNext和JSX,这些语法必须转换为浏览器兼容的JavaScript。 + +[webpack](https://webpack.js.org/concepts/why-webpack/) 是一个可插拔工具,用于处理和打包JavaScript以实现浏览器兼容性。[Babel](https://babeljs.io/) 作为webpack的插件,能够将ESNext和JSX转换为标准JavaScript。 + +配置webpack和Babel可能具有挑战性,因此建议使用 [`@wordpress/scripts`](https://developer.wordpress.org/block-editor/reference-guides/packages/packages-scripts/) 工具包。该工具通过预配置两者来简化开发,您通常无需编写自定义的webpack或Babel配置。 + +入门指南请参阅 [wp-scripts入门指南](/docs/getting-started/devenv/get-started-with-wp-scripts.md)。 + +### `wp-scripts`概览 + +下图概述了使用`wp-scripts`工具包时的构建流程。该工具专为开发和生产环境的[标准配置](/docs/getting-started/devenv/get-started-with-wp-scripts.md#basic-usage)设计,开箱即用。 + +[![打开构建流程图](https://developer.wordpress.org/files/2023/11/build-process.png)](https://developer.wordpress.org/files/2023/11/build-process.png "打开构建流程图") + +- **生产模式 (`npm run build`):** 在此模式下,`wp-scripts`会编译JavaScript并对输出进行压缩,以减小文件大小并提升浏览器加载速度。这非常适合将代码部署至生产环境。 + +- **开发模式 (`npm start`):** 此模式专为活跃开发定制。它会跳过压缩以便于调试,生成源码映射以优化错误追踪,并监听源文件变更。当检测到更改时,会自动重建受影响文件,让您可以实时查看更新。 + +`wp-scripts`工具包还支持使用JavaScript模块,允许代码分布在多个文件中,并在构建流程后生成精简的打包文件。[block-development-example](https://github.com/WordPress/block-development-examples/tree/trunk/plugins/data-basics-59c8f8) GitHub代码库提供了一些优秀示例。 + +
+ 大多数情况下无需自定义配置,但您可以在使用wp-scripts时提供webpack.config.js文件来调整构建流程以满足需求。 +
+ +## 无构建流程的JavaScript开发 + +在某些特定场景下,无需构建流程即可将JavaScript集成到WordPress项目中可能是最直接的方法。这对于不涉及JSX或其他需要编译的高级JavaScript特性的项目尤为适用。 + +当您选择不使用构建流程时,需通过全局`wp`对象直接与WordPress的[JavaScript API](/docs/reference-guides/packages.md)进行交互。这意味着WordPress提供的所有方法和工具包都立即可用,但需要注意:您必须手动管理脚本依赖关系。这需要将每个对应工具包的[句柄](/docs/contributors/code/scripts.md)添加到您入队JavaScript文件的依赖数组中。 + +例如,假设您正在创建一个使用[`blocks`](/packages/blocks/README.md)工具包中的`registerBlockVariation`函数来注册新[区块变体](/docs/reference-guides/block-api/block-variations.md)的脚本。您必须在脚本的依赖数组中包含`wp-blocks`。这能确保在脚本执行时,`wp.blocks.registerBlockVariation`方法可用且已定义。 + +以下示例展示了在入队`variations.js`文件时定义`wp-blocks`依赖项: + +```php +function example_enqueue_block_variations() { + wp_enqueue_script( + 'example-enqueue-block-variations', + get_template_directory_uri() . '/assets/js/variations.js', + array( 'wp-blocks' ), + wp_get_theme()->get( 'Version' ), + false + ); +} +add_action( 'enqueue_block_editor_assets', 'example_enqueue_block_variations' ); +``` + +然后在`variations.js`文件中,您可以像这样为"媒体与文本"区块注册新变体: + +```js +wp.blocks.registerBlockVariation( + 'core/media-text', + { + name: 'media-text-custom', + title: '自定义媒体与文本', + attributes: { + align: 'wide', + backgroundColor: 'tertiary' + }, + } +); +``` + +对于需要在区块编辑器中运行的脚本,请确保使用[`enqueue_block_editor_assets`](https://developer.wordpress.org/reference/hooks/enqueue_block_editor_assets/)钩子配合标准的[`wp_enqueue_script`](https://developer.wordpress.org/reference/functions/wp_enqueue_script/)函数。 + +更多信息请参阅[在编辑器中加载资源](/docs/how-to-guides/enqueueing-assets-in-the-editor.md)。 + +
+ 在编辑文章或使用站点编辑器时,打开浏览器开发者工具并在控制台中尝试运行wp.data.select('core/editor').getBlocks()。此命令将返回所有可用区块。 +
+ +## 扩展资源 + +- [工具包参考](/docs/reference-guides/packages.md) +- [wp-scripts入门指南](/docs/getting-started/devenv/get-started-with-wp-scripts.md) +- [在编辑器中加载资源](/docs/how-to-guides/enqueueing-assets-in-the-editor.md) +- [WordPress工具包句柄](/docs/contributors/code/scripts.md) +- [JavaScript参考](https://developer.mozilla.org/en-US/docs/Web/JavaScript) | MDN Web文档 +- [block-development-examples](https://github.com/WordPress/block-development-examples) | GitHub代码库 +- [block-theme-examples](https://github.com/wptrainingteam/block-theme-examples) | GitHub代码库 +- [webpack与WordPress工具包的交互原理](https://developer.wordpress.org/news/2023/04/how-webpack-and-wordpress-packages-interact/) | 开发者博客 +- [构建流程图](https://excalidraw.com/#json=4aNG9JUti3pMnsfoga35b,ihEAI8p5dwkpjWr6gQmjuw) \ No newline at end of file diff --git a/getting-started/fundamentals/markup-representation-block.md b/getting-started/fundamentals/markup-representation-block.md new file mode 100644 index 0000000..c02a10c --- /dev/null +++ b/getting-started/fundamentals/markup-representation-block.md @@ -0,0 +1,51 @@ +# 区块的标记表示 + +区块通过独特的[基于HTML的语法](https://developer.wordpress.org/block-editor/explanations/architecture/key-concepts/#data-and-attributes)存储在数据库或HTML模板中,这种语法以HTML注释作为清晰的区块分隔符。这确保了区块标记在技术上是有效的HTML。 + +以下是定义区块标记的几条准则: + +- 核心区块以`wp:`前缀开头,后跟区块名称(例如`wp:image`)。值得注意的是,`core`命名空间被省略。 +- 自定义区块以`wp:`前缀开头,后跟区块命名空间和名称(例如`wp:namespace/name`)。 +- 注释可以是单行、自闭合或HTML内容的包装器。 +- 区块设置和属性以JSON对象形式存储在区块注释内部。 + +以下是图像区块的简化标记表示: + +```html + +
+ +
+ +``` + +区块标记在区块编辑器和前端显示区块时都至关重要: + +- WordPress在编辑器内分析区块标记以提取其数据,并向用户呈现可编辑版本。 +- 在前端,WordPress再次解析标记以提取数据并渲染最终HTML输出。 + +
+ 有关区块数据在WordPress中如何解析的深入探讨,请参阅数据流文章。 +
+ +当区块保存时,会执行`save`函数(在[客户端注册区块](https://developer.wordpress.org/block-editor/getting-started/fundamentals/registration-of-a-block/#registration-of-the-block-with-javascript-client-side)时定义)以生成存储在数据库中的标记,并用区块分隔符注释包裹。对于动态渲染的区块(通常将`save`设置为`null`),仅保存带有区块属性的占位符注释。 + +以下是动态渲染区块(`save` = `null`)的标记表示。请注意,除了注释外没有HTML标记。 + +```html + +``` + +当区块具有`save`函数时,区块编辑器会检查`save`函数创建的标记是否与保存到数据库的区块标记相同: + +- 差异将触发[验证错误](https://developer.wordpress.org/block-editor/reference-guides/block-api/block-edit-save/#validation),通常是由于`save`函数输出的更改引起的。 +- 开发者可以通过实现[区块弃用](https://developer.wordpress.org/block-editor/reference-guides/block-api/block-deprecation/)来解决潜在的验证问题,以应对更改。 + +如上例所示,动态渲染区块的存储标记非常简洁。通常,这只是一个包含区块属性的分隔符注释,不受区块编辑器验证的约束。这种方法反映了这些区块的动态特性,实际HTML在服务器端生成,不存储在数据库中。 + +## 附加资源 + +- [数据流与数据格式](https://developer.wordpress.org/block-editor/explanations/architecture/data-flow/) +- [静态与动态区块:有何不同?](https://developer.wordpress.org/news/2023/02/27/static-vs-dynamic-blocks-whats-the-difference/) | 开发者博客 +- [区块弃用教程](https://developer.wordpress.org/news/2023/03/10/block-deprecation-a-tutorial/) | 开发者博客 +- [模板入门 > 区块标记](https://developer.wordpress.org/themes/templates/introduction-to-templates/#block-markup) | 主题手册 \ No newline at end of file diff --git a/getting-started/fundamentals/registration-of-a-block.md b/getting-started/fundamentals/registration-of-a-block.md new file mode 100644 index 0000000..01599f5 --- /dev/null +++ b/getting-started/fundamentals/registration-of-a-block.md @@ -0,0 +1,122 @@ +# 区块注册 + +WordPress中的区块通常以插件形式打包,并通过`block.json`元数据在服务端和客户端进行注册。 + +虽然可以仅在客户端注册区块,但最佳实践强烈建议在服务端和客户端同时注册。这种双重注册对于启用服务端功能至关重要,例如动态渲染、区块支持、区块钩子和样式变体。若缺少服务端注册,这些功能将无法正常运行。 + +例如,若希望区块[通过`theme.json`设置样式](https://developer.wordpress.org/themes/global-settings-and-styles/settings/blocks/),则必须在服务端进行注册。否则,该区块将无法识别或应用在`theme.json`中为其分配的任何样式。 + +以下流程图详细说明了区块的注册过程。 + +[![打开区块注册流程图](https://developer.wordpress.org/files/2023/11/block-registration-e1700493399839.png)](https://developer.wordpress.org/files/2023/11/block-registration-e1700493399839.png "打开区块注册流程图") + +## 使用PHP注册区块(服务端) + +区块在服务端的注册通常发生在主插件PHP文件中,通过[`init`](https://developer.wordpress.org/reference/hooks/init/)钩子调用[`register_block_type()`](https://developer.wordpress.org/reference/functions/register_block_type/)函数。该函数通过读取`block.json`文件中存储的元数据来简化区块类型注册。 + +此函数专为注册区块类型而设计,在此上下文中主要使用两个参数(虽然可支持更多变体): + +- **`$block_type`(字符串):** 可以是包含`block.json`文件的目录路径,或元数据文件的完整路径(如果文件名不同)。此参数告知WordPress在哪里查找区块配置。 + +- **`$args`(数组):** 这是一个可选参数,可用于指定区块类型的其他参数。默认情况下为空数组,但可以包含各种选项,其中之一是`$render_callback`。此回调用于在前端渲染区块,是`block.json`中`render`属性的替代方案。 + +在开发过程中,`block.json`文件通常作为代码编译的一部分从`src`(源)目录移动到`build`目录。因此,在注册区块时,请确保`$block_type`路径指向`build`目录中的`block.json`文件。 + +`register_block_type()`函数在成功时返回注册的区块类型(`WP_Block_Type`),失败时返回`false`。以下是一个使用`render_callback`的简单示例。 + +```php +register_block_type( + __DIR__ . '/build', + array( + 'render_callback' => 'render_block_core_notice', + ) +); +``` + +以下是一个更完整的示例,包括`init`钩子。 + +```php +function minimal_block_ca6eda___register_block() { + register_block_type( __DIR__ . '/build' ); +} +add_action( 'init', 'minimal_block_ca6eda___register_block' ); +``` + +_查看[上述代码](https://github.com/WordPress/block-development-examples/blob/trunk/plugins/minimal-block-ca6eda/plugin.php)的[完整区块示例](https://github.com/WordPress/block-development-examples/tree/trunk/plugins/minimal-block-ca6eda)_ + +### 仅限PHP的自动注册区块 + +对于仅需服务端渲染的区块,您可以使用[`auto_register`](/docs/reference-guides/block-api/block-supports.md#auto_register)标志和`render_callback`在PHP中单独注册。这些区块会自动出现在编辑器中,无需任何JavaScript注册或客户端代码,并使用[动态渲染](/docs/getting-started/fundamentals/static-dynamic-rendering.md)。 + +```php +register_block_type( 'my-plugin/server-block', array( + 'render_callback' => function( $attributes ) { + $wrapper_attributes = get_block_wrapper_attributes(); + + return sprintf( + '
服务器内容
', + $wrapper_attributes + ); + }, + 'supports' => array( + 'auto_register' => true, + 'color' => array( + 'background' => true, + ), + ), +) ); +``` + +## 使用JavaScript注册区块(客户端) + +当区块已在服务端注册且未使用[仅限PHP的自动注册区块](#仅限php的自动注册区块)时,您只需使用`@wordpress/blocks`包中的[`registerBlockType`](https://developer.wordpress.org/block-editor/reference-guides/packages/packages-blocks/#registerblocktype)方法在JavaScript中注册客户端设置。只需确保使用与区块`block.json`文件中定义的相同区块名称。以下是一个示例: + +```js +import { registerBlockType } from '@wordpress/blocks'; + +registerBlockType( 'my-plugin/notice', { + edit: Edit, + // ...其他客户端设置 +} ); +``` + +虽然通常建议使用PHP在服务端注册区块,以获得["使用元数据文件的好处"](https://developer.wordpress.org/block-editor/reference-guides/block-api/block-metadata/#benefits-using-the-metadata-file)部分所述的优势,但您也可以选择仅在客户端注册区块。`registerBlockType`方法允许您使用元数据注册区块类型。 + +该函数接受两个参数: + +- **`blockNameOrMetadata`(字符串|对象):** 可以是区块类型的名称(字符串),也可以是包含区块元数据的对象(通常从`block.json`文件加载)。 +- **`settings`(对象):** 这是一个包含区块客户端设置的对象。 + +
+ 如果您使用构建过程(例如wp-scripts提供的构建过程),可以直接将block.json文件(或任何其他.json文件)的内容导入到JavaScript文件中。 +
+ +作为第二个参数传递的`settings`对象包含许多属性,但以下两个是最重要的: + +- **`edit`:** 在编辑器中用于我们区块的React组件。 +- **`save`:** 返回保存到数据库的静态HTML标记的函数。 + +`registerBlockType()`函数在成功时返回注册的区块类型(`WPBlock`),失败时返回`undefined`。以下是一个示例: + +```js +import { registerBlockType } from '@wordpress/blocks'; +import { useBlockProps } from '@wordpress/block-editor'; +import metadata from './block.json'; + +const Edit = () =>

Hello World - 区块编辑器

; +const save = () =>

Hello World - 前端

; + +registerBlockType( metadata.name, { + edit: Edit, + save, +} ); +``` + +_查看[上述代码](https://github.com/WordPress/block-development-examples/blob/trunk/plugins/minimal-block-ca6eda/src/index.js)的[完整区块示例](https://github.com/WordPress/block-development-examples/tree/trunk/plugins/minimal-block-ca6eda)_ + +## 附加资源 + +- [`register_block_type` PHP函数](https://developer.wordpress.org/reference/functions/register_block_type/) +- [`registerBlockType` JS函数](https://developer.wordpress.org/block-editor/reference-guides/packages/packages-blocks/#registerblocktype) +- [为什么区块需要在服务端和客户端同时注册?](https://github.com/WordPress/gutenberg/discussions/55884) | GitHub讨论 +- [区块注册流程图](https://excalidraw.com/#json=PUQu7jpvbKsUHYfpHWn7s,61QnhpZtjykp3s44lbUN_g) \ No newline at end of file diff --git a/getting-started/fundamentals/static-dynamic-rendering.md b/getting-started/fundamentals/static-dynamic-rendering.md new file mode 100644 index 0000000..c0010c8 --- /dev/null +++ b/getting-started/fundamentals/static-dynamic-rendering.md @@ -0,0 +1,178 @@ +### 数据库中动态区块的HTML呈现方式(`save`方法) + +对于动态区块,`save`回调函数可以直接返回`null`,这将告知编辑器仅将区块分隔注释(以及现有的[区块属性](https://developer.wordpress.org/block-editor/reference-guides/block-api/block-attributes/))保存至数据库。这些属性随后会被传递到服务端渲染回调函数,由该函数决定如何在前端站点上显示区块。 + +当`save`返回`null`时,区块编辑器将跳过[区块标记验证流程](https://developer.wordpress.org/block-editor/reference-guides/block-api/block-edit-save/#validation),避免因频繁变化的标记引发问题。 + +具有动态渲染功能的区块也可以保存区块的HTML表示作为备用方案。如果您提供了服务端渲染回调,数据库中代表区块的HTML将被回调输出所替代,但若区块被停用(注册区块的插件被卸载)或渲染回调被移除,则会呈现原保存的HTML。 + +在某些情况下,区块会保存其HTML表示,并在满足特定条件时使用动态渲染来优化标记。核心区块中使用此方法的示例包括: + +- [封面区块](https://github.com/WordPress/gutenberg/blob/trunk/packages/block-library/src/cover)通过[save方法](https://github.com/WordPress/gutenberg/blob/trunk/packages/block-library/src/cover/save.js)在数据库中保存完整的HTML表示。该标记通过[`render_callback`](https://github.com/WordPress/gutenberg/blob/22741661998834e69db74ad863705ee2ce97b446/packages/block-library/src/cover/index.php#L74)处理,当启用“使用特色图像”设置时,会[动态注入](https://github.com/WordPress/gutenberg/blob/22741661998834e69db74ad863705ee2ce97b446/packages/block-library/src/cover/index.php#L16)特色图像。 +- [图像区块](https://github.com/WordPress/gutenberg/blob/trunk/packages/block-library/src/image)同样通过[save方法](https://github.com/WordPress/gutenberg/blob/trunk/packages/block-library/src/image/save.js)将其HTML表示保存至数据库。该标记通过[`render_callback`](https://github.com/WordPress/gutenberg/blob/22741661998834e69db74ad863705ee2ce97b446/packages/block-library/src/image/index.php#L363)处理,当满足特定条件时,会[为标记添加额外属性](https://github.com/WordPress/gutenberg/blob/22741661998834e69db74ad863705ee2ce97b446/packages/block-library/src/image/index.php#L18)。 + +如果在动态区块中使用[内部区块](https://developer.wordpress.org/block-editor/how-to-guides/block-tutorial/nested-blocks-inner-blocks/),您需要通过``在`save`回调函数中保存`InnerBlocks`。 + +## 扩展资源 + +- [静态区块 vs 动态区块:差异解析](https://developer.wordpress.org/news/2023/02/27/static-vs-dynamic-blocks-whats-the-difference/) | 开发者博客 +- [区块弃用机制详解](https://developer.wordpress.org/news/2023/03/10/block-deprecation-a-tutorial/) | 开发者博客 + +# 区块的静态与动态渲染 + +区块的前端标记既可以在请求时由服务器端动态生成(动态区块),也可以在区块编辑器的保存过程中静态生成(静态区块)。本文将深入探讨这两种实现方式。 + +
+《静态区块与动态区块:差异解析》这篇技术文章为本主题提供了绝佳的入门指引。 +
+ +## 静态渲染 + +采用"静态渲染"的区块在保存时就会生成固定不变的前端输出并存储至数据库。这类区块完全依赖其 `save` 函数来定义其 [HTML 标记结构](https://developer.wordpress.org/block-editor/getting-started/fundamentals/markup-representation-block/),除非在区块编辑器中进行手动修改,否则这些标记将始终保持不变。 + +若某个区块未采用动态渲染方式——即不通过 PHP 在页面加载时实时生成内容——则会被归类为"静态区块"。 + +下图直观展示了静态区块内容如何保存至数据库,并在前端被检索并渲染为 HTML 的过程。 + +![静态渲染区块示意图](https://developer.wordpress.org/files/2024/01/static-rendering.png) + +### 如何为区块定义静态渲染 + +通过[在客户端注册区块](https://developer.wordpress.org/block-editor/getting-started/fundamentals/registration-of-a-block/#registration-of-the-block-with-javascript-client-side)时可定义的 `save` 函数,可以指定区块的 HTML 结构。每当在编辑器中保存区块时,该 HTML 结构就会被存入数据库,并最终用于前端展示。 + +WordPress 中的区块会被封装在特殊的注释标签内,这些标签作为唯一的[区块定界符](https://developer.wordpress.org/block-editor/getting-started/fundamentals/markup-representation-block/)存在。但最终渲染时,只会呈现静态区块 `save` 函数中定义的 HTML 内容(不包含这些定界符)。 + +
查看预格式化区块中的静态渲染示例 +
+以下是 预格式化 核心区块的 save 函数示例: + +```js +import { RichText, useBlockProps } from '@wordpress/block-editor'; + +export default function save( { attributes } ) { + const { content } = attributes; + + return ( +
+			
+		
+ ); +} +``` + +当 `attributes.content` 的值为 `"这是一段预格式化文本"` 时,该函数会生成如下区块标记表示: + +```html + +
这是一段预格式化文本
+ +``` + +在前端,该区块将返回以下标记。请注意定界符已不复存在。 + +```html +
这是一段预格式化文本
+``` +
+
+ +我们将在下一节探讨的动态区块,虽然也能通过 `save` 函数指定初始 HTML 结构(类似于静态区块),但它们主要依赖服务器端渲染来生成内容。如果因故无法进行动态渲染(比如区块插件被停用),系统将转而使用数据库中保存的 HTML 结构来在前端显示该区块。 + +要了解具体运作机制,请参阅[构建你的第一个区块](/docs/getting-started/tutorial.md)教程。特别是[添加静态渲染](/docs/getting-started/tutorial.md#adding-static-rendering)章节,生动演示了区块如何同时具备保存 HTML 结构和动态渲染能力。 + +
+WordPress 提供了诸如 render_blockrender_callback 等机制,用于在区块最终呈现到前端之前修改其保存的 HTML。这些工具赋予开发者动态定制区块输出的能力,以满足复杂交互场景的需求。 +
+ +更多采用静态渲染的 WordPress 区块示例(即其输出在保存时即固定,不依赖服务器端处理)包括: + +- [分隔符区块](https://github.com/WordPress/gutenberg/blob/trunk/packages/block-library/src/separator/save.js) +- [间距区块](https://github.com/WordPress/gutenberg/blob/trunk/packages/block-library/src/spacer/save.js) +- [按钮区块](https://github.com/WordPress/gutenberg/blob/trunk/packages/block-library/src/button/save.js) + +## 动态渲染 + +具备“动态渲染”功能的区块旨在前端请求时实时生成其内容和结构。与在数据库中保存固定HTML结构的静态区块不同,“动态区块”依赖服务器端处理来动态构建输出,这使其具有高度灵活性,非常适合需要频繁更新或依赖外部数据的内容。 + +下图展示了动态区块的表示形式如何在数据库中保存,然后在前端检索并动态渲染为HTML。 + +![动态渲染区块示意图](https://developer.wordpress.org/files/2024/01/dynamic-rendering.png) + +动态区块的常见应用场景包括: + +1. **内容需在文章未更新时自动变化的区块**:例如[最新文章](https://github.com/WordPress/gutenberg/tree/trunk/packages/block-library/src/latest-posts)区块,每当有新文章发布时会自动更新。 +2. **标记更新需立即在前端显示的区块**:若通过添加新类、HTML元素或任何方式更新区块结构,使用动态区块可确保这些更改立即应用到全站所有该区块实例。若无动态区块,类似更新可能触发区块编辑器中的[验证错误](https://developer.wordpress.org/block-editor/reference-guides/block-api/block-edit-save/#validation)。 + +### 如何为区块定义动态渲染 + +区块可通过两种主要方式定义动态渲染: + +1. 使用可传递给[`register_block_type()`](https://developer.wordpress.org/block-editor/getting-started/fundamentals/registration-of-a-block/#registration-of-the-block-with-php-server-side)函数的`render_callback`参数。这对于[纯PHP区块](/docs/getting-started/fundamentals/registration-of-a-block.md#php-only-blocks-with-auto-registration)是必需的。 +2. 使用通常命名为`render.php`的独立PHP文件。该文件路径应在`block.json`文件中通过[`render`](https://developer.wordpress.org/block-editor/getting-started/fundamentals/block-json/#files-for-the-blocks-behavior-output-or-style)属性定义。 + +这两种方法都会接收以下数据: + + - `$attributes`:区块的属性数组。 + - `$content`:数据库中存储的区块标记(如有)。 + - `$block`:表示已渲染区块的[WP_Block](https://developer.wordpress.org/reference/classes/wp_block/)类实例([区块元数据](https://developer.wordpress.org/block-editor/reference-guides/block-api/block-metadata/))。 + +
查看站点标题区块中的动态渲染示例 +
+ +[站点标题](https://github.com/WordPress/gutenberg/tree/trunk/packages/block-library/src/site-title)区块使用以下[`render_callback`](https://github.com/WordPress/gutenberg/blob/trunk/packages/block-library/src/site-title/index.php): + +```php +function render_block_core_site_title( $attributes ) { + $site_title = get_bloginfo( 'name' ); + if ( ! $site_title ) { + return; + } + + $tag_name = 'h1'; + $classes = empty( $attributes['textAlign'] ) ? '' : "has-text-align-{$attributes['textAlign']}"; + if ( isset( $attributes['style']['elements']['link']['color']['text'] ) ) { + $classes .= ' has-link-color'; + } + + if ( isset( $attributes['level'] ) ) { + $tag_name = 0 === $attributes['level'] ? 'p' : 'h' . (int) $attributes['level']; + } + + if ( $attributes['isLink'] ) { + $aria_current = is_home() || ( is_front_page() && 'page' === get_option( 'show_on_front' ) ) ? ' aria-current="page"' : ''; + $link_target = ! empty( $attributes['linkTarget'] ) ? $attributes['linkTarget'] : '_self'; + + $site_title = sprintf( + '%4$s', + esc_url( home_url() ), + esc_attr( $link_target ), + $aria_current, + esc_html( $site_title ) + ); + } + $wrapper_attributes = get_block_wrapper_attributes( array( 'class' => trim( $classes ) ) ); + + return sprintf( + '<%1$s %2$s>%3$s', + $tag_name, + $wrapper_attributes, + // 如果是链接则已预转义 + $attributes['isLink'] ? $site_title : esc_html( $site_title ) + ); +} +``` + +但该区块未定义`save`函数,从其[`index.js`](https://github.com/WordPress/gutenberg/blob/trunk/packages/block-library/src/site-title/index.js)文件可见,这意味着区块在数据库中的标记表示如下: + +```html + +``` + +在前端,`render_callback`会根据请求区块时服务器的具体值动态渲染区块标记。这些值包括当前站点标题、URL、链接目标等。 + +``` +

我的WordPress网站

+``` + +
+
\ No newline at end of file diff --git a/getting-started/glossary.md b/getting-started/glossary.md new file mode 100644 index 0000000..385d484 --- /dev/null +++ b/getting-started/glossary.md @@ -0,0 +1,139 @@ +## 工具栏 + +一组按钮控件。在区块环境中,通常指选中区块上方显示的区块控制工具栏。 + +# 术语表 + +## 属性来源 + +描述区块属性结构的对象。其键名可根据需要自由命名以描述区块类型的状态。每个键对应的值是一个函数,描述从已保存文章内容中提取属性值的策略。处理时会根据属性来源定义的键创建新对象,其中每个值都是属性来源函数的执行结果。 + +## 属性 + +表征文章内容中区块当前状态的对象。加载已保存文章时,该对象由对应区块类型的属性来源决定。在编辑过程中,这些值会随着用户修改区块而产生变化,并在序列化区块时被使用。 + +## 区块 + +用于描述标记单元的抽象术语,这些单元组合后构成网页的内容或布局。该概念将WordPress中通过短代码、自定义HTML和嵌入发现实现的功能整合为统一的API和用户体验。 + +## 区块样式 + +通过样式表或区块标记本身实现的CSS样式。例如,附加在区块标记上的类即被视为区块样式。 + +对比全局样式。与全局样式相对,区块样式有时也被称作局部样式。 + +了解更多关于[区块样式](/docs/explanations/architecture/styles.md#block-styles)的内容。 + +## 区块支持 + +供区块声明支持功能的API。通过声明对某项功能的支持,该API会为区块添加额外的属性,并为大多数现有区块支持功能提供匹配的UI控件。 + +参阅区块支持参考文档深入了解该API。 + +## 区块主题 + +采用区块优先方式构建的主题,支持全站编辑功能。区块主题的核心是其区块模板和区块模板部件。截至目前,区块主题模板是由区块标记组成的HTML文件,对应标准WordPress模板层级中的模板。 + +## 区块分类 + +这些并非WordPress的分类法,而是在区块库中用于内部排序区块的机制。 + +## 区块~插入器~库 + +选择可用区块的主要界面,通过区块上的加号图标按钮或编辑器界面左上角的按钮触发。 + +## 区块名称 + +区块类型的唯一标识符,由插件特定命名空间和描述区块用途的简短标签组成。例如:core/image + +## 区块模板 + +预定义区块排列方式的模板,可能包含预定义属性或占位内容。您可以为文章类型提供模板,为用户创建新内容时提供起点,或在自定义区块中通过InnerBlocks组件使用模板。本质上,模板是由区块标记组成的HTML文件,对应标准WordPress模板层级中的模板(如索引页、单篇文章页或归档页)。这有助于控制未通过页面编辑器或文章编辑器修改的网站前端默认样式。更多信息请参阅模板文档。 + +## 区块模板部件 + +在区块模板基础上构建,这些部件帮助设置可复用项目(如页脚或页眉)的结构,这些结构在WordPress站点中十分常见。它们主要构成站点结构,绝不能与文章内容编辑器混用。通过全站编辑和基于区块的主题,用户可以创建自定义模板部件,将其保存至站点数据库,并在整个站点中重复使用。模板部件在区块体系中相当于主题模板部件,通常由主题首先定义,具有特定语义(可在不同主题间互换,如页眉),且只能在站点编辑器环境中(“模板”内)插入。 + +## 区块类型 + +与构成特定文章的区块不同,区块类型描述了该类区块应有的行为蓝图。因此,虽然一篇文章中可能包含多个图片区块,但每个图片区块都遵循统一的图片区块类型定义。 + +## 经典区块 + +将 TinyMCE 编辑器作为区块嵌入的区块类型。TinyMCE 是旧版核心编辑器的基础。在区块编辑器之前创建的旧内容将被加载到单个经典区块中。 + +## 动态区块 + +这类区块的内容可能发生变化,且在保存文章时无法确定具体内容,而是在文章显示于网站前端时实时计算生成。这类区块在其 JavaScript 实现中可能保存备用内容或不保存任何内容,转而依赖 PHP 区块实现在运行时进行渲染。 + +## 全站编辑 + +指一系列功能的集合,最终允许用户以区块为起点编辑整个网站。该功能集涵盖从区块模式到全局样式,从模板到区块设计工具(及更多)的所有内容。首次发布于 WordPress 5.9。 + +## 全局样式 + +由 WordPress 生成并作为嵌入式样式表排入网站前端的 CSS 样式。该样式表 ID 为 `global-styles-inline-css`。其内容来源于 WordPress 默认的 `theme.json`、主题的 `theme.json` 以及用户通过网站编辑器中全局样式侧边栏提供的样式。 + +详见[theme.json 参考文档](/docs/reference-guides/theme-json-reference/README.md)、[操作指南](/docs/how-to-guides/themes/global-settings-and-styles.md)以及[区块编辑器样式介绍](/docs/explanations/architecture/styles.md)。 + +对比区块样式。 + +## 检查器 + +已弃用术语。参见设置侧边栏。 + +## 本地样式 + +参见区块样式。 + +## 导航区块 + +允许编辑网站导航菜单结构和设计的区块。 + +## 模式 + +模式是预定义的区块布局,可作为初始内容插入,每次插入后都需要用户进行修改。一旦插入,它们将以本地保存形式存在,不具备全局性。 + +## 文章设置 + +包含文章元数据字段的侧边栏区域,包括发布时间安排、可见性、分类项和特色图片。 + +## 查询区块 + +复制经典 WP_Query 功能并支持通过附加功能进行进一步自定义的区块。 + +## 可复用区块 + +保存后可作为可复用内容片段共享的区块。 + +## 富文本 + +支持富内容编辑的通用组件,包括粗体、斜体、超链接等功能。 + +## 序列化 + +将区块属性对象转换为 HTML 标记的过程,每次编辑区块时都会执行此操作。 + +## 设置侧边栏 + +右侧包含文档和区块设置的面板。可通过设置齿轮图标切换侧边栏显示。选中区块时显示区块设置,否则显示文档设置。 + +## 网站编辑器 + +允许直接编辑各种模板、模板部件、样式选项等并可在其间导航的完整操作体验。 + +## 静态区块 + +这类区块的内容在保存文章时即可确定。静态区块的 HTML 标记将直接保存在文章内容中。 + +## 模板编辑模式 + +精简的直接编辑体验,允许编辑/更改/创建文章/页面使用的模板。 + +## 主题区块 + +通过传统模板标签实现所有功能的区块(例如:文章作者区块)。完整列表可在[此处](https://github.com/WordPress/gutenberg/issues/22724)查看。 + +## TinyMCE + +TinyMCE 是一款基于 JavaScript 的网页所见即所得编辑器。 \ No newline at end of file diff --git a/getting-started/quick-start-guide.md b/getting-started/quick-start-guide.md new file mode 100644 index 0000000..6ae9bc7 --- /dev/null +++ b/getting-started/quick-start-guide.md @@ -0,0 +1,54 @@ +# 快速入门指南 + +本指南旨在通过实践操作演示WordPress区块开发的基本原理。按照以下步骤,您将在几分钟内创建一个使用现代JavaScript(ESNext和JSX)的自定义区块插件。该示例区块将显示版权符号(©)和当前年份,是任何网站页脚的完美补充。您可以通过以下短视频演示观看这些步骤的实际操作。 + + + +## 搭建区块插件 + +首先确保您的计算机已安装Node.js和`npm`。如果尚未安装,请查阅[Node.js开发环境](https://developer.wordpress.org/block-editor/getting-started/devenv/nodejs-development/)指南。 + +接下来,使用[`@wordpress/create-block`](https://developer.wordpress.org/block-editor/reference-guides/packages/packages-create-block/)包和[`@wordpress/create-block-tutorial-template`](https://developer.wordpress.org/block-editor/reference-guides/packages/packages-create-block-tutorial-template/)模板来搭建完整的“版权日期区块”插件。 + +
+

您可以使用create-block在任意位置搭建区块,然后在生成的插件文件夹内使用wp-env。这将创建一个已安装并激活新区块插件的本地WordPress开发环境。

+

如果您已有自己的本地WordPress开发环境,请通过终端导航至plugins/文件夹。

+
+ +选择要创建插件的文件夹,然后在该文件夹的终端中执行以下命令: + +```sh +npx @wordpress/create-block copyright-date-block --template @wordpress/create-block-tutorial-template +``` + +提供的`slug`参数(`copyright-date-block`)定义了搭建插件的文件夹名称和内部区块名称。 + +导航至本地WordPress安装的插件页面,激活“版权日期区块”插件。随后该示例区块将在编辑器中可用。 + +## 基本使用 + +激活插件后,您可以探索区块的工作方式。使用以下命令进入新创建的插件文件夹并启动开发过程: + +```sh +cd copyright-date-block && npm start +``` + +当`create-block`搭建区块时,它会安装`wp-scripts`并将最常用的脚本添加到区块的`package.json`文件中。请参阅[wp-scripts入门](https://developer.wordpress.org/block-editor/getting-started/devenv/get-started-with-wp-scripts/)文章了解该包的介绍。 + +`npm start`命令将启动开发服务器并监视区块代码的更改,在修改时重新构建区块。 + +完成更改后,运行`npm run build`命令。这将优化区块代码并使其达到生产就绪状态。 + +## 查看实际效果 + +您可以使用任何本地WordPress开发环境测试新区块,但搭建的插件包含`wp-env`配置。您的计算机必须已安装并运行[Docker](https://www.docker.com/products/docker-desktop),如果满足条件,请运行`npx wp-env start`命令。 + +脚本运行完成后,您可以通过以下地址访问本地环境:http://localhost:8888。使用用户名`admin`和密码`password`登录WordPress仪表板。插件将已安装并激活。打开编辑器或站点编辑器,像插入任何其他区块一样插入版权日期区块。 + +访问[入门指南](https://developer.wordpress.org/block-editor/getting-started/devenv/get-started-with-wp-env/)了解更多关于`wp-env`的信息。 + +## 附加资源 + +- [create-block入门指南](https://developer.wordpress.org/block-editor/getting-started/devenv/get-started-with-create-block/) +- [wp-scripts入门指南](https://developer.wordpress.org/block-editor/getting-started/devenv/get-started-with-wp-scripts/) +- [wp-env入门指南](https://developer.wordpress.org/block-editor/getting-started/devenv/get-started-with-wp-env/) \ No newline at end of file diff --git a/getting-started/tutorial.md b/getting-started/tutorial.md new file mode 100644 index 0000000..5a3725e --- /dev/null +++ b/getting-started/tutorial.md @@ -0,0 +1,1013 @@ +#### 优化 render.php + +最后一步是优化 `render.php` 文件。如果 `currentYear` 属性与 `fallbackCurrentYear` 属性相同,则无需动态生成区块内容。相关内容已保存在数据库中,并可通过 `$content` 变量在 `render.php` 文件中调用。 + +因此,请更新该文件,使其在 `currentYear` 与 `fallbackCurrentYear` 不匹配时渲染生成的内容。 + +```php +$current_year = date( "Y" ); + +// 确定要显示的内容 +if ( isset( $attributes['fallbackCurrentYear'] ) && $attributes['fallbackCurrentYear'] === $current_year ) { + + // 当前年份与备用值相同,直接使用数据库中保存的区块内容(由 save.js 函数生成) + $block_content = $content; +} else { + + // 当前年份与备用值不同,渲染更新后的区块内容 + if ( ! empty( $attributes['startingYear'] ) && ! empty( $attributes['showStartingYear'] ) ) { + $display_date = $attributes['startingYear'] . '–' . $current_year; + } else { + $display_date = $current_year; + } + + $block_content = '

© ' . esc_html( $display_date ) . '

'; +} + +echo wp_kses_post( $block_content ); +``` + +大功告成!现在您已拥有一个同时支持动态与静态渲染的区块。 + +## 收官之笔 + +恭喜您完成本教程并成功构建专属的版权日期区块!通过这段学习旅程,您已掌握 WordPress 区块开发的基础知识,现在可以开始创建自己的区块了。 + +本教程完整代码请参考 GitHub 上的 [区块开发示例库](https://github.com/WordPress/block-development-examples/tree/trunk/plugins/copyright-date-block-09aac3)。 + +若您希望精进技能、挑战更复杂的项目或紧跟 WordPress 最新动态,以下资源将助您提升区块开发能力: + +- [区块开发环境配置](https://developer.wordpress.org/block-editor/getting-started/devenv/) +- [区块开发基础指南](https://developer.wordpress.org/block-editor/getting-started/fundamentals/) +- [WordPress 开发者博客](https://developer.wordpress.org/news/) +- [区块开发示例库](https://github.com/WordPress/block-development-examples) | GitHub 代码库 + +请记住:每位专家都曾是初学者。持续学习、勇于实践,最重要的是,享受使用 WordPress 构建作品的乐趣! + +# 教程:构建你的第一个区块 + +在本教程中,你将构建一个"版权日期区块"——这是一个基础但实用的区块,用于显示版权符号(©)、当前年份以及可选的起始年份。这类内容通常用于网站页脚。 + +本教程将引导你完成从使用 [`create-block`](https://developer.wordpress.org/block-editor/getting-started/devenv/get-started-with-create-block/) 包搭建区块插件脚手架到修改每个文件的完整流程。虽然具备WordPress开发经验会更有帮助,但这不是学习本教程的必要条件。 + +完成本指南后,你将清晰理解区块开发的基础知识,并掌握创建自定义WordPress区块的必要技能。 + +## 你将构建什么 + +快速预览即将构建的内容。 + +![即将构建的内容](https://developer.wordpress.org/files/2023/12/block-tutorial-1.png) + +你也可以在 [WordPress Playground](https://playground.wordpress.net/?blueprint-url=https://raw.githubusercontent.com/WordPress/block-development-examples/trunk/plugins/copyright-date-block-09aac3/_playground/blueprint.json) 中体验完成后的项目,或使用[快速入门指南](https://developer.wordpress.org/block-editor/getting-started/quick-start-guide/)将完整区块插件安装到本地WordPress环境中。 + +## 前置要求 + +完成本教程需要: + +1. 代码编辑器 +2. Node.js开发工具 +3. 本地WordPress环境 + +若缺少其中任何一项,[区块开发环境](https://developer.wordpress.org/block-editor/getting-started/devenv/)文档将帮助你完成配置。准备就绪后请返回继续学习。 + +
+ 本教程使用 wp-env 创建本地WordPress开发环境。当然你也可以使用任何满足上述前置要求的开发环境。 +
+ +## 搭建区块脚手架 + +创建版权日期区块的第一步是使用 [`@wordpress/create-block`](https://developer.wordpress.org/block-editor/reference-guides/packages/packages-create-block/) 包搭建初始区块结构。 + +
+ 建议先阅读create-block入门指南了解该工具包的基本用法。 +
+ +你可以在计算机的任意目录中运行 `create-block`,然后使用 `wp-env` 创建已安装并激活新区块插件的本地WordPress开发环境。 + +因此请选择存放区块插件的目录,也可新建名为"Block Tutorial"的文件夹。打开终端并进入该目录,然后运行以下命令。 + +
+ 若未使用 wp-env,请通过终端进入本地WordPress安装目录的 plugins/ 文件夹再运行以下命令。 +
+ +```bash +npx @wordpress/create-block@latest copyright-date-block --variant=dynamic +cd copyright-date-block +``` + +执行命令后,插件文件夹中会出现名为 `copyright-date-block` 的新目录,其中包含定制区块所需的所有初始文件。 + +此命令同时建立了区块的基础结构,`copyright-date-block` 将作为区块在WordPress中的唯一标识符。 + +
+ 你可能注意到命令中使用了 --variant=dynamic 标志,这表示要搭建动态渲染区块的脚手架。后续章节将介绍动态与静态渲染的区别,并为该区块添加静态渲染功能。 +
+ +前往WordPress后台的插件页面确认插件已激活。接着新建页面或文章,确保可以插入版权日期区块。插入后的效果应如下图所示。 + +![编辑器中的区块脚手架](https://developer.wordpress.org/files/2023/12/block-tutorial-2.png) + +### 更新 render.php 文件 + +虽然区块在编辑器中运行正常,但前端仍会显示默认的区块提示信息。要解决此问题,请打开 [`render.php`](https://developer.wordpress.org/block-editor/getting-started/fundamentals/file-structure-of-a-block/#render-php) 文件,您将看到以下内容: + +```php + +

> + +

+``` + +与编辑器中的 `useBlockProps()` 函数类似,[`get_block_wrapper_attributes()`](https://developer.wordpress.org/reference/functions/get_block_wrapper_attributes/) 会在[区块包装元素](https://developer.wordpress.org/block-editor/getting-started/fundamentals/block-wrapper/#the-server-side-render-markup)中输出所有必要的 CSS 类和样式。只需更新内容部分即可。 + +您可以使用 `date( "Y" )` 在 PHP 中获取当前年份,修改后的 `render.php` 文件应如下所示: + +```php + +

+``` + +保存文件后,请确认区块在编辑器和前端均能正确显示。 + +### 清理文件 + +当使用 `create-block` 工具包搭建区块时,可能会包含一些非必需文件。在本教程中,该区块并未使用样式表或前端 JavaScript。请通过以下操作清理插件的 `src/` 目录: + +1. 在 `edit.js` 文件中移除导入 `editor.scss` 的代码行 +2. 在 `index.js` 文件中移除导入 `style.scss` 的代码行 +3. 删除 editor.scss、style.scss 和 view.js 文件 + +最后请确保所有修改均已保存,然后终止 `npm run start` 命令。运行 `npm run build` 以优化代码并完成生产环境准备。 + +至此您已构建了一个功能完整的 WordPress 区块,但我们的工作尚未结束。后续章节将继续添加功能并启用静态渲染。 + +## 添加区块属性 + +当前构建的版权日期区块仅显示当前年份,但如果需要同时显示起始年份该如何实现? + +![即将构建的功能效果](https://developer.wordpress.org/files/2023/12/block-tutorial-1.png) + +此功能需要用户在区块的某个位置输入起始年份,并且还应具备启用/禁用该功能的开关。 + +实现方式可以多样化,但都需要使用[区块属性](https://developer.wordpress.org/block-editor/reference-guides/block-api/block-attributes/)。属性允许您为区块存储自定义数据,这些数据随后可用于修改区块的标记。 + +要实现起始年份功能,您需要定义一个属性存储起始年份,另一个属性用于控制是否显示起始年份。 + +### 更新 block.json 文件 + +区块属性通常在 [`block.json`](https://developer.wordpress.org/block-editor/getting-started/fundamentals/block-json/#data-storage-in-the-block-with-attributes) 文件中定义。请打开该文件,在 `example` 属性后添加以下配置段: + +```json +"example": {}, +"attributes": { + "showStartingYear": { + "type": "boolean" + }, + "startingYear": { + "type": "string" + } +}, +``` + +定义属性时必须指明 `type` 类型。本例中 `showStartingYear` 需存储布尔值,因此设为 `boolean` 类型;`startingYear` 则设为字符串类型。 + +保存文件后,即可开始编辑器的调整工作。 + +### 更新 edit.js 文件 + +打开 `edit.js` 文件,您需要完成两项任务: + +- 添加用户界面控件,允许用户输入起始年份、切换功能开关,并将这些设置存储为属性 +- 根据已定义的属性更新区块显示内容 + +### 更新 render.php 文件 + +虽然编辑器看起来很棒,但起始年份功能尚未添加到前端。让我们通过更新 `render.php` 文件来解决这个问题。 + +首先,添加一个名为 `$display_date` 的变量,并复制你在上面的 `Edit()` 函数中所做的操作。 + +这个变量应该显示 `startingYear` 属性和 `$current_year` 变量的值,用破折号分隔;如果 `showStartingYear` 属性为 `false`,则仅显示 `$current_year`。 + +
+

render.php 中暴露了三个变量,你可以使用它们来自定义块的输出:

+
    +
  • $attributes(数组):块的属性。
  • +
  • $content(字符串):块的默认内容。
  • +
  • $block(WP_Block):块的实例。
  • +
+
+ +代码应该看起来像这样。 + +```php +if ( ! empty( $attributes['startingYear'] ) && ! empty( $attributes['showStartingYear'] ) ) { + $display_date = $attributes['startingYear'] . '–' . $current_year; +} else { + $display_date = $current_year; +} +``` + +接下来,你只需要更新块内容,使用 `$display_date` 而不是 `$current_year` 变量。 + +你最终的 `render.php` 文件应该如下所示。 + +```php + +

> + © +

+``` + +保存文件,并确认正确的块内容现在出现在你网站的前端。 + +你现在已经成功构建了一个动态渲染的自定义块,它利用了块支持、WordPress 核心组件和自定义属性。在许多情况下,对于一个显示版权日期并具有一些额外功能的块来说,这已经足够了。 + +然而,在下一节中,你将向块添加静态渲染。这个练习将说明块数据是如何存储在 WordPress 中的,并在此插件意外禁用时提供回退方案。 + +## 添加静态渲染 + +一个块可以利用动态渲染、静态渲染,或者两者兼用。到目前为止,你构建的块是动态渲染的。它的块标记和关联的属性存储在数据库中,但其 HTML 输出并未存储。 + +静态渲染的块将始终将块标记、属性和输出存储在数据库中。块还可以在数据库中存储静态输出,同时在前端动态进一步增强,这是两种方法的结合。 + +如果你在编辑器中切换到代码编辑器,你将看到以下内容。 + +```html + +``` + +将其与静态渲染的块(如段落块)进行比较。 + +```html + +

这是一个测试。

+ +``` + +段落的 HTML 存储在文章内容中,并保存在数据库中。 + +你可以在[基础文档](https://developer.wordpress.org/block-editor/getting-started/fundamentals/static-dynamic-rendering/)中了解更多关于动态和静态渲染的内容。虽然大多数块要么是动态渲染,要么是静态渲染,但你可以构建一个同时使用两种方法的块。 + +### 为什么要添加静态渲染? + +当你向动态渲染的块添加静态渲染时,`render.php` 文件仍将控制前端的输出,但块的 HTML 内容将被保存在数据库中。这意味着,即使插件从网站中移除,内容仍然会保留。对于这个版权日期块,内容将恢复为一个自定义 HTML 块,你可以轻松地将其转换为段落块。 + +![当块类型不再存在时,编辑器中的错误消息](https://developer.wordpress.org/files/2023/12/block-tutorial-14.png) + +虽然并非在所有情况下都需要,但向动态渲染的块添加静态渲染可以在插件意外禁用时提供有用的回退方案。 + +此外,考虑一种情况,块标记包含在块模式或主题模板中。如果用户安装该主题或使用该模式时未安装版权日期块,他们将收到一个块不可用的通知,但内容仍将显示。 + +添加静态渲染也是探索块内容在 WordPress 中如何存储和渲染的好方法。 + +## 文件结构审阅 +在开始修改脚手架生成的区块之前,有必要先了解插件的文件结构。请用代码编辑器打开插件文件夹。 + +![构成区块插件的文件](https://developer.wordpress.org/files/2023/12/block-tutorial-3.png) + +接下来查阅[区块文件结构](https://developer.wordpress.org/block-editor/getting-started/fundamentals/file-structure-of-a-block/)文档,详细了解每个文件的功能。如果现在觉得内容太多不必担心,本教程将逐步讲解每个文件的使用方法。 + +
+ 由于您创建的是动态区块,因此不会看到save.js文件。后续教程中将会把这个文件添加到插件以实现静态渲染,敬请关注。 +
+ +## 初始设置 + +让我们从创建最简单的版权日期区块开始,这将是一个动态渲染的区块,仅显示版权符号(©)和当前年份。我们还将添加若干控件,允许用户修改字体大小和文本颜色。 + +在执行后续步骤前,请在插件目录的终端中运行 `npm run start` 命令。该命令将监听 `/src` 文件夹中每个文件的变更。每次保存文件时,区块的构建文件都会自动更新。 + +请参阅[区块编辑器的JavaScript开发指南](https://developer.wordpress.org/block-editor/getting-started/fundamentals/javascript-in-the-block-editor/)文档以了解更多信息。 + +### 更新block.json + +打开 `/src` 文件夹中的 `block.json` 文件。 + +```json +{ + "$schema": "https://schemas.wp.org/trunk/block.json", + "apiVersion": 3, + "name": "create-block/copyright-date-block", + "version": "0.1.0", + "title": "Copyright Date Block", + "category": "widgets", + "icon": "smiley", + "description": "Example block scaffolded with Create Block tool.", + "example": {}, + "supports": { + "html": false + }, + "textdomain": "copyright-date-block", + "editorScript": "file:./index.js", + "editorStyle": "file:./index.css", + "style": "file:./style-index.css", + "render": "file:./render.php", + "viewScript": "file:./view.js" +} +``` + +
+ 建议查阅block.json文档了解此文件的基本信息。 +
+ +虽然脚手架工具已生成此文件,但需要根据版权日期区块的需求进行一些调整。 + +#### 修改区块标识 + +首先移除图标并添加更合适的描述(后续将添加自定义图标): +1. 删除 `icon` 这一行 +2. 将描述更新为“显示您网站的版权日期” +3. 保存文件 + +刷新编辑器后,您会看到区块不再显示笑脸图标,且描述已更新。 + +![更新信息后的编辑器区块显示](https://developer.wordpress.org/files/2023/12/block-tutorial-4.png) + +#### 添加区块支持功能 + +接下来添加若干[区块支持功能](https://developer.wordpress.org/block-editor/reference-guides/block-api/block-supports/),以便用户控制区块的字体大小和文本颜色。 + +
+ 在构建自定义功能前,应优先使用原生区块支持功能。这种方式能为用户提供跨区块的一致编辑体验,且仅需少量代码即可让区块获得核心功能支持。 +
+ +将 `block.json` 文件中的 [`supports`](https://developer.wordpress.org/block-editor/getting-started/fundamentals/block-json/#enable-ui-settings-panels-for-the-block-with-supports) 部分更新为: + +```json +"supports": { + "color": { + "background": false, + "text": true + }, + "html": false, + "typography": { + "fontSize": true + } +}, +``` + +请注意当通过 `"text": true` 启用文本颜色支持时,背景颜色功能默认也会启用。您可以保留此设置,但本教程不需要该功能,因此可手动设置为 `"background": false`。 + +保存文件后在编辑器中选择区块,您将在设置面板中看到颜色和版式面板。尝试修改这些设置并观察效果。 + +![具备区块支持功能的编辑器区块显示](https://developer.wordpress.org/files/2023/12/block-tutorial-5.png) + +### 添加保存功能 + +首先在 `src/` 文件夹中新建名为 `save.js` 的文件,并添加以下内容: + +```js +import { useBlockProps } from '@wordpress/block-editor'; + +export default function save() { + return ( +

+ { 'Copyright Date Block – hello from the saved content!' } +

+ ); +} +``` + +这段代码与原有的 `edit.js` 文件结构相似,更多细节可参考[区块包装器](https://developer.wordpress.org/block-editor/getting-started/fundamentals/block-wrapper/#the-save-components-markup)文档。 + +接着在 `index.js` 文件中导入这个 `save()` 函数,并为 `registerBlockType()` 函数添加 save 属性。以下是更新后文件的简化视图: + +```js +import save from './save'; + +... + +registerBlockType( metadata.name, { + icon: calendarIcon, + edit: Edit, + save +} ); +``` + +
+ 在定义对象属性时,若属性名与变量名相同,可使用属性简写语法。这就是为什么上面的代码使用 save 而非 save: save。 +
+ +保存 `save.js` 和 `index.js` 文件后刷新编辑器,你将看到如下界面: + +![编辑器中显示的区块验证错误信息](https://developer.wordpress.org/files/2023/12/block-tutorial-15.png) + +不必担心,这个错误是预期内的。打开浏览器检查器,你会看到以下信息: + +![控制台显示的区块验证错误信息](https://developer.wordpress.org/files/2023/12/block-tutorial-16.png) + +出现区块验证错误是因为 `save()` 函数返回了区块内容,但由于之前保存的是动态区块,区块标记中并未存储HTML。当前标记结构如下所示: + +```html + +``` + +在后续步骤中更新 `save()` 函数时,你会多次遇到这类错误。只需点击“尝试恢复区块”并刷新页面即可。 + +完成区块恢复后,打开代码编辑器会看到标记已更新为: + +```html + + + +``` + +使用静态渲染构建区块时经常会遇到验证错误,这属于正常现象。`save()` 函数的输出必须与文章内容中的HTML完全匹配,随着功能添加可能会出现不同步。只要在完整构建区块后没有验证错误,就表示大功告成。 + +### 更新 save.js + +接下来更新 `save()` 函数的输出以显示正确内容。首先采用与 `edit.js` 相同的实现方式: + +1. 为函数添加 `attributes` 参数 +2. 定义 `showStartingYear` 和 `startingYear` 变量 +3. 定义 `currentYear` 变量 +4. 根据 `currentYear`、`showStartingYear` 和 `startingYear` 的值定义 `displayDate` 变量 + +最终代码应如下所示: + +```js +export default function save( { attributes } ) { + const { showStartingYear, startingYear } = attributes; + const currentYear = new Date().getFullYear().toString(); + + let displayDate; + + if ( showStartingYear && startingYear ) { + displayDate = startingYear + '–' + currentYear; + } else { + displayDate = currentYear; + } + + return ( +

© { displayDate }

+ ); +} +``` + +保存文件并刷新编辑器,点击“尝试恢复区块”后更新页面。在代码编辑器中查看,区块标记现在应显示为: + +```html + + + +``` + +至此看似已完成所有工作——区块内容已以HTML形式存入数据库,前端输出也能动态渲染。但仍有几个问题需要解决。 + +试想这种情况:用户在2023年将区块添加到页面,然后在2024年重新编辑该页面。前端显示会正常更新,但在编辑器中会出现区块验证错误。因为 `save()` 函数识别当前是2024年,而数据库中存储的区块内容仍显示2023年。 + +我们将在下一节解决这个问题。 + +##### 切换控件 + +接下来,我们添加一个用于开启或关闭起始年份功能的切换开关。您可以通过设置 `showStartingYear` 属性的 `ToggleControl` 组件实现。该组件应包含: + +1. 设置为“显示起始年份”的 `label` 属性 +2. 设置为 `showStartingYear` 属性的 `checked` 属性 +3. 在切换开关被勾选或取消时更新 `showStartingYear` 属性的 `onChange` 属性 + +您还可以更新“起始年份”文本输入框,使其仅在 `showStartingYear` 为 `true` 时显示,这可以通过 `&&` 逻辑运算符实现。 + +`Edit()` 函数应如下所示。 + +```js +export default function Edit( { attributes, setAttributes } ) { + const { showStartingYear, startingYear } = attributes; + const currentYear = new Date().getFullYear().toString(); + + return ( + <> + + + + setAttributes( { + showStartingYear: ! showStartingYear, + } ) + } + /> + { showStartingYear && ( + + setAttributes( { startingYear: value } ) + } + /> + ) } + + +

© { currentYear }

+ + ); +} +``` + +保存文件并刷新编辑器。确认点击切换开关会显示文本输入框,并且在更新页面时切换开关保持激活状态。 + +![在设置侧边栏中编辑新的“显示起始年份”切换开关的实时预览](https://developer.wordpress.org/files/2023/12/block-tutorial-12.gif) + +#### 更新区块内容 + +到目前为止,您已经创建了用于添加起始年份并更新相关区块属性的用户界面。现在需要实际更新编辑器中的区块内容。 + +我们创建一个名为 `displayDate` 的新变量。当 `showStartingYear` 为 `true` 且用户提供了 `startingYear` 时,`displayDate` 应包含由破折号连接的 `startingYear` 和 `currentYear`。否则,仅显示 `currentYear`。 + +代码应如下所示。 + +```js +let displayDate; + +if ( showStartingYear && startingYear ) { + displayDate = startingYear + '–' + currentYear; +} else { + displayDate = currentYear; +} +``` + +
+ 当您使用 let 声明变量时,表示该变量后续可能会被重新赋值。使用 const 声明变量则表示该变量永远不会改变。您也可以使用 const 重写此代码,这仅是个人偏好问题。 +
+ +接下来,您只需更新区块内容,使用 `displayDate` 替代 `currentYear` 变量。 + +`Edit()` 函数应如下所示。 + +```js +export default function Edit( { attributes, setAttributes } ) { + const { showStartingYear, startingYear } = attributes; + const currentYear = new Date().getFullYear().toString(); + + let displayDate; + + if ( showStartingYear && startingYear ) { + displayDate = startingYear + '–' + currentYear; + } else { + displayDate = currentYear; + } + + return ( + <> + + + + setAttributes( { + showStartingYear: ! showStartingYear, + } ) + } + /> + { showStartingYear && ( + + setAttributes( { startingYear: value } ) + } + /> + ) } + + +

© { displayDate }

+ + ); +} +``` + +保存文件并刷新编辑器。确认当您在设置面板中进行更改时,区块内容会正确更新。 + +![设置侧边栏中新字段更新区块内容的实时预览](https://developer.wordpress.org/files/2023/12/block-tutorial-13.gif) + +### 处理静态渲染区块中的动态内容 + +通常来说,您需要避免在静态渲染区块中使用动态内容。这也是为什么在提及动态渲染时会使用"动态"这个术语的原因之一。 + +不过在本教程中,您将结合使用两种渲染方法,只需要添加少量代码就能在年份变更时避免区块验证错误。 + +问题的根源在于`currentYear`变量是在`save()`函数中动态设置的。实际上,这应该是函数内的静态变量,可以通过添加属性来解决这个问题。 + +#### 添加新属性 + +打开`block.json`文件,添加一个名为`fallbackCurrentYear`、类型为`string`的新属性。此时文件的`attributes`部分应如下所示: + +```json +"attributes": { + "fallbackCurrentYear": { + "type": "string" + }, + "showStartingYear": { + "type": "boolean" + }, + "startingYear": { + "type": "string" + } +}, +``` + +接下来打开`save.js`文件,使用新的`fallbackCurrentYear`属性替代`currentYear`。更新后的`save()`函数应如下所示: + +```js +export default function save( { attributes } ) { + const { fallbackCurrentYear, showStartingYear, startingYear } = attributes; + + let displayDate; + + if ( showStartingYear && startingYear ) { + displayDate = startingYear + '–' + fallbackCurrentYear; + } else { + displayDate = fallbackCurrentYear; + } + + return ( +

© { displayDate }

+ ); +} +``` + +现在,如果`fallbackCurrentYear`未定义会发生什么? + +之前`currentYear`是在函数内定义的,因此即使`showStartingYear`和`startingYear`未定义,`save()`函数也始终有内容可以返回。 + +与其只返回版权符号,不如添加一个条件判断:如果`fallbackCurrentYear`未设置,则返回`null`。通常来说,在数据库中保存空内容比保存不完整的数据更好。 + +最终的`save()`函数应如下所示: + +```js +export default function save( { attributes } ) { + const { fallbackCurrentYear, showStartingYear, startingYear } = attributes; + + if ( ! fallbackCurrentYear ) { + return null; + } + + let displayDate; + + if ( showStartingYear && startingYear ) { + displayDate = startingYear + '–' + fallbackCurrentYear; + } else { + displayDate = fallbackCurrentYear; + } + + return ( +

© { displayDate }

+ ); +} +``` + +保存`block.json`和`save.js`文件,您无需再进行其他修改。 + +#### 在edit.js中设置属性 + +既然`save()`函数现在使用新的`fallbackCurrentYear`,就需要在某个地方设置这个值。让我们使用`Edit()`函数来实现。 + +打开`edit.js`文件,首先在`Edit()`函数顶部与其他属性一起定义`fallbackCurrentYear`变量。接着检查函数中的现有逻辑。 + +当区块在编辑器中加载时,会定义`currentYear`变量。函数随后使用该变量来设置区块内容。 + +现在,让我们在区块加载时,如果`fallbackCurrentYear`属性尚未设置,就将其设置为`currentYear`的值: + +```js +if ( currentYear !== fallbackCurrentYear ) { + setAttributes( { fallbackCurrentYear: currentYear } ); +} +``` + +这样虽然可行,但可以通过确保此代码仅在区块初始化时运行一次来进行优化。为此,您可以使用React的[`useEffect`](https://react.dev/reference/react/useEffect)钩子。有关如何使用此钩子的更多信息,请参阅React文档。 + +首先通过以下代码导入`useEffect`: + +```js +import { useEffect } from 'react'; +``` + +然后将上面的`setAttribute()`代码包装在`useEffect`中,并将这段代码放在`Edit()`函数的`currentYear`定义之后。最终结果应如下所示: + +```js +export default function Edit( { attributes, setAttributes } ) { + const { fallbackCurrentYear, showStartingYear, startingYear } = attributes; + + // 获取当前年份并确保其为字符串类型 + const currentYear = new Date().getFullYear().toString(); + + // 当区块加载时,如果fallbackCurrentYear尚未设置, + // 则将其属性设置为当前年份 + useEffect( () => { + if ( currentYear !== fallbackCurrentYear ) { + setAttributes( { fallbackCurrentYear: currentYear } ); + } + }, [ currentYear, fallbackCurrentYear, setAttributes ] ); + + ... +``` + +当区块在编辑器中初始化时,`fallbackCurrentYear`属性将立即被设置。这个值随后可供`save()`函数使用,正确的区块内容将显示且不会出现区块验证错误。 + +唯一需要注意的是年份变更的情况。如果版权日期区块在2023年添加到页面,然后在2024年进行编辑,`fallbackCurrentYear`属性将不再等于`currentYear`,该属性将自动更新为`2024`。这将更新`save()`函数返回的HTML。 + +虽然您不会遇到任何区块验证错误,但编辑器会检测到页面已发生更改,并提示您进行更新。 + +#### 添加用户界面 + +在本教程的前面部分,您添加了区块支持功能,这些功能会在区块的设置侧边栏中自动创建颜色和排版面板。您可以使用 `InspectorControls` 组件创建自定义面板。 + +##### 检查器控件 + +`InspectorControls` 属于 [`@wordpress/block-editor`](https://developer.wordpress.org/block-editor/reference-guides/packages/packages-block-editor/) 包,因此您可以通过在第 14 行添加组件名称将其导入到 `edit.js` 文件中。结果应如下所示: + +```js +import { InspectorControls, useBlockProps } from '@wordpress/block-editor'; +``` + +接下来,更新 Edit 函数,使其返回当前区块内容和一个包含“测试”文本的 `InspectorControls` 组件。您可以将所有内容包装在 [Fragment](https://react.dev/reference/react/Fragment) (`<>`) 中,以确保 JSX 语法正确。结果应如下所示: + +```js +export default function Edit() { + const currentYear = new Date().getFullYear().toString(); + + return ( + <> + + 测试 + +

© { currentYear }

+ + ); +} +``` + +保存文件并刷新编辑器。选择区块时,您应在设置侧边栏中看到“测试”消息。 + +![设置侧边栏现在显示消息](https://developer.wordpress.org/files/2023/12/block-tutorial-9.png) + +##### 组件和面板 + +现在,让我们使用更多核心组件来添加自定义面板和起始年份功能的用户界面。您需要从 [`@wordpress/components`](https://developer.wordpress.org/block-editor/reference-guides/packages/packages-components/) 包中导入 [`PanelBody`](https://developer.wordpress.org/block-editor/reference-guides/components/panel/#panelbody)、[`TextControl`](https://developer.wordpress.org/block-editor/reference-guides/components/text-control/) 和 [`ToggleControl`](https://developer.wordpress.org/block-editor/reference-guides/components/toggle-control/)。 + +在 `edit.js` 文件的其他导入下方添加以下行: + +```js +import { PanelBody, TextControl, ToggleControl } from '@wordpress/components'; +``` + +然后将“测试”消息包装在 `PanelBody` 组件中,并将 `title` 参数设置为“设置”。有关其他参数选项,请参阅[组件文档](https://developer.wordpress.org/block-editor/reference-guides/components/panel/#panelbody)。 + +```js +export default function Edit() { + const currentYear = new Date().getFullYear().toString(); + + return ( + <> + + + 测试 + + +

© { currentYear }

+ + ); +} +``` + +保存文件并刷新编辑器。您现在应该可以看到新的设置面板。 + +![设置侧边栏现在显示自定义面板](https://developer.wordpress.org/files/2023/12/block-tutorial-10.png) + +##### 文本控件 + +下一步是将“测试”消息替换为 `TextControl` 组件,该组件允许用户设置 `startingYear` 属性。但在执行此操作之前,您必须在 `Edit()` 函数中包含两个参数: + +- `attributes` 是一个包含区块所有属性的对象。 +- `setAttributes` 是一个允许您更新属性值的函数。 + +包含这些参数后,您可以获取 `showStartingYear` 和 `startingYear` 属性。 + +更新 `Edit()` 函数的开头部分,使其如下所示: + +```js +export default function Edit( { attributes, setAttributes } ) { + const { showStartingYear, startingYear } = attributes; + ... +``` + +
+ 要查看与版权日期区块关联的所有属性,请在 Edit() 函数的开头添加 console.log( attributes );。这在构建和测试自定义区块时非常有用。 +
+ +现在,您可以删除“测试”消息并添加 `TextControl`。它应包括: + +1. 一个设置为“起始年份”的 `label` 属性。 +2. 一个设置为属性 `startingYear` 的 `value` 属性。 +3. 一个 `onChange` 属性,用于在值更改时更新 `startingYear` 属性。 + +将所有内容整合后,`Edit()` 函数应如下所示: + +```js +export default function Edit( { attributes, setAttributes } ) { + const { showStartingYear, startingYear } = attributes; + const currentYear = new Date().getFullYear().toString(); + + return ( + <> + + + + setAttributes( { startingYear: value } ) + } + /> + + +

© { currentYear }

+ + ); +} +``` + +
+ 您可能已经注意到 value 属性的值为 startingYear || ''。符号 || 称为 逻辑或(逻辑析取)运算符。这可以防止当 startingYear 为空时在 React 中出现警告。详情请参阅 受控和非受控组件。 +
+ +保存文件并刷新编辑器。确认设置面板中现在存在一个文本字段。添加一个起始年份,并确认在更新页面时该值已保存。 + +![在设置侧边栏中编辑新的起始年份字段的实时预览](https://developer.wordpress.org/files/2023/12/block-tutorial-11.gif) + +#### 移除不必要的代码 + +为简化操作,版权日期区块的样式将完全由颜色与排版区块支持功能控制。该区块也不包含任何前端JavaScript代码。因此,您无需在`block.json`文件中指定样式表或`viewScript`参数。 + +1. 删除`editorStyle`配置行 +2. 删除`style`配置行 +3. 删除`viewScript`配置行 +4. 保存文件 + +刷新编辑器后,您将看到区块样式已与当前主题保持一致。 + +![编辑器中去掉默认样式的区块效果](https://developer.wordpress.org/files/2023/12/block-tutorial-6.png) + +#### 完整配置汇总 + +最终完成的`block.json`文件应如下所示: + +```json +{ + "$schema": "https://schemas.wp.org/trunk/block.json", + "apiVersion": 3, + "name": "create-block/copyright-date-block", + "version": "0.1.0", + "title": "版权日期区块", + "category": "widgets", + "description": "显示站点版权日期", + "example": {}, + "supports": { + "color": { + "background": false, + "text": true + }, + "html": false, + "typography": { + "fontSize": true + } + }, + "textdomain": "copyright-date-block", + "editorScript": "file:./index.js", + "render": "file:./render.php" +} +``` + +### 更新index.js文件 + +在开始构建区块功能之前,我们先进行清理工作并为区块添加自定义图标。 + +打开[`index.js`](https://developer.wordpress.org/block-editor/getting-started/fundamentals/file-structure-of-a-block/#index-js)文件,这是区块的主JavaScript文件,用于在客户端注册区块。您可以通过[区块注册文档](https://developer.wordpress.org/block-editor/getting-started/fundamentals/registration-of-a-block/)深入了解客户端与服务端注册机制。 + +首先查看[`registerBlockType`](https://developer.wordpress.org/block-editor/reference-guides/block-api/block-registration/)函数。该函数接收从导入的`block.json`文件中获取的区块名称,以及区块配置对象。 + +```js +import Edit from './edit'; +import metadata from './block.json'; + +registerBlockType( metadata.name, { + edit: Edit, +} ); +``` + +默认配置对象仅包含`edit`属性,但您还可以添加包括`icon`在内的更多属性。虽然大部分属性已在`block.json`中定义,但若需使用自定义SVG图标,则需要在此处专门指定。 + +#### 添加自定义图标 + +从[Gutenberg组件库](https://wordpress.github.io/gutenberg/?path=/story/icons-icon--library)获取日历图标,按如下方式将SVG代码添加到函数中: + +```js +const calendarIcon = ( + +); + +registerBlockType( metadata.name, { + icon: calendarIcon, + edit: Edit +} ); +``` + +
+ 所有区块图标尺寸应为24x24像素。请注意上方代码中的viewBox参数。 +
+ +保存`index.js`文件并刷新编辑器,您将看到默认图标已替换为日历图标。 + +![编辑器中使用自定义图标的区块](https://developer.wordpress.org/files/2023/12/block-tutorial-7.png) + +至此,区块的图标与描述信息已正确配置,区块支持功能也允许您调整字体大小和文本颜色。接下来将进入区块实际功能的开发阶段。 + +### 更新edit.js文件 + +[`edit.js`](https://developer.wordpress.org/block-editor/getting-started/fundamentals/file-structure-of-a-block/#edit-js)文件控制着区块在编辑器中的功能与显示效果。当前用户会看到“Copyright Date Block – hello from the editor!”提示信息,现在我们来修改这一内容。 + +打开文件可见`Edit()`函数返回包含默认信息的段落标签: + +```js +export default function Edit() { + return ( +

+ { __( + 'Copyright Date Block – hello from the editor!', + 'copyright-date-block-demo' + ) } +

+ ); +} +``` + +其实际逻辑比表面看起来更简洁: + +- [`useBlockProps()`](https://developer.wordpress.org/block-editor/reference-guides/block-api/block-edit-save/#block-wrapper-props)用于输出编辑器所需的[区块包装器](https://developer.wordpress.org/block-editor/getting-started/fundamentals/block-wrapper/#the-edit-components-markup)中的所有CSS类与样式,包含先前添加的区块支持功能提供的样式 +- [`__()`](https://developer.wordpress.org/block-editor/reference-guides/packages/packages-i18n/)用于实现文本字符串的国际化 + +
+ 建议查阅区块包装器文档,了解如何确保区块标记包装器具备正确属性的入门指南。 +
+ +需要明确的是,本区块的核心功能是显示版权符号(©)与当前年份。首先需要以字符串格式获取当前年份,可通过以下代码实现: + +```js +const currentYear = new Date().getFullYear().toString(); +``` + +接着更新函数以显示正确信息: + +```js +export default function Edit() { + const currentYear = new Date().getFullYear().toString(); + + return ( +

© { currentYear }

+ ); +} +``` + +保存`edit.js`文件并刷新编辑器,您现在将看到版权符号(©)后接当前年份的显示效果。 + +![编辑器中正确显示内容的区块](https://developer.wordpress.org/files/2023/12/block-tutorial-8.png) \ No newline at end of file diff --git a/how-to-guides/README.md b/how-to-guides/README.md new file mode 100644 index 0000000..da3716f --- /dev/null +++ b/how-to-guides/README.md @@ -0,0 +1,45 @@ +# 操作指南 + +与WordPress大多数功能一样,新版编辑器具有高度灵活性。您可以创建自定义区块、修改编辑器外观、添加特殊插件等众多功能。 + +## 创建区块 + +编辑器的核心是区块,主要扩展接口是区块API。通过该接口您可以创建静态区块、[动态区块](/docs/how-to-guides/block-tutorial/creating-dynamic-blocks.md)(在服务器端渲染),以及能够将数据保存至文章元数据的区块,从而实现更具结构化的内容呈现。 + +若想深入了解区块创建,请参阅[创建区块教程](/docs/getting-started/devenv/get-started-with-create-block.md),这是最佳入门指南。 + +## 扩展区块 + +通过过滤器可以修改现有区块的行为,甚至完全移除区块。 + +更多内容请参阅[区块过滤器](/docs/reference-guides/filters/block-filters.md)章节。 + +特别针对「查询循环」区块,除了现有过滤器外,还有更多扩展方式和定制版本创建方法。详见[扩展查询循环区块](/docs/how-to-guides/block-tutorial/extending-the-query-loop-block.md)章节。 + +## 扩展编辑器界面 + +通过`registerPlugin`接口可扩展编辑器界面,允许您集中定义所有插件界面元素。 + +更多信息请参考[插件](/packages/plugins/README.md)和[编辑文章](/packages/edit-post/README.md)章节。 + +您还可以对编辑器的特定功能进行过滤,相关文档请参阅[编辑器过滤器](/docs/reference-guides/filters/editor-filters.md)页面。 + +## 元数据框 + +强烈建议将PHP元数据框迁移至区块或侧边栏插件,具体方法请参阅[元数据框](/docs/how-to-guides/metabox.md)与[侧边栏插件](/docs/how-to-guides/plugin-sidebar-0.md)指南。 + +## 主题支持 + +默认情况下,区块会提供样式以确保在未修改的主题中正常显示。主题可以添加/覆盖这些样式,或直接使用默认样式。 + +部分高级区块功能需要主题主动声明支持。请参阅[主题支持](/docs/how-to-guides/themes/theme-support.md)与[全局样式过滤指南](/docs/reference-guides/filters/global-styles-filters.md)。 + +## 自动补全 + +区块内的自动补全功能可进行扩展和重写。了解更多关于[自动补全](/docs/reference-guides/filters/autocomplete-filters.md)过滤器的信息。 + +## 区块解析与序列化 + +编辑器中的文章在存储至`post_content`与呈现在编辑器之间会经历多个处理阶段。由于区块本身是内存中的数据结构,需要通过解析与序列化步骤实现与数据库存储格式的相互转换。 + +自定义解析器属于高级主题,您可以在[扩展解析器](/docs/reference-guides/filters/parser-filters.md)章节深入了解。 \ No newline at end of file diff --git a/how-to-guides/accessibility.md b/how-to-guides/accessibility.md new file mode 100644 index 0000000..6eede4b --- /dev/null +++ b/how-to-guides/accessibility.md @@ -0,0 +1,16 @@ +# 可访问性 + +为参与古腾堡项目开发的工程师提供的无障碍功能文档。 + +有关WordPress无障碍功能的更多信息,请参阅[WordPress无障碍功能手册](https://make.wordpress.org/accessibility/handbook/)及[无障碍功能团队专区](https://make.wordpress.org/accessibility/)。 + +## 地标区域 + +最佳实践是将页面所有内容都包含在地标区域内,这样依赖地标区域在版块间导航的屏幕阅读器用户就不会遗漏内容。 + +关于设置不同区域间的导航功能,请参阅[navigateRegions组件包](/packages/components/src/higher-order/navigate-regions/README.md)获取详细文档。 + +更多关于地标设计的W3C标准: +- [地标设计通用原则](https://www.w3.org/WAI/ARIA/apg/practices/landmark-regions/#x4-2-general-principles-of-landmark-design) +- [ARIA地标区域范例](https://www.w3.org/WAI/ARIA/apg/example-index/landmarks/) +- [默认定义ARIA地标的HTML5元素](https://www.w3.org/WAI/ARIA/apg/practices/landmark-regions/#x4-1-html-sectioning-elements) \ No newline at end of file diff --git a/how-to-guides/block-tutorial/README.md b/how-to-guides/block-tutorial/README.md new file mode 100644 index 0000000..e9fb8dc --- /dev/null +++ b/how-to-guides/block-tutorial/README.md @@ -0,0 +1,11 @@ +# 区块 + +本教程旨在逐步讲解创建新区块类型的基础知识。从最简单的示例开始,每个新章节将逐步扩展内容,涵盖您在实现自己的区块类型时可能需要使用的常见功能。 + +要跟随本教程学习,您可以下载[配套的 WordPress 插件](https://github.com/WordPress/block-development-examples),其中包含所有示例供您在自己的站点上尝试。在每个步骤中,可以通过修改示例来实践自己的想法,并观察它们对区块行为产生的影响。 + +> 要获取最新版本的 .zip 文件,请前往代码库的[发布页面](https://github.com/WordPress/block-development-examples/releases),在最新发布的「资源」部分查找。 + +代码片段提供两种格式:「JSX」和「原生」。JSX 指使用 JSX 语法的 JavaScript 代码,需要构建步骤。原生指无需构建的「经典」JavaScript。您可以通过每个代码示例上方的选项卡切换这两种格式。使用 JSX 需要您运行 [JavaScript 构建步骤](/docs/how-to-guides/javascript/js-build-setup/),将代码编译为浏览器兼容的格式。 + +请注意,创建区块或扩展编辑器并不强制要求使用 JSX,您可以使用经典 JavaScript。然而,一旦熟悉了 JSX 和构建步骤,许多开发者往往会发现它更易于阅读和编写,因此您看到的大多数代码示例都使用 JSX 语法。 \ No newline at end of file diff --git a/how-to-guides/block-tutorial/applying-styles-with-stylesheets.md b/how-to-guides/block-tutorial/applying-styles-with-stylesheets.md new file mode 100644 index 0000000..20690c2 --- /dev/null +++ b/how-to-guides/block-tutorial/applying-styles-with-stylesheets.md @@ -0,0 +1,163 @@ +# 使用样式与样式表 + +## 概述 + +区块通常会在文章内容中插入需要特定样式化的标记(HTML)。本指南将详细介绍几种在区块编辑器中使用 CSS 的不同方法,以及如何处理样式和样式表。 + +## 开始之前 + +您需要准备一个基础区块和 WordPress 开发环境来实现本指南中的示例。请参阅[快速入门指南](/docs/getting-started/quick-start-guide.md)或[区块教程](/docs/getting-started/tutorial.md)完成环境配置。 + +## 添加样式的方法 + +以下是为区块添加样式的不同方法(适用于编辑器界面或保存后的显示效果): + +## 方法一:内联样式 + +第一种方法演示了如何添加内联样式。这种方式会将定义的样式转换为插入元素的属性。 + +通过 `useBlockProps` React 钩子可以设置并应用区块包装元素的属性。具体示例如下: + +```jsx +import { registerBlockType } from '@wordpress/blocks'; +import { useBlockProps } from '@wordpress/block-editor'; + +registerBlockType( 'gutenberg-examples/example-02-stylesheets', { + edit() { + const greenBackground = { + backgroundColor: '#090', + color: '#fff', + padding: '20px', + }; + + const blockProps = useBlockProps( { style: greenBackground } ); + + return ( +

Hello World(编辑器界面显示,绿色背景)。

+ ); + }, + save() { + const redBackground = { + backgroundColor: '#900', + color: '#fff', + padding: '20px', + }; + + const blockProps = useBlockProps.save( { style: redBackground } ); + + return ( +

Hello World(前端显示,红色背景)。

+ ); + }, +} ); +``` + +## 方法二:区块类名 + +内联样式适用于少量 CSS 的情况。如果需要更复杂的样式,使用独立的样式表文件会更便于管理。 + +`useBlockProps` 钩子会自动包含区块的类名,它会根据区块名称生成以 `wp-block-` 为前缀的类名,并将命名空间分隔符 `/` 替换为 `-`。 + +例如区块名称 `gutenberg-examples/example-02-stylesheets` 对应的类名为:`wp-block-gutenberg-examples-example-02-stylesheets`。虽然较长但能有效避免与其他区块冲突。 + +```jsx +import { registerBlockType } from '@wordpress/blocks'; +import { useBlockProps } from '@wordpress/block-editor'; + +registerBlockType( 'gutenberg-examples/example-02-stylesheets', { + edit() { + const blockProps = useBlockProps(); + + return ( +

Hello World(编辑器界面显示,绿色背景)。

+ ); + }, + save() { + const blockProps = useBlockProps.save(); + + return ( +

Hello World(前端显示,红色背景)。

+ ); + }, +} ); +``` + +### 构建或添加依赖 + +如需将 blockEditor 添加为依赖项,请确保执行构建步骤或更新资源 PHP 文件。 + +构建脚本并更新用于跟踪依赖关系和构建版本的资源文件: +```bash +npm run build +``` + +### 加载样式表 + +与脚本类似,您可以通过 `block.json` 文件加载区块的样式表。 + +- `editorStyle` 属性:指定仅在编辑器界面加载的 CSS 文件 +- `style` 属性:指定在编辑器界面和前端都会加载的 CSS 文件 +- `viewStyle` 属性:指定仅在前端加载的 CSS 文件(当区块被使用时) + +值得注意的是,如果编辑器内容位于 iframe 中,`style` 和 `editorStyle` 都会在 iframe 内加载。`editorStyle` 还会在 iframe 外加载,因此可用于编辑器内容和界面样式。 + +示例: + +```json +{ + "apiVersion": 3, + "name": "gutenberg-examples/example-02-stylesheets", + "title": "示例:样式表", + "icon": "universal-access-alt", + "category": "layout", + "editorScript": "file:./block.js", + "editorStyle": "file:./editor.css", + "style": "file:./style.css" +} +``` + +在插件目录中创建 `editor.css` 文件用于编辑器界面: + +```css +/* 绿色背景 */ +.wp-block-gutenberg-examples-example-02-stylesheets { + background: #090; + color: white; + padding: 20px; +} +``` + +创建 `style.css` 文件用于前端显示: + +```css +/* 红色背景 */ +.wp-block-gutenberg-examples-example-02-stylesheets { + background: #900; + color: white; + padding: 20px; +} +``` + +在 block.json 中指定后,这些文件将自动加载。 + +
+ +如果使用 `@wordpress/scripts`,需要在对应的 JavaScript 文件中导入样式表,以便 `@wordpress/scripts` 处理样式表。 + +示例: + +- 在 `edit.js` 中添加 `import './editor.scss';` +- 在 `index.js` 中添加 `import './style.scss';` +- 在 `view.js` 中添加 `import './view.scss';`(交互式区块模板) +
+ +**注意:** 如需加载多个文件,可以像其他插件或主题一样使用标准的 `wp_enqueue_style` 函数。区块编辑器建议使用以下钩子: + +- `enqueue_block_editor_assets` - 仅在编辑器界面加载 +- `enqueue_block_assets` - 在前端和编辑器界面同时加载 + +## 总结 + +本指南展示了通过内联样式或独立样式表为区块应用样式的不同方法。这两种方法都使用了 `useBlockProps` 钩子,更多细节请参阅[区块包装器参考文档](/docs/reference-guides/block-api/block-edit-save.md#block-wrapper-props)。 + +完整代码请查看[区块开发示例库](https://github.com/WordPress/block-development-examples)中的 [stylesheets-79a4c3](https://github.com/WordPress/block-development-examples/tree/trunk/plugins/stylesheets-79a4c3) 示例。 \ No newline at end of file diff --git a/how-to-guides/block-tutorial/creating-dynamic-blocks.md b/how-to-guides/block-tutorial/creating-dynamic-blocks.md new file mode 100644 index 0000000..a0b79ce --- /dev/null +++ b/how-to-guides/block-tutorial/creating-dynamic-blocks.md @@ -0,0 +1,137 @@ +# 创建动态区块 + +动态区块是在前端渲染时实时构建结构和内容的区块。 + +动态区块主要有两个用途: + +1. **内容需要实时更新的区块**:即使文章未更新,区块内容也会自动变化。WordPress 自带的「最新文章」区块就是典型示例——每当发布新文章时,所有使用该区块的位置都会同步更新。 +2. **需要即时呈现代码更新的区块**:当修改区块代码(HTML/CSS/JS)时,使用动态区块可确保修改立即在全站所有该区块实例上生效。(若不使用动态区块,Gutenberg 的[验证流程](/docs/reference-guides/block-api/block-edit-save.md#validation)会触发,导致用户看到“此区块似乎已被外部修改”的提示) + +对于多数动态区块,`save` 回调函数应返回 `null`,这会让编辑器仅将[区块属性](/docs/reference-guides/block-api/block-attributes.md)保存至数据库。这些属性随后会传入服务端渲染回调,从而由开发者决定如何在前端展示区块。返回 `null` 时,编辑器会跳过区块标记验证流程,避免因频繁变化的标记引发问题。 + +若在动态区块中使用 [InnerBlocks](/docs/how-to-guides/block-tutorial/nested-blocks-inner-blocks.md),需通过 `` 在 `save` 回调中保存内部区块内容。 + +也可保存区块的 HTML 表示形式。若设置了服务端渲染回调,该 HTML 会被回调输出替换;但当区块被停用或渲染回调被移除时,此 HTML 仍会正常渲染。 + +区块属性可用于保存任何需要存储的内容或设置。例如最新文章区块中,可将要显示的文章数量保存为属性;又或者针对需要在前端展示的每项内容(如标题文本、段落文字、图片、链接等),都可通过属性进行配置。 + +以下代码示例展示了如何创建仅显示最新文章链接的动态区块: + +```jsx +import { registerBlockType } from '@wordpress/blocks'; +import { useSelect } from '@wordpress/data'; +import { useBlockProps } from '@wordpress/block-editor'; + +registerBlockType( 'gutenberg-examples/example-dynamic', { + apiVersion: 3, + title: '示例:最新文章', + icon: 'megaphone', + category: 'widgets', + + edit: () => { + const blockProps = useBlockProps(); + const posts = useSelect( ( select ) => { + return select( 'core' ).getEntityRecords( 'postType', 'post' ); + }, [] ); + + return ( +
+ { ! posts && '加载中' } + { posts && posts.length === 0 && '暂无文章' } + { posts && posts.length > 0 && ( + + { posts[ 0 ].title.rendered } + + ) } +
+ ); + }, +} ); +``` + +由于是动态区块,无需在客户端重写默认的 `save` 实现,但需要配置服务端组件。前端显示内容取决于 `register_block_type` 的 `render_callback` 属性所调用的函数。 + +```php + 1, + 'post_status' => 'publish', + ) ); + if ( count( $recent_posts ) === 0 ) { + return '暂无文章'; + } + $post = $recent_posts[ 0 ]; + $post_id = $post['ID']; + return sprintf( + '%2$s', + esc_url( get_permalink( $post_id ) ), + esc_html( get_the_title( $post_id ) ) + ); +} + +function gutenberg_examples_dynamic() { + // 自动加载依赖项与版本号 + $asset_file = include( plugin_dir_path( __FILE__ ) . 'build/index.asset.php'); + + wp_register_script( + 'gutenberg-examples-dynamic', + plugins_url( 'build/block.js', __FILE__ ), + $asset_file['dependencies'], + $asset_file['version'] + ); + + register_block_type( 'gutenberg-examples/example-dynamic', array( + 'api_version' => 3, + 'editor_script' => 'gutenberg-examples-dynamic', + 'render_callback' => 'gutenberg_examples_dynamic_render_callback' + ) ); + +} +add_action( 'init', 'gutenberg_examples_dynamic' ); +``` + +需要注意以下几点: +- `edit` 函数仍在编辑器中展示区块预览(可与渲染版本完全不同,由区块作者决定) +- 内置 `save` 函数直接返回 `null`,因为渲染在服务端完成 +- 服务端渲染函数接收区块属性及内部内容作为参数,返回标记(与短代码机制高度相似) + +**注意:** 关于颜色、边框、间距等常见自定义设置,我们将在[下一章](/docs/how-to-guides/block-tutorial/block-supports-in-dynamic-blocks.md)了解如何通过区块支持功能高效实现。 + +## 在区块编辑器中实时渲染 + +Gutenberg 2.8 版本引入了 [``](/packages/server-side-render/README.md) 区块,支持在服务端使用 PHP 进行渲染而非 JavaScript。 + +*服务端渲染仅作为降级方案,始终推荐使用客户端 JavaScript 渲染(速度更快且支持更灵活的编辑器操作)。* + +```jsx +import { registerBlockType } from '@wordpress/blocks'; +import ServerSideRender from '@wordpress/server-side-render'; +import { useBlockProps } from '@wordpress/block-editor'; + +registerBlockType( 'gutenberg-examples/example-dynamic', { + apiVersion: 3, + title: '示例:最新文章', + icon: 'megaphone', + category: 'widgets', + + edit: function ( props ) { + const blockProps = useBlockProps(); + return ( +
+ +
+ ); + }, +} ); +``` + +注意此代码使用了 `wp-server-side-render` 包但未使用 `wp-data`,请确保在 PHP 代码中更新依赖项。建议使用 wp-scripts 自动构建依赖(具体 PHP 代码配置可参考 [block-development-examples 代码库](https://github.com/WordPress/block-development-examples/tree/trunk/plugins/basic-esnext-a2ab62))。 \ No newline at end of file diff --git a/how-to-guides/block-tutorial/extending-the-query-loop-block.md b/how-to-guides/block-tutorial/extending-the-query-loop-block.md new file mode 100644 index 0000000..ec05880 --- /dev/null +++ b/how-to-guides/block-tutorial/extending-the-query-loop-block.md @@ -0,0 +1,286 @@ +### 在前端实现自定义查询功能 + +查询循环区块主要通过文章模板区块运作,该区块接收属性并据此构建查询。查询循环区块的其他一级子元素(如分页区块)也以相同方式工作。它们构建查询后,通过 [`query_loop_block_query_vars`](https://developer.wordpress.org/reference/hooks/query_loop_block_query_vars/) 过滤器暴露结果。 + +您可以通过挂接该过滤器来修改查询。请务必确保不会对其他查询循环区块产生副作用,至少需要检查仅将过滤器应用于您的变体! + +```php +if( 'my-plugin/books-list' === $block[ 'attrs' ][ 'namespace' ] ) { + add_filter( + 'query_loop_block_query_vars', + function( $query ) { + /** 在此处读取区块的自定义查询参数并构建查询 */ + }, + ); +} +``` + +(上述代码中,我们假设您有某种访问区块的方式,例如通过 [`pre_render_block`](https://developer.wordpress.org/reference/hooks/pre_render_block/) 过滤器,但具体解决方案可能因使用场景而异,因此这并非硬性建议)。 + +### 在编辑器端实现自定义查询功能 + +要完成自定义变体,我们可能希望编辑器能响应自定义查询的更改,并相应显示适当的预览。虽然这对区块功能并非必需,但能为区块使用者提供完全集成的用户体验。 + +查询循环区块通过 [WordPress REST API](https://developer.wordpress.org/rest-api/) 获取文章以显示预览。任何添加到 `query` 对象的额外参数都将作为查询参数传递给 API。这意味着这些额外参数要么需要 REST API 支持,要么需要通过自定义过滤器处理,例如 [`rest_{$this->post_type}_query`](https://developer.wordpress.org/reference/hooks/rest_this-post_type_query/) 过滤器,该过滤器允许您介入自定义文章类型的任何 API 请求。如下所示: + +```php +add_filter( + 'rest_book_query', + function( $args, $request ) { + /** 我们可以从这里访问自定义参数 */ + $book_author = $request->get_param( 'bookAuthor' ); + /** ...您的自定义查询逻辑 */ + } +); +``` + +就这样,您就创建了一个功能完整的查询循环区块变体! + +# 扩展查询循环区块 + +查询循环区块是一个功能强大的工具,允许用户循环遍历指定的文章列表,并显示一系列将继承列表中每篇文章上下文的区块。例如,可以将其设置为循环遍历特定分类下的所有文章,并为每篇文章显示其特色图像。当然,功能远不止于此! + +但正因为查询循环区块功能强大且支持高度自定义,它也可能令人望而生畏。大多数用户不希望面对查询循环区块的全部功能,因为他们可能不熟悉“查询”概念及其相关技术术语。相反,大多数用户可能更青睐预设版本的区块,具有更少的设置项和更清晰的命名。默认提供的“文章列表”变体就是这种做法的良好示例:用户可以在不接触技术细节的情况下使用查询循环区块,同时也更有可能发现并理解该区块的用途。 + +同样地,许多扩展开发者可能需要提供定制化区块版本的方法,这些版本具有自己的预设配置、附加设置,并剔除与其使用场景无关的自定义选项(例如,通常针对其自定义文章类型)。查询循环区块提供了非常强大的方式来创建此类变体。 + +## 通过变体扩展区块 + +通过注册具有特定查询循环区块设置的自定义区块变体,您可以更精细地控制其呈现方式,同时仍能使用查询循环区块底层提供的全部功能。如果您不熟悉区块变体,可在此处了解更多信息。 + +通过区块变体API,您可以为您的使用场景提供最合理的默认设置。 + +要使查询循环变体正常工作,我们需要: +- 为`core/query`区块注册具有某些默认值的区块变体 +- 为区块变体定义布局 +- 在`isActive`区块变体属性中使用`namespace`属性 + +让我们以为一个注册了`book`自定义文章类型的插件设置变体为例,开始这段旅程。 + +### 1. 提供合理的默认值 + +您的第一步是创建一个变体,该变体将默认显示书籍列表而非博客文章。完整的变体代码如下所示: + +```js +const MY_VARIATION_NAME = 'my-plugin/books-list'; + +registerBlockVariation( 'core/query', { + name: MY_VARIATION_NAME, + title: '书籍列表', + description: '显示书籍列表', + isActive: ( { namespace, query } ) => { + return ( + namespace === MY_VARIATION_NAME + && query.postType === 'book' + ); + }, + icon: /** 此处可放置SVG图标 */, + attributes: { + namespace: MY_VARIATION_NAME, + query: { + perPage: 6, + pages: 0, + offset: 0, + postType: 'book', + order: 'desc', + orderBy: 'date', + author: '', + search: '', + exclude: [], + sticky: '', + inherit: false, + }, + }, + scope: [ 'inserter' ], + } +); +``` + +如果这看起来很多,请不要担心,让我们逐一查看这里的属性,了解它们的存在意义和作用。 + +本质上,您可以从这样的代码开始: + +```js +registerBlockVariation( 'core/query', { + name: 'my-plugin/books-list', + attributes: { + query: { + /** ...更多查询设置(如需要) */ + postType: 'book', + }, + }, +} ); +``` + +通过这种方式,用户无需从下拉菜单中选择自定义的`postType`,即可直接获得正确的配置。但您可能会问,用户如何找到并插入这个变体?好问题!要实现这一点,您应该添加: + +```js +{ + /** ...变体属性 */ + scope: [ 'inserter' ], +} +``` + +这样,当用户在编辑器中搜索时,您的区块将像任何其他区块一样显示。此时,您可能还想为变体添加自定义图标、标题和描述,如下所示: + +```js +{ + /** ...变体属性 */ + title: '书籍列表', + description: '显示书籍列表', + icon: /* 您的SVG图标在此 */, +} +``` + +至此,您的自定义变体将与独立区块几乎无法区分。完全为您的插件品牌化,易于发现,并作为即插即用的选项直接提供给用户。 + +然而,您的查询循环变体尚不能正常工作——我们仍需定义布局以便其正确渲染。 + +## 扩展查询功能 + +即便具备上述所有功能,您的自定义文章类型可能仍有独特需求:可能需要支持某些用于筛选和查询的自定义属性,或者某些查询参数可能无关紧要甚至完全不支持!我们在设计查询循环区块时已考虑到这类使用场景,下面来看看如何解决这个问题。 + +### 禁用无关或不支持的查询控件 + +假设您的图书数据完全未使用`sticky`属性,那么该设置对区块定制将毫无意义。为避免用户困惑并呈现清晰的交互界面,我们需要隐藏此控件。再假设您完全未使用`author`字段(该字段通常表示将文章添加到数据库的用户),而是使用自定义的`bookAuthor`字段。此时若保留`author`筛选器不仅会造成混淆,更会直接"破坏"您的查询。 + +为此,查询循环区块变体支持名为`allowedControls`的属性,该属性接收数组参数用于指定要在检查器侧边栏显示的控件键值。默认情况下会显示所有控件,但一旦设置该属性,我们就需要明确指定相关控件! + +截至Gutenberg 14.2版本,可用控件如下: + +- `inherit` - 显示切换开关,允许查询直接从模板继承 +- `postType` - 显示可用文章类型下拉菜单 +- `order` - 显示查询排序方式下拉菜单 +- `sticky` - 显示置顶文章处理方式下拉菜单 +- `taxQuery` - 显示当前所选文章类型的分类法筛选器 +- `author` - 显示按作者筛选查询的输入字段 +- `search` - 显示按关键词筛选查询的输入字段 +- `format` - 显示按[格式集合](https://developer.wordpress.org/advanced-administration/wordpress/post-formats/#supported-formats)筛选查询的输入字段 +- `parents` - 显示使用父实体筛选查询的输入字段 + +在本案例中,该属性配置如下: + +```js +{ + /** ...变体属性 */ + allowedControls: [ 'inherit', 'order', 'taxQuery', 'search' ], +} +``` + +如需隐藏所有可用控件,可将`allowedControls`值设为空数组。 + +注意我们同时禁用了`postType`控件。当用户选择我们的变体时,为何还要显示令人困惑的文章类型下拉菜单?更重要的是,正如稍后将介绍的,这可能会破坏我们实现自定义控件的区块功能。 + +### 添加额外控件 + +由于我们的插件需要使用自定义属性进行查询,我们需要添加专属控件来替代刚刚从核心检查器禁用的控件。这可以通过接入[区块过滤器](https://developer.wordpress.org/block-editor/reference-guides/filters/block-filters/)的[React HOC](https://reactjs.org/docs/higher-order-components.html)实现: + +```jsx +import { InspectorControls } from '@wordpress/block-editor'; + +export const withBookQueryControls = ( BlockEdit ) => ( props ) => { + // 仅当属于我们的变体时才添加这些控件 + // 此处可实现自定义逻辑进行验证,类似于前述的`isActive`函数 + // 以下假设您已编写自定义的`isMyBooksVariation`函数进行处理 + return isMyBooksVariation( props ) ? ( + <> + + + { /** 我们的自定义组件 */ } + + + ) : ( + + ); +}; + +addFilter( 'editor.BlockEdit', 'core/query', withBookQueryControls ); +``` + +当然,您需要自行实现控件的逻辑(建议参考[`@wordpress/components`](https://www.npmjs.com/package/@wordpress/components)使控件完美融入Gutenberg界面)。通过少量额外工作,您在区块属性的`query`对象内设置的任何额外参数,都可用于创建符合需求的自定义查询。 + +当前您可能需要实现略有差异的路径,以确保在前端(即最终用户侧)正确执行查询,并在编辑器端显示正确的预览。 + +```js +{ + /** ...变体属性 */ + attributes: { + /** ...变体属性 */ + query: { + /** ...更多查询设置(如需要) */ + postType: 'book', + /** 我们的自定义查询参数 */ + bookAuthor: 'J. R. R. Tolkien' + } + } +} +``` + +### 2. 自定义变体布局 + +请注意,查询循环区块支持在 `scope` 属性中使用字符串 `'block'`。理论上,这是为了让变体在插入区块后被识别。更多关于区块变体选择器的信息请[参阅此处](https://github.com/WordPress/gutenberg/blob/HEAD/packages/block-editor/src/components/block-variation-picker/README.md)。 + +然而,目前**不建议**使用此方式,这是因为查询循环通过模式和 `scope: [ 'block' ]` 变体进行设置时,所选模式的所有属性将被使用,但 `postType` 和 `inherit` 查询属性除外,这可能导致冲突和变体功能异常。 + +为解决此问题,有两种方法。第一种是添加默认的 `innerBlocks`,如下所示: + +```js +innerBlocks: [ + [ + 'core/post-template', + {}, + [ [ 'core/post-title' ], [ 'core/post-excerpt' ] ], + ], + [ 'core/query-pagination' ], + [ 'core/query-no-results' ], +], +``` + +通过在变体中包含 `innerBlocks`,您实际上跳过了查询循环区块通过建议模式进行设置的阶段,区块将以这些内部区块作为初始内容插入。 + +另一种方法是为您的变体注册特定模式,这些模式将在设置过程中被推荐,并替换区块的流程。 + +查询循环区块会判断自身是否有活跃变体,以及该变体是否有特定模式可用。如果有,这些模式将是唯一推荐给用户的模式,不会包含原始查询循环区块的默认模式。否则,如果没有此类模式,将推荐默认模式。 + +若要将模式与查询循环变体“关联”,您应在模式的 `blockTypes` 属性中添加以查询循环名称前缀的变体名称(例如 `core/query/$variation_name`)。有关注册模式的更多详情,请[参阅此处](https://developer.wordpress.org/block-editor/reference-guides/block-api/block-patterns/)。 + +如果您未在变体中提供 `innerBlocks`,还有一种方法可以在用户于设置阶段选择 `Start blank` 时推荐“关联”变体。这与“关联”模式的处理方式类似,通过检查查询循环是否有活跃变体以及是否有任何关联变体可推荐。 + +若要将一个变体与另一个查询循环变体关联,我们需要将 `scope` 属性定义为 `['block']`,并将 `namespace` 属性定义为一个数组。该数组应包含希望关联的变体的名称(`name` 属性)。 + +例如,如果我们有一个名称为 `products` 的查询循环变体暴露于插入器中(`scope: ['inserter']`),我们可以通过将其 `namespace` 属性设置为 `['products']` 来关联一个作用域为 `block` 的变体。如果用户在点击 `Start blank` 后选择此变体,`namespace` 属性将被主要的插入器变体覆盖。 + +### 3. 让 Gutenberg 识别您的变体 + +在实现此变体后,您可能意识到一个小问题:虽然用户在插入时对此无感知,但 Gutenberg 仍会将变体识别为查询循环区块的核心功能,因此在插入后,例如在编辑器的树形视图中,它将显示为查询循环区块。 + +我们需要一种方式告诉编辑器,此区块实际上是您的特定变体。这正是 `isActive` 属性的用途:它是一种基于区块属性判断特定变体是否活跃的方法。您可以像这样使用它: + +```js +{ + /** ...变体属性 */ + isActive: ( { namespace, query } ) => { + return ( + namespace === MY_VARIATION_NAME + && query.postType === 'book' + ); + }, +} +``` + +您可能倾向于仅比较 `postType`,以便当 `postType` 匹配 `book` 时,Gutenberg 会将区块识别为您的变体。然而,这种方式范围过广,因为其他插件可能也希望基于 `book` 文章类型发布变体,或者我们可能不希望用户在编辑器设置中手动将类型设置为 `book` 时每次都识别为变体。 + +这就是为什么查询循环区块暴露了一个名为 `namespace` 的特殊属性。它在区块实现中实际上没有任何作用,而是作为一种简单且一致的方式供扩展者识别和限定自己的变体。此外,`isActive` 还可以接受一个字符串数组,用于比较属性。通常,`namespace` 就足够了,您可以这样使用: + +```js +{ + /** ...变体属性 */ + attributes: { + /** ...变体属性 */ + namespace: 'my-plugin/books-list', + }, + isActive: [ 'namespace' ], +} +``` + +这样,Gutenberg 只有在匹配您的自定义命名空间时才会知道这是您的特定变体!非常方便! \ No newline at end of file diff --git a/how-to-guides/block-tutorial/nested-blocks-inner-blocks.md b/how-to-guides/block-tutorial/nested-blocks-inner-blocks.md new file mode 100644 index 0000000..973c520 --- /dev/null +++ b/how-to-guides/block-tutorial/nested-blocks-inner-blocks.md @@ -0,0 +1,278 @@ +## 使用 React 钩子 + +你可以使用名为 `useInnerBlocksProps` 的 React 钩子来替代 `InnerBlocks` 组件。该钩子能让你更灵活地控制内部区块区域的标记结构。 + +`useInnerBlocksProps` 与 `InnerBlocks` 组件同样从 `@wordpress/block-editor` 包中导出,并支持该组件的所有功能。其工作方式类似于 `useBlockProps` 钩子。 + +需特别注意:必须在调用 `useInnerBlocksProps` *之前* 调用 `useBlockProps` 钩子,否则 `useBlockProps` 将返回空对象。 + +以下是基础的 `useInnerBlocksProps` 钩子用法: + +```js +import { registerBlockType } from '@wordpress/blocks'; +import { useBlockProps, useInnerBlocksProps } from '@wordpress/block-editor'; + +registerBlockType( 'gutenberg-examples/example-06', { + // ... + + edit: () => { + const blockProps = useBlockProps(); + const innerBlocksProps = useInnerBlocksProps(); + + return ( +
+
+
+ ); + }, + + save: () => { + const blockProps = useBlockProps.save(); + const innerBlocksProps = useInnerBlocksProps.save(); + + return ( +
+
+
+ ); + }, +} ); +``` + +该钩子还可将 `useBlockProps` 钩子返回的对象传递给 `useInnerBlocksProps` 钩子,从而减少需要创建的元素数量: + +```js +import { registerBlockType } from '@wordpress/blocks'; +import { useBlockProps, useInnerBlocksProps } from '@wordpress/block-editor'; + +registerBlockType( 'gutenberg-examples/example-06', { + // ... + + edit: () => { + const blockProps = useBlockProps(); + const innerBlocksProps = useInnerBlocksProps( blockProps ); + + return ( +
+ ); + }, + + save: () => { + const blockProps = useBlockProps.save(); + const innerBlocksProps = useInnerBlocksProps.save( blockProps ); + + return ( +
+ ); + }, +} ); +``` + +上述代码将在编辑器中渲染为以下标记结构: + +```html +
+ +
+``` + +使用钩子方案的另一个优势是:可以利用返回值(仅为一个对象)并通过解构获取其中的 React 子元素。该属性包含实际的子内部区块,因此我们可以将元素放置在与内部区块同一层级: + +```js +import { registerBlockType } from '@wordpress/blocks'; +import { useBlockProps, useInnerBlocksProps } from '@wordpress/block-editor'; + +registerBlockType( 'gutenberg-examples/example-06', { + // ... + + edit: () => { + const blockProps = useBlockProps(); + const { children, ...innerBlocksProps } = useInnerBlocksProps( blockProps ); + + return ( +
+ { children } + +
+ ); + }, + + // ... +} ); +``` + +```html +
+ + +
+``` + +## 在区块中使用父级、祖先与子级关系 + +使用 InnerBlocks 的常见模式是创建一个仅在其父级区块插入时才可用的自定义区块。这使构建者能够在建立区块间关系的同时,限制嵌套区块的可发现性。构建者可使用三种关系类型:`parent`(父级)、`ancestor`(祖先)和 `allowedBlocks`(允许的区块)。它们的区别在于: + +- 若指定 `parent`,则表示嵌套区块仅能作为__父级的直接后代__被使用和插入 +- 若指定 `ancestor`,则表示嵌套区块仅能作为__父级的后代__被使用和插入 +- 若指定 `allowedBlocks`,则表示反向关系——即哪些区块可以作为__此区块的直接后代__被使用和插入 + +`parent` 与 `ancestor` 的核心区别在于:`parent` 具有更精确的特定性,而 `ancestor` 在嵌套层级中具有更高灵活性。 + +### 定义父级区块关系 + +典型案例是 Column(列)区块,它被设置了 `parent` 区块配置。这使得 Column 区块仅能作为其父级 Columns(列组)区块的嵌套直接后代使用。否则该区块将不会在区块插入器中显示。参阅 [Column 代码实现](https://github.com/WordPress/gutenberg/tree/HEAD/packages/block-library/src/column)。 + +定义直接后代区块时,需使用 `parent` 区块配置来声明父级区块。这能防止嵌套区块在其定义的 InnerBlock 之外显示于插入器中。 + +```json +{ + "title": "栏目", + "name": "core/column", + "parent": [ "core/columns" ], + // ... +} +``` + +### 定义祖先区块关系 + +典型案例是 Comment Author Name(评论作者名称)区块,它被设置了 `ancestor` 区块配置。这使得该区块仅能作为其祖先 Comment Template(评论模板)区块的嵌套后代使用。否则该区块将不会在区块插入器中显示。参阅 [Comment Author Name 代码实现](https://github.com/WordPress/gutenberg/tree/HEAD/packages/block-library/src/comment-author-name)。 + +通过 `ancestor` 关系,Comment Author Name 区块可以存在于层级树中的任意位置,而__不仅限于__父级 Comment Template 区块的直接子级,同时仍能限制其在区块插入器中的可见性——仅当 Comment Template 区块存在时才显示为可插入选项。 + +定义后代区块时,需使用 `ancestor` 区块配置。这能防止嵌套区块在其定义的 InnerBlock 之外显示于插入器中。 + +```json +{ + "title": "评论作者名称", + "name": "core/comment-author-name", + "ancestor": [ "core/comment-template" ], + // ... +} +``` + +### 定义子级区块关系 + +典型案例是 Navigation(导航)区块,它被设置了 `allowedBlocks` 区块配置。这使得仅特定类型的区块能作为 Navigation 区块的直接后代使用。参阅 [Navigation 代码实现](https://github.com/WordPress/gutenberg/tree/HEAD/packages/block-library/src/navigation)。 + +自定义区块构建者可扩展 `allowedBlocks` 设置。通过挂载 `blocks.registerBlockType` 过滤器,自定义区块可将自身添加到 Navigation 的可用子级列表中。 + +定义可能的子代区块集合时,需使用 `allowedBlocks` 区块配置。这会限制插入新子区块时插入器中显示的区块类型。 + +```json +{ + "title": "导航", + "name": "core/navigation", + "allowedBlocks": [ "core/navigation-link", "core/search", "core/social-links", "core/page-list", "core/spacer" ], + // ... +} +``` + +# 嵌套区块:使用 InnerBlocks + +您可以使用 [InnerBlocks](https://github.com/WordPress/gutenberg/tree/HEAD/packages/block-editor/src/components/inner-blocks/README.md) 组件创建包含其他区块的独立区块。这一技术被广泛应用于分栏区块、社交链接区块等需要容纳其他区块的组件中。 + +注意:单个区块只能包含一个 `InnerBlocks` 组件。 + +以下是 InnerBlocks 的基本用法: + +```js +import { registerBlockType } from '@wordpress/blocks'; +import { InnerBlocks, useBlockProps } from '@wordpress/block-editor'; + +registerBlockType( 'gutenberg-examples/example-06', { + // ... + + edit: () => { + const blockProps = useBlockProps(); + + return ( +
+ +
+ ); + }, + + save: () => { + const blockProps = useBlockProps.save(); + + return ( +
+ +
+ ); + }, +} ); +``` + +## 允许的区块 + +通过 `allowedBlocks` 属性,您可以在 `block.json` 的 `allowedBlocks` 字段基础上,进一步限制哪些区块可以作为该区块的直接子级插入。这对于针对每个区块动态确定允许的区块列表非常实用,例如根据区块属性来决定: + +```js +const { allowedBlocks } = attributes; +//... +; +``` + +如果允许的区块列表始终保持不变,建议改用 [`allowedBlocks` 区块设置](#定义子级区块关系)。 + +## 排列方向 + +默认情况下,`InnerBlocks` 假定其包含的区块以垂直列表形式呈现。在某些应用场景中,可能需要通过为内部区块包装器添加 CSS 弹性布局或网格属性,使内部区块水平排列。当区块采用此类样式时,可通过设置 `orientation` 属性来指示当前正在使用水平布局: + +```js + +``` + +设置此属性不会影响内部区块的布局,但会使子区块中的区块移动器图标水平显示,同时确保拖放功能正常工作。 + +## 默认区块 + +默认情况下,当点击区块添加器时,`InnerBlocks` 会通过 `allowedBlocks` 显示允许的区块列表。您可以使用 `defaultBlock` 属性来修改点击初始区块添加器时插入的默认区块及其属性。例如: + +```js + +``` + +默认情况下,此功能处于禁用状态,除非将 `directInsert` 属性设置为 `true`。这使您能够指定默认区块是否应该插入的条件。 + +## 模板 + +使用 template 属性可以定义一组区块,在 InnerBlocks 组件没有现有内容时预填充内容。您可以为这些区块设置属性来定义其用途。以下示例展示了使用 InnerBlocks 组件设置书评模板,并通过占位符值展示区块用途: + +```js +const MY_TEMPLATE = [ + [ 'core/image', {} ], + [ 'core/heading', { placeholder: '书名' } ], + [ 'core/paragraph', { placeholder: '内容摘要' } ], +]; + +//... + + edit: () => { + return ( + + ); + }, +``` + +使用 `templateLock` 属性可以锁定模板。设置为 `all` 时将完全锁定模板,禁止任何修改;设置为 `insert` 时则仅允许重新排序现有区块,禁止插入新区块。更多信息请参阅 [templateLock 文档](https://github.com/WordPress/gutenberg/tree/HEAD/packages/block-editor/src/components/inner-blocks/README.md#templatelock)。 + +### 文章模板 + +虽然与 `InnerBlocks` 无直接关联,但值得在此说明:您可以按文章类型创建[文章模板](https://developer.wordpress.org/block-editor/developers/block-api/block-templates/),使区块编辑器预加载一组特定区块。 + +`InnerBlocks` 模板仅适用于您创建的单个区块组件,而文章的其余部分可以包含用户喜欢的任何区块。使用文章模板则可以将整篇文章锁定为您定义的模板结构。 + +```php +add_action( 'init', function() { + $post_type_object = get_post_type_object( 'post' ); + $post_type_object->template = array( + array( 'core/image' ), + array( 'core/heading' ) + ); +} ); +``` \ No newline at end of file diff --git a/how-to-guides/curating-the-editor-experience/README.md b/how-to-guides/curating-the-editor-experience/README.md new file mode 100644 index 0000000..fed1522 --- /dev/null +++ b/how-to-guides/curating-the-editor-experience/README.md @@ -0,0 +1,25 @@ +# 优化编辑器使用体验 + +优化WordPress编辑器体验至关重要,它能让您简化编辑流程,确保内容与网站风格及品牌指南保持一致。同时,这也能让用户更轻松高效地创建和管理内容,避免意外修改或布局变动,从而打造更高效、更个性化的使用体验。 + +本指南旨在介绍多种锁定和优化WordPress使用体验的方法,特别是在引入更多设计工具和站点编辑器之后。 + +在本节中,您将了解: + +1. [**区块锁定**](https://developer.wordpress.org/block-editor/how-to-guides/curating-the-editor-experience/block-locking):如何通过限制用户在编辑器中对特定区块的交互来实现更好的内容控制 +2. [**模式**](https://developer.wordpress.org/block-editor/how-to-guides/curating-the-editor-experience/patterns):关于创建和实现预定义区块布局以确保设计与内容的统一性 +3. [**theme.json**](https://developer.wordpress.org/block-editor/how-to-guides/curating-the-editor-experience/theme-json):通过theme.json文件配置主题的全局样式和设置 +4. [**过滤器和钩子**](https://developer.wordpress.org/block-editor/how-to-guides/curating-the-editor-experience/filters-and-hooks):用于修改编辑器的核心过滤器和钩子 +5. [**禁用编辑器功能**](https://developer.wordpress.org/block-editor/how-to-guides/curating-the-editor-experience/disable-editor-functionality):选择性禁用编辑器功能或组件的其他方法,以优化用户体验 + +## 组合运用多种方法 + +请注意,上述文档中提供的方法可以根据需要组合使用。例如,您可以在创建新页面时提供自定义模式,同时限制对这些模式的某些方面进行自定义,比如仅允许对封面区块背景使用特定的预设颜色,或锁定可删除的区块。 + +在考虑采用哪些方法时,请思考您希望以何种具体方式既开放使用体验又进行精心管控。 + +## 扩展资源 + +- [构建器基础——全站编辑中的模板运用(第三部分)](https://wordpress.tv/2022/05/24/nick-diego-builder-basics-working-with-templates-in-full-site-editing-part-3/) +- [核心编辑器改进:通过锁定API和theme.json实现定制化体验](https://make.wordpress.org/core/2022/02/09/core-editor-improvement-curated-experiences-with-locking-apis-theme-json/) +- [关于优化编辑器体验的WordPress学习课程](https://wordpress.tv/2022/07/22/nick-diego-curating-the-editor-experience/) \ No newline at end of file diff --git a/how-to-guides/curating-the-editor-experience/block-locking.md b/how-to-guides/curating-the-editor-experience/block-locking.md new file mode 100644 index 0000000..991a522 --- /dev/null +++ b/how-to-guides/curating-the-editor-experience/block-locking.md @@ -0,0 +1,69 @@ +# 区块锁定 API + +区块锁定 API 允许您限制编辑器内特定区块的操作。该 API 可用于防止用户移动、删除或编辑某些区块,从而确保布局一致性和内容完整性。 + +## 锁定移动或删除特定区块的功能 + +用户可通过编辑器锁定和解锁区块。锁定界面提供防止区块在内容画布内移动或被删除的选项: + +![锁定界面示意图](https://raw.githubusercontent.com/WordPress/gutenberg/HEAD/docs/assets/Locking%20interface.png?raw=true) + +请注意,通过启用"应用于内部所有区块"选项,您可以将锁定选项应用于容器区块内的嵌套区块。但除此之外,无法批量锁定区块。 + +## 锁定编辑特定区块的功能 + +除了锁定移动或删除区块的功能外,[导航区块](https://github.com/WordPress/gutenberg/pull/44739)和[可重用区块](https://github.com/WordPress/gutenberg/pull/39950)还具备额外功能:锁定编辑区块内容的能力。这将禁止对这两种区块类型内部的任何区块进行修改。 + +## 对模式或模板应用区块锁定 + +在构建模式或模板时,主题作者可以使用相同的界面工具设置区块的默认锁定状态。例如,主题作者可以锁定页眉的不同组成部分。请注意,默认情况下具有编辑权限的用户可以解锁这些区块。[这里有一个包含不同锁定方式区块的模式示例](https://gist.github.com/annezazu/acee30f8b6e8995e1b1a52796e6ef805),以及关于[创建包含锁定区块的模板](https://make.wordpress.org/core/2022/02/09/core-editor-improvement-curated-experiences-with-locking-apis-theme-json/)的更多背景信息。您可以在编辑器内直接构建这些模式(包括添加锁定选项),然后按照[注册文档](/docs/reference-guides/block-api/block-patterns.md)进行操作。 + +## 在模式或模板中应用仅内容编辑 + +此功能在 WordPress 6.1 中引入。与禁用移动或删除区块功能的区块锁定不同,仅内容编辑功能专为模式或模板层级设计,它会隐藏所有设计工具,同时仍允许编辑区块内容。这为简化用户界面和保留设计提供了绝佳方案。启用该选项后会发生以下变化: + +- 非内容子区块(容器、间距器、列等)将从列表视图隐藏,在画布上不可点击,且完全不可编辑。 +- 检查器将显示所有"内容"子区块列表,点击列表中的区块可显示其设置面板。 +- 主列表视图仅显示内容区块,所有区块均处于同一层级(忽略实际嵌套关系)。 +- 整体内容锁定容器内的子区块会自动启用移动/删除锁定。 +- 无法插入额外子区块,进一步保护设计和布局。 +- 区块工具栏设有"修改"链接,用户可切换此选项来获取更全面的设计工具。目前无法通过编程方式移除该选项。 + +该选项可应用于 Columns、Cover 和 Group 区块,以及在其 block.json 中具有 templateLock 属性的第三方区块。要启用此功能,需使用 `"templateLock":"contentOnly"`。[此处是具备此功能的模式示例](https://gist.github.com/annezazu/d62acd2514cea558be6cea97fe28ff3c)。更多信息请[查阅相关文档](/docs/reference-guides/block-api/block-templates.md#locking)。 + +注意:目前没有管理内容锁定的可视化界面,必须通过代码层级进行管理。 + +## 更改权限以控制锁定能力 + +机构和插件开发者可通过限制用户[锁定和解锁区块的权限](https://make.wordpress.org/core/2022/05/05/block-locking-settings-in-wordpress-6-0/),提供更精细的定制体验。默认情况下,所有管理员都具备锁定和解锁区块的权限。 + +开发者可通过向 [block_editor_settings_all](https://developer.wordpress.org/reference/hooks/block_editor_settings_all/) 钩子添加过滤器来配置区块锁定权限。该钩子向回调函数传递两个参数: + +- `$settings` - 编辑器的可配置设置数组 +- `$context` - WP_Block_Editor_Context 实例,包含当前编辑器信息的对象 + +具体而言,开发者可通过设置 `$settings['canLockBlocks']` 值为 `true` 或 `false` 来修改权限,通常需要执行一个或多个条件判断。 + +以下示例在编辑页面时为所有用户禁用区块锁定权限: + +```php +add_filter( 'block_editor_settings_all', function( $settings, $context ) { + if ( $context->post && 'page' === $context->post->post_type ) { + $settings['canLockBlocks'] = false; + } + + return $settings; +}, 10, 2 ); +``` + +另一种常见使用场景是仅允许可编辑网站视觉设计(主题编辑)的用户锁定或解锁区块。目前最佳方案是通过检测 `edit_theme_options` 权限来实现,如下列代码片段所示: + +```php +add_filter( 'block_editor_settings_all', function( $settings ) { + $settings['canLockBlocks'] = current_user_can( 'edit_theme_options' ); + + return $settings; +} ); +``` + +开发者可使用任何类型的条件判断来确定锁定/解锁区块的权限。这仅是通过过滤钩子实现功能的冰山一角。 \ No newline at end of file diff --git a/how-to-guides/curating-the-editor-experience/disable-editor-functionality.md b/how-to-guides/curating-the-editor-experience/disable-editor-functionality.md new file mode 100644 index 0000000..71b3e69 --- /dev/null +++ b/how-to-guides/curating-the-editor-experience/disable-editor-functionality.md @@ -0,0 +1,125 @@ +# 禁用编辑器功能 + +本文专门介绍在文章编辑器和站点编辑器中禁用特定功能的多种方法,这些内容未包含在管理文档的其他部分。 + +## 限制区块选项 + +有时您可能希望完全禁止用户使用某些区块。要控制插入器中可用的内容,您可以采用两种方法:[允许列表](/docs/reference-guides/filters/block-filters.md#using-an-allow-list)(仅启用列表中的区块)或[拒绝列表](/docs/reference-guides/filters/block-filters.md#using-a-deny-list)(取消注册特定区块)。 + +## 管理标题层级 + +具有标题层级下拉菜单的WordPress核心区块支持`levelOptions`属性。这适用于标题、站点标题、站点标语、查询标题、文章标题和评论标题区块。`levelOptions`属性接受对应标题层级的数字数组,其中`1`代表H1,`2`代表H2,依此类推。 + +此属性允许您指定哪些标题层级应出现在下拉UI中,提供了一种轻量级的管理方法,无需弃用区块。现有标题层级会在标记中保留,而`levelOptions`仅影响UI显示。 + +您可以直接在区块标记中应用此属性,这种技术将常用于区块模板、模板部件和模式。例如,以下标记通过设置`"levelOptions":[3,4,5]`在标题区块中禁用H1、H2和H6。 + +```html + +

标记示例

+ +``` + +您也可以使用[区块过滤器](/docs/reference-guides/filters/block-filters.md)全局或为特定区块设置此属性的默认值。以下示例为所有标题区块禁用H1、H2和H6。您可以通过基于用户权限等条件限制特定标题层级来进一步自定义。 + +```php +function example_modify_heading_levels_globally( $args, $block_type ) { + + if ( 'core/heading' !== $block_type ) { + return $args; + } + + // 移除H1、H2和H6 + $args['attributes']['levelOptions']['default'] = [ 3, 4, 5 ]; + + return $args; +} +add_filter( 'register_block_type_args', 'example_modify_heading_levels_globally', 10, 2 ); +``` + +## 禁用模式目录 + +要从插入器中完全移除WordPress核心捆绑的模式,可将以下代码添加到您的`functions.php`文件: + +```php +function example_theme_support() { + remove_theme_support( 'core-block-patterns' ); +} +add_action( 'after_setup_theme', 'example_theme_support' ); +``` + +## 禁用区块变体 + +某些核心区块实际上是[区块变体](https://developer.wordpress.org/block-editor/reference-guides/block-api/block-variations/)。一个很好的例子是行和堆栈区块,它们实际上是群组区块的变体。如果您想禁用这些"区块",实际上需要禁用相应的变体。 + +区块变体使用JavaScript注册,需要使用JavaScript禁用。以下代码将禁用行变体。 + +```js +wp.domReady( () => { + wp.blocks.unregisterBlockVariation( 'core/group', 'group-row' ); +}); +``` + +假设代码放置在主题文件夹根目录的`disable-variations.js`文件中,您可以使用以下代码在主题的`functions.php`中加载此文件。 + +```php +function example_disable_variations_script() { + wp_enqueue_script( + 'example-disable-variations-script', + get_template_directory_uri() . '/disable-variations.js', + array( 'wp-dom-ready' ), + wp_get_theme()->get( 'Version' ), + true + ); +} +add_action( 'enqueue_block_editor_assets', 'example_disable_variations_script' ); +``` + +## 禁用区块样式 + +有几个核心区块包含自己的[区块样式](https://developer.wordpress.org/block-editor/reference-guides/block-api/block-styles/)。例如图像区块包含一个名为"圆形"的圆角图像样式。您可能不希望用户使用圆形图像,或者更倾向于使用边框半径控件而非区块样式。无论哪种情况,都可以轻松禁用不需要的区块样式。 + +与区块变体不同,您可以使用JavaScript或PHP注册样式。如果样式是用JavaScript注册的,必须用JavaScript禁用。如果使用PHP注册,可以用任一方法禁用样式。所有核心区块样式都是用JavaScript注册的。 + +因此,您可以使用以下代码禁用图像区块的"圆形"样式。 + +```js +wp.domReady( () => { + wp.blocks.unregisterBlockStyle( 'core/image', 'rounded' ); +}); +``` + +此JavaScript的加载方式与上面的区块变体示例类似。有关使用PHP注册和取消注册样式的方法,请参阅[区块样式](https://developer.wordpress.org/block-editor/reference-guides/block-api/block-styles/)文档。 + +## 禁用代码编辑器访问权限 + +代码编辑器允许您查看页面或文章的底层区块标记。虽然此视图对有经验的用户很方便,但编辑内容可能会意外破坏区块标记。将以下代码添加到`functions.php`文件以限制访问。 + +```php +function example_restrict_code_editor_access( $settings, $context ) { + $settings[ 'codeEditingEnabled' ] = false; + + return $settings; +} +add_filter( 'block_editor_settings_all', 'example_restrict_code_editor_access', 10, 2 ); +``` + +此代码阻止所有用户访问代码编辑器。您也可以添加[权限](https://wordpress.org/documentation/article/roles-and-capabilities/)检查来为特定用户禁用访问。 + +## 禁用富文本区块的格式化选项 + +支持[富文本](https://developer.wordpress.org/block-editor/reference-guides/richtext/)的区块附带WordPress提供的默认格式化选项。 + +需要使用`unregisterFormatType`通过JavaScript禁用格式化选项。以下代码将全局禁用内联图像、语言、键盘输入、下标和上标选项。 + +```js +wp.domReady( () => { + wp.richText.unregisterFormatType( 'core/image' ); + wp.richText.unregisterFormatType( 'core/language' ); + wp.richText.unregisterFormatType( 'core/keyboard' ); + wp.richText.unregisterFormatType( 'core/subscript' ); + wp.richText.unregisterFormatType( 'core/superscript' ); +}); +``` + +此JavaScript的加载方式与上面的区块变体示例类似。 \ No newline at end of file diff --git a/how-to-guides/curating-the-editor-experience/filters-and-hooks.md b/how-to-guides/curating-the-editor-experience/filters-and-hooks.md new file mode 100644 index 0000000..0b64836 --- /dev/null +++ b/how-to-guides/curating-the-editor-experience/filters-and-hooks.md @@ -0,0 +1,178 @@ +## 更多资源 + +- [如何使用服务器端过滤器修改 theme.json 数据](https://developer.wordpress.org/news/2023/07/05/how-to-modify-theme-json-data-using-server-side-filters/)(WordPress 开发者博客) +- [通过客户端过滤器定制编辑器体验](https://developer.wordpress.org/news/2023/05/24/curating-the-editor-experience-with-client-side-filters/)(WordPress 开发者博客) + +## 客户端(编辑器)过滤器 + +WordPress 6.2 引入了一个全新的客户端过滤器,允许您在编辑器渲染前修改区块层级的 [theme.json 设置](/docs/reference-guides/theme-json-reference/theme-json-living.md#settings)。 + +该过滤器名为 `blockEditor.useSetting.before`,可通过以下方式在 JavaScript 代码中使用: + +```js +import { addFilter } from '@wordpress/hooks'; + +/** + * 将栏目区块的间距选项限制为像素单位。 + */ +addFilter( + 'blockEditor.useSetting.before', + 'example/useSetting.before', + ( settingValue, settingName, clientId, blockName ) => { + if ( blockName === 'core/column' && settingName === 'spacing.units' ) { + return [ 'px' ]; + } + return settingValue; + } +); +``` + +此示例将把栏目区块的可用间距单位限制为仅像素单位。如前所述,类似的限制也可以通过 theme.json 过滤器或直接在主题的 theme.json 文件中使用区块层级设置来实现。 + +但 `blockEditor.useSetting.before` 过滤器的独特之处在于,它允许您根据区块位置、相邻区块、当前用户角色等条件动态修改设置,定制可能性极为丰富。 + +下面这个示例展示了当标题区块被放置在「媒体与文本」区块内部时,会自动禁用其文字颜色控制功能: + +```js +import { select } from '@wordpress/data'; +import { addFilter } from '@wordpress/hooks'; + +/** + * 当标题区块置于媒体与文本区块内时,禁用其文字颜色控制功能。 + */ +addFilter( + 'blockEditor.useSetting.before', + 'example/useSetting.before', + ( settingValue, settingName, clientId, blockName ) => { + if ( blockName === 'core/heading' ) { + const { getBlockParents, getBlockName } = select( 'core/block-editor' ); + const blockParents = getBlockParents( clientId, true ); + const inMediaText = blockParents.some( ( ancestorId ) => getBlockName( ancestorId ) === 'core/media-text' ); + + if ( inMediaText && settingName === 'color.text' ) { + return false; + } + } + + return settingValue; + } +); +``` + +## 区块过滤器 + +除了优化编辑器界面外,您还可以通过多种方式修改单个区块。比如禁用特定区块支持功能(如背景色),或定义特定区块默认显示的设置项。 + +其中最常用的过滤器之一是 [`block_type_metadata`](https://developer.wordpress.org/reference/hooks/block_type_metadata/)。当区块类型在服务器端通过 PHP 注册时,该过滤器允许您拦截从区块 `block.json` 文件加载的原始元数据。 + +该过滤器接收一个参数: + +- `$metadata` (`array`) – 从 `block.json` 加载的区块类型注册元数据。 + +`$metadata` 数组包含您可能需要了解的所有区块信息,从描述和[属性](https://developer.wordpress.org/block-editor/reference-guides/block-api/block-attributes/)到区块[支持功能](https://developer.wordpress.org/block-editor/reference-guides/block-api/block-supports/)。 + +以下示例演示了如何禁用标题区块的背景色与渐变支持: + +```php +function example_disable_heading_background_color_and_gradients( $metadata ) { + + // 仅对标题区块应用此过滤器 + if ( ! isset( $metadata['name'] ) || 'core/heading' !== $metadata['name'] ) { + return $metadata; + } + + // 检查是否存在支持功能配置 + if ( isset( $metadata['supports'] ) && isset( $metadata['supports']['color'] ) ) { + + // 移除背景色与渐变支持 + $metadata['supports']['color']['background'] = false; + $metadata['supports']['color']['gradients'] = false; + } + + return $metadata; +} +add_filter( 'block_type_metadata', 'example_disable_heading_background_color_and_gradients' ); +``` + +您可以通过[区块过滤器文档](https://developer.wordpress.org/block-editor/reference-guides/filters/block-filters/)了解更多可用过滤器。 + +# 过滤器与钩子 + +编辑器提供了丰富的过滤器和钩子,让您可以自定义编辑体验。以下列举部分功能: + +## 编辑器设置 + +最常见的编辑器修改方式是通过 [`block_editor_settings_all`](https://developer.wordpress.org/reference/hooks/block_editor_settings_all/) PHP 过滤器,该过滤器会在设置发送到初始化编辑器之前生效。 + +`block_editor_settings_all` 钩子会向回调函数传递两个参数: + +- `$settings` - 编辑器[可配置设置](https://developer.wordpress.org/block-editor/reference-guides/filters/editor-filters/#editor-settings)的数组 +- `$context` - [`WP_Block_Editor_Context`](https://developer.wordpress.org/reference/classes/wp_block_editor_context/) 实例,包含当前编辑器信息的对象 + +以下示例将为无法激活插件的用户(管理员除外)禁用代码编辑器。将此代码添加到插件或主题的 `functions.php` 文件中进行测试: + +```php +add_filter( 'block_editor_settings_all', 'example_restrict_code_editor' ); + +function example_restrict_code_editor( $settings ) { + $can_active_plugins = current_user_can( 'activate_plugins' ); + + // 对无法激活插件的用户禁用代码编辑器 + if ( ! $can_active_plugins ) { + $settings[ 'codeEditingEnabled' ] = false; + } + + return $settings; +} +``` + +更多示例请参阅[编辑器钩子](https://developer.wordpress.org/block-editor/reference-guides/filters/editor-filters/)文档,包含以下用例: + +- [设置默认图片尺寸](https://developer.wordpress.org/block-editor/reference-guides/filters/editor-filters/#set-a-default-image-size) +- [禁用 Openverse](https://developer.wordpress.org/block-editor/reference-guides/filters/editor-filters/#disable-openverse) +- [禁用字体库](https://developer.wordpress.org/block-editor/reference-guides/filters/editor-filters/#disable-the-font-library) +- [禁用区块检查器标签页](https://developer.wordpress.org/block-editor/reference-guides/filters/editor-filters/#disable-block-inspector-tabs) + +## 服务端 theme.json 过滤器 + +theme.json 文件是控制界面选项的重要方式,但它仅支持全局或区块层级的修改,在某些场景下可能受限。 + +例如在前述章节中,我们使用 theme.json 全局禁用了颜色和版式控制。但假设您需要为管理员用户启用颜色设置。 + +为提供更灵活的配置,WordPress 6.1 引入了服务端过滤器,允许在四个数据层自定义 theme.json 数据: + +- [`wp_theme_json_data_default`](https://developer.wordpress.org/reference/hooks/wp_theme_json_data_default/) - 挂钩 WordPress 提供的默认数据 +- [`wp_theme_json_data_blocks`](https://developer.wordpress.org/reference/hooks/wp_theme_json_data_blocks/) - 挂钩区块提供的数据 +- [`wp_theme_json_data_theme`](https://developer.wordpress.org/reference/hooks/wp_theme_json_data_theme/) - 挂钩当前主题提供的数据 +- [`wp_theme_json_data_user`](https://developer.wordpress.org/reference/hooks/wp_theme_json_data_user/) - 挂钩用户提供的数据 + +以下示例使用 `wp_theme_json_data_theme` 过滤器更新当前主题的 theme.json 数据。如果当前用户是管理员,则会恢复颜色控制功能: + +```php +// 为除管理员外的所有用户禁用颜色控制 +function example_filter_theme_json_data_theme( $theme_json ){ + $is_administrator = current_user_can( 'edit_theme_options' ); + + if ( $is_administrator ) { + $new_data = array( + 'version' => 2, + 'settings' => array( + 'color' => array( + 'background' => true, + 'custom' => true, + 'customDuotone' => true, + 'customGradient' => true, + 'defaultGradients' => true, + 'defaultPalette' => true, + 'text' => true, + ), + ), + ); + } + + return $theme_json->update_with( $new_data ); +} +add_filter( 'wp_theme_json_data_theme', 'example_filter_theme_json_data_theme' ); +``` + +该过滤器会接收到包含对应数据层数据的 `WP_Theme_JSON_Data` 类实例。随后您需要向 `update_with( $new_data )` 方法传入符合 theme.json 规范的新数据。注意 `$new_data` 中必须包含 theme.json 版本号。 \ No newline at end of file diff --git a/how-to-guides/curating-the-editor-experience/patterns.md b/how-to-guides/curating-the-editor-experience/patterns.md new file mode 100644 index 0000000..09019a1 --- /dev/null +++ b/how-to-guides/curating-the-editor-experience/patterns.md @@ -0,0 +1,90 @@ +# 模式 + +区块[模式](https://developer.wordpress.org/block-editor/reference-guides/block-api/block-patterns/)是为用户提供独特定制编辑体验的最佳方式之一。 + +## 为所有文章类型优先设置起始模式 + +当用户创建新内容时,无论文章类型如何,都会面对一个空白画布。然而,通过设置特定类型的模式在创建新内容时优先显示,可以改善这一体验。当网站中存在声明支持`core/post-content`区块类型的模式时,用户每次创建新项目都会出现模态窗口。默认情况下,WordPress不包含任何此类模式,因此除非至少添加了两个此类文章内容模式,否则模态窗口不会出现。 + +要启用此功能,请在模式的区块类型中包含`core/post-content`。然后,可以通过文章类型选项控制模式应显示的文章类型。以下是一个在创建新文章时出现的模式示例: + +```php + + + +

详情

+ + + +

路线指引

+ + + +

预约确认

+ + + +

如需确认预约,请加入Make Slack中的#fse-outreach-experiment频道。

+ + + + + + + +
+

期待您的参与!

+
+ +``` + +在[WordPress 6.0开发说明中的页面创建模式](https://make.wordpress.org/core/2022/05/03/page-creation-patterns-in-wordpress-6-0/)中了解更多关于此功能的信息,并[注意WordPress 6.1将此功能扩展到所有文章类型](https://make.wordpress.org/core/2022/10/10/miscellaneous-editor-changes-for-wordpress-6-1/#start-content-patterns-for-all-post-types)。 + +## 为模板创建优先设置起始模式 + +与为新文章或页面优先设置模式类似,同样的体验可以添加到模板创建过程中。当模式声明支持'templateTypes'属性时,只要创建了符合指定条件的模板,这些模式就会出现,同时提供从空白状态开始或使用当前模板回退选项的选择。默认情况下,WordPress不包含任何此类模式。 + +要启用此功能,模式需要指定一个名为`templateTypes`的属性,这是一个包含可使用该模式作为完整内容的模板数组。以下是一个在创建404模板时出现的模式示例: + +```php +register_block_pattern( + 'wp-my-theme/404-template-pattern', + array( + 'title' => __( '404专用模板模式', 'wp-my-theme' ), + 'templateTypes' => array( '404' ), + 'content' => '

404模式

', + ) +); +``` + +在[WordPress 6.3开发说明中的新建模板模态窗口模式](https://make.wordpress.org/core/2023/07/18/miscellaneous-editor-changes-in-wordpress-6-3/#patterns-on-the-create-a-new-template-modal)中了解更多关于此功能的信息。 + +## 锁定模式 + +如先前关于锁定API的部分所述,模式本身的某些方面可以被锁定,以保护设计的重要部分。[这里有一个模式示例](https://gist.github.com/annezazu/acee30f8b6e8995e1b1a52796e6ef805),其中包含以不同方式锁定的各种区块。您可以在编辑器中构建这些模式,包括添加锁定选项,然后[按照文档注册它们](/docs/reference-guides/block-api/block-patterns.md)。 + +## 从模式目录中优先选择特定模式 + +从WordPress 6.0开始,主题可以通过theme.json从[模式目录](https://wordpress.org/patterns/)注册模式。为实现这一点,主题应使用theme.json中新的patterns顶级键。在此字段中,主题可以列出要从模式目录注册的模式。patterns字段是模式目录中模式别名的数组。模式别名可以从模式目录中单个模式视图的URL中提取。例如:此URL https://wordpress.org/patterns/pattern/partner-logos 的别名是partner-logos。 + +```json +{ + "patterns": [ "short-text-surrounded-by-round-images", "partner-logos" ] +} +``` + +内容创建者随后将在插入器的“模式”选项卡中,找到与模式目录分类匹配的相应模式。 + +## 附加资源 + +- [使用模板模式构建多个首页设计](https://developer.wordpress.org/news/2023/04/13/using-template-patterns-to-build-multiple-homepage-designs/)(WordPress开发者博客) \ No newline at end of file diff --git a/how-to-guides/curating-the-editor-experience/theme-json.md b/how-to-guides/curating-the-editor-experience/theme-json.md new file mode 100644 index 0000000..4f1807a --- /dev/null +++ b/how-to-guides/curating-the-editor-experience/theme-json.md @@ -0,0 +1,206 @@ +# theme.json + +主题的 theme.json 文件是定制编辑器体验的最佳方式之一,很可能是在使用更复杂解决方案之前最先用到的工具。 + +## 提供默认控件/选项 + +由于 theme.json 作为配置工具,可通过多种方式精细定义可用选项。本节将以双色调为例进行说明,该功能横跨多个区块并支持不同层级的访问权限。 + +*为所有图片相关区块启用核心双色调选项和自定义功能:* + +```json +{ + "version": 3, + "settings": { + "color": { + "customDuotone": true, + "duotone": [ + ] + } + } +} +``` + +*为所有图片相关区块提供主题预定义色彩选项、核心选项及自定义功能:* + +```json +{ + "version": 3, + "settings": { + "color": { + "duotone": [ + { + "colors": [ "#000000", "#ffffff" ], + "slug": "foreground-and-background", + "name": "前景色与背景色" + }, + { + "colors": [ "#000000", "#ff0200" ], + "slug": "foreground-and-secondary", + "name": "前景色与辅助色" + }, + { + "colors": [ "#000000", "#7f5dee" ], + "slug": "foreground-and-tertiary", + "name": "前景色与第三色" + }, + ] + } + } +} +``` + +*为文章特色图片区块提供预定义默认选项并开放全部自定义功能:* + +```json +{ + "version": 3, + "settings": { + "color": { + "custom": true, + "customDuotone": true + }, + "blocks": { + "core/post-featured-image": { + "color": { + "duotone": [ + { + "colors": [ "#282828", "#ff5837" ], + "slug": "black-and-orange", + "name": "黑橙配色" + }, + { + "colors": [ "#282828", "#0288d1" ], + "slug": "black-and-blue", + "name": "黑蓝配色" + } + ], + "customDuotone": true, + "custom": true + } + } + } + } +} +``` + +*为文章特色图片区块仅提供预定义默认选项和核心选项(禁用自定义功能):* + +```json +{ + "version": 3, + "settings": { + "color": { + "custom": true, + "customDuotone": true + }, + "blocks": { + "core/post-featured-image": { + "color": { + "duotone": [ + { + "colors": [ "#282828", "#ff5837" ], + "slug": "black-and-orange", + "name": "黑橙配色" + }, + { + "colors": [ "#282828", "#0288d1" ], + "slug": "black-and-blue", + "name": "黑蓝配色" + } + ], + "customDuotone": false, + "custom": false + } + } + } + } +} +``` + +## 使用 theme.json 限制界面选项 + +### 按区块限制选项 + +除了定义默认值,使用 theme.json 还可以完全移除某些选项,转而依赖主题预设配置。下图直观展示了同一段落区块的两种极端设置: + +![受限界面示意图](https://raw.githubusercontent.com/WordPress/gutenberg/HEAD/docs/assets/Locking%20comparison%20visual.png?raw=true) + +延续双色调的示例,这意味着您可以对图片区块开放全部双色调功能,而仅限制文章特色图片区块的权限: + +```json +{ + "version": 3, + "settings": { + "color": { + "custom": true, + "customDuotone": true + }, + "blocks": { + "core/image": { + "color": { + "duotone": [], + "customDuotone": true, + "custom": true + } + }, + "core/post-featured-image": { + "color": { + "duotone": [], + "customDuotone": false, + "custom": false + } + } + } + } +} +``` + +您可阅读[此文档](/docs/how-to-guides/themes/global-settings-and-styles.md)深入了解如何通过 theme.json 启用/禁用选项。 + +### 禁用继承默认布局 + +如需对群组区块等容器区块禁用“继承默认布局”设置,请移除以下配置段: + +```json +"layout": { + "contentSize": null, + "wideSize": null +}, +``` + +### 全局限制选项 + +在区块主题或经典主题中使用 theme.json 时,以下设置将全局禁用默认颜色与排版控件,大幅限制可用功能: + +```json +{ + "version": 3, + "settings": { + "layout": { + "contentSize": "750px" + }, + "color": { + "background": false, + "custom": false, + "customDuotone": false, + "customGradient": false, + "defaultGradients": false, + "defaultPalette": false, + "text": false + }, + "typography": { + "customFontSize": false, + "dropCap": false, + "fontStyle": false, + "fontWeight": false, + "letterSpacing": false, + "lineHeight": false, + "textDecoration": false, + "textTransform": false + } + } +} +``` + +若需启用上述某项功能,只需将对应值改为 `true` 即可实现更精细的控制。 \ No newline at end of file diff --git a/how-to-guides/data-basics/1-data-basics-setup.md b/how-to-guides/data-basics/1-data-basics-setup.md new file mode 100644 index 0000000..f1c4a71 --- /dev/null +++ b/how-to-guides/data-basics/1-data-basics-setup.md @@ -0,0 +1,216 @@ +# 环境设置 + +我们将把应用程序构建为WordPress插件,这意味着您需要先安装WordPress本体。其中一种安装方式是按照[快速入门](/docs/contributors/code/getting-started-with-code-contribution.md)页面的说明进行操作。完成环境配置后,您就可以继续学习本教程的后续内容。 + +另外,本教程将大量涉及Redux相关概念,例如状态(state)、操作(actions)和选择器(selectors)。如果您对这些概念不熟悉,建议先阅读[Redux入门指南](https://redux.js.org/introduction/getting-started)。 + +## 创建插件 + +我们将在WordPress插件中完成所有开发工作。首先在本地WordPress环境的`wp-content/plugins/my-first-gutenberg-app`目录中创建以下四个文件: + +- my-first-gutenberg-app.php - 用于创建新的管理页面 +- src/index.js - 存放JavaScript应用程序代码 +- src/style.css - 存放基础样式表 +- package.json - 用于构建流程配置 + +请使用以下代码片段创建这些文件: + +**src/index.js:** + +```js +import { createRoot } from 'react-dom'; +import './style.css'; + +function MyFirstApp() { + return Hello from JavaScript!; +} + +const root = createRoot( document.getElementById( 'my-first-gutenberg-app' ) ); +window.addEventListener( + 'load', + function () { + root.render( + , + ); + }, + false +); +``` + +**src/style.css:** + +```css +.toplevel_page_my-first-gutenberg-app #wpcontent { + background: #fff; + height: 1000px; +} +button .components-spinner { + width: 15px; + height: 15px; + margin-top: 0; + margin-bottom: 0; + margin-left: 0; +} +.form-buttons { + display: flex; +} +.my-gutenberg-form .form-buttons { + margin-top: 20px; + margin-left: 1px; +} +.form-error { + color: #cc1818; +} +.form-buttons button { + margin-right: 4px; +} +.form-buttons .components-spinner { + margin-top: 0; +} +#my-first-gutenberg-app { + max-width: 500px; +} +#my-first-gutenberg-app ul, +#my-first-gutenberg-app ul li { + list-style-type: disc; +} +#my-first-gutenberg-app ul { + padding-left: 20px; +} +#my-first-gutenberg-app .components-search-control__input { + height: 36px; + margin-left: 0; +} + +#my-first-gutenberg-app .list-controls { + display: flex; + width: 100%; +} + +#my-first-gutenberg-app .list-controls .components-search-control { + flex-grow: 1; + margin-right: 8px; +} +``` + +**my-first-gutenberg-app.php:** + +```php +Pages

+
+ '; + }, + 'dashicons-schedule', + 3 + ); +} + +add_action( 'admin_menu', 'my_admin_menu' ); + +function load_custom_wp_admin_scripts( $hook ) { + // 仅在 ?page=my-first-gutenberg-app 页面加载 + if ( 'toplevel_page_my-first-gutenberg-app' !== $hook ) { + return; + } + + // 加载必需的WordPress包 + + // 自动加载导入的依赖项和资源版本 + $asset_file = include plugin_dir_path( __FILE__ ) . 'build/index.asset.php'; + + // 入队CSS依赖 + foreach ( $asset_file['dependencies'] as $style ) { + wp_enqueue_style( $style ); + } + + // 加载我们的app.js + wp_register_script( + 'my-first-gutenberg-app', + plugins_url( 'build/index.js', __FILE__ ), + $asset_file['dependencies'], + $asset_file['version'] + ); + wp_enqueue_script( 'my-first-gutenberg-app' ); + + // 加载我们的style.css + wp_register_style( + 'my-first-gutenberg-app', + plugins_url( 'build/style-index.css', __FILE__ ), + array(), + $asset_file['version'] + ); + wp_enqueue_style( 'my-first-gutenberg-app' ); +} + +add_action( 'admin_enqueue_scripts', 'load_custom_wp_admin_scripts' ); +``` + +**package.json:** + +```json +{ + "name": "09-code-data-basics-esnext", + "version": "1.1.0", + "private": true, + "description": "My first Gutenberg App", + "author": "The WordPress Contributors", + "license": "GPL-2.0-or-later", + "keywords": [ + "WordPress", + "block" + ], + "homepage": "https://github.com/WordPress/gutenberg-examples/", + "repository": "git+https://github.com/WordPress/gutenberg-examples.git", + "bugs": { + "url": "https://github.com/WordPress/gutenberg-examples/issues" + }, + "main": "build/index.js", + "devDependencies": { + "@wordpress/scripts": "^24.0.0" + }, + "scripts": { + "build": "wp-scripts build", + "format": "wp-scripts format", + "lint:js": "wp-scripts lint-js", + "packages-update": "wp-scripts packages-update", + "start": "wp-scripts start" + } +} +``` + +## 配置构建流程 + +本教程假设读者熟悉ESNext语法和构建工具(如webpack)的概念。如果这些概念让您感到困惑,建议先阅读[JavaScript构建环境配置入门指南](/docs/how-to-guides/javascript/js-build-setup.md)。 + +要安装构建工具,请使用终端进入插件目录并运行`npm install`命令。 + +所有依赖项安装完成后,只需运行`npm start`即可!终端中将运行一个监听器。之后您可以在文本编辑器中随意编辑代码,每次保存后都会自动构建。 + +## 测试运行效果 + +现在进入插件页面,您应该能看到名为**My first Gutenberg App**的插件。请激活该插件,此时会出现一个标为_My first Gutenberg app_的新菜单项。点击该菜单项后,您将看到一个显示_Hello from JavaScript!_的页面: + +![](https://raw.githubusercontent.com/WordPress/gutenberg/HEAD/docs/how-to-guides/data-basics/media/setup/hello-from-js.jpg) + +恭喜!您现在可以开始构建应用程序了! + +## 后续步骤 + +- 上一部分:[介绍](/docs/how-to-guides/data-basics/README.md) +- 下一部分:[构建基础页面列表](/docs/how-to-guides/data-basics/2-building-a-list-of-pages.md) +- (可选)在block-development-examples仓库中查看[完整应用示例](https://github.com/WordPress/block-development-examples/tree/trunk/plugins/data-basics-59c8f8) \ No newline at end of file diff --git a/how-to-guides/data-basics/2-building-a-list-of-pages.md b/how-to-guides/data-basics/2-building-a-list-of-pages.md new file mode 100644 index 0000000..08d1fad --- /dev/null +++ b/how-to-guides/data-basics/2-building-a-list-of-pages.md @@ -0,0 +1,448 @@ +### 整合所有模块 + +所有组件已就位,太棒了!以下是我们应用的完整JavaScript代码: + +```js +import { useState } from 'react'; +import { createRoot } from 'react-dom'; +import { SearchControl, Spinner } from '@wordpress/components'; +import { useSelect } from '@wordpress/data'; +import { store as coreDataStore } from '@wordpress/core-data'; +import { decodeEntities } from '@wordpress/html-entities'; +import './style.css'; + +function MyFirstApp() { + const [ searchTerm, setSearchTerm ] = useState( '' ); + const { pages, hasResolved } = useSelect( + ( select ) => { + const query = {}; + if ( searchTerm ) { + query.search = searchTerm; + } + const selectorArgs = [ 'postType', 'page', query ]; + return { + pages: select( coreDataStore ).getEntityRecords( + ...selectorArgs + ), + hasResolved: select( coreDataStore ).hasFinishedResolution( + 'getEntityRecords', + selectorArgs + ), + }; + }, + [ searchTerm ] + ); + + return ( +
+ + +
+ ); +} + +function PagesList( { hasResolved, pages } ) { + if ( ! hasResolved ) { + return ; + } + if ( ! pages?.length ) { + return
暂无结果
; + } + + return ( +
+ + + + + + + { pages?.map( ( page ) => ( + + + + ) ) } + +
标题
{ decodeEntities( page.title.rendered ) }
+ ); +} + +const root = createRoot( + document.querySelector( '#my-first-gutenberg-app' ) +); +window.addEventListener( + 'load', + function () { + root.render( + + ); + }, + false +); +``` + +现在只需刷新页面即可体验全新的状态指示器: + +![搜索WordPress页面时显示的加载指示器](https://raw.githubusercontent.com/WordPress/gutenberg/HEAD/docs/how-to-guides/data-basics/media/list-of-pages/indicator.jpg) +![WordPress页面搜索未找到结果](https://raw.githubusercontent.com/WordPress/gutenberg/HEAD/docs/how-to-guides/data-basics/media/list-of-pages/no-results.jpg) + +## 后续步骤 + +* **上一部分:** [环境设置](/docs/how-to-guides/data-basics/1-data-basics-setup.md) +* **下一部分:** [构建编辑表单](/docs/how-to-guides/data-basics/3-building-an-edit-form.md) +* (可选)在block-development-examples代码库中查看[完整应用](https://github.com/WordPress/block-development-examples/tree/trunk/plugins/data-basics-59c8f8) + +### 使用核心数据替代直接调用API + +让我们稍作停顿,思考一下另一种可能采用的方法——直接操作API——所带来的弊端。设想我们直接发送API请求: + +```js +import apiFetch from '@wordpress/api-fetch'; +function MyFirstApp() { + // ... + const [pages, setPages] = useState( [] ); + useEffect( () => { + const url = '/wp-json/wp/v2/pages?search=' + searchTerm; + apiFetch( { url } ) + .then( setPages ) + }, [searchTerm] ); + // ... +} +``` + +在核心数据之外进行操作,我们需要解决两个问题。 + +首先,乱序更新。搜索“About”会触发五个API请求,分别过滤`A`、`Ab`、`Abo`、`Abou`和`About`。这些请求的完成顺序可能与启动顺序不同。有可能_search=A_在_search=About_之后才解析完成,从而导致我们显示错误的数据。 + +Gutenberg数据通过在幕后处理异步部分来解决这个问题。`useSelect`会记住最近的调用,并仅返回我们预期的数据。 + +其次,每次按键都会触发一个API请求。如果你输入`About`,删除它,然后重新输入,即使我们可以重用数据,也会总共发出10个请求。 + +Gutenberg数据通过缓存由`getEntityRecords()`触发的API请求的响应,并在后续调用中重用它们来解决这个问题。当其他组件依赖相同的实体记录时,这一点尤其重要。 + +总而言之,核心数据内置的工具旨在解决典型问题,以便你可以专注于应用程序本身。 + +## 步骤5:加载指示器 + +我们的搜索功能存在一个问题。我们无法完全确定它仍在搜索还是显示无结果: + +![未找到匹配搜索条件的WordPress页面](https://raw.githubusercontent.com/WordPress/gutenberg/HEAD/docs/how-to-guides/data-basics/media/list-of-pages/unclear-status.jpg) + +像“加载中...”或“无结果”这样的几条消息可以澄清状态。让我们来实现它们!首先,`PagesList`需要了解当前状态: + +```js +import { SearchControl, Spinner } from '@wordpress/components'; +function PagesList( { hasResolved, pages } ) { + if ( !hasResolved ) { + return + } + if ( !pages?.length ) { + return
无结果
+ } + // ... +} + +function MyFirstApp() { + // ... + + return ( +
+ // ... + +
+ ) +} +``` + +请注意,我们没有构建自定义的加载指示器,而是利用了[Spinner](https://developer.wordpress.org/block-editor/reference-guides/components/spinner/)组件。 + +我们仍然需要知道页面选择器`hasResolved`与否。我们可以使用`hasFinishedResolution`选择器来查明: + +`wp.data.select('core').hasFinishedResolution( 'getEntityRecords', [ 'postType', 'page', { search: 'home' } ] )` + +它接受选择器的名称和_你传递给该选择器的完全相同参数_,如果数据已加载则返回`true`,如果我们仍在等待则返回`false`。让我们将其添加到`useSelect`中: + +```js +import { useSelect } from '@wordpress/data'; +import { store as coreDataStore } from '@wordpress/core-data'; + +function MyFirstApp() { + // ... + const { pages, hasResolved } = useSelect( select => { + // ... + return { + pages: select( coreDataStore ).getEntityRecords( 'postType', 'page', query ), + hasResolved: + select( coreDataStore ).hasFinishedResolution( 'getEntityRecords', ['postType', 'page', query] ), + } + }, [searchTerm] ); + + // ... +} +``` + +还有最后一个问题。很容易出现拼写错误,最终传递给`getEntityRecords`和`hasFinishedResolution`的参数不同。确保它们完全相同至关重要。我们可以通过将参数存储在变量中来消除这种风险: + +```js +import { useSelect } from '@wordpress/data'; +import { store as coreDataStore } from '@wordpress/core-data'; +function MyFirstApp() { + // ... + const { pages, hasResolved } = useSelect( select => { + // ... + const selectorArgs = [ 'postType', 'page', query ]; + return { + pages: select( coreDataStore ).getEntityRecords( ...selectorArgs ), + hasResolved: + select( coreDataStore ).hasFinishedResolution( 'getEntityRecords', selectorArgs ), + } + }, [searchTerm] ); + + // ... +} +``` + +瞧!大功告成。 + +# 构建页面列表 + +在这一部分,我们将构建一个可筛选的WordPress页面列表。本节完成后,应用将呈现如下效果: + +![可搜索的WordPress页面列表](https://raw.githubusercontent.com/WordPress/gutenberg/HEAD/docs/how-to-guides/data-basics/media/list-of-pages/part1-finished.jpg) + +让我们逐步了解实现过程。 + +## 步骤一:构建PagesList组件 + +首先构建一个基础React组件来展示页面列表: + +```js +function MyFirstApp() { + const pages = [{ id: 'mock', title: '示例页面' }] + return ; +} + +function PagesList( { pages } ) { + return ( +
    + { pages?.map( page => ( +
  • + { page.title } +
  • + ) ) } +
+ ); +} +``` + +注意该组件尚未获取真实数据,仅展示预设的页面列表。刷新页面后你将看到: + +![显示示例页面的WordPress页面列表](https://raw.githubusercontent.com/WordPress/gutenberg/HEAD/docs/how-to-guides/data-basics/media/list-of-pages/simple-list.jpg) + +## 步骤二:获取数据 + +预设的示例页面并不实用。我们需要从[WordPress REST API](https://developer.wordpress.org/rest-api/)获取真实的页面列表。 + +首先请确保存在可获取的页面数据。在WPAdmin中通过侧边栏菜单进入“页面”栏目,确认至少存在四到五个页面: + +![WordPress后台页面列表](https://raw.githubusercontent.com/WordPress/gutenberg/HEAD/docs/how-to-guides/data-basics/media/list-of-pages/pages-list.jpg) + +若页面不足,请创建新页面(可参考上图所示标题),注意务必执行*发布*操作而非仅*保存*。 + +接下来我们使用[`@wordpress/core-data`](https://github.com/WordPress/gutenberg/tree/trunk/packages/core-data)包来处理WordPress核心API,该包基于[`@wordpress/data`](https://github.com/WordPress/gutenberg/tree/trunk/packages/data)包构建。 + +通过[`getEntityRecords`](/docs/reference-guides/data/data-core/#getentityrecords)选择器获取页面列表,该选择器会自动发起API请求、缓存结果并返回记录列表: + +```js +wp.data.select( 'core' ).getEntityRecords( 'postType', 'page' ) +``` + +在浏览器开发者工具中运行此代码会返回`null`,因为首次运行选择器后,`getEntityRecords`解析器才会请求页面数据。稍等片刻再次运行即可获取完整页面列表。 + +*注意:直接运行此命令需确保浏览器当前显示区块编辑器界面(任意页面均可),否则`select( 'core' )`函数将不可用并报错。* + +同理,`MyFirstApp`组件需要在数据就绪后重新运行选择器,这正是`useSelect`钩子的作用: + +```js +import { useSelect } from '@wordpress/data'; +import { store as coreDataStore } from '@wordpress/core-data'; + +function MyFirstApp() { + const pages = useSelect( + select => + select( coreDataStore ).getEntityRecords( 'postType', 'page' ), + [] + ); + // ... +} + +function PagesList({ pages }) { + // ... +
  • + {page.title.rendered} +
  • + // ... +} +``` + +注意我们在index.js中使用`import`语句,这使得插件能通过`wp_enqueue_script`自动加载依赖。所有对`coreDataStore`的引用都会被编译为浏览器开发工具中使用的`wp.data`引用。 + +`useSelect`接收两个参数:回调和依赖项。其作用是在依赖项或底层数据存储变更时重新执行回调。可在[数据模块文档](/packages/data/README.md#useselect)中深入了解[useSelect](/packages/data/README.md#useselect)。 + +完整代码如下: + +```js +import { useSelect } from '@wordpress/data'; +import { store as coreDataStore } from '@wordpress/core-data'; +import { decodeEntities } from '@wordpress/html-entities'; + +function MyFirstApp() { + const pages = useSelect( + select => + select( coreDataStore ).getEntityRecords( 'postType', 'page' ), + [] + ); + return ; +} + +function PagesList( { pages } ) { + return ( +
      + { pages?.map( page => ( +
    • + { decodeEntities( page.title.rendered ) } +
    • + ) ) } +
    + ) +} +``` + +注意文章标题可能包含HTML实体(如`á`),需要使用[`decodeEntities`](https://developer.wordpress.org/block-editor/reference-guides/packages/packages-html-entities/)函数将其转换为对应符号(如`á`)。 + +刷新页面后将显示类似这样的列表: + +![网站页面列表](https://raw.githubusercontent.com/WordPress/gutenberg/HEAD/docs/how-to-guides/data-basics/media/list-of-pages/fetch-the-data.jpg) + +## 步骤三:转换为表格形式 + +```js +function PagesList( { pages } ) { + return ( + + + + + + + + { pages?.map( page => ( + + + + ) ) } + +
    标题
    { decodeEntities( page.title.rendered ) }
    + ); +} +``` + +![展示网站页面标题的表格](https://raw.githubusercontent.com/WordPress/gutenberg/HEAD/docs/how-to-guides/data-basics/media/list-of-pages/make-a-table.jpg) + +## 步骤四:添加搜索框 + +当前页面列表虽然简短,但随着内容增长,操作会愈发困难。WordPress管理员通常通过搜索框解决这个问题——现在让我们也来实现一个! + +首先添加搜索字段: + +```js +import { useState } from 'react'; +import { SearchControl } from '@wordpress/components'; + +function MyFirstApp() { + const [searchTerm, setSearchTerm] = useState( '' ); + // ... + return ( +
    + + {/* ... */ } +
    + ) +} +``` + +请注意,这里我们并未使用原生`input`标签,而是利用了[SearchControl](https://developer.wordpress.org/block-editor/reference-guides/components/search-control/)组件。其实际效果如下: + +![可搜索的WordPress页面列表](https://raw.githubusercontent.com/WordPress/gutenberg/HEAD/docs/how-to-guides/data-basics/media/list-of-pages/filter-field.jpg) + +搜索框初始为空,输入内容会存储在`searchTerm`状态值中。若您不熟悉[useState](https://react.dev/reference/react/useState)钩子函数,可查阅[React官方文档](https://react.dev/reference/react/useState)了解更多。 + +现在我们可以仅请求匹配`searchTerm`的页面数据。查阅[WordPress API文档](https://developer.wordpress.org/rest-api/reference/pages/)可知,[/wp/v2/pages](https://developer.wordpress.org/rest-api/reference/pages/)接口支持`search`查询参数,用于_限定返回匹配字符串的结果_。具体使用方法如下: + +```js +wp.data.select( 'core' ).getEntityRecords( 'postType', 'page', { search: 'home' } ) +``` + +在浏览器开发者工具中运行此代码段,将触发请求至`/wp/v2/pages?search=home`(而非基础的`/wp/v2/pages`)。 + +接下来在`useSelect`调用中实现对应逻辑: + +```js +import { useSelect } from '@wordpress/data'; +import { store as coreDataStore } from '@wordpress/core-data'; + +function MyFirstApp() { + // ... + const { pages } = useSelect( select => { + const query = {}; + if ( searchTerm ) { + query.search = searchTerm; + } + return { + pages: select( coreDataStore ).getEntityRecords( 'postType', 'page', query ) + } + }, [searchTerm] ); + + // ... +} +``` + +当存在搜索词时,`searchTerm`将作为`search`查询参数使用。请注意,`searchTerm`也被列入`useSelect`的依赖项数组,确保在搜索词变更时重新执行`getEntityRecords`。 + +最终整合后的`MyFirstApp`组件代码如下: + +```js +import { useState } from 'react'; +import { createRoot } from 'react-dom'; +import { SearchControl } from '@wordpress/components'; +import { useSelect } from '@wordpress/data'; +import { store as coreDataStore } from '@wordpress/core-data'; + +function MyFirstApp() { + const [searchTerm, setSearchTerm] = useState( '' ); + const pages = useSelect( select => { + const query = {}; + if ( searchTerm ) { + query.search = searchTerm; + } + return select( coreDataStore ).getEntityRecords( 'postType', 'page', query ); + }, [searchTerm] ); + + return ( +
    + + +
    + ) +} +``` + +大功告成!现在我们可以对结果进行筛选了: + +![筛选后的WordPress页面列表显示'关于我们'](https://raw.githubusercontent.com/WordPress/gutenberg/HEAD/docs/how-to-guides/data-basics/media/list-of-pages/filter.jpg) \ No newline at end of file diff --git a/how-to-guides/data-basics/3-building-an-edit-form.md b/how-to-guides/data-basics/3-building-an-edit-form.md new file mode 100644 index 0000000..c9d529b --- /dev/null +++ b/how-to-guides/data-basics/3-building-an-edit-form.md @@ -0,0 +1,552 @@ +## 接下来做什么? + +* **上一篇:** [构建页面列表](/docs/how-to-guides/data-basics/2-building-a-list-of-pages.md) +* **下一篇:** [构建创建页面表单](/docs/how-to-guides/data-basics/4-building-a-create-page-form.md) +* (可选)在 block-development-examples 代码库中查看[完整示例应用](https://github.com/WordPress/block-development-examples/tree/trunk/plugins/data-basics-59c8f8) + +# 构建编辑表单 + +本节内容将为我们的应用添加*编辑*功能。以下是即将构建功能的预览图: + +![](https://raw.githubusercontent.com/WordPress/gutenberg/HEAD/docs/how-to-guides/data-basics/media/edit-form/form-finished.png) + +### 步骤一:添加“编辑”按钮 + +要实现*编辑*功能首先需要添加编辑按钮,让我们从在`PagesList`组件中添加按钮开始: + +```js +import { Button } from '@wordpress/components'; +import { decodeEntities } from '@wordpress/html-entities'; + +const PageEditButton = () => ( + +) + +function PagesList( { hasResolved, pages } ) { + if ( ! hasResolved ) { + return ; + } + if ( ! pages?.length ) { + return
    暂无结果
    ; + } + + return ( + + + + + + + + + { pages?.map( ( page ) => ( + + + + + ) ) } + +
    标题操作
    { decodeEntities( page.title.rendered ) } + +
    + ); +} +``` + +`PagesList`组件中唯一的变化是新增了标为“操作”的列: + +![](https://raw.githubusercontent.com/WordPress/gutenberg/HEAD/docs/how-to-guides/data-basics/media/edit-form/edit-button.png) + +### 步骤二:显示编辑表单 + +我们的按钮外观不错但尚未实现功能。要显示编辑表单,首先需要创建它: + +```js +import { Button, TextControl } from '@wordpress/components'; +function EditPageForm( { pageId, onCancel, onSaveFinished } ) { + return ( +
    + +
    + + +
    +
    + ); +} +``` + +现在让按钮触发显示刚创建的编辑表单。由于本教程不侧重网页设计,我们将使用需要最少代码量的[`Modal`](https://developer.wordpress.org/block-editor/reference-guides/components/modal/)组件将两者连接。更新`PageEditButton`如下: + +```js +import { Button, Modal, TextControl } from '@wordpress/components'; + +function PageEditButton({ pageId }) { + const [ isOpen, setOpen ] = useState( false ); + const openModal = () => setOpen( true ); + const closeModal = () => setOpen( false ); + return ( + <> + + { isOpen && ( + + + + ) } + + ) +} +``` + +现在点击*编辑*按钮,您将看到如下模态框: + +![](https://raw.githubusercontent.com/WordPress/gutenberg/HEAD/docs/how-to-guides/data-basics/media/edit-form/form-scaffold.png) + +很好!我们现在有了可操作的基础用户界面。 + +### 步骤三:在表单中填充页面详情 + +我们需要让`EditPageForm`显示当前编辑页面的标题。您可能注意到它并未接收`page`属性,仅接收`pageId`。这没有问题,Gutenberg Data让我们能够轻松在任何组件中访问实体记录。 + +这里我们需要使用[`getEntityRecord`](/docs/reference-guides/data/data-core.md#getentityrecord)选择器。得益于`MyFirstApp`中的`getEntityRecords`调用,记录列表已准备就绪,甚至无需发起额外的HTTP请求——我们可以直接获取缓存记录。 + +您可以在浏览器开发工具中这样尝试: + +```js +wp.data.select( 'core' ).getEntityRecord( 'postType', 'page', 9 ); // 将9替换为实际页面ID +``` + +接下来更新`EditPageForm`: + +```js +function EditPageForm( { pageId, onCancel, onSaveFinished } ) { + const page = useSelect( + select => select( coreDataStore ).getEntityRecord( 'postType', 'page', pageId ), + [pageId] + ); + return ( +
    + + { /* ... */ } +
    + ); +} +``` + +现在效果应如图所示: + +![](https://raw.githubusercontent.com/WordPress/gutenberg/HEAD/docs/how-to-guides/data-basics/media/edit-form/form-populated.png) + +### 步骤五:保存表单数据 + +既然我们已经能够编辑页面标题,接下来要确保能够保存它。在 Gutenberg 数据系统中,我们使用 `saveEditedEntityRecord` 操作将变更保存到 WordPress REST API。该操作会发送请求、处理结果,并更新 Redux 状态中的缓存数据。 + +以下示例可在浏览器开发者工具中尝试: + +```js +// 将数字9替换为实际页面ID +wp.data.dispatch( 'core' ).editEntityRecord( 'postType', 'page', 9, { title: '更新后的标题' } ); +wp.data.dispatch( 'core' ).saveEditedEntityRecord( 'postType', 'page', 9 ); +``` + +以上代码片段保存了新标题。与之前不同,现在 `getEntityRecord` 会反映更新后的标题: + +```js +// 将数字9替换为实际页面ID +wp.data.select( 'core' ).getEntityRecord( 'postType', 'page', 9 ).title.rendered +// 返回:"更新后的标题" +``` + +在 REST API 请求完成后,实体记录会立即更新以反映所有已保存的变更。 + +这是带有生效*保存*按钮的 `EditPageForm` 组件示例: + +```js +function EditPageForm( { pageId, onCancel, onSaveFinished } ) { + // ... + const { saveEditedEntityRecord } = useDispatch( coreDataStore ); + const handleSave = () => saveEditedEntityRecord( 'postType', 'page', pageId ); + + return ( +
    + {/* ... */} +
    + + {/* ... */} +
    +
    + ); +} +``` + +虽然功能已实现,但还需修复一个问题:表单模态框不会自动关闭,因为我们从未调用 `onSaveFinished`。幸运的是,`saveEditedEntityRecord` 返回的 Promise 会在保存操作完成后解析。让我们在 `EditPageForm` 中利用这个特性: + +```js +function EditPageForm( { pageId, onCancel, onSaveFinished } ) { + // ... + const handleSave = async () => { + await saveEditedEntityRecord( 'postType', 'page', pageId ); + onSaveFinished(); + }; + // ... +} +``` + +### 步骤六:错误处理 + +此前我们乐观地假设*保存*操作总能成功。但实际操作可能因以下原因失败: + +* 网站可能宕机 +* 更新内容可能无效 +* 页面可能已被他人删除 + +为了在出现这些问题时通知用户,我们需要进行两处调整。当更新失败时,我们不希望关闭表单模态框。仅当更新确实成功时,`saveEditedEntityRecord` 返回的 Promise 才会解析为更新后的记录。若出现异常,则会解析为空值。我们可以利用这一点来保持模态框开启状态: + +```js +function EditPageForm( { pageId, onSaveFinished } ) { + // ... + const handleSave = async () => { + const updatedRecord = await saveEditedEntityRecord( 'postType', 'page', pageId ); + if ( updatedRecord ) { + onSaveFinished(); + } + }; + // ... +} +``` + +很好!现在让我们来显示错误信息。可以通过 `getLastEntitySaveError` 选择器获取失败详情: + +```js +// 将数字9替换为实际页面ID +wp.data.select( 'core' ).getLastEntitySaveError( 'postType', 'page', 9 ) +``` + +以下是在 `EditPageForm` 中的具体应用: + +```js +function EditPageForm( { pageId, onSaveFinished } ) { + // ... + const { lastError, page } = useSelect( + select => ({ + page: select( coreDataStore ).getEditedEntityRecord( 'postType', 'page', pageId ), + lastError: select( coreDataStore ).getLastEntitySaveError( 'postType', 'page', pageId ) + }), + [ pageId ] + ) + // ... + return ( +
    + {/* ... */} + { lastError ? ( +
    + 错误:{ lastError.message } +
    + ) : false } + {/* ... */} +
    + ); +} +``` + +太棒了!现在 `EditPageForm` 已能完全感知错误状态。 + +让我们通过触发无效更新来查看错误提示效果。由于文章标题很难引发错误,我们可以将 `date` 属性设置为 `-1` —— 这必定会触发验证错误: + +```js +function EditPageForm( { pageId, onCancel, onSaveFinished } ) { + // ... + const handleChange = ( title ) => editEntityRecord( 'postType', 'page', pageId, { title, date: -1 } ); + // ... +} +``` + +刷新页面后,打开表单修改标题并点击保存,您将看到如下错误提示: + +![](https://raw.githubusercontent.com/WordPress/gutenberg/HEAD/docs/how-to-guides/data-basics/media/edit-form/form-error.png) + +非常好!现在我们可以**恢复 `handleChange` 函数的先前版本**,继续下一步操作。 + +### 步骤七:状态指示器 + +我们的表单还存在最后一个问题:缺乏视觉反馈。在表单消失或显示错误信息之前,我们无法完全确定*保存*按钮是否生效。 + +现在我们将解决这个问题,并向用户传达两种状态:_保存中_和_未检测到更改_。相关的选择器是`isSavingEntityRecord`和`hasEditsForEntityRecord`。与`getEntityRecord`不同,这些选择器从不发起HTTP请求,仅返回当前实体记录状态。 + +让我们在`EditPageForm`中使用它们: + +```js +function EditPageForm( { pageId, onSaveFinished } ) { + // ... + const { isSaving, hasEdits, /* ... */ } = useSelect( + select => ({ + isSaving: select( coreDataStore ).isSavingEntityRecord( 'postType', 'page', pageId ), + hasEdits: select( coreDataStore ).hasEditsForEntityRecord( 'postType', 'page', pageId ), + // ... + }), + [ pageId ] + ) +} +``` + +现在我们可以使用`isSaving`和`hasEdits`在保存过程中显示加载动画,并在无编辑内容时禁用保存按钮: + +```js +function EditPageForm( { pageId, onSaveFinished } ) { + // ... + return ( + // ... +
    + + +
    + // ... + ); +} +``` + +请注意,当没有编辑内容或页面正在保存时,我们会禁用*保存*按钮。这是为了防止用户意外重复点击按钮。 + +此外,由于`@wordpress/data`不支持中断正在进行的*保存*操作,我们也相应禁用了*取消*按钮。 + +实际运行效果如下: + +![](https://raw.githubusercontent.com/WordPress/gutenberg/HEAD/docs/how-to-guides/data-basics/media/edit-form/form-inactive.png) +![](https://raw.githubusercontent.com/WordPress/gutenberg/HEAD/docs/how-to-guides/data-basics/media/edit-form/form-spinner.png) + +### 整体联调 + +所有组件都已就位,太棒了!以下是我们本章构建的完整代码: + +```js +import { useDispatch } from '@wordpress/data'; +import { Button, Modal, TextControl } from '@wordpress/components'; + +function PageEditButton( { pageId } ) { + const [ isOpen, setOpen ] = useState( false ); + const openModal = () => setOpen( true ); + const closeModal = () => setOpen( false ); + return ( + <> + + { isOpen && ( + + + + ) } + + ); +} + +function EditPageForm( { pageId, onCancel, onSaveFinished } ) { + const { page, lastError, isSaving, hasEdits } = useSelect( + ( select ) => ( { + page: select( coreDataStore ).getEditedEntityRecord( 'postType', 'page', pageId ), + lastError: select( coreDataStore ).getLastEntitySaveError( 'postType', 'page', pageId ), + isSaving: select( coreDataStore ).isSavingEntityRecord( 'postType', 'page', pageId ), + hasEdits: select( coreDataStore ).hasEditsForEntityRecord( 'postType', 'page', pageId ), + } ), + [ pageId ] + ); + + const { saveEditedEntityRecord, editEntityRecord } = useDispatch( coreDataStore ); + const handleSave = async () => { + const savedRecord = await saveEditedEntityRecord( 'postType', 'page', pageId ); + if ( savedRecord ) { + onSaveFinished(); + } + }; + const handleChange = ( title ) => editEntityRecord( 'postType', 'page', page.id, { title } ); + + return ( +
    + + { lastError ? ( +
    错误:{ lastError.message }
    + ) : ( + false + ) } +
    + + +
    +
    + ); +} +``` + +### 步骤四:实现页面标题字段的可编辑功能 + +我们的*页面标题*字段存在一个问题:无法编辑。它接收固定值但在输入时不会更新。我们需要一个 `onChange` 处理函数。 + +您可能在其他 React 应用中也见过类似的模式,这被称为["受控组件"](https://reactjs.org/docs/forms.html#controlled-components): + +```js +function VanillaReactForm({ initialTitle }) { + const [title, setTitle] = useState( initialTitle ); + return ( + + ); +} +``` + +在 Gutenberg 数据中更新实体记录与此类似,但不同之处在于,我们不使用 `setTitle` 将数据存储在本地(组件级别)状态,而是使用 `editEntityRecord` 操作将更新存储在 *Redux* 状态中。以下是在浏览器的开发工具中尝试的方法: + +```js +// 我们需要一个有效的页面 ID 来调用 editEntityRecord,因此使用 getEntityRecords 获取第一个可用的 ID。 +const pageId = wp.data.select( 'core' ).getEntityRecords( 'postType', 'page' )[0].id; + +// 更新标题 +wp.data.dispatch( 'core' ).editEntityRecord( 'postType', 'page', pageId, { title: '更新后的标题' } ); +``` + +此时,您可能会问,`editEntityRecord` 比 `useState` 好在哪里?答案是它提供了一些额外功能。 + +首先,我们可以像检索数据一样轻松地保存更改,并确保所有缓存都能正确更新。 + +其次,通过 `editEntityRecord` 应用的更改可以通过 `undo` 和 `redo` 操作轻松撤销或重做。 + +最后,由于更改存储在 *Redux* 状态中,它们是“全局的”,可以被其他组件访问。例如,我们可以让 `PagesList` 显示当前编辑的标题。 + +关于最后一点,让我们看看使用 `getEntityRecord` 访问刚刚更新的实体记录时会发生什么: + +```js +wp.data.select( 'core' ).getEntityRecord( 'postType', 'page', pageId ).title +``` + +它并未反映编辑后的内容。这是怎么回事? + +实际上,`` 渲染的是 `getEntityRecord()` 返回的数据。如果 `getEntityRecord()` 反映了更新后的标题,那么用户在 `TextControl` 中输入的任何内容也会立即显示在 `` 中。这并不是我们想要的效果。在用户决定保存之前,编辑内容不应泄漏到表单之外。 + +Gutenberg 数据通过区分*实体记录*和*已编辑的实体记录*来解决这个问题。*实体记录*反映来自 API 的数据,忽略任何本地编辑,而*已编辑的实体记录*则在数据基础上应用了所有本地编辑。两者同时存在于 Redux 状态中。 + +让我们看看调用 `getEditedEntityRecord` 会发生什么: + +```js +wp.data.select( 'core' ).getEditedEntityRecord( 'postType', 'page', pageId ).title +// "更新后的标题" + +wp.data.select( 'core' ).getEntityRecord( 'postType', 'page', pageId ).title +// { "rendered": "<原始未更改的标题>", "raw": "..." } +``` + +如您所见,实体记录的 `title` 是一个对象,而已编辑实体记录的 `title` 是一个字符串。 + +这并非偶然。像 `title`、`excerpt` 和 `content` 这样的字段可能包含[短代码](https://developer.wordpress.org/apis/handbook/shortcode/)或[动态区块](/docs/how-to-guides/block-tutorial/creating-dynamic-blocks.md),这意味着它们只能在服务器上渲染。对于这些字段,REST API 同时暴露了 `raw` 标记和 `rendered` 字符串。例如,在区块编辑器中,`content.rendered` 可用于视觉预览,而 `content.raw` 可用于填充代码编辑器。 + +那么,为什么已编辑实体记录的 `content` 是一个字符串?由于 JavaScript 无法正确渲染任意的区块标记,它仅存储 `raw` 标记,而不包含 `rendered` 部分。由于这是一个字符串,整个字段就变成了字符串。 + +现在我们可以相应地更新 `EditPageForm`。我们可以使用 [`useDispatch`](/packages/data/README.md#usedispatch) 钩子访问操作,类似于使用 `useSelect` 访问选择器: + +```js +import { useDispatch } from '@wordpress/data'; + +function EditPageForm( { pageId, onCancel, onSaveFinished } ) { + const page = useSelect( + select => select( coreDataStore ).getEditedEntityRecord( 'postType', 'page', pageId ), + [ pageId ] + ); + const { editEntityRecord } = useDispatch( coreDataStore ); + const handleChange = ( title ) => editEntityRecord( 'postType', 'page', pageId, { title } ); + + return ( +
    + +
    + + +
    +
    + ); +} +``` + +我们添加了一个 `onChange` 处理函数,通过 `editEntityRecord` 操作跟踪编辑,然后将选择器更改为 `getEditedEntityRecord`,以便 `page.title` 始终反映更改。 + +现在的效果如下: + +![](https://raw.githubusercontent.com/WordPress/gutenberg/HEAD/docs/how-to-guides/data-basics/media/edit-form/form-editable.png) \ No newline at end of file diff --git a/how-to-guides/data-basics/4-building-a-create-page-form.md b/how-to-guides/data-basics/4-building-a-create-page-form.md new file mode 100644 index 0000000..493e38d --- /dev/null +++ b/how-to-guides/data-basics/4-building-a-create-page-form.md @@ -0,0 +1,395 @@ +# 构建创建页面表单 + +在[上一章节](/docs/how-to-guides/data-basics/3-building-an-edit-form.md)中我们创建了*编辑页面*功能,本章节我们将新增*创建页面*功能。以下是我们即将构建功能的预览: + +![](https://raw.githubusercontent.com/WordPress/gutenberg/HEAD/docs/how-to-guides/data-basics/media/create-form/create-form-with-text.png) + +### 步骤一:添加“创建新页面”按钮 + +首先我们构建一个用于显示创建页面表单的按钮,这与我们在[第三章节](/docs/how-to-guides/data-basics/3-building-an-edit-form.md)构建的编辑按钮类似: + +```js +import { useDispatch } from '@wordpress/data'; +import { Button, Modal, TextControl } from '@wordpress/components'; + +function CreatePageButton() { + const [isOpen, setOpen] = useState( false ); + const openModal = () => setOpen( true ); + const closeModal = () => setOpen( false ); + return ( + <> + + { isOpen && ( + + + + ) } + + ); +} + +function CreatePageForm() { + // 暂时留空 + return
    ; +} +``` + +很好!现在让`MyFirstApp`显示我们全新的按钮: + +```js +function MyFirstApp() { + // ... + return ( +
    +
    + + +
    + +
    + ); +} +``` + +最终效果如下所示: + +![](https://raw.githubusercontent.com/WordPress/gutenberg/HEAD/docs/how-to-guides/data-basics/media/create-form/create-button.png) + +### 步骤二:提取受控页面表单 + +按钮就位后,我们可以全力构建表单。本教程重点在于数据管理,因此不会构建完整的页面编辑器。表单将仅包含一个字段:文章标题。 + +幸运的是,我们在[第三章节](/docs/how-to-guides/data-basics/3-building-an-edit-form.md)构建的`EditPageForm`已经实现了80%的功能。大部分用户界面已就绪,我们将在`CreatePageForm`中复用这些组件。首先将表单UI提取为独立组件: + +```js +function EditPageForm( { pageId, onCancel, onSaveFinished } ) { + // ... + return ( + + ); +} + +function PageForm( { title, onChangeTitle, hasEdits, lastError, isSaving, onCancel, onSave } ) { + return ( +
    + + { lastError ? ( +
    错误:{ lastError.message }
    + ) : ( + false + ) } +
    + + +
    +
    + ); +} +``` + +这段代码的质量优化不应改变应用程序的任何功能。让我们尝试编辑页面来确认: + +![](https://raw.githubusercontent.com/WordPress/gutenberg/HEAD/docs/how-to-guides/data-basics/media/create-form/edit-page-form.png) + +很好!编辑表单依然存在,现在我们有了构建新`CreatePageForm`的基础模块。 + +### 整合所有代码 + +以下是本章节构建的全部内容: + +```js +function CreatePageForm( { onCancel, onSaveFinished } ) { + const [title, setTitle] = useState(); + const { lastError, isSaving } = useSelect( + ( select ) => ( { + lastError: select( coreDataStore ) + .getLastEntitySaveError( 'postType', 'page' ), + isSaving: select( coreDataStore ) + .isSavingEntityRecord( 'postType', 'page' ), + } ), + [] + ); + + const { saveEntityRecord } = useDispatch( coreDataStore ); + const handleSave = async () => { + const savedRecord = await saveEntityRecord( + 'postType', + 'page', + { title, status: 'publish' } + ); + if ( savedRecord ) { + onSaveFinished(); + } + }; + + return ( + + ); +} + +function EditPageForm( { pageId, onCancel, onSaveFinished } ) { + const { page, lastError, isSaving, hasEdits } = useSelect( + ( select ) => ( { + page: select( coreDataStore ).getEditedEntityRecord( 'postType', 'page', pageId ), + lastError: select( coreDataStore ).getLastEntitySaveError( 'postType', 'page', pageId ), + isSaving: select( coreDataStore ).isSavingEntityRecord( 'postType', 'page', pageId ), + hasEdits: select( coreDataStore ).hasEditsForEntityRecord( 'postType', 'page', pageId ), + } ), + [pageId] + ); + + const { saveEditedEntityRecord, editEntityRecord } = useDispatch( coreDataStore ); + const handleSave = async () => { + const savedRecord = await saveEditedEntityRecord( 'postType', 'page', pageId ); + if ( savedRecord ) { + onSaveFinished(); + } + }; + const handleChange = ( title ) => editEntityRecord( 'postType', 'page', page.id, { title } ); + + return ( + + ); +} + +function PageForm( { title, onChangeTitle, hasEdits, lastError, isSaving, onCancel, onSave } ) { + return ( +
    + + { lastError ? ( +
    错误:{ lastError.message }
    + ) : ( + false + ) } +
    + + +
    +
    + ); +} +``` + +现在只需刷新页面即可体验表单功能: + +![](https://raw.githubusercontent.com/WordPress/gutenberg/HEAD/docs/how-to-guides/data-basics/media/create-form/create-form-with-text.png) + +## 后续步骤 + +* **下一章节:** [添加删除按钮](/docs/how-to-guides/data-basics/5-adding-a-delete-button.md) +* **上一章节:** [构建编辑表单](/docs/how-to-guides/data-basics/3-building-an-edit-form.md) +* (可选)在 block-development-examples 代码库中查看[完整应用](https://github.com/WordPress/block-development-examples/tree/trunk/plugins/data-basics-59c8f8) + +### 步骤三:构建CreatePageForm组件 + +`CreatePageForm`组件只需提供渲染`PageForm`组件所需的七个属性: + +* 标题 +* 标题变更处理函数 +* 编辑状态标识 +* 最后错误信息 +* 保存状态标识 +* 取消处理函数 +* 保存处理函数 + +具体实现如下: + +#### 标题、标题变更处理、编辑状态 + +`EditPageForm`组件更新并保存的是Redux状态中已存在的实体记录,因此我们依赖`editedEntityRecords`选择器。 + +而`CreatePageForm`不存在预先的实体记录,只有空表单。用户输入的内容仅存在于本地表单,可通过React的`useState`钩子进行跟踪: + +```js +function CreatePageForm( { onCancel, onSaveFinished } ) { + const [title, setTitle] = useState(); + const handleChange = ( title ) => setTitle( title ); + return ( + + ); +} +``` + +#### 保存处理、取消处理 + +在`EditPageForm`中,我们通过`saveEditedEntityRecord('postType', 'page', pageId )`操作保存Redux状态中的编辑内容。 + +但`CreatePageForm`既无Redux状态中的编辑内容,也无pageId。此时需要调用的是[`saveEntityRecord`](https://developer.wordpress.org/block-editor/reference-guides/data/data-core/#saveentityrecord)操作(名称中不含Edited),它接收的是代表新实体记录的对象而非pageId。 + +传递给`saveEntityRecord`的数据会通过POST请求发送到对应REST API接口。例如执行以下操作: + +```js +saveEntityRecord( 'postType', 'page', { title: "测试页面" } ); +``` + +将向[WordPress页面REST API接口](/wp/v2/pages)发起POST请求,请求体中包含单个字段:`title=测试页面`。 + +现在我们将其应用到`CreatePageForm`: + +```js +function CreatePageForm( { onSaveFinished, onCancel } ) { + // ... + const { saveEntityRecord } = useDispatch( coreDataStore ); + const handleSave = async () => { + const savedRecord = await saveEntityRecord( + 'postType', + 'page', + { title } + ); + if ( savedRecord ) { + onSaveFinished(); + } + }; + return ( + + ); +} +``` + +还需注意:新建页面默认不会被`PagesList`获取。根据REST API文档,`/wp/v2/pages`接口在创建(POST请求)时默认生成`status=draft`的页面,但返回(GET请求)的是`status=publish`的页面。解决方案是显式传递status参数: + +```js +function CreatePageForm( { onSaveFinished, onCancel } ) { + // ... + const { saveEntityRecord } = useDispatch( coreDataStore ); + const handleSave = async () => { + const savedRecord = await saveEntityRecord( + 'postType', + 'page', + { title, status: 'publish' } + ); + if ( savedRecord ) { + onSaveFinished(); + } + }; + return ( + + ); +} +``` + +请将此更改应用到本地的`CreatePageForm`组件,接下来处理剩余两个属性。 + +#### 最后错误、保存状态 + +`EditPageForm`通过`getLastEntitySaveError`和`isSavingEntityRecord`选择器获取错误和进度信息,两者都传递三个参数:`( 'postType', 'page', pageId )`。 + +但`CreatePageForm`没有pageId参数。此时可省略pageId参数来获取未指定ID的实体记录信息(即新建记录)。`useSelect`调用与`EditPageForm`非常相似: + +```js +function CreatePageForm( { onCancel, onSaveFinished } ) { + // ... + const { lastError, isSaving } = useSelect( + ( select ) => ( { + // 注意省略了pageId参数: + lastError: select( coreDataStore ) + .getLastEntitySaveError( 'postType', 'page' ), + // 注意省略了pageId参数 + isSaving: select( coreDataStore ) + .isSavingEntityRecord( 'postType', 'page' ), + } ), + [] + ); + // ... + return ( + + ); +} +``` + +大功告成!以下是我们新表单的实际运行效果: + +![](https://raw.githubusercontent.com/WordPress/gutenberg/HEAD/docs/how-to-guides/data-basics/media/create-form/create-saving.png) +![](https://raw.githubusercontent.com/WordPress/gutenberg/HEAD/docs/how-to-guides/data-basics/media/create-form/created-item.png) \ No newline at end of file diff --git a/how-to-guides/data-basics/5-adding-a-delete-button.md b/how-to-guides/data-basics/5-adding-a-delete-button.md new file mode 100644 index 0000000..8846de4 --- /dev/null +++ b/how-to-guides/data-basics/5-adding-a-delete-button.md @@ -0,0 +1,440 @@ +## 下一步做什么? + +* **上一部分:** [构建*创建页面表单*](/docs/how-to-guides/data-basics/4-building-a-create-page-form.md) +* (可选)在 block-development-examples 代码库中查看[已完成的应用程序](https://github.com/WordPress/block-development-examples/tree/trunk/plugins/data-basics-59c8f8) + +# 添加删除按钮 + +在[上一章节](/docs/how-to-guides/data-basics/3-building-an-edit-form.md)中,我们实现了新建页面的功能,本章节将为应用添加*删除*功能。 + +以下是我们即将实现的效果预览: + +![](https://raw.githubusercontent.com/WordPress/gutenberg/HEAD/docs/how-to-guides/data-basics/media/delete-button/delete-button.png) + +### 步骤一:添加删除按钮 + +首先创建 `DeletePageButton` 组件并更新 `PagesList` 组件的用户界面: + +```js +import { Button } from '@wordpress/components'; +import { decodeEntities } from '@wordpress/html-entities'; + +const DeletePageButton = () => ( + +) + +function PagesList( { hasResolved, pages } ) { + if ( ! hasResolved ) { + return ; + } + if ( ! pages?.length ) { + return
    暂无数据
    ; + } + + return ( + + + + + + + + + { pages?.map( ( page ) => ( + + + + + ) ) } + +
    标题操作
    { decodeEntities( page.title.rendered ) } +
    + + {/* ↓ 这是 PagesList 组件中的唯一改动 */} + +
    +
    + ); +} +``` + +此时 PagesList 的显示效果如下: + +![](https://raw.githubusercontent.com/WordPress/gutenberg/HEAD/docs/how-to-guides/data-basics/media/delete-button/delete-button.png) + +### 步骤二:为按钮绑定删除操作 + +在 Gutenberg 数据层中,我们通过 `deleteEntityRecord` 操作从 WordPress REST API 删除实体记录。该操作会发送请求、处理结果并更新 Redux 状态中的缓存数据。 + +以下是在浏览器开发者工具中尝试删除实体记录的方法: + +```js +// 调用 deleteEntityRecord 需要有效的页面ID,先通过 getEntityRecords 获取首个可用ID +const pageId = wp.data.select( 'core' ).getEntityRecords( 'postType', 'page' )[0].id; + +// 执行删除操作: +const promise = wp.data.dispatch( 'core' ).deleteEntityRecord( 'postType', 'page', pageId ); + +// 当 API 请求成功或失败时,promise 会相应地被解析或拒绝 +``` + +REST API 请求完成后,您会注意到列表中的某个页面已消失。这是因为列表通过 `useSelect()` 钩子和 `select( coreDataStore ).getEntityRecords( 'postType', 'page' )` 选择器动态生成。只要底层数据发生变化,列表就会立即使用新数据重新渲染,这非常便捷! + +接下来让我们在点击 `DeletePageButton` 时触发该操作: + +```js +const DeletePageButton = ({ pageId }) => { + const { deleteEntityRecord } = useDispatch( coreDataStore ); + const handleDelete = () => deleteEntityRecord( 'postType', 'page', pageId ); + return ( + + ); +} +``` + +### 步骤三:添加视觉反馈 + +点击*删除*按钮后,REST API 请求可能需要一些时间才能完成。与之前章节类似,我们可以通过 `` 组件来直观展示这一状态。 + +这里需要使用 `isDeletingEntityRecord` 选择器,它与[第三章节](/docs/how-to-guides/data-basics/3-building-an-edit-form.md)中提到的 `isSavingEntityRecord` 选择器类似:返回 `true` 或 `false` 且不会触发任何 HTTP 请求: + +```js +const DeletePageButton = ({ pageId }) => { + // ... + const { isDeleting } = useSelect( + select => ({ + isDeleting: select( coreDataStore ).isDeletingEntityRecord( 'postType', 'page', pageId ), + }), + [ pageId ] + ) + return ( + + ); +} +``` + +实际运行效果如下: + +![](https://raw.githubusercontent.com/WordPress/gutenberg/HEAD/docs/how-to-guides/data-basics/media/delete-button/deleting-in-progress.png) + +### 步骤4:错误处理 + +我们之前乐观地假设*删除*操作总能成功。但不幸的是,其底层是通过REST API发起的请求,可能因多种原因失败: + +* 网站可能宕机 +* 删除请求可能无效 +* 页面可能已被其他用户删除 + +为了在发生这些错误时通知用户,我们需要使用`getLastEntityDeleteError`选择器提取错误信息: + +```js +// 将9替换为实际页面ID +wp.data.select( 'core' ).getLastEntityDeleteError( 'postType', 'page', 9 ) +``` + +以下是在`DeletePageButton`中的具体实现: + +```js +import { useEffect } from 'react'; +const DeletePageButton = ({ pageId }) => { + // ... + const { error, /* ... */ } = useSelect( + select => ( { + error: select( coreDataStore ).getLastEntityDeleteError( 'postType', 'page', pageId ), + // ... + } ), + [pageId] + ); + useEffect( () => { + if ( error ) { + // 显示错误信息 + } + }, [error] ) + + // ... +} +``` + +`error`对象来自`@wordpress/api-fetch`,包含以下错误属性: + +* `message` – 人类可读的错误信息(如`Invalid post ID`) +* `code` – 字符串型错误代码(如`rest_post_invalid_id`)。所有错误代码需参考[`/v2/pages`端点源码](https://github.com/WordPress/wordpress-develop/blob/2648a5f984b8abf06872151898e3a61d3458a628/src/wp-includes/rest-api/endpoints/class-wp-rest-revisions-controller.php#L226-L230) +* `data`(可选)– 错误详情,包含HTTP响应码的`code`属性 + +本教程将直接显示`error.message`来转换错误信息。 + +WordPress采用`Snackbar`组件显示状态信息,下图是**小工具编辑器**中的效果: + +![](https://raw.githubusercontent.com/WordPress/gutenberg/HEAD/docs/how-to-guides/data-basics/media/delete-button/snackbar-example.png) + +现在为插件实现相同通知功能,包含两个部分: + +1. 显示通知 +2. 触发通知 + +#### 显示通知 + +当前应用只能显示页面,需要新增通知显示功能。WordPress提供了完整的React通知组件,其中[`Snackbar`组件](https://wordpress.github.io/gutenberg/?path=/story/components-snackbar--default)可呈现单条通知: + +![](https://raw.githubusercontent.com/WordPress/gutenberg/HEAD/docs/how-to-guides/data-basics/media/delete-button/snackbar.png) + +不过我们不会直接使用`Snackbar`,而是采用能显示多条通知、支持平滑动画和自动隐藏的`SnackbarList`组件——这正是小工具编辑器和其他wp-admin页面使用的同款组件! + +创建自定义`Notifications`组件: + +```js +import { SnackbarList } from '@wordpress/components'; +import { store as noticesStore } from '@wordpress/notices'; + +function Notifications() { + const notices = []; // 此处稍后完善 + + return ( + + ); +} +``` + +基础框架已搭建,但当前通知列表为空。如何填充?我们将沿用WordPress使用的[`@wordpress/notices`](https://github.com/WordPress/gutenberg/blob/895ca1f6a7d7e492974ea55f693aecbeb1d5bbe3/docs/reference-guides/data/data-core-notices.md)方案: + +```js +import { SnackbarList } from '@wordpress/components'; +import { store as noticesStore } from '@wordpress/notices'; + +function Notifications() { + const notices = useSelect( + ( select ) => select( noticesStore ).getNotices(), + [] + ); + const { removeNotice } = useDispatch( noticesStore ); + const snackbarNotices = notices.filter( ({ type }) => type === 'snackbar' ); + + return ( + + ); +} + +function MyFirstApp() { + // ... + return ( +
    + {/* ... */} + +
    + ); +} +``` + +本教程重点在于页面管理,不深入讨论上述代码细节。若想了解`@wordpress/notices`的详细信息,建议查阅[手册页面](https://developer.wordpress.org/block-editor/reference-guides/data/data-core-notices/)。 + +现在我们已经准备好向用户报告可能发生的错误了。 + +#### 发送通知 + +有了 SnackbarNotices 组件后,我们现在可以发送通知了!具体操作如下: + +```js +import { useEffect } from 'react'; +import { store as noticesStore } from '@wordpress/notices'; +function DeletePageButton( { pageId } ) { + const { createSuccessNotice, createErrorNotice } = useDispatch( noticesStore ); + // 如果传入存储句柄而非回调函数,useSelect 将返回选择器列表: + const { getLastEntityDeleteError } = useSelect( coreDataStore ) + const handleDelete = async () => { + const success = await deleteEntityRecord( 'postType', 'page', pageId); + if ( success ) { + // 告知用户操作成功: + createSuccessNotice( "页面已删除!", { + type: 'snackbar', + } ); + } else { + // 在 deleteEntityRecord 执行失败后,直接使用选择器获取最新错误信息 + const lastError = getLastEntityDeleteError( 'postType', 'page', pageId ); + const message = ( lastError?.message || '出现错误。' ) + ' 请刷新页面后重试。' + // 向用户明确展示操作失败原因: + createErrorNotice( message, { + type: 'snackbar', + } ); + } + } + // ... +} +``` + +太好了!现在 `DeletePageButton` 已完全具备错误感知能力。让我们看看实际错误提示效果。通过将 `pageId` 乘以一个大数值来触发无效删除操作: + +```js +function DeletePageButton( { pageId, onCancel, onSaveFinished } ) { + pageId = pageId * 1000; + // ... +} +``` + +刷新页面并点击任意 `删除` 按钮后,您将看到如下错误提示: + +![](https://raw.githubusercontent.com/WordPress/gutenberg/HEAD/docs/how-to-guides/data-basics/media/delete-button/snackbar-error.png) + +完美!现在可以**移除 `pageId = pageId * 1000;` 这行代码**。 + +接下来尝试实际删除页面。刷新浏览器并点击删除按钮后,您将看到: + +![](https://raw.githubusercontent.com/WordPress/gutenberg/HEAD/docs/how-to-guides/data-basics/media/delete-button/snackbar-success.png) + +大功告成! + +### 完整功能集成 + +所有组件已就绪,太棒了!以下是本章节完成的所有代码变更: + +```js +import { useState, useEffect } from 'react'; +import { useSelect, useDispatch } from '@wordpress/data'; +import { Button, Modal, TextControl } from '@wordpress/components'; + +function MyFirstApp() { + const [searchTerm, setSearchTerm] = useState( '' ); + const { pages, hasResolved } = useSelect( + ( select ) => { + const query = {}; + if ( searchTerm ) { + query.search = searchTerm; + } + const selectorArgs = ['postType', 'page', query]; + const pages = select( coreDataStore ).getEntityRecords( ...selectorArgs ); + return { + pages, + hasResolved: select( coreDataStore ).hasFinishedResolution( + 'getEntityRecords', + selectorArgs, + ), + }; + }, + [searchTerm], + ); + + return ( +
    +
    + + +
    + + +
    + ); +} + +function SnackbarNotices() { + const notices = useSelect( + ( select ) => select( noticesStore ).getNotices(), + [] + ); + const { removeNotice } = useDispatch( noticesStore ); + const snackbarNotices = notices.filter( ( { type } ) => type === 'snackbar' ); + + return ( + + ); +} + +function PagesList( { hasResolved, pages } ) { + if ( !hasResolved ) { + return ; + } + if ( !pages?.length ) { + return
    暂无结果
    ; + } + + return ( + + + + + + + + + { pages?.map( ( page ) => ( + + + + + ) ) } + +
    标题操作
    { page.title.rendered } +
    + + +
    +
    + ); +} + +function DeletePageButton( { pageId } ) { + const { createSuccessNotice, createErrorNotice } = useDispatch( noticesStore ); + // 如果传入存储句柄而非回调函数,useSelect 将返回选择器列表: + const { getLastEntityDeleteError } = useSelect( coreDataStore ) + const handleDelete = async () => { + const success = await deleteEntityRecord( 'postType', 'page', pageId); + if ( success ) { + // 告知用户操作成功: + createSuccessNotice( "页面已删除!", { + type: 'snackbar', + } ); + } else { + // 此时直接使用选择器获取错误信息 + // 假设我们通过以下方式获取错误: + // const { lastError } = useSelect( function() { /* ... */ } ); + // 那么 lastError 在 handleDelete 内部将显示为 null + // 为什么?因为这里引用的是在 handleDelete 被调用前就计算好的旧版本 + const lastError = getLastEntityDeleteError( 'postType', 'page', pageId ); + const message = ( lastError?.message || '出现错误。' ) + ' 请刷新页面后重试。' + // 向用户明确展示操作失败原因: + createErrorNotice( message, { + type: 'snackbar', + } ); + } + } + + const { deleteEntityRecord } = useDispatch( coreDataStore ); + const { isDeleting } = useSelect( + select => ( { + isDeleting: select( coreDataStore ).isDeletingEntityRecord( 'postType', 'page', pageId ), + } ), + [ pageId ] + ); + + return ( + + ); +} +``` \ No newline at end of file diff --git a/how-to-guides/data-basics/README.md b/how-to-guides/data-basics/README.md new file mode 100644 index 0000000..e42ff48 --- /dev/null +++ b/how-to-guides/data-basics/README.md @@ -0,0 +1,15 @@ +# 使用Gutenberg数据创建你的首个应用 + +本教程旨在帮助你熟悉Gutenberg数据层。它将指导你构建一个简单的React应用程序,让用户能够管理他们的WordPress页面。完成后的应用效果如下: + +[![在WordPress Playground中打开演示](https://raw.githubusercontent.com/WordPress/gutenberg/HEAD/docs/how-to-guides/data-basics/media/list-of-pages/part1-finished.jpg)](https://playground.wordpress.net/?blueprint-url=https://raw.githubusercontent.com/WordPress/block-development-examples/trunk/plugins/data-basics-59c8f8/_playground/blueprint.json "在WordPress Playground中打开演示") + +你可以在block-development-examples代码库中查看[完整应用](https://github.com/WordPress/block-development-examples/tree/trunk/plugins/data-basics-59c8f8)。 + +### 目录 + +1. [环境设置](/docs/how-to-guides/data-basics/1-data-basics-setup.md) +2. [构建基础页面列表](/docs/how-to-guides/data-basics/2-building-a-list-of-pages.md) +3. [构建编辑表单](/docs/how-to-guides/data-basics/3-building-an-edit-form.md) +4. [构建创建页面表单](/docs/how-to-guides/data-basics/4-building-a-create-page-form.md) +5. [添加删除按钮](/docs/how-to-guides/data-basics/5-adding-a-delete-button.md) \ No newline at end of file diff --git a/how-to-guides/data-basics/media/create-form/basic-create-form.png b/how-to-guides/data-basics/media/create-form/basic-create-form.png new file mode 100644 index 0000000..f061d34 Binary files /dev/null and b/how-to-guides/data-basics/media/create-form/basic-create-form.png differ diff --git a/how-to-guides/data-basics/media/create-form/create-button.png b/how-to-guides/data-basics/media/create-form/create-button.png new file mode 100644 index 0000000..63c1a91 Binary files /dev/null and b/how-to-guides/data-basics/media/create-form/create-button.png differ diff --git a/how-to-guides/data-basics/media/create-form/create-form-with-text.png b/how-to-guides/data-basics/media/create-form/create-form-with-text.png new file mode 100644 index 0000000..d785313 Binary files /dev/null and b/how-to-guides/data-basics/media/create-form/create-form-with-text.png differ diff --git a/how-to-guides/data-basics/media/create-form/create-saving.png b/how-to-guides/data-basics/media/create-form/create-saving.png new file mode 100644 index 0000000..cea62fa Binary files /dev/null and b/how-to-guides/data-basics/media/create-form/create-saving.png differ diff --git a/how-to-guides/data-basics/media/create-form/created-item.png b/how-to-guides/data-basics/media/create-form/created-item.png new file mode 100644 index 0000000..64cd31f Binary files /dev/null and b/how-to-guides/data-basics/media/create-form/created-item.png differ diff --git a/how-to-guides/data-basics/media/create-form/edit-page-form.png b/how-to-guides/data-basics/media/create-form/edit-page-form.png new file mode 100644 index 0000000..08ca7ea Binary files /dev/null and b/how-to-guides/data-basics/media/create-form/edit-page-form.png differ diff --git a/how-to-guides/data-basics/media/delete-button/delete-button.png b/how-to-guides/data-basics/media/delete-button/delete-button.png new file mode 100644 index 0000000..e28a1d2 Binary files /dev/null and b/how-to-guides/data-basics/media/delete-button/delete-button.png differ diff --git a/how-to-guides/data-basics/media/delete-button/deleting-in-progress.png b/how-to-guides/data-basics/media/delete-button/deleting-in-progress.png new file mode 100644 index 0000000..b51f6b5 Binary files /dev/null and b/how-to-guides/data-basics/media/delete-button/deleting-in-progress.png differ diff --git a/how-to-guides/data-basics/media/delete-button/snackbar-error.png b/how-to-guides/data-basics/media/delete-button/snackbar-error.png new file mode 100644 index 0000000..f0db06f Binary files /dev/null and b/how-to-guides/data-basics/media/delete-button/snackbar-error.png differ diff --git a/how-to-guides/data-basics/media/delete-button/snackbar-example.png b/how-to-guides/data-basics/media/delete-button/snackbar-example.png new file mode 100644 index 0000000..8faf6b6 Binary files /dev/null and b/how-to-guides/data-basics/media/delete-button/snackbar-example.png differ diff --git a/how-to-guides/data-basics/media/delete-button/snackbar-success.png b/how-to-guides/data-basics/media/delete-button/snackbar-success.png new file mode 100644 index 0000000..a0fc847 Binary files /dev/null and b/how-to-guides/data-basics/media/delete-button/snackbar-success.png differ diff --git a/how-to-guides/data-basics/media/delete-button/snackbar.png b/how-to-guides/data-basics/media/delete-button/snackbar.png new file mode 100644 index 0000000..bc21fce Binary files /dev/null and b/how-to-guides/data-basics/media/delete-button/snackbar.png differ diff --git a/how-to-guides/data-basics/media/edit-form/edit-button.png b/how-to-guides/data-basics/media/edit-form/edit-button.png new file mode 100644 index 0000000..bc0c069 Binary files /dev/null and b/how-to-guides/data-basics/media/edit-form/edit-button.png differ diff --git a/how-to-guides/data-basics/media/edit-form/form-editable.png b/how-to-guides/data-basics/media/edit-form/form-editable.png new file mode 100644 index 0000000..38babc2 Binary files /dev/null and b/how-to-guides/data-basics/media/edit-form/form-editable.png differ diff --git a/how-to-guides/data-basics/media/edit-form/form-error.png b/how-to-guides/data-basics/media/edit-form/form-error.png new file mode 100644 index 0000000..116a321 Binary files /dev/null and b/how-to-guides/data-basics/media/edit-form/form-error.png differ diff --git a/how-to-guides/data-basics/media/edit-form/form-finished.png b/how-to-guides/data-basics/media/edit-form/form-finished.png new file mode 100644 index 0000000..1d1b1f0 Binary files /dev/null and b/how-to-guides/data-basics/media/edit-form/form-finished.png differ diff --git a/how-to-guides/data-basics/media/edit-form/form-inactive.png b/how-to-guides/data-basics/media/edit-form/form-inactive.png new file mode 100644 index 0000000..6965388 Binary files /dev/null and b/how-to-guides/data-basics/media/edit-form/form-inactive.png differ diff --git a/how-to-guides/data-basics/media/edit-form/form-populated.png b/how-to-guides/data-basics/media/edit-form/form-populated.png new file mode 100644 index 0000000..f412ff6 Binary files /dev/null and b/how-to-guides/data-basics/media/edit-form/form-populated.png differ diff --git a/how-to-guides/data-basics/media/edit-form/form-scaffold.png b/how-to-guides/data-basics/media/edit-form/form-scaffold.png new file mode 100644 index 0000000..a569a8f Binary files /dev/null and b/how-to-guides/data-basics/media/edit-form/form-scaffold.png differ diff --git a/how-to-guides/data-basics/media/edit-form/form-spinner.png b/how-to-guides/data-basics/media/edit-form/form-spinner.png new file mode 100644 index 0000000..4606630 Binary files /dev/null and b/how-to-guides/data-basics/media/edit-form/form-spinner.png differ diff --git a/how-to-guides/data-basics/media/edit-form/modal-initial.png b/how-to-guides/data-basics/media/edit-form/modal-initial.png new file mode 100644 index 0000000..b98f34f Binary files /dev/null and b/how-to-guides/data-basics/media/edit-form/modal-initial.png differ diff --git a/how-to-guides/data-basics/media/finished-app.jpg b/how-to-guides/data-basics/media/finished-app.jpg new file mode 100644 index 0000000..533b371 Binary files /dev/null and b/how-to-guides/data-basics/media/finished-app.jpg differ diff --git a/how-to-guides/data-basics/media/list-of-pages/fetch-the-data.jpg b/how-to-guides/data-basics/media/list-of-pages/fetch-the-data.jpg new file mode 100644 index 0000000..c87c994 Binary files /dev/null and b/how-to-guides/data-basics/media/list-of-pages/fetch-the-data.jpg differ diff --git a/how-to-guides/data-basics/media/list-of-pages/filter-field.jpg b/how-to-guides/data-basics/media/list-of-pages/filter-field.jpg new file mode 100644 index 0000000..877152f Binary files /dev/null and b/how-to-guides/data-basics/media/list-of-pages/filter-field.jpg differ diff --git a/how-to-guides/data-basics/media/list-of-pages/filter.jpg b/how-to-guides/data-basics/media/list-of-pages/filter.jpg new file mode 100644 index 0000000..27d22fa Binary files /dev/null and b/how-to-guides/data-basics/media/list-of-pages/filter.jpg differ diff --git a/how-to-guides/data-basics/media/list-of-pages/indicator.jpg b/how-to-guides/data-basics/media/list-of-pages/indicator.jpg new file mode 100644 index 0000000..92da78d Binary files /dev/null and b/how-to-guides/data-basics/media/list-of-pages/indicator.jpg differ diff --git a/how-to-guides/data-basics/media/list-of-pages/make-a-table.jpg b/how-to-guides/data-basics/media/list-of-pages/make-a-table.jpg new file mode 100644 index 0000000..4455e1e Binary files /dev/null and b/how-to-guides/data-basics/media/list-of-pages/make-a-table.jpg differ diff --git a/how-to-guides/data-basics/media/list-of-pages/no-results.jpg b/how-to-guides/data-basics/media/list-of-pages/no-results.jpg new file mode 100644 index 0000000..4c855c1 Binary files /dev/null and b/how-to-guides/data-basics/media/list-of-pages/no-results.jpg differ diff --git a/how-to-guides/data-basics/media/list-of-pages/pages-list.jpg b/how-to-guides/data-basics/media/list-of-pages/pages-list.jpg new file mode 100644 index 0000000..f597019 Binary files /dev/null and b/how-to-guides/data-basics/media/list-of-pages/pages-list.jpg differ diff --git a/how-to-guides/data-basics/media/list-of-pages/part1-finished.jpg b/how-to-guides/data-basics/media/list-of-pages/part1-finished.jpg new file mode 100644 index 0000000..0e13899 Binary files /dev/null and b/how-to-guides/data-basics/media/list-of-pages/part1-finished.jpg differ diff --git a/how-to-guides/data-basics/media/list-of-pages/simple-list.jpg b/how-to-guides/data-basics/media/list-of-pages/simple-list.jpg new file mode 100644 index 0000000..be3550c Binary files /dev/null and b/how-to-guides/data-basics/media/list-of-pages/simple-list.jpg differ diff --git a/how-to-guides/data-basics/media/list-of-pages/unclear-status.jpg b/how-to-guides/data-basics/media/list-of-pages/unclear-status.jpg new file mode 100644 index 0000000..6438ea6 Binary files /dev/null and b/how-to-guides/data-basics/media/list-of-pages/unclear-status.jpg differ diff --git a/how-to-guides/data-basics/media/setup/hello-from-js.jpg b/how-to-guides/data-basics/media/setup/hello-from-js.jpg new file mode 100644 index 0000000..6fe0bcd Binary files /dev/null and b/how-to-guides/data-basics/media/setup/hello-from-js.jpg differ diff --git a/how-to-guides/enqueueing-assets-in-the-editor.md b/how-to-guides/enqueueing-assets-in-the-editor.md new file mode 100644 index 0000000..467dfa8 --- /dev/null +++ b/how-to-guides/enqueueing-assets-in-the-editor.md @@ -0,0 +1,120 @@ +### 区块脚本与样式 + +在构建区块时,推荐使用 `block.json` 来加载区块本身所需的所有脚本和样式。您可以为编辑器、前端或两者同时加载资源。详见[区块元数据](https://developer.wordpress.org/block-editor/reference-guides/block-api/block-metadata/)文档。 + +### 主题脚本与样式 + +若需在主题中加载编辑器JavaScript,可参照上文使用 `enqueue_block_assets` 或 `enqueue_block_editor_assets`。编辑器专用样式表应始终通过 [`add_editor_style()`](https://developer.wordpress.org/reference/functions/add_editor_style/) 或 [`wp_enqueue_block_style()`](https://developer.wordpress.org/reference/functions/wp_enqueue_block_style/) 添加。 + +`wp_enqueue_block_style()` 函数支持在编辑器及前端加载逐区块样式表。结合 `theme.json` 使用,这是样式化区块的最佳方案之一。更多细节请参阅WordPress开发者博客文章[运用theme.json与逐区块样式打造高性能主题](https://developer.wordpress.org/news/2022/12/leveraging-theme-json-and-per-block-styles-for-more-performant-themes/)。 + +## 向后兼容性与已知问题 + +通常而言,在iframe模式编辑器中加载的资源,在WordPress 6.3+版本的非iframe模式下也会同步加载,但反向兼容并不总是成立。 + +若您开发的插件或主题需兼容6.2及以下版本,同时保持与WordPress 6.3的兼容性,则无法使用 `enqueue_block_assets` 钩子——因为在6.3之前该钩子不会在iframe编辑器内容中加载资源。 + +替代方案是使用 `enqueue_block_editor_assets`,只要加载的样式表包含以下任一选择器:`.editor-styles-wrapper`、`.wp-block` 或 `.wp-block-*`。控制台会显示警告信息,但该钩子仍会将样式应用于编辑器内容。 + +需特别注意:从WordPress 6.3开始,为保持向后兼容,通过 `enqueue_block_assets` 加载的资源会在编辑器iframe内外同时加载。根据所加载的脚本库不同,这可能引发问题。Gutenberg项目的[GitHub代码库](https://github.com/WordPress/gutenberg/issues/53590)正在持续讨论这一机制。 + +如果您在使用本指南所述方法时遇到未被报告过的问题,请在GitHub上[提交问题报告](https://github.com/WordPress/gutenberg/issues/new/choose)。 + +# 在编辑器中加载资源 + +本指南旨在成为在编辑器中加载资源(脚本和样式)的权威参考。这里概述的方法代表了推荐实践,但请注意,随着WordPress的发展,本资源也会不断更新。欢迎贡献更新内容。 + +自WordPress 6.3起,如果所有已注册区块都具备[`区块API版本3`](https://developer.wordpress.org/block-editor/reference-guides/block-api/block-metadata/)或更高版本,且未注册传统元框,则文章编辑器将采用iframe嵌入。站点编辑器始终采用iframe嵌入。本指南假设您需要为iframe嵌入的编辑器加载资源,但请参考下文的后向兼容性章节了解其他注意事项。 + +若需了解编辑器为何采用iframe嵌入,请重温[iframe嵌入(模板)编辑器中的区块](https://make.wordpress.org/core/2021/06/29/blocks-in-an-iframed-template-editor/)这篇文章。 + +## 编辑器与编辑器内容 +在编辑器中加载资源前,首先需要明确目标范围。 + +您是要为用户生成内容(区块)添加样式或JavaScript?还是要修改编辑器用户界面组件或与编辑器API交互?这可能包括从创建自定义区块控件到注册区块变体等各种场景。 + +根据这些问题的答案,需要使用不同的钩子。如果您正在构建区块或主题,还需要考虑其他方法。请参考下文对应章节。 + +## 资源加载场景 +### 编辑器脚本与样式 + +当需要为编辑器本身(即非用户生成内容)加载资源时,应使用[`enqueue_block_editor_assets`](https://developer.wordpress.org/reference/hooks/enqueue_block_editor_assets/)钩子,配合标准的[`wp_enqueue_script`](https://developer.wordpress.org/reference/functions/wp_enqueue_script/)和[`wp_enqueue_style`](https://developer.wordpress.org/reference/functions/wp_enqueue_style/)函数。 + +典型应用场景包括:添加自定义检查器或工具栏控件、通过JavaScript注册区块样式和变体、注册编辑器插件等。 + +```php +/** + * 加载编辑器资源 + */ +function example_enqueue_editor_assets() { + wp_enqueue_script( + 'example-editor-scripts', + plugins_url( 'editor-scripts.js', __FILE__ ) + ); + wp_enqueue_style( + 'example-editor-styles', + plugins_url( 'editor-styles.css', __FILE__ ) + ); +} +add_action( 'enqueue_block_editor_assets', 'example_enqueue_editor_assets' ); +``` + +虽然不推荐,但需注意出于后向兼容考虑,`enqueue_block_editor_assets`也可用于设置编辑器内容样式。详见下文说明。 + +### 编辑器内容脚本与样式 + +自WordPress 6.3起,所有通过[`enqueue_block_assets`](https://developer.wordpress.org/reference/hooks/enqueue_block_assets/)PHP操作添加的资源都会在iframe嵌入的编辑器中加载。详见[#48286](https://github.com/WordPress/gutenberg/pull/48286)。 + +这是为用户生成内容(区块)加载资源的主要方法,该钩子会在编辑器环境和网站前端同时触发。不应用于添加针对编辑器UI的资源或与编辑器API交互。后向兼容性说明详见下文。 + +某些情况下可能只需在编辑器中加载资源而不在前端加载。可通过[`is_admin()`](https://developer.wordpress.org/reference/functions/is_admin/)检测实现。 + +```php +/** + * 仅限编辑器内加载内容资源 + */ +function example_enqueue_editor_content_assets() { + if ( is_admin() ) { + wp_enqueue_script( + 'example-editor-content-scripts', + plugins_url( 'content-scripts.js', __FILE__ ) + ); + wp_enqueue_style( + 'example-editor-content-styles', + plugins_url( 'content-styles.css', __FILE__ ) + ); + } +} +add_action( 'enqueue_block_assets', 'example_enqueue_editor_content_assets' ); +``` + +也可使用[`block_editor_settings_all`](https://developer.wordpress.org/reference/hooks/block_editor_settings_all/)钩子直接修改编辑器设置。此方法实现稍复杂但灵活性更高,仅当`enqueue_block_assets`无法满足需求时使用。 + +以下示例将所有段落默认文字颜色设为`绿色`: + +```php +/** + * 通过添加自定义样式修改编辑器设置 + * + * @param array $editor_settings 包含当前编辑器设置的数组 + * @param string $editor_context 编辑器上下文 + * + * @return array 添加自定义CSS样式后的编辑器设置 + */ +function example_modify_editor_settings( $editor_settings, $editor_context ) { + $editor_settings["styles"][] = array( + "css" => 'p { color: green }' + ); + + return $editor_settings; +} +add_filter( 'block_editor_settings_all', 'example_modify_editor_settings', 10,2 ); +``` + +这些样式会以内联方式插入iframe编辑器的`body`中,并添加`.editor-styles-wrapper`前缀。最终生成的标记如下: + +```css + +``` + +从WordPress 6.3开始,还可通过这种修改编辑器设置的方法,使用JavaScript动态更改样式。详见[#52767](https://github.com/WordPress/gutenberg/pull/52767#top)。 \ No newline at end of file diff --git a/how-to-guides/feature-flags.md b/how-to-guides/feature-flags.md new file mode 100644 index 0000000..78906ac --- /dev/null +++ b/how-to-guides/feature-flags.md @@ -0,0 +1,107 @@ +# 功能标志 + +「功能标志」是允许您阻止 Gutenberg 项目中的特定代码被发布到 WordPress 核心,并仅在插件中运行某些实验性功能的变量。 + +## 介绍 `globalThis.IS_GUTENBERG_PLUGIN` + +`globalThis.IS_GUTENBERG_PLUGIN` 是一个环境变量,其值用于「标志」代码是否在 Gutenberg 插件内运行。 + +当代码库为插件构建时,此变量将被设为 `true`。当为 WordPress 核心构建时,它将被设为 `false` 或 `undefined`。 + +## 基本用法 + +### 导出功能 + +仅限插件使用的函数或常量应使用以下三元语法导出: + +```js +function myPluginOnlyFeature() { + // 具体实现 +} + +export const pluginOnlyFeature = globalThis.IS_GUTENBERG_PLUGIN + ? myPluginOnlyFeature + : undefined; +``` + +在上面的示例中,`pluginOnlyFeature` 导出在非插件环境(例如 WordPress 核心)中将是 `undefined`。 + +### 导入功能 + +如果您尝试导入并调用仅限插件使用的功能,请务必将函数调用包装在 `if` 语句中以避免错误: + +```js +import { pluginOnlyFeature } from '@wordpress/foo'; + +if ( globalThis.IS_GUTENBERG_PLUGIN ) { + pluginOnlyFeature(); +} +``` + +## 工作原理 + +在 webpack 构建过程中,`globalThis.IS_GUTENBERG_PLUGIN` 的实例将使用 webpack 的 [define 插件](https://webpack.js.org/plugins/define-plugin/)进行替换。 + +例如,在以下代码中 – + +```js +if ( globalThis.IS_GUTENBERG_PLUGIN ) { + pluginOnlyFeature(); +} +``` + +– 变量 `globalThis.IS_GUTENBERG_PLUGIN` 将在仅限插件的构建过程中被替换为布尔值 `true`: + +```js +if ( true ) { + // Webpack 已将 `globalThis.IS_GUTENBERG_PLUGIN` 替换为 `true` + pluginOnlyFeature(); +} +``` + +这确保了 `if` 语句体内的代码将始终被执行。 + +在 WordPress 核心中,`globalThis.IS_GUTENBERG_PLUGIN` 变量被替换为 `undefined`。构建后的代码如下所示: + +```js +if ( undefined ) { + // Webpack 已将 `globalThis.IS_GUTENBERG_PLUGIN` 替换为 `undefined` + pluginOnlyFeature(); +} +``` + +`undefined` 会被判定为 `false`,因此仅限插件的功能将不会被执行。 + +### 死代码消除 + +对于生产构建,webpack 会对代码进行[「压缩」](https://en.wikipedia.org/wiki/Minification_(programming)),尽可能移除不必要的 JavaScript。 + +其中一个步骤涉及称为「死代码消除」的过程。例如,当遇到以下代码时,webpack 会判定周围的 `if` 语句是不必要的: + +```js +if ( true ) { + pluginOnlyFeature(); +} +``` + +该条件将始终判定为 `true`,因此 webpack 会移除它,仅保留原本在体内的代码: + +```js +pluginOnlyFeature(); // `if` 条件块已被移除,仅保留体内的代码。 +``` + +类似地,在为 WordPress 核心构建时,以下 `if` 语句中的条件始终解析为 false: + +```js +if ( undefined ) { + pluginOnlyFeature(); +} +``` + +在这种情况下,压缩过程将移除整个 `if` 语句(包括其体内的代码),确保仅限插件的代码不会包含在 WordPress 核心构建中。 + +## 常见问题解答 + +### 为什么我不应该将涉及 `IS_GUTENBERG_PLUGIN` 的表达式结果赋值给变量,例如 `const isMyFeatureActive = ! Object.is( undefined, globalThis.IS_GUTENBERG_PLUGIN )`? + +引入复杂性可能会阻止 webpack 的压缩器识别并因此消除死代码。因此,建议使用本文档中的示例,以确保您的功能标志按预期工作。更多详细信息,请参阅[死代码消除](#死代码消除)部分。 \ No newline at end of file diff --git a/how-to-guides/format-api.md b/how-to-guides/format-api.md new file mode 100644 index 0000000..96e3611 --- /dev/null +++ b/how-to-guides/format-api.md @@ -0,0 +1,237 @@ +### 步骤4:仅对特定区块显示按钮(可选) + +默认情况下,该按钮会显示在每个富文本工具栏中(如图片说明、按钮、段落等)。您可以通过使用[数据API](https://developer.wordpress.org/block-editor/reference-guides/packages/packages-data),仅针对特定类型的区块显示该按钮。 + +以下示例仅对段落区块显示按钮: + +```js +import { registerFormatType, toggleFormat } from '@wordpress/rich-text'; +import { RichTextToolbarButton } from '@wordpress/block-editor'; +import { useSelect } from '@wordpress/data'; + +function ConditionalButton( { isActive, onChange, value } ) { + const selectedBlock = useSelect( ( select ) => { + return select( 'core/block-editor' ).getSelectedBlock(); + }, [] ); + + if ( selectedBlock && selectedBlock.name !== 'core/paragraph' ) { + return null; + } + + return ( + { + onChange( + toggleFormat( value, { + type: 'my-custom-format/sample-output', + } ) + ); + } } + isActive={ isActive } + /> + ); +} + +registerFormatType( 'my-custom-format/sample-output', { + title: '示例输出', + tagName: 'samp', + className: null, + edit: ConditionalButton, +} ); +``` + +### 步骤5:在下拉菜单外添加按钮(可选) + +使用 `RichTextToolbarButton` 组件时,按钮会被添加到默认的下拉菜单中。您可以通过使用 `BlockControls` 组件将按钮直接添加到工具栏。 + +```js +import { registerFormatType, toggleFormat } from '@wordpress/rich-text'; +import { BlockControls } from '@wordpress/block-editor'; +import { ToolbarGroup, ToolbarButton } from '@wordpress/components'; + +const MyCustomButton = ( { isActive, onChange, value } ) => { + return ( + + + { + onChange( + toggleFormat( value, { + type: 'my-custom-format/sample-output', + } ) + ); + } } + isActive={ isActive } + /> + + + ); +}; + +registerFormatType( 'my-custom-format/sample-output', { + title: '示例输出', + tagName: 'samp', + className: null, + edit: MyCustomButton, +} ); +``` + +## 故障排除 + +如果遇到错误: + +- 请再次确认是否已先运行 `npm run build`。 +- 确认构建过程中没有语法错误或其他问题。 +- 确认JavaScript已在编辑器中加载。 +- 检查控制台是否有错误消息。 + +## 其他资源 + +本指南中使用的参考文档: + +- RichText:[`registerFormatType`](/packages/rich-text/README.md#registerformattype) +- 组件:[`RichTextToolbarButton`](/packages/block-editor/README.md#richtexttoolbarbutton) +- RichText:[`applyFormat`](/packages/rich-text/README.md#applyformat) +- RichText:[`removeFormat`](/packages/rich-text/README.md#removeformat) +- RichText:[`toggleFormat`](/packages/rich-text/README.md#toggleformat) + +## 结论 + +本指南向您展示了如何在工具栏中添加按钮,并将其格式应用于选定的文本。请尝试使用它,并在下一个插件中探索更多可能性。 + +从 [block-development-examples](https://github.com/WordPress/block-development-examples) 仓库下载 [format-api 示例](https://github.com/WordPress/block-development-examples/tree/trunk/plugins/format-api-f14b86)。 + +# 格式化工具栏 API + +## 概述 + +格式化 API 使开发者能够向格式化工具栏添加自定义按钮,并将特定_格式_应用于文本选区。加粗按钮就是格式化工具栏中标准按钮的一个示例。 + +![格式化 API 工具栏动态示例](https://developer.wordpress.org/files/2021/12/format-api-example.gif) + +在 WordPress 术语中,_格式_是指具有[文本级语义的 HTML 标签](https://www.w3.org/TR/html5/textlevel-semantics.html#text-level-semantics-usage-summary),用于为文本选区赋予特殊含义。例如,本教程中要挂钩到格式工具栏的按钮将使用 `` HTML 标签包裹特定文本选区。 + +## 开始之前 + +本指南假设您已熟悉 WordPress 插件及其 JavaScript 加载方式,如需复习请参阅[插件手册](https://developer.wordpress.org/plugins/)或 [JavaScript 教程](/docs/getting-started/fundamentals/javascript-in-the-block-editor.md)。 + +您需要准备: + +- WordPress 开发环境 +- 已激活并配置就绪的最小化插件 +- 用于构建和入队的 JavaScript 设置 + +您可参考[完整的 format-api 示例](https://github.com/WordPress/block-development-examples/tree/trunk/plugins/format-api-f14b86)进行设置。 + +## 分步指南 + +本指南将引用 `src/index.js` 作为进行更改的 JavaScript 文件。每步完成后,运行 `npm run build` 会生成 `build/index.js`,该文件随后将加载到文章编辑器界面。 + +### 步骤 1:注册新格式 + +第一步是注册新格式,在 `src/index.js` 中添加以下内容: + +```js +import { registerFormatType } from '@wordpress/rich-text'; + +registerFormatType( 'my-custom-format/sample-output', { + title: '示例输出', + tagName: 'samp', + className: null, +} ); +``` + +可用格式类型列表由 `core/rich-text` 存储库维护。您可查询存储库以确认自定义格式现已可用。 + +在浏览器控制台中运行以下代码进行验证: + +```js +wp.data.select( 'core/rich-text' ).getFormatTypes(); +``` + +这将返回包含格式类型的数组,其中包括您自定义的格式。 + +### 步骤 2:向工具栏添加按钮 + +格式就绪后,下一步是通过为 edit 属性注册组件来向 UI 添加按钮。 + +使用 `RichTextToolbarButton` 组件,更新 `src/index.js`: + +```js +import { registerFormatType } from '@wordpress/rich-text'; +import { RichTextToolbarButton } from '@wordpress/block-editor'; + +const MyCustomButton = ( props ) => { + return ( + { + console.log( '切换格式' ); + } } + /> + ); +}; + +registerFormatType( 'my-custom-format/sample-output', { + title: '示例输出', + tagName: 'samp', + className: null, + edit: MyCustomButton, +} ); +``` + +检查功能是否正常:构建并重新加载后,选择任意包含文本的区块(例如段落区块)。确认新按钮已添加到格式工具栏。 + +![含自定义按钮的工具栏](https://developer.wordpress.org/files/2021/12/format-api-toolbar.png) + +点击按钮并在控制台中查看 "toggle format" 消息。 + +如果未看到按钮或消息,请仔细检查是否正确构建和加载了 JavaScript,并查看控制台是否有错误信息。 + +### 步骤 3:点击时应用格式 + +接下来是更新按钮,在点击时应用格式。 + +在本示例中,`` 标签格式是二元的——文本选区要么具有该标签,要么没有,因此我们可以使用 RichText 包中的 `toggleFormat` 方法。 + +更新 `src/index.js`,修改 `onClick` 操作: + +```js +import { registerFormatType, toggleFormat } from '@wordpress/rich-text'; +import { RichTextToolbarButton } from '@wordpress/block-editor'; + +const MyCustomButton = ( { isActive, onChange, value } ) => { + return ( + { + onChange( + toggleFormat( value, { + type: 'my-custom-format/sample-output', + } ) + ); + } } + isActive={ isActive } + /> + ); +}; + +registerFormatType( 'my-custom-format/sample-output', { + title: '示例输出', + tagName: 'samp', + className: null, + edit: MyCustomButton, +} ); +``` + +验证功能:先构建并重新加载,然后选择文本并点击按钮。浏览器可能会以不同于周围文本的方式显示该选区。 + +您也可以通过切换到 HTML 视图(代码编辑器 `Ctrl+Shift+Alt+M`)来确认,查看被 `` HTML 标签包裹的文本选区。 + +注册时使用 `className` 选项可向标签添加自定义类。您可以使用该类配合自定义 CSS 来定位元素并按需设置样式。 \ No newline at end of file diff --git a/how-to-guides/internationalization.md b/how-to-guides/internationalization.md new file mode 100644 index 0000000..543403a --- /dev/null +++ b/how-to-guides/internationalization.md @@ -0,0 +1,231 @@ +### 测试翻译 + +您需要将WordPress安装设置为世界语(Esperanto)语言。请前往“设置”>“常规”,将站点语言更改为世界语。 + +设置好语言后,创建一篇新文章,添加区块,您将看到所使用的翻译内容。 + +### 过滤翻译 + +翻译函数(`__()`、`_x()`、`_n()` 和 `_nx()`)的输出是可过滤的,完整信息请参阅 [国际化过滤器](/docs/reference-guides/filters/i18n-filters.md)。 + +# 国际化 + +## 什么是国际化? + +国际化是指为软件提供多语言支持的过程,在本文中特指 WordPress 的国际化。国际化通常缩写为 **i18n**,其中 18 代表首字母 _i_ 和末字母 _n_ 之间的字母数量。 + +为您的插件和主题提供 i18n 支持能够最大限度地扩大其受众范围,甚至无需您亲自提供额外的语言翻译。当您将软件上传至 WordPress.org 时,所有 JS 和 PHP 文件都将被自动解析。检测到的翻译字符串会被添加到 [translate.wordpress.org](https://translate.wordpress.org/),供社区成员进行翻译,从而确保 WordPress 插件和主题能够支持尽可能多的语言。 + +对于 PHP,WordPress 已建立成熟的流程,请参阅[如何为您的插件进行国际化](https://developer.wordpress.org/plugins/internationalization/how-to-internationalize-your-plugin/)。随着 WordPress 5.0 的发布,JavaScript 代码的翻译也引入了类似流程。 + +## 如何在 JavaScript 中使用 i18n + +WordPress 5.0 引入了 wp-i18n JavaScript 工具包,它提供了与 PHP 类似的翻译字符串所需功能。 + +首先,在注册脚本时将 **wp-i18n** 添加为依赖项: + +```php + 3, + 'editor_script' => 'myguten-script', + ) ); +} +add_action( 'init', 'myguten_block_init' ); +``` + +在代码中,您可以引入 i18n 函数。最常用的函数是 **__**(双下划线),用于简单字符串的翻译。以下是一个基础区块示例: + +```js +import { __ } from '@wordpress/i18n'; +import { registerBlockType } from '@wordpress/blocks'; +import { useBlockProps } from '@wordpress/block-editor'; + +registerBlockType( 'myguten/simple', { + apiVersion: 3, + title: __( '简单区块', 'myguten' ), + category: 'widgets', + + edit: () => { + const blockProps = useBlockProps( { style: { color: 'red' } } ); + + return

    { __( '你好世界', 'myguten' ) }

    ; + }, + + save: () => { + const blockProps = useBlockProps.save( { style: { color: 'red' } } ); + + return

    { __( '你好世界', 'myguten' ) }

    ; + }, +} ); +``` + +在上述示例中,该函数将使用第一个参数作为要翻译的字符串。第二个参数是文本域,必须与插件指定的文本域标识符匹配。 + +常用函数(这些函数与它们的 PHP 版本相对应)包括: + +- `__( '你好世界', 'my-text-domain' )` - 翻译特定字符串。 +- `_n( '%s 条评论', '%s 条评论', numberOfComments, 'my-text-domain' )` - 根据给定数字翻译并返回单数或复数形式。 +- `_x( '默认', '区块样式', 'my-text-domain' )` - 在特定上下文中翻译字符串。 + +
    +注意:所有向用户显示的字符串都应使用 i18n 函数进行包装。 +
    + +当代码中的所有字符串都完成包装后,最后一步是使用 [wp_set_script_translations()](https://developer.wordpress.org/reference/functions/wp_set_script_translations/) 函数告知 WordPress 您的 JavaScript 包含翻译内容。 + +```php +\n" +"Language-Team: 语言团队 \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"POT-Creation-Date: 2019-03-08T11:26:56-08:00\n" +"PO-Revision-Date: 年-月-日 时:分+时区\n" +"X-Generator: WP-CLI 2.1.0\n" +"X-Domain: myguten\n" + +#. 插件名称 +msgid "Scratch Plugin" +msgstr "" + +#: block.js:6 +msgid "Simple Block" +msgstr "" + +#: block.js:13 +#: block.js:21 +msgid "Hello World" +msgstr "" +``` + +此处 `msgid` 为待翻译字符串,`msgstr` 为实际翻译内容。在 POT 文件中,`msgstr` 将始终为空。 + +此 POT 文件可作为新翻译的模板。您需要**复制文件**并使用目标语言代码进行重命名,本例使用世界语(eo): + +```bash +cp myguten.pot myguten-eo.po +``` + +对于简单示例,您可以直接在编辑器中修改 `.po` 文件,为所有 `msgstr` 添加翻译内容。对于更复杂的大型翻译项目,可使用 [GlotPress](https://glotpress.blog/) 和 [Poedit](https://poedit.net/) 工具辅助完成。 + +同时需要添加 `Language: eo` 参数。以下是完整翻译后的 `myguten-eo.po` 文件: + +``` +# Copyright (C) 2019 +# 本文件遵循与 Scratch 插件相同的许可协议进行分发。 +msgid "" +msgstr "" +"Project-Id-Version: Scratch Plugin\n" +"Report-Msgid-Bugs-To: https://wordpress.org/support/plugin/scratch\n" +"Last-Translator: Marcus Kazmierczak \n" +"Language-Team: Esperanto \n" +"Language: eo\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"POT-Creation-Date: 2019-02-18T07:20:46-08:00\n" +"PO-Revision-Date: 2019-02-18 08:16-0800\n" +"X-Generator: Poedit 2.2.1\n" +"X-Domain: myguten\n" + +#. 插件名称 +msgid "Scratch Plugin" +msgstr "Scratch 扩展插件" + +#: block.js:6 +msgid "Simple Block" +msgstr "简单区块" + +#: block.js:13 block.js:21 +msgid "Hello World" +msgstr "世界你好" +``` + +创建翻译文件的最后一步是将 `myguten-eo.po` 转换为所需的 JSON 格式。可使用 WP-CLI 的 [`wp i18n make-json` 命令](https://developer.wordpress.org/cli/commands/i18n/make-json/)(要求 WP-CLI v2.2.0 及以上版本): + +```bash +wp i18n make-json myguten-eo.po --no-purge +``` + +这将生成包含以下内容的 JSON 文件 `myguten-eo-[md5].json`: + +```json +{ + "translation-revision-date": "2019-04-26T13:30:11-07:00", + "generator": "WP-CLI/2.2.0", + "source": "block.js", + "domain": "messages", + "locale_data": { + "messages": { + "": { + "domain": "messages", + "lang": "eo", + "plural-forms": "nplurals=2; plural=(n != 1);" + }, + "Simple Block": [ "简单区块" ], + "Hello World": [ "世界你好" ] + } + } +} +``` + +### 加载翻译文件 + +最后一步是告知 WordPress 翻译文件的存放位置。`wp_set_script_translations` 函数支持可选的第三个参数,用于指定优先查找翻译文件的路径。例如: + +```php + %s ", $content, esc_html( $value ) ); + } else { + return $content; + } +} +add_filter( 'the_content', 'myguten_content_filter' ); +``` + +#### 在区块中使用文章元数据 + +您也可以在其他区块中使用文章元数据。在此示例中,数据会在每个段落区块渲染时(即显示给用户时)加载到其末尾。您可以根据需要将其替换为任何核心或自定义区块类型。 + +在 PHP 中,使用 [register_block_type](https://developer.wordpress.org/reference/functions/register_block_type/) 函数设置区块渲染时的回调,以包含元值。 + +```php +function myguten_render_paragraph( $block_attributes, $content ) { + $value = get_post_meta( get_the_ID(), 'myguten_meta_block_field', true ); + // 输出前检查值是否已设置 + if ( $value ) { + return sprintf( "%s (%s)", $content, esc_html( $value ) ); + } else { + return $content; + } +} + +register_block_type( 'core/paragraph', array( + 'api_version' => 3, + 'render_callback' => 'myguten_render_paragraph', +) ); +``` + +### 第四步:使用区块模板(可选) + +使用元区块的一个问题是作者很容易忘记添加,因为它需要添加到每篇文章中。您可以通过使用[区块模板](/docs/reference-guides/block-api/block-templates.md)来解决这个问题。区块模板是按文章类型预定义的区块项列表。模板允许您为文章类型指定默认的初始状态。 + +在此示例中,您将使用模板自动在文章顶部插入元区块。 + +将以下代码添加到 `myguten-meta-block.php` 文件中: + +```php +function myguten_register_template() { + $post_type_object = get_post_type_object( 'post' ); + $post_type_object->template = array( + array( 'myguten/meta-block' ), + ); +} +add_action( 'init', 'myguten_register_template' ); +``` + +您还可以在数组中添加其他区块类型,包括占位符,甚至可以将文章锁定为一组特定的区块。模板是控制编辑体验的强大工具,更多信息请参阅上面链接的文档。 + +## 总结 + +本指南展示了如何使用区块读取和写入文章元数据。以下部分介绍了与现有元框的向后兼容性。 + +## 向后兼容性 + +### 测试、转换和维护现有元框 + +在将元框转换为区块之前,可以先测试元框是否与区块编辑器兼容,并明确标记。 + +如果某个元框与区块编辑器不兼容,且无法更新以使其正常工作,下一步是在元框声明中添加 `__block_editor_compatible_meta_box` 参数: + +```php +add_meta_box( 'my-meta-box', 'My Meta Box', 'my_meta_box_callback', + null, 'normal', 'high', + array( + '__block_editor_compatible_meta_box' => false, + ) +); +``` + +WordPress 不会显示该元框,而是会显示一条消息,说明它与区块编辑器不兼容,并包含指向经典编辑器插件的链接。默认情况下,`__block_editor_compatible_meta_box` 为 `true`。 + +将元框转换为区块后,可以声明其存在以保持向后兼容性: + +```php +add_meta_box( 'my-meta-box', 'My Meta Box', 'my_meta_box_callback', + null, 'normal', 'high', + array( + '__back_compat_meta_box' => true, + ) +); +``` + +当使用区块编辑器时,此元框将不再显示在元框区域中,因为它现在仅用于向后兼容。在经典编辑器中,它将像以前一样显示。 + +### 元数据框数据收集 + +在每次区块编辑器页面加载时,我们会注册一个用于收集元数据框数据的操作,以判断某个区域是否为空。收集元数据框数据后,原始全局状态将被重置。 + +详见 [`register_and_do_post_meta_boxes`](https://developer.wordpress.org/reference/functions/register_and_do_post_meta_boxes/)。 + +该操作将遍历 `post.php` 用于注册元数据框的函数和钩子,包括 `add_meta_boxes`、`add_meta_boxes_{$post->post_type}` 和 `do_meta_boxes`。 + +元数据框会经过过滤,移除所有核心元数据框、标准自定义分类法元数据框,以及任何声明仅用于向后兼容的元数据框。 + +随后检查该特定类型元数据框的每个位置是否处于活动状态。若非空则存储值为 true,若为空则存储值为 false。这些元数据框位置数据随后会通过编辑器 Redux 存储库在 `INITIALIZE_META_BOX_STATE` 中分发。 + +理想情况下,这可以在编辑器实例化时完成,从而简化流程。但在 `admin_enqueue_scripts`(即调用 `initializeEditor()` 的阶段)之前无法获知元数据框状态。除非我们将 `initializeEditor()` 移至页脚或 `admin_head` 之后的某个阶段执行,否则目前只能如此处理。随着编辑器引导机制的最新改进,现在可能已具备可行性。需使用 ACF 进行测试验证。 + +### Redux 与 React 元数据框管理 + +渲染区块编辑器时,元数据框会被渲染到隐藏的 `#metaboxes` 容器中。 + +*Redux 存储库默认将所有元数据框设为非活动状态*。当接收到 `INITIALIZE_META_BOX_STATE` 时,存储库会将活动元数据框区域的 `isActive` 标志更新为 `true`。随后 React 将检查 Redux 传递给 `MetaBox` 组件的新属性。若该 `MetaBox` 处于活动状态,则会渲染 `MetaBoxArea` 组件而非空内容。`MetaBox` 组件是协调 `MetaBoxArea` 与 Redux 存储库的容器组件。*若无活动元数据框,则保持默认状态。由于所有核心元数据框已被移除,这将成为默认行为。* + +#### MetaBoxArea 组件 + +组件渲染时会存储元数据框容器的引用,并从预取位置获取元数据框的 HTML 内容。 + +文章更新时,仅会提交处于活动状态的元数据框区域,以避免不必要的请求。元数据框提交不会创建额外修订版本。任何活动元数据框在触发 `REQUEST_POST_UPDATE` 时都会激活 Redux 操作(参见 `editor/effects.js`)。`REQUEST_META_BOX_UPDATES` 操作会将该元数据框状态设为 `isUpdating`。`isUpdating` 属性将传递至 `MetaBoxArea` 并触发表单提交。 + +当元数据框区域保存时,我们会显示更新遮罩层,防止用户在保存过程中修改表单值。 + +示例保存地址形如: + +`example.org/wp-admin/post.php?post=1&action=edit&meta-box-loader=1` + +该地址通过 `_wpMetaBoxUrl` 全局变量自动传递给 React。 + +此页面模拟 `post.php` 的文章表单,提交时会触发所有常规钩子和操作,并具备正确的全局状态以正常执行所有 PHP 元数据框逻辑,无需修改现有代码。提交成功后,React 会触发 `handleMetaBoxReload` 来移除更新遮罩。 + +### 常见兼容性问题 + +大多数 PHP 元数据框在区块编辑器中应能继续工作,但包含高级功能的元数据框可能会出现异常。以下是元数据框在区块编辑器中可能无法正常工作的常见原因: + +- 依赖针对旧编辑器文章标题、内容字段及其他元数据框选择器的插件 +- 依赖 TinyMCE API 的插件(区块编辑器中不再存在单一 TinyMCE 实例) +- 在“提交”或“保存”时更新 DOM 的插件 + +另请注意:若插件触发输出 PHP 警告或通知,将导致 HTML 文档类型(``)输出异常,从而使浏览器启用“怪异模式”(当浏览器无法识别文档类型时激活的兼容模式)。区块编辑器不适用于此模式,但可能*看似*正常运行。若遇到*元数据框覆盖编辑器*等布局问题,请检查网页源代码确认文档类型声明是否为页面首行内容。JavaScript 控制台也会显示相关警告提示。 + +# 元数据框 + +## 概述 + +在区块编辑器出现之前,自定义元数据框被用于扩展编辑器功能。现在有了新的扩展方式,既赋予开发者更多能力,又为内容创作者带来更佳体验。建议将旧版自定义元数据框迁移至以下新方法之一,为编辑器用户创造更统一连贯的体验。 + +区块编辑器确实支持大多数现有元数据框,详见[下文向后兼容性说明](#向后兼容性)。 + +若您需要在编辑器外部处理文章元数据,请参阅[侧边栏教程](/docs/how-to-guides/sidebar-tutorial/plugin-sidebar-0.md)。 + +### 使用区块存储元数据 + +通常区块会将属性值存储在序列化的区块HTML中。但您也可以创建将属性值保存为文章元数据的区块,这样就能在模板的任何位置通过编程方式访问这些数据。 + +本指南将演示如何创建能够提示用户输入单个值,并将其保存为文章元数据的区块。 + +## 准备工作 + +本指南假设您已熟悉WordPress插件、文章元数据和基础JavaScript。建议先学习[JavaScript入门教程](/docs/getting-started/fundamentals/javascript-in-the-block-editor.md)打好基础。 + +虽然本指南将逐步创建基础区块,但推荐通过[创建区块教程](/docs/getting-started/tutorial.md)来更深入理解自定义区块的开发流程。 + +您需要准备: + +- WordPress开发环境 +- 已激活并可编辑的基础插件 +- 配置好构建和队列加载的JavaScript环境 + +您可参考[完整的元数据区块示例](https://github.com/WordPress/block-development-examples/tree/trunk/plugins/meta-block-bb1e55)来搭建开发环境。 + +## 分步指南 + +1. [注册元字段](#步骤1-注册元字段) +2. [添加元数据区块](#步骤2-添加元数据区块) +3. [使用文章元数据](#步骤3-使用文章元数据) +4. [收尾工作](#步骤4-使用区块模板-可选) + +### 步骤1:注册元字段 + +文章元字段是用于存储文章扩展数据的WordPress对象。使用前需先注册新元字段。详见[文章元数据管理](https://developer.wordpress.org/plugins/metadata/managing-post-metadata/)文档。 + +注册字段时请注意`show_in_rest`参数,这能确保数据被包含在REST API中(区块编辑器通过该API加载和保存元数据)。更多信息请参阅[`register_post_meta`](https://developer.wordpress.org/reference/functions/register_post_meta/)函数说明。 + +此外,文章类型需支持`custom-fields`才能使`register_post_meta`函数正常工作。 + +将以下代码添加到PHP插件中以注册字段: + +```php + true, + 'single' => true, + 'type' => 'string', + ) ); +} +add_action( 'init', 'myguten_register_post_meta' ); +``` + +### 步骤2:添加元数据区块 + +完成上一步的元字段注册后,接下来创建用于向用户显示字段值的新区块。 + +区块可通过`useEntityProp`钩子获取或修改元数据值。 + +将以下代码添加到JavaScript `src/index.js`: + +```js +import { registerBlockType } from '@wordpress/blocks'; +import { TextControl } from '@wordpress/components'; +import { useSelect } from '@wordpress/data'; +import { useEntityProp } from '@wordpress/core-data'; +import { useBlockProps } from '@wordpress/block-editor'; + +registerBlockType( 'myguten/meta-block', { + edit: ( { setAttributes, attributes } ) => { + const blockProps = useBlockProps(); + const postType = useSelect( + ( select ) => select( 'core/editor' ).getCurrentPostType(), + [] + ); + + const [ meta, setMeta ] = useEntityProp( 'postType', postType, 'meta' ); + + const metaFieldValue = meta[ 'myguten_meta_block_field' ]; + const updateMetaValue = ( newValue ) => { + setMeta( { ...meta, myguten_meta_block_field: newValue } ); + }; + + return ( +
    + +
    + ); + }, + + // 不保存信息至区块 + // 数据通过钩子保存至文章元数据 + save: () => { + return null; + }, +} ); +``` + +创建文章并添加元数据区块即可验证功能。您将看到可输入值的字段。当保存文章(草稿或已发布)时,文章元数据值也会同步保存。可通过保存后重新加载草稿来验证——重新加载后表单仍将保留输入内容。 + +您还可以检查数据库表`wp_postmeta`,确认新文章ID包含新字段数据。 + +**故障排除**:请确保在每次修改后重新构建代码(您更新了步骤1的PHP代码,且JavaScript文件已正确加入队列)。检查构建输出和开发者控制台是否有错误信息。 \ No newline at end of file diff --git a/how-to-guides/notices/README.md b/how-to-guides/notices/README.md new file mode 100644 index 0000000..7015197 --- /dev/null +++ b/how-to-guides/notices/README.md @@ -0,0 +1,82 @@ +# 通知 + +通知是显示在管理页面顶部附近的信息性用户界面元素。WordPress核心程序、主题和插件都使用通知来指示操作结果,或吸引用户关注必要信息。 + +在经典编辑器中,挂接到`admin_notices`操作的通知可以渲染任意HTML内容。而在区块编辑器中,通知则受限于更规范的API。 + +## 经典编辑器中的通知 + +在经典编辑器中,"文章草稿已更新"通知的示例如下: + +![经典编辑器中的文章草稿更新通知](https://raw.githubusercontent.com/WordPress/gutenberg/HEAD/docs/how-to-guides/notices/classic-editor-notice.png) + +生成等效的"文章草稿已更新"通知需要如下代码: + +```php +/** + * 挂接到'admin_notices'操作来渲染 + * 通用HTML通知。 + */ +function myguten_admin_notice() { + $screen = get_current_screen(); + // 仅在文章编辑器中显示此通知。 + if ( ! $screen || 'post' !== $screen->base ) { + return; + } + // 渲染通知的HTML内容。 + wp_admin_notice( + sprintf( __( '文章草稿已更新。预览文章' ), get_preview_post_link() ), + array( + 'type' => 'success', + 'dismissible' => true, + ) + ); +}; +add_action( 'admin_notices', 'myguten_admin_notice' ); +``` + +重要的是,`admin_notices`钩子允许开发者渲染任意HTML内容。这样做的主要优势是开发者拥有极大的灵活性。关键劣势在于,任意HTML使得通知功能的后续迭代更加困难(甚至不可能),因为迭代需要兼容各种任意HTML。这就是区块编辑器采用规范API的原因。 + +## 区块编辑器中的通知 + +在区块编辑器中,"文章已发布"通知的示例如下: + +![区块编辑器中的文章发布通知](https://raw.githubusercontent.com/WordPress/gutenberg/HEAD/docs/how-to-guides/notices/block-editor-notice.png) + +生成等效的"文章已发布"通知需要如下代码: + +```js +( function ( wp ) { + wp.data.dispatch( 'core/notices' ).createNotice( + 'success', // 可选类型:success, info, warning, error + '文章已发布。', // 显示的文本内容 + { + isDismissible: true, // 用户是否可关闭该通知 + // 用户可执行的操作 + actions: [ + { + url: '#', + label: '查看文章', + }, + ], + } + ); +} )( window.wp ); +``` + +当在JavaScript应用程序生命周期中生成通知时,您应该使用此_通知数据API_。 + +为了更好地理解上述具体代码示例: + +- `wp`是WordPress的全局窗口变量 +- `wp.data`是区块编辑器提供的用于访问数据存储的对象 +- `wp.data.dispatch('core/notices')`访问由通知包注册到区块编辑器数据存储的功能 +- `createNotice()`是通知包提供的用于注册新通知的函数。区块编辑器通过读取通知数据存储来了解需要显示哪些通知 + +请查看[_在编辑器中加载资源_](/docs/how-to-guides/enqueueing-assets-in-the-editor.md)教程,了解如何将自定义JavaScript加载到区块编辑器中的基础知识。 + +## 了解更多 + +区块编辑器提供完整的API用于生成通知。官方文档是了解功能可能性的绝佳资源。 + +要获取可用操作和选择器的完整列表,请参阅[通知数据手册](/docs/reference-guides/data/data-core-notices.md)页面。 \ No newline at end of file diff --git a/how-to-guides/notices/block-editor-notice.png b/how-to-guides/notices/block-editor-notice.png new file mode 100644 index 0000000..89bf3e4 Binary files /dev/null and b/how-to-guides/notices/block-editor-notice.png differ diff --git a/how-to-guides/notices/classic-editor-notice.png b/how-to-guides/notices/classic-editor-notice.png new file mode 100644 index 0000000..d96f4aa Binary files /dev/null and b/how-to-guides/notices/classic-editor-notice.png differ diff --git a/how-to-guides/platform/README.md b/how-to-guides/platform/README.md new file mode 100644 index 0000000..205e3c5 --- /dev/null +++ b/how-to-guides/platform/README.md @@ -0,0 +1,60 @@ +# 开发平台 + +古腾堡项目不仅致力于为WordPress打造更优秀的编辑器,更在构建一个可扩展的开发平台。该平台包含一系列可用于Web应用程序的JavaScript工具包。[查看npm上的可用工具包列表](https://www.npmjs.com/org/wordpress)。 + +## 用户界面组件 + +[WordPress组件包](/packages/components/README.md)提供了一系列可在项目中直接使用的UI组件。访问[WordPress Storybook网站](https://wordpress.github.io/gutenberg/)可交互式查看可用组件及其设置。 + +以下是在项目中使用组件的快速示例: + +安装依赖: + +```bash +npm install --save @wordpress/components +``` + +在React中使用: + +```jsx +import { Button } from '@wordpress/components'; + +function MyApp() { + return ; +} +``` + +多数组件包含样式CSS文件,需要引入才能正常显示样式。组件样式表位于`node_modules/@wordpress/components/build-style/style.css`,可直接链接或复制到项目中引入。 + +## 开发脚本 + +[`@wordpress/scripts`工具包](/packages/scripts/README.md)是一组可复用的JavaScript开发脚本——包含构建、代码检查和测试脚本,无需额外配置文件即可使用。 + +以下是使用`wp-scripts`工具的快速示例: + +安装依赖: + +```bash +npm install --save-dev @wordpress/scripts +``` + +在package.json文件中添加脚本配置: + +```json + "scripts": { + "build": "wp-scripts build", + "format": "wp-scripts format", + "lint:js": "wp-scripts lint-js", + "start": "wp-scripts start" + } +``` + +配置后即可使用`npm run build`命令,通过预设的webpack配置构建项目,代码格式化和检查同理。`start`命令用于开发模式。完整文档请参阅[`@wordpress/scripts`工具包](/packackages/scripts/README.md)。 + +更多信息请参考区块编辑器手册中的[JavaScript入门教程](/docs/how-to-guides/javascript/js-build-setup.md)。 + +## 区块编辑器 + +[`@wordpress/block-editor`工具包](https://developer.wordpress.org/block-editor/packages/packages-block-editor/)支持创建和使用独立的区块编辑器。 + +详细了解请阅读[“构建自定义区块编辑器”教程](/docs/how-to-guides/platform/custom-block-editor.md)。 \ No newline at end of file diff --git a/how-to-guides/platform/custom-block-editor.md b/how-to-guides/platform/custom-block-editor.md new file mode 100644 index 0000000..2f51bd3 --- /dev/null +++ b/how-to-guides/platform/custom-block-editor.md @@ -0,0 +1,531 @@ +## 总结 + +恭喜您完成本指南的学习!现在您应该对区块编辑器的工作原理有了更深入的理解。 + +您刚刚构建的自定义区块编辑器完整代码已[发布于GitHub](https://github.com/getdave/standalone-block-editor),欢迎下载并亲自体验。在实践操作中不断探索,将所学知识推向新的高度。 + +## 区块持久化 + +在创建自定义区块编辑器的旅程中,您已经取得了长足进展。但还有一个重要领域有待探讨——区块持久化。换句话说,就是让您的区块在页面刷新之间保持保存并可用。 + +![展示页面刷新时区块恢复效果的WordPress自定义区块编辑器界面截屏](https://developer.wordpress.org/files/2023/07/custom-block-editor-persistance.gif) + +由于这仅是一项实验,本指南选择使用浏览器的`localStorage` API来处理区块数据保存。在实际应用场景中,您可能会选择更可靠稳健的系统(例如数据库)。 + +接下来,让我们深入探讨如何处理区块保存。 + +### 在状态中存储区块 + +查看`src/components/block-editor/index.js`文件时,您会注意到已创建了将区块存储为数组的状态: + +```jsx +// 文件:src/components/block-editor/index.js + +const [ blocks, updateBlocks ] = useState( [] ); +``` + +如前所述,`blocks`作为`value`属性传递给"受控"组件``,这为其注入了一组初始区块。同样地,`updateBlocks`设置器被连接到``的`onInput`回调,确保区块状态与编辑器内对区块所做的更改保持同步。 + +### 保存区块数据 + +现在将注意力转向`onChange`处理程序,您会注意到它连接到了一个名为`persistBlocks()`的函数,该函数定义如下: + +```js +// 文件:src/components/block-editor/index.js + +function persistBlocks( newBlocks ) { + updateBlocks( newBlocks ); + window.localStorage.setItem( 'getdavesbeBlocks', serialize( newBlocks ) ); +} +``` + +此函数接收已"提交"的区块更改数组,并调用状态设置器`updateBlocks`。同时,它还将区块存储在LocalStorage中,键名为`getdavesbeBlocks`。为实现这一点,区块数据被序列化为[Gutenberg"区块语法"](https://developer.wordpress.org/block-editor/principles/key-concepts/#blocks)格式,这意味着可以安全地将其存储为字符串。 + +如果打开开发者工具并检查LocalStorage,您会看到序列化的区块数据随着编辑器中发生的更改而存储和更新。以下是该格式的示例: + +``` + +

    在WordPress后台进行独立区块编辑器的实验

    + + + +

    本实验旨在探索在WordPress后台创建独立区块编辑器实例的难易程度。

    + +``` + +### 检索历史区块数据 + +实现持久化固然重要,但只有当这些数据在每次完整页面重载时被检索并在编辑器中恢复,才能真正发挥作用。 + +访问数据会产生副作用,因此必须使用`useEffect`钩子来处理: + +```jsx +// 文件:src/components/block-editor/index.js + +useEffect( () => { + const storedBlocks = window.localStorage.getItem( 'getdavesbeBlocks' ); + + if ( storedBlocks && storedBlocks.length ) { + updateBlocks( () => parse( storedBlocks ) ); + createInfoNotice( '区块已加载', { + type: 'snackbar', + isDismissible: true, + } ); + } +}, [] ); +``` + +该处理程序: + +- 从本地存储中获取序列化的区块数据 +- 使用`parse()`工具将序列化区块转换回JavaScript对象 +- 调用状态设置器`updateBlocks`,使状态中的`blocks`值更新为从LocalStorage检索到的区块 + +这些操作的结果是,受控的``组件会使用从LocalStorage恢复的区块进行更新,从而使编辑器显示这些区块。 + +最后,您需要生成一个通知——该通知将以"snackbar"形式显示在``组件中——以指示区块已恢复。 + +# 构建自定义区块编辑器 + +WordPress 区块编辑器是一款功能强大的工具,允许您以多种方式创建和格式化内容。其部分功能由 [`@wordpress/block-editor`](/packages/block-editor/README.md) 包驱动,这是一个提供编辑器核心功能的 JavaScript 库。 + +该软件包还可用于为几乎任何其他网络应用程序创建自定义区块编辑器。这意味着您可以在 WordPress 之外使用相同的区块和区块编辑体验。 + +![显示内容区块和编辑选项的WordPress区块编辑器界面](https://developer.wordpress.org/files/2023/07/custom-block-editor.png '在自定义WordPress管理页面中运行的独立编辑器实例,其中包含示例区块') + +这种灵活性和互操作性使区块成为跨多个应用程序构建和管理内容的强大工具。同时也让开发人员能更轻松地创建最适合用户的内容编辑器。 + +本指南将介绍创建首个自定义区块编辑器的基础知识。 + +## 引言 + +尽管古腾堡代码库包含众多软件包和组件,初看可能令人望而生畏。但其核心始终是管理和编辑区块。因此若想在编辑器上进行开发,必须从基础层面理解区块编辑的工作原理。 + +本指南将引导您在 WordPress 内构建一个功能完整的自定义区块编辑器"实例"。在此过程中,我们将向您介绍关键软件包和组件,帮助您洞悉区块编辑器的工作机制。 + +阅读完本文后,您将对区块编辑器的内部运作有扎实的理解,并为创建自己的区块编辑器实例奠定坚实基础。 + +
    + 本指南涉及的完整代码可通过配套的WordPress插件下载。该插件中的演示代码是核心学习资源。 +
    + +## 代码语法 + +本指南中的代码片段使用 JSX 语法。当然您也可以选择使用原生 JavaScript。不过许多开发者在熟悉 JSX 后认为其更易读写,因此《区块编辑器手册》中的所有代码示例均采用此语法。 + +## 即将构建的内容 + +通过本指南,您将创建一个(近乎)功能完整的区块编辑器实例。最终效果如下所示: + +![在自定义WordPress管理页面中运行的独立编辑器实例,其中包含示例区块](https://developer.wordpress.org/files/2023/07/custom-block-editor.png) + +虽然外观相似,但这个编辑器并非您在WordPress中创建文章和页面时熟悉的那个*区块编辑器*,而是一个完全自定义的实例,将存在于名为"区块编辑器"的自定义WordPress管理页面中。 + +该编辑器将具备以下特性: + +- 支持添加和编辑所有核心区块 +- 熟悉的视觉样式和主界面/侧边栏布局 +- 页面刷新后保持*基础*的区块持久化 + +## 插件设置与组织架构 + +自定义编辑器将以WordPress插件形式构建。为简化概念,插件将命名为 `Standalone Block Editor Demo`(独立区块编辑器演示),这正是其功能体现。 + +插件文件结构如下所示: + +![包含配置文件和源码的项目目录列表](https://wordpress.org/gutenberg/files/2020/03/repo-files.png 'https://github.com/getdave/standalone-block-editor 插件文件结构截图') + +以下是核心文件说明: + +- `plugin.php` – 包含注释元数据的标准插件入口文件,负责引入 `init.php` +- `init.php` - 处理主插件逻辑的初始化 +- `src/` (目录) - 存放 JavaScript 和 CSS 源文件的位置(这些文件*不*由插件直接加载) +- `webpack.config.js` - 自定义 Webpack 配置,扩展了 [`@wordpress/scripts`](https://developer.wordpress.org/block-editor/reference-guides/packages/packages-scripts/) npm 包提供的默认配置,以支持自定义CSS样式(通过Sass实现) + +未在上图显示的是 `build/` 目录,该目录用于存放通过 `@wordpress/scripts` 编译输出的 JS 和 CSS 文件。这些文件由插件单独加载。 + +
    + 在本指南中,每个代码片段的顶部都会以注释形式标注文件名引用,方便您对照学习。 +
    + +基础文件结构就绪后,接下来让我们了解需要哪些软件包。 + +### 键盘导航 + +完成基础组件结构搭建后,最后一步是将所有内容包裹在 [`navigateRegions` 高阶组件](https://github.com/WordPress/gutenberg/tree/e38dbe958c04d8089695eb686d4f5caff2707505/packages/components/src/higher-order/navigate-regions) 中,为布局中的不同"区域"提供键盘导航功能。 + +```jsx +// 文件:src/editor.js + +export default navigateRegions( Editor ); +``` + +## 自定义 `` 组件 + +核心布局和组件搭建完成后,现在开始探索区块编辑器本身的自定义实现。 + +这个组件名为 ``,也是实现核心功能的关键所在。 + +打开 `src/components/block-editor/index.js` 文件可以看到,这是目前遇到的最复杂组件。由于涉及大量逻辑,我们先聚焦于 `` 组件渲染的内容: + +```js +// 文件:src/components/block-editor/index.js + +return ( +
    + + + + + + +
    +); +``` + +其中的关键组件是 `` 和 ``,下面我们来详细分析。 + +### 理解 `` 组件 + +[``](https://github.com/WordPress/gutenberg/tree/e38dbe958c04d8089695eb686d4f5caff2707505/packages/block-editor/src/components/provider) 是组件层级中最重要的组件之一。它为新区块编辑器建立了一个独立的区块编辑上下文环境。 + +因此,该组件对本项目的核心目标具有*奠基性*意义。 + +`` 的子组件构成了区块编辑器的用户界面。这些组件通过上下文(`Context`)获取数据,从而能够在编辑器内*渲染*和*管理*区块及其行为。 + +```jsx +// 文件:src/components/block-editor/index.js + + +``` + +#### `BlockEditor` 属性说明 + +可以看到 `` 接收一个(已解析的)区块对象数组作为其 `value` 属性。当检测到编辑器内部发生变更时,会调用 `onChange` 和/或 `onInput` 处理函数(将新的区块数组作为参数传递)。 + +其内部实现机制是通过订阅提供的注册表(通过 [`withRegistryProvider` 高阶组件](https://github.com/WordPress/gutenberg/blob/e38dbe958c04d8089695eb686d4f5caff2707505/packages/block-editor/src/components/provider/index.js#L158)),监听区块变更事件,判断区块变更是否具有持久性,然后相应调用合适的 `onChange|Input` 处理函数。 + +在这个简单项目中,这些功能使您可以实现: + +- 将当前区块数组作为状态存储于 `blocks` +- 通过调用钩子设置器 `updateBlocks(blocks)`,在 `onInput` 时更新内存中的 `blocks` 状态 +- 使用 `onChange` 将区块基础数据持久化到 `localStorage`(该事件在[区块更新被确认为"已提交"时触发](https://github.com/WordPress/gutenberg/tree/HEAD/packages/block-editor/src/components/provider#onchange)) + +需要特别注意的是,该组件还接收 `settings` 属性。这里将配置之前在 `init.php` 中以 JSON 格式内联的编辑器设置,可用于配置自定义颜色、可用图片尺寸等功能,以及[更多设置](https://github.com/WordPress/gutenberg/tree/4c472c3443513d070a50ba1e96f3a476861447b3/packages/block-editor#SETTINGS_DEFAULTS)。 + +## 编辑器的“核心” + +虽然 WordPress 编辑器由众多动态组件构成,但其核心是 [`@wordpress/block-editor`](/packages/block-editor/README.md) 包,该包的功能可通过其自述文件精辟概括: + +> 此模块允许您创建并使用独立的区块编辑器。 + +完美!这正是您用来创建自定义区块编辑器实例的核心工具包。但首先,您需要为编辑器创建载体。 + +## 创建自定义“区块编辑器”页面 + +让我们从在 WordPress 后台创建自定义页面开始,该页面将承载自定义区块编辑器实例。 + +
    + 如果您已熟悉在 WordPress 中创建自定义后台页面的流程,可以直接跳至后续步骤。 +
    + +### 注册页面 + +您需要使用标准的 WordPress [`add_menu_page()`](https://developer.wordpress.org/reference/functions/add_menu_page/) 辅助函数来[注册自定义后台页面](https://developer.wordpress.org/reference/functions/add_menu_page/): + +```php +// 文件:init.php + +add_menu_page( + '独立区块编辑器', // 页面可见名称 + '区块编辑器', // 菜单标签 + 'edit_posts', // 所需权限 + 'getdavesbe', // 页面钩子/别名 + 'getdave_sbe_render_block_editor', // 页面渲染函数 + 'dashicons-welcome-widgets-menus' // 自定义图标 +); +``` + +`getdave_sbe_render_block_editor` 函数将用于渲染后台页面的内容。温馨提示:每个步骤的源代码均可在[配套插件](https://github.com/getdave/standalone-block-editor)中查看。 + +### 添加目标 HTML + +由于区块编辑器是基于 React 的应用程序,您需要在自定义页面中输出 HTML 容器,以便 JavaScript 渲染区块编辑器。 + +让我们使用上一步中引用的 `getdave_sbe_render_block_editor` 函数: + +```php +// 文件:init.php + +function getdave_sbe_render_block_editor() { + ?> +
    + 编辑器加载中... +
    + ` 组件渲染到自定义管理页面的等待 `
    ` 中。 + +```jsx +domReady( function () { + const root = createRoot( document.getElementById( 'getdave-sbe-block-editor' ) ); + const settings = window.getdaveSbeSettings || {}; + registerCoreBlocks(); + root.render( + + ); +} ); +``` + +
    + 无需创建不必要的 JS 全局变量,即可从 PHP 渲染编辑器。请查看 Gutenberg 插件中的 编辑站点 包以获取示例。 +
    + +## 审查 `` 组件 + +让我们仔细看看上面代码中使用的 `` 组件,它位于[配套插件](https://github.com/getdave/standalone-block-editor)的 `src/editor.js` 文件中。 + +尽管名称如此,但这并不是块编辑器的实际核心。相反,它是一个*包装器*组件,将包含构成自定义编辑器主体的组件。 + +### 依赖项 + +在 `` 中的第一件事是引入一些依赖项。 + +```jsx +// 文件:src/editor.js + +import Notices from 'components/notices'; +import Header from 'components/header'; +import Sidebar from 'components/sidebar'; +import BlockEditor from 'components/block-editor'; +``` + +其中最重要的是内部组件 `BlockEditor` 和 `Sidebar`,稍后将详细介绍。 + +其余组件主要包括构成编辑器布局和周围用户界面(UI)的静态元素,包括标题和通知区域等。 + +### 编辑器渲染 + +有了这些组件后,您可以定义 `` 组件。 + +```jsx +// 文件:src/editor.js + +function Editor( { settings } ) { + return ( + +
    + +
    + + +
    +
    + ); +} +``` + +在此过程中,编辑器的核心布局以及一些专门的[上下文提供者](https://react.dev/reference/react/createContext#provider)被搭建起来,这些提供者使特定功能在整个组件层次结构中可用。 + +让我们更详细地研究这些: + +- `` – 启用[拖放功能的放置区域](https://github.com/WordPress/gutenberg/tree/e38dbe958c04d8089695eb686d4f5caff2707505/packages/components/src/drop-zone) +- `` – 提供一个“消息栏”通知,如果有任何消息发送到 `core/notices` 存储,则会渲染该通知 +- `
    ` – 在编辑器 UI 顶部渲染静态标题“独立块编辑器” +- `` – 自定义块编辑器组件 + +### 理解 `` 组件 + +除了 `` 之外,下一个最有趣的组件是 [``](https://github.com/WordPress/gutenberg/blob/e38dbe958c04d8089695eb686d4f5caff2707505/packages/block-editor/src/components/block-list/index.js)。 + +这是最重要的组件之一,其作用是**将区块列表渲染到编辑器中**。 + +它能够实现这一功能,部分原因在于它被放置为 `` 的子组件,这使得它可以完全访问编辑器中当前区块状态的所有信息。 + +#### `BlockList` 是如何工作的? + +在底层,`` 依赖于其他几个低级组件来渲染区块列表。 + +这些组件的层次结构可以*近似*如下: + +```jsx +// 以下为伪代码,仅作示例。 + + + /* 从 rootClientId 渲染区块列表。 */ + + /* 从 BlockList 渲染单个区块。 */ + + /* 渲染区块的标准可编辑区域。 */ + /* 根据其 `edit()` 实现渲染区块 UI。 */ + + + +``` + +以下是这些组件如何协同工作以渲染区块列表的大致过程: + +- `` 遍历所有区块的 `clientIds`,并通过 [``](https://github.com/WordPress/gutenberg/blob/e38dbe958c04d8089695eb686d4f5caff2707505/packages/block-editor/src/components/block-list/block.js) 渲染每个区块。 +- `` 则使用其自身的子组件 [``](https://github.com/WordPress/gutenberg/blob/def076809d25e2ad680beda8b9205ab9dea45a0f/packages/block-editor/src/components/block-edit/index.js) 渲染单个区块。 +- 最后,[区块本身](https://github.com/WordPress/gutenberg/blob/def076809d25e2ad680beda8b9205ab9dea45a0f/packages/block-editor/src/components/block-edit/edit.js) 使用 `Component` 占位符组件进行渲染。 + +`@wordpress/block-editor` 包中的组件是最复杂且涉及最多的组件之一。如果你想从根本上理解编辑器的工作原理,理解这些组件至关重要。强烈建议研究这些组件。 + +## 审查侧边栏 + +在 `` 的渲染中,还有一个 `` 组件。 + +```jsx +// 文件:src/components/block-editor/index.js + +return ( +
    + + /* <-- 侧边栏 */ + + + + +
    +); +``` + +这部分用于通过 `` 组件显示高级区块设置。 + +```jsx + + + +``` + +然而,细心的读者可能已经注意到,在 ``(`src/editor.js`)组件的布局中已经存在一个 `` 组件: + +```jsx +// 文件:src/editor.js + +
    + // <-- 这是什么? + +``` + +打开 `src/components/sidebar/index.js` 文件,可以看到这实际上是上面 `` 中渲染的组件。然而,该实现使用了 Slot/Fill 来暴露一个 `Fill`(``),随后该 `Fill` 被导入并在 `` 组件中渲染(见上文)。 + +通过这种方式,你可以将 `` 作为 `Sidebar.InspectorFill` 的子组件渲染。这样做的结果是,你可以在 `` 的 React 上下文中保留 ``,同时允许它在 DOM 中的另一个位置(即 `` 中)渲染。 + +这可能看起来过于复杂,但这是为了让 `` 能够访问当前区块的信息所必需的。如果没有 Slot/Fill,这种设置将极难实现。 + +至此,你已经了解了自定义 `` 的渲染过程。 + +
    +<BlockInspector> +本身实际上渲染了一个用于 <InspectorControls>Slot。这使你可以在区块的 edit() 定义中渲染一个 <InspectorControls>> 组件,并在编辑器的侧边栏中显示它。建议进一步探索该组件的更多细节。 +
    \ No newline at end of file diff --git a/how-to-guides/plugin-sidebar-0.md b/how-to-guides/plugin-sidebar-0.md new file mode 100644 index 0000000..abc4c53 --- /dev/null +++ b/how-to-guides/plugin-sidebar-0.md @@ -0,0 +1,409 @@ +### 步骤三:注册元字段 + +若要在 `post_meta` 表中操作字段,请使用 [register_post_meta](https://developer.wordpress.org/reference/functions/register_post_meta/) 函数创建名为 `sidebar_plugin_meta_block_field` 的新字段。 + +注意:此字段需对 REST API 可用,因为区块编辑器通过该接口访问数据。 + +将以下 PHP 代码添加到插件的 `init` 回调函数中: + +```php +register_post_meta( 'post', 'sidebar_plugin_meta_block_field', array( + 'show_in_rest' => true, + 'single' => true, + 'type' => 'string', +) ); +``` + +可通过查询区块编辑器存储空间来验证字段是否加载成功。实现后,重新加载编辑器页面并打开浏览器开发者控制台,在控制台中执行以下 JavaScript 代码片段进行确认: + +```js +wp.data.select( 'core/editor' ).getCurrentPost().meta; +``` + +该函数将返回包含已注册元字段的对象。 + +若代码返回 `undefined`,请确保文章类型支持 `custom-fields`。可通过[注册文章类型](https://developer.wordpress.org/reference/functions/register_post_type/#supports)时设置,或使用 [add_post_type_support 函数](https://developer.wordpress.org/reference/functions/add_post_type_support/)实现。 + +### 步骤四:初始化输入控件 + +当字段在编辑器存储中就绪后,即可将其呈现在用户界面中。我们将输入控件提取为独立函数以保持代码整洁,便于后续功能扩展。 + +```js +( function ( wp ) { + var el = React.createElement; + var registerPlugin = wp.plugins.registerPlugin; + var PluginSidebar = wp.editor.PluginSidebar; + var TextControl = wp.components.TextControl; + + var MetaBlockField = function () { + return el( TextControl, { + label: '元区块字段', + value: '初始值', + onChange: function ( content ) { + console.log( '内容已更改为 ', content ); + }, + } ); + }; + + registerPlugin( 'my-plugin-sidebar', { + render: function () { + return el( + PluginSidebar, + { + name: 'my-plugin-sidebar', + icon: 'admin-post', + title: '我的插件侧边栏', + }, + el( + 'div', + { className: 'plugin-sidebar-content' }, + el( MetaBlockField ) + ) + ); + }, + } ); +} )( window.wp ); +``` + +我们需要将 `MetaBlockField` 组件的初始值设为 `sidebar_plugin_meta_block_field` 的值,并在该值变化时保持同步更新。 + +`useSelect` 函数用于在组件加载时获取数据,并在数据变更时自动更新。以下是使用 `useSelect` 的代码更新版本: + +```js +( function ( wp ) { + var el = React.createElement; + var registerPlugin = wp.plugins.registerPlugin; + var PluginSidebar = wp.editor.PluginSidebar; + var Text = wp.components.TextControl; + var useSelect = wp.data.useSelect; + + var MetaBlockField = function () { + var metaFieldValue = useSelect( function ( select ) { + return select( 'core/editor' ).getEditedPostAttribute( + 'meta' + )[ 'sidebar_plugin_meta_block_field' ]; + }, [] ); + + return el( Text, { + label: '元区块字段', + value: metaFieldValue, + onChange: function ( content ) { + console.log( '内容已更改为 ', content ); + }, + } ); + }; + + registerPlugin( 'my-plugin-sidebar', { + render: function () { + return el( + PluginSidebar, + { + name: 'my-plugin-sidebar', + icon: 'admin-post', + title: '我的插件侧边栏', + }, + el( + 'div', + { className: 'plugin-sidebar-content' }, + el( MetaBlockField ) + ) + ); + }, + } ); +} )( window.wp ); +``` + +`wp.data.useSelect` 函数来自 `@wordpress/data` 包,因此需要在 PHP 的 `wp_register_script` 函数中将 `wp-data` 添加为依赖项。 + +注意:`getEditedPostAttribute` 调用用于获取文章的最新值,包括用户尚未保存的编辑内容。 + +更新代码后重新加载并打开侧边栏即可验证功能。此时输入框内容不再是「初始值」而显示为空字符串。虽然用户暂时还无法输入值,但可以验证当存储中的值发生变化时组件是否会更新。打开浏览器控制台执行: + +```js +wp.data + .dispatch( 'core/editor' ) + .editPost( { meta: { sidebar_plugin_meta_block_field: 'hello world!' } } ); +``` + +即可观察到输入组件中的内容实时更新。 + +### 步骤5:在输入内容变化时更新元字段 + +最后一步是在输入内容发生变化时更新元字段。 +`useDispatch` 函数接收一个存储名称作为唯一参数,并返回可用于更新存储的方法,这里我们将使用 `editPost`。 + +```js +( function ( wp ) { + var el = React.createElement; + var registerPlugin = wp.plugins.registerPlugin; + var PluginSidebar = wp.editor.PluginSidebar; + var TextControl = wp.components.TextControl; + var useSelect = wp.data.useSelect; + var useDispatch = wp.data.useDispatch; + + var MetaBlockField = function ( props ) { + var metaFieldValue = useSelect( function ( select ) { + return select( 'core/editor' ).getEditedPostAttribute( + 'meta' + )[ 'sidebar_plugin_meta_block_field' ]; + }, [] ); + + var editPost = useDispatch( 'core/editor' ).editPost; + + return el( TextControl, { + label: '元区块字段', + value: metaFieldValue, + onChange: function ( content ) { + editPost( { + meta: { sidebar_plugin_meta_block_field: content }, + } ); + }, + } ); + }; + + registerPlugin( 'my-plugin-sidebar', { + render: function () { + return el( + PluginSidebar, + { + name: 'my-plugin-sidebar', + icon: 'admin-post', + title: '我的插件侧边栏', + }, + el( + 'div', + { className: 'plugin-sidebar-content' }, + el( MetaBlockField ) + ) + ); + }, + } ); +} )( window.wp ); +``` + +更新后,当用户输入时,输入控件会调用 `editPost` 并在每次按键时更新编辑器存储。 + +更新 JavaScript 代码,加载侧边栏并在输入框中输入内容。您可以通过在输入控件中输入内容并在浏览器的开发控制台中执行以下 JavaScript 代码片段来确认内容已保存: + +```js +wp.data.select( 'core/editor' ).getEditedPostAttribute( 'meta' )[ + 'sidebar_plugin_meta_block_field' +]; +``` + +显示的消息应该是您在输入框中输入的内容。 + +在保存文章时,您可以通过保存后重新加载页面并确认输入控件已初始化为您最后输入的值,来验证内容是否正确存储到数据库中。 + +## 附加资源 + +有关使用 [@wordpress/data 包](/packages/data/README.md) 的文档。 + +本指南中使用的函数: + +- [useSelect](/packages/data/README.md#useselect) +- [getEditedPostAttribute](/docs/reference-guides/data/data-core-editor.md#geteditedpostattribute) +- [useDispatch](/packages/data/README.md#usedispatch) + +## 总结 + +您现在拥有一个自定义侧边栏,可用于更新 `post_meta` 内容。 + +完整示例可在 [block-development-examples](https://github.com/WordPress/block-development-examples) 代码库中下载 [plugin-sidebar 示例](https://github.com/WordPress/block-development-examples/tree/trunk/plugins/plugin-sidebar-9ee4a6)。 + +### 注意 + +如果您在编辑器“偏好设置”的“面板”页面中启用了“自定义字段”(通过右上角的三个点),与 TextControl 同名的字段(在本例中为 `sidebar_plugin_meta_block_field`)也会出现在编辑器窗口底部的自定义字段面板中。这两个字段可以访问相同的元属性。 + +![文本控件与自定义字段](https://raw.githubusercontent.com/WordPress/gutenberg/HEAD/docs/assets/plugin-sidebar-text-control-custom-field.png) + +在保存文章时,TextControl 中的值会先保存,而自定义字段中的值会后保存,因此最终保存到数据库的是自定义字段中的值。因此,如果您更改了 TextControl 中的值,最终保存的仍然是自定义字段中的值。 + +如果未启用自定义字段,则不会出现此问题。 + +如果您需要启用自定义字段并在侧边栏中显示文章元数据,有两种可能的解决方案: + +1. 在元字段名称前添加下划线,例如上述示例中的名称改为 `_sidebar_plugin_meta_block_field`。这表示该文章元数据应视为私有,不会在文章的自定义字段部分显示。使用此解决方案时,除非您在传递给 `register_post_meta` 的 `args` 数组中添加一个 `auth_callback` 属性,并提供一个最终返回 `true` 的函数,否则在保存文章时会生成错误。有关更多信息,请参阅 [post_meta](https://developer.wordpress.org/reference/functions/register_meta/#parameters) 页面中的 `args` 文档。 +2. 在 TextControl 的 `onChange` 函数中,将目标指向值字段的文本区域,并将其值设置为与 TextControl 元字段中的值相同。这样,两个位置的值将保持一致,因此您可以确保即使更改了 TextControl 中的值,它仍然会被保存到数据库中。 + +```js +return el( TextControl, { + label: '元区块字段', + value: metaFieldValue, + onChange: function ( content ) { + editPost( { + meta: { sidebar_plugin_meta_block_field: content } + }) + document.querySelector( {值字段文本区域} ).innerHTML = content; + }, +} ); +``` + +# 插件侧边栏 + +## 概述 + +如何为插件添加侧边栏。侧边栏位于编辑器最右侧区域。您的插件可以在检查器控件(齿轮图标)旁添加额外图标,该图标可展开显示。 + +![侧边栏示例](https://raw.githubusercontent.com/WordPress/gutenberg/HEAD/docs/assets/sidebar-up-and-running.png) + +*注意:本教程主要讲解自定义侧边栏,如需在侧边栏添加控件请参阅[区块工具栏与设置侧边栏](/docs/getting-started/fundamentals/block-in-the-editor.md)* + +## 准备工作 + +本教程假设您已具备插件基础环境,并准备添加PHP和JavaScript代码。请先参阅[JavaScript入门指南](/docs/getting-started/fundamentals/javascript-in-the-block-editor.md)了解WordPress插件基础知识及如何使用JavaScript扩展区块编辑器。 + +## 分步指南 + +### 步骤1:启动侧边栏 + +首先需要告知编辑器存在包含独立侧边栏的新插件。请分别使用`@wordpress/plugins`、`@wordpress/editor`和`react`包提供的[registerPlugin](/packages/plugins/README.md)、[PluginSidebar](/packages/editor/README.md#pluginsidebar)和[createElement](/packages/element/README.md)工具。 + +将以下代码添加至名为`plugin-sidebar.js`的JavaScript文件,并保存到插件目录: + +```js +( function ( wp, React ) { + var el = React.createElement; + var registerPlugin = wp.plugins.registerPlugin; + var PluginSidebar = wp.editor.PluginSidebar; + + registerPlugin( 'my-plugin-sidebar', { + render: function () { + return el( + PluginSidebar, + { + name: 'my-plugin-sidebar', + icon: 'admin-post', + title: '我的插件侧边栏', + }, + '元字段' + ); + }, + } ); +} )( window.wp, window.React ); +``` + +为使代码正常运行,需确保这些工具在浏览器中可用,因此必须将`wp-plugins`、`wp-editor`和`react`指定为脚本依赖项。 + +以下是注册脚本并声明依赖项的PHP代码: + +```php + +
    + +
    + +``` + +这种方法并非万无一失,用户仍可通过编辑器界面修改类名。但由于该设置位于“高级”面板下,大多数情况下会保持原样。这为主题开发者提供了一定程度的CSS控制权,使其能更新现有使用实例,但无法阻止用户进行无法更新的重大修改。 + +### 同步模式 + +顾名思义,这类模式天然支持全站同步。需注意,依赖同步模式处理特定更新目前存在局限——更新时内容、HTML结构和样式将保持同步。若需要更精细的控制,这是需要考量的关键因素,此时动态区块可能是更优选择。 + +### 模板部件与模板 + +由于区块主题允许用户直接编辑模板和模板部件,管理变更的方式需根据用户更高权限进行调整。具体来说,当用户修改模板或模板部件后,主题更新提供的新模板将不会对已修改用户显示。只有新主题用户或未编辑过模板的用户会体验到更新后的模板。若用户未修改文件,您在文件系统中的变更将直接体现在用户站点上——只需更新文件即可生效。但若用户已修改模板,则更新其模板的唯一方式是: + +- 回滚所有用户修改 +- 更新数据库中的模板和模板部件 + +一般而言,若用户已修改模板,建议维持现状(除非在代理场景中与用户达成共识)。 + +更新模板时需注意对新增或不同模板部件的引用。例如 page.html 模板在1.0版本中引用 parts/header.html,而在2.0版本中改为引用 parts/header-alt.html。某些开发者可能将此视为用户修改原 header.html 时的“解决方案”,但这很可能破坏用户的定制设计,因为 page.html 模板将不再引用正确部件(除非用户也修改并保存了页面模板)。 + +同样地,在主题更新中删除模板部件通常不可取。这种情况下,用户可能创建了引用该部件的自定义顶层模板,并预期其持续存在。 + +## 相关资源 + +- [模式、模板部件与可重用区块对比](https://wordpress.org/documentation/article/comparing-patterns-template-parts-and-reusable-blocks/) +- [区块弃用机制](https://developer.wordpress.org/block-editor/reference-guides/block-api/block-deprecation/) +- [Create Block 工具](https://developer.wordpress.org/block-editor/reference-guides/packages/packages-create-block/) \ No newline at end of file diff --git a/how-to-guides/themes/README.md b/how-to-guides/themes/README.md new file mode 100644 index 0000000..aaf61d7 --- /dev/null +++ b/how-to-guides/themes/README.md @@ -0,0 +1,22 @@ +# 主题 + +区块编辑器为主题设计师和开发者提供了多种交互选项,包括主题定义的颜色设置、字体大小控制等功能。 + +## 主题类型 + +### 经典主题 + +在区块编辑器的术语体系中,指那些采用传统`.php`文件格式定义模板,且在`/block-templates`或`/templates`文件夹中不包含`index.html`格式模板的主题。经典主题可通过[主题支持功能](/docs/how-to-guides/themes/theme-support.md)或引入[theme.json](/docs/how-to-guides/themes/global-settings-and-styles.md)文件,为区块编辑器及区块内容提供配置与样式选项。即使不是区块主题,也能通过使用`theme.json`文件获得灵活配置能力。 + +### 区块主题 + +指至少在`/block-templates`或`/templates`文件夹中包含`index.html`格式模板,并以区块内容标记形式提供模板的主题。虽然多数区块主题会使用`theme.json`文件进行配置和样式设置,但该文件并非区块主题的必备要素。区块主题的优势在于可以通过区块编辑器编辑网站所有区域:页眉、页脚、侧边栏等。 + +### 全站编辑(FSE) + +并不存在特定的FSE主题类型。在WordPress 5.9及以上版本中,所有区块主题(即在`/block-templates`或`/templates`文件夹中包含`index.html`格式模板的主题)均支持全站编辑功能。 + +**目录** + +- [全局设置(theme.json)](/docs/how-to-guides/themes/global-settings-and-styles.md) +- [主题支持](/docs/how-to-guides/themes/theme-support.md) \ No newline at end of file diff --git a/how-to-guides/themes/global-settings-and-styles.md b/how-to-guides/themes/global-settings-and-styles.md new file mode 100644 index 0000000..fc74782 --- /dev/null +++ b/how-to-guides/themes/global-settings-and-styles.md @@ -0,0 +1,1410 @@ +### 用户自定义链接颜色的特异性处理 + +在WordPress 5.8版本中,当用户为特定区块选择链接颜色时,系统会为该区块附加一个`.wp-element-`类,并加入以下样式: + +```css +.wp-element- a { color: <用户颜色值> !important; } +``` + +虽然这种方式始终保留了用户偏好设置,但过高的特异性会导致与某些合法使用HTML元素的区块产生冲突。为解决该问题,WordPress 5.9版本移除了`!important`声明,并更新了相关区块的样式设置,使其具有比用户链接颜色设置更高的特异性。调整后的样式为: + +```css +.wp-element- a { color: <用户颜色值>; } +``` + +此项调整后,区块开发者和主题开发者需要确保用户设置始终生效,避免用户提供的链接颜色(特异性值为011)被意外覆盖。 + +### 什么是blockGap及其应用方式 + +对于包含内部区块的组件(如群组、列、按钮组、社交图标等),`blockGap`用于控制内部区块之间的间距。要使`blockGap`生效,区块必须启用布局区块支持功能,该功能提供可通过区块间距控件调整的布局样式。根据区块布局的不同,`blockGap`值将作为垂直边距或CSS网格间隙值输出。在编辑器中,`blockGap`的控制选项名为"区块间距",位于尺寸设置面板中。 + +```json +{ + "version": 3, + "settings": { + "spacing": { + "blockGap": true, + } + }, + "styles": { + "spacing": { + "blockGap": "1.5rem" + } + } +} +``` + +`blockGap`设置支持布尔值或`null`,默认值为`null`,这为样式输出提供了更精细的控制层级。在`theme.json`文件中,`settings.spacing.blockGap`设置接受以下参数: + +- `true`:启用编辑器界面中的"区块间距"控件,并输出`blockGap`样式 +- `false`:禁用编辑器界面中的"区块间距"控件,但仍会渲染存储在`theme.json`中的`blockGap`样式,允许主题使用`blockGap`值的同时限制用户在编辑器中进行修改 +- `null`(默认值):同时禁用"区块间距"控件和`blockGap`样式输出 + +根级别`styles.spacing.blockGap`样式定义的值同时会作为CSS自定义属性输出,其命名为`--wp--style--block-gap`。 + +### 浏览器样式更新延迟的原因分析 + +在使用theme.json进行主题开发时,您可能会注意到样式更改需要30秒以上才能在浏览器中显示,这是因为`theme.json`存在缓存机制。要解决此缓存问题,请在`wp-config.php`文件中将`WP_DEBUG`或`SCRIPT_DEBUG`设置为'true'。这将指示WordPress跳过缓存并始终使用最新数据。 + +# 全局设置与样式 (theme.json) + +WordPress 5.8 引入了一套[全新机制](https://make.wordpress.org/core/2021/06/25/introducing-theme-json-in-wordpress-5-8/)来配置编辑器,该机制支持更精细化的控制,并为未来WordPress版本的样式管理迈出了第一步:即通过`theme.json`文件实现。 + +## 设计理念 + +区块编辑器API在不同层面的演进速度不一,尤其在影响主题功能的领域出现了一些发展阵痛。例如:[通过编程方式控制编辑器](https://make.wordpress.org/core/2020/01/23/controlling-the-block-editor/)的能力,或是能够协调用户、主题和核心样式偏好的[区块样式系统](https://github.com/WordPress/gutenberg/issues/9534)。 + +当前我们正致力于将多个与样式相关的API整合至统一入口——即放置在主题目录根路径下的`theme.json`文件。 + +### 区块编辑器设置 + +相较于不断新增的主题支持标志或替代方案,`theme.json`文件提供了定义区块编辑器设置的标准化方式。这些设置包括: + +- 应向用户显示或隐藏哪些自定义选项 +- 用户可使用的默认颜色、字体大小等配置 +- 定义编辑器的默认布局(宽度和对齐方式) + +### 支持按区块精细控制 + +为实现更精细的调控,这些设置同样支持在`theme.json`中针对特定区块进行配置。例如: + +- 为特定区块(如表格)使用专属预设,其余区块使用通用预设 +- 为除标题区块外的所有区块启用字体大小UI控件 +- 等等 + +### 集中化管理样式 + +通过结构化方式在`theme.json`文件中设置样式属性,区块编辑器能够统筹管理来自不同来源(用户、主题和核心CSS)的CSS。例如当主题和用户同时设置段落字体大小时,系统只会加载用户设置的样式而非主题样式。 + +这样做的好处包括: + +- 减少CSS加载量 +- 避免样式优先级冲突 + +### CSS自定义属性:预设与自定义 + +某些样式领域需要通过共享值来实现全站统一调整,为此我们开始在部分场景中实验CSS自定义属性(即CSS变量): + +- **预设配置**:主题声明的[色彩调色板](/docs/how-to-guides/themes/theme-support.md#block-color-palettes)、[字体尺寸](/docs/how-to-guides/themes/theme-support.md#block-font-sizes)或[渐变效果](/docs/how-to-guides/themes/theme-support.md#block-gradient-presets)会被转换为CSS自定义属性,同时在前端和编辑器中加载。 + +{% codetabs %} +{% 输入 %} + +```json +{ + "version": 3, + "settings": { + "color": { + "palette": [ + { + "name": "黑色", + "slug": "black", + "color": "#000000" + }, + { + "name": "白色", + "slug": "white", + "color": "#ffffff" + } + ] + } + } +} +``` + +{% 输出 %} + +```css +body { + --wp--preset--color--black: #000000; + --wp--preset--color--white: #ffffff; +} +``` + +{% end %} + +- **自定义属性**:系统还提供了创建自定义CSS属性的机制。 + +{% codetabs %} +{% 输入 %} + +```json +{ + "version": 3, + "settings": { + "custom": { + "line-height": { + "body": 1.7, + "heading": 1.3 + } + } + } +} +``` + +{% 输出 %} + +```css +body { + --wp--custom--line-height--body: 1.7; + --wp--custom--line-height--heading: 1.3; +} +``` + +{% end %} + +## 技术规范 + +本规范适用于使用此格式的三大来源:核心系统、主题和用户。主题可通过创建`theme.json`文件来覆盖核心默认设置。用户则可通过正在开发中的站点编辑器界面,覆盖主题或核心的预设配置。 + +```json +{ + "version": 3, + "settings": {}, + "styles": {}, + "customTemplates": {}, + "templateParts": {} +} +``` + +### 自定义模板 + +
    自 WordPress 5.9 版本起支持。
    + +在此字段中,主题可以列出 `templates` 文件夹中的自定义模板。例如,对于名为 `my-custom-template.html` 的自定义模板,`theme.json` 可以声明哪些文章类型可以使用它以及向用户显示的标题: + +- name:必需。 +- title:必需,可翻译。 +- postTypes:可选,默认仅适用于 `page`。 + +```json +{ + "version": 3, + "customTemplates": [ + { + "name": "my-custom-template", + "title": "模板标题", + "postTypes": [ + "page", + "post", + "my-cpt" + ] + } + ] +} +``` + +### 模板部件 + +
    自 WordPress 5.9 版本起支持。
    + +在此字段中,主题可以列出 `parts` 文件夹中的模板部件。例如,对于名为 `my-template-part.html` 的模板部件,`theme.json` 可以声明模板部件实体的区域术语,该术语负责在编辑器中渲染相应的区块变体(如页眉区块、页脚区块等)。在 JSON 中定义此区域术语将使该设置在该模板部件实体的所有使用中保持一致,而区块属性仅影响单个区块。不建议将区域作为区块属性定义,因为这仅用于“幕后”辅助占位符流程与实体创建之间的衔接。 + +目前,区域术语的“header”和“footer”值存在区块变体,任何其他值以及未在 JSON 中定义的模板部件将默认为通用模板部件区块。变体将在编辑器界面中通过特定图标表示,默认使用对应的语义化 HTML 元素作为包装器(这也可以通过模板部件区块上设置的 `tagName` 属性覆盖),并将模板部件上下文化,以便在未来的编辑器改进中实现更自定义的流程。 + +- name:必需。 +- title:可选,可翻译。 +- area:可选,默认为 `uncategorized`,且不会触发区块变体。 + +```json +{ + "version": 3, + "templateParts": [ + { + "name": "my-template-part", + "title": "页眉", + "area": "header" + } + ] +} +``` + +### 模式 + +
    自 WordPress 6.0 版本起支持。
    + +在此字段中,主题可以列出要从[模式目录](https://wordpress.org/patterns/)注册的模式。`patterns` 字段是模式目录中的模式 `slugs` 数组。模式 slugs 可以从模式目录中单个模式视图的 `url` 中提取。例如,在 URL `https://wordpress.org/patterns/pattern/partner-logos` 中,slug 为 `partner-logos`。 + +```json +{ + "version": 3, + "patterns": [ "short-text-surrounded-by-round-images", "partner-logos" ] +} +``` + +## 使用 theme.json 进行开发 + +在开发过程中,可能难以记住 theme.json 的设置和属性以及不同 WordPress 版本支持哪些设置,因此使用提供的 theme.json JSON 模式会很有帮助。 + +许多代码编辑器支持 JSON 模式,并可以在编辑器中提供工具提示、自动完成或模式验证等帮助。 + +每个 WordPress 版本的 theme.json 模式均可在 `https://schemas.wp.org/wp/{{version}}/theme.json` 获取。例如,WordPress 5.8 的模式可在 `https://schemas.wp.org/wp/5.8/theme.json` 获取。为确保仅使用对用户可用的功能,最好使用主题支持的最旧版本。 + +包含 Gutenberg 插件所有最新更改的最新模式可在 `https://schemas.wp.org/trunk/theme.json` 获取。 + +请查阅编辑器的文档以了解 JSON 模式支持。例如,在 Visual Studio Code 中,您需要在 theme.json 文件的顶级属性中添加 `"$schema": "https://schemas.wp.org/wp/x.x/theme.json"`,但其他编辑器的配置可能不同。 + +![使用模式验证的示例](https://developer.wordpress.org/files/2021/11/theme-json-schema-updated.gif) + +## 常见问题解答 + +### CSS自定义属性的命名规范 + +您可能已经注意到系统创建的CSS自定义属性采用了特定的命名规范,包括使用双连字符`--`来分隔不同"概念"。请看以下示例: + +**预设属性**(如`--wp--preset--color--black`)可拆分为以下部分: +- `--wp`:命名空间前缀 +- `preset`:标识属于预设值的CSS变量 +- `color`:指示变量所属的预设类别(可以是`color`、`font-size`、`gradients`) +- `black`:特定预设值的标识符 + +**自定义属性**(如`--wp--custom--line-height--body`)可拆分为: +- `--wp`:命名空间前缀 +- `custom`:标识由主题创建的"自由形式"CSS变量 +- `line-height--body`:将"custom"对象键名转换后的字符串结果 + +双连字符`--`作为分隔符具有双重作用: +- 增强人类可读性:类似于BEM命名规范,用于区分不同"类别" +- 提升机器可解析性:通过既定结构让机器理解属性含义,例如能识别`--wp--preset--color--black`是绑定到标识符为"black"的色彩预设值,为后续操作留出空间 + +### 为何选用`--`作为分隔符? + +理论上可以使用其他分隔符(如单连字符`-`),但这会产生问题:除非强制主题作者不在变量名中使用`-`,否则无法将`--wp-custom-line-height-template-header`准确转换回对象形式。 + +通过保留`--`作为类别分隔符,并允许主题作者使用`-`作为单词边界,命名将更加清晰:`--wp--custom--line-height--template-header` + +### "custom"配置如何生成CSS自定义属性 + +从"custom"配置项生成CSS变量的算法原理如下(以示例说明): + +{% codetabs %} +{% 输入 %} + +```json +{ + "version": 3, + "settings": { + "custom": { + "lineHeight": { + "body": 1.7 + }, + "font-primary": "-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen-Sans, Ubuntu, Cantarell, 'Helvetica Neue', sans-serif" + } + } +} +``` + +{% 输出 %} + +```css +body { + --wp--custom--line-height--body: 1.7; + --wp--custom--font-primary: "-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen-Sans, Ubuntu, Cantarell, 'Helvetica Neue', sans-serif"; +} +``` +{% end %} + +注意事项: +- 驼峰式命名的键名会转换为短横线命名法(如`lineHeight`转为`line-height`),遵循CSS属性命名规范 +- 不同层级的键名通过`--`分隔(这就是`line-height`与`body`之间使用`--`的原因) +- 禁止在`custom`对象的键名中使用`--`(错误示例如下): + +```json +{ + "version": 3, + "settings": { + "custom": { + "line--height": { // 禁止此种写法 + "body": 1.7 + } + } + } +} +``` + +### 全局样式表 + +WordPress 5.8版本中,WordPress定义的预设样式(字体大小、颜色和渐变)在多数主题中会重复加载:既通过区块库样式表加载,又通过全局样式表加载,且两处CSS存在细微差异。 + +WordPress 5.9版本将预设样式的CSS统一整合到全局样式表中,该样式表现已为所有主题加载。每个预设值会生成单个CSS自定义属性和对应类: + +```css +/* 预设值的CSS自定义属性 */ +body { + --wp--preset--<预设类型>--<预设标识>: <默认值>; + --wp--preset--color--pale-pink: #f78da7; + --wp--preset--font-size--large: 36px; + /* 其他属性 */ +} + +/* 预设值的CSS类 */ +.has-<预设标识>-<预设类型> { ... } +.has-pale-pink-color { color: var(--wp--preset--color--pale-pink) !important; } +.has-large-font-size { font-size: var(--wp--preset--font-size--large) !important; } +``` + +主题如需覆盖默认值,可通过`theme.json`文件提供相同标识符进行配置。未使用`theme.json`的主题仍可通过加载CSS来设置对应的CSS自定义属性实现覆盖。 + +示例(修改默认大字号的值): +```css +body { + --wp--preset--font-size--large: <新值>; +} +``` + +##### 元素伪类选择器 + +古腾堡编辑器支持伪类选择器 `:hover`、`:focus`、`:focus-visible`、`:visited`、`:active`、`:link`、`:any-link`。 + +```json +"elements": { + "link": { + "color": { + "text": "绿色" + }, + ":hover": { + "color": { + "text": "亮粉色" + } + } + } + } +``` + +#### 样式变体 + +区块可以拥有“样式变体”,具体定义参见[block.json规范](https://developer.wordpress.org/block-editor/reference-guides/block-api/block-registration/#styles-optional)。主题开发者可以通过`theme.json`文件为已存在的样式变体定义样式属性。未注册样式变体的样式配置将被忽略。 + +需注意:变体是“区块概念”——仅当与区块绑定时才存在。`theme.json`规范遵循这一特性,只允许在区块层级而非顶层配置`variations`。特别需要强调的是,只有通过区块`block.json`文件或服务端`register_block_style`注册的变体,才会被视作`theme.json`样式配置中的“已注册变体”。 + +例如,以下是为`core/quote`区块现有`plain`变体定义样式的方式: + +```json +{ + "version": 3, + "styles": { + "blocks": { + "core/quote": { + "variations": { + "plain": { + "color": { + "background": "红色" + } + } + } + } + } + } +} +``` + +最终输出的CSS为: + +```css +.wp-block-quote.is-style-plain { + background-color: red; +} +``` + +多个区块类型也可以共享相同的变体样式。推荐以下两种定义共享样式的方法: + +1. `theme.json`分部文件 +2. 通过`register_block_style`编程注册 + +##### 变体Theme.json分部文件 + +与主题样式变体分部文件类似,区块样式变体分部文件也存放在主题的`/styles`目录中。但通过顶层属性`blockTypes`与主题样式变体进行区分。`blockTypes`属性是一个数组,包含已注册该区块样式变体的所有区块类型。 + +此外,通过`slug`属性可确保不同来源定义的区块样式变体保持一致性,并将可翻译的`title`属性与`slug`解耦。 + +以下是一个`theme.json`分部文件示例,为Group、Columns和Media & Text区块类型定义“变体A”区块样式: + +```json +{ + "$schema": "https://schemas.wp.org/trunk/theme.json", + "version": 3, + "title": "变体A", + "slug": "variation-a", + "blockTypes": [ "core/group", "core/columns", "core/media-text" ], + "styles": { + "color": { + "background": "#eed8d3", + "text": "#201819" + }, + "elements": { + "heading": { + "color": { + "text": "#201819" + } + } + }, + "blocks": { + "core/group": { + "color": { + "background": "#825f58", + "text": "#eed8d3" + }, + "elements": { + "heading": { + "color": { + "text": "#eed8d3" + } + } + } + } + } + } +} +``` + +##### 编程注册变体样式 + +除了使用`theme.json`分部文件,您还可以在通过`register_block_style`注册变体时同步注册样式。该方法通过为区块数组注册区块样式,并在`style_data`参数中传入“样式对象”实现。 + +以下示例为Group和Columns区块注册“绿色”变体。注意通过`style_data`传递的样式对象结构与`theme.json`分部文件中的`styles`属性结构一致。 + +```php +register_block_style( + array( 'core/group', 'core/columns' ), + array( + 'name' => 'green', + 'label' => __( '绿色' ), + 'style_data' => array( + 'color' => array( + 'background' => '#4f6f52', + 'text' => '#d2e3c8', + ), + 'blocks' => array( + 'core/group' => array( + 'color' => array( + 'background' => '#739072', + 'text' => '#e3eedd', + ), + ), + ), + 'elements' => array( + 'link' => array( + 'color' => array( + 'text' => '#ead196', + ), + ':hover' => array( + 'color' => array( + 'text' => '#ebd9b4', + ), + ), + ), + ), + ), + ) +); +``` + +### 顶层样式 + +位于顶层的样式将使用 `body` 选择器进行注册。 + +{% codetabs %} +{% Input %} + +```json +{ + "version": 3, + "styles": { + "color": { + "text": "var(--wp--preset--color--primary)" + } + } +} +``` + +{% Output %} + +```css +body { + color: var( --wp--preset--color--primary ); +} +``` + +{% end %} +### 区块样式 + +位于区块内的样式将使用区块选择器进行注册。 + +默认情况下,区块选择器会根据其名称生成,例如 `.wp-block-<不含命名空间的区块名称>`。例如,`core/group` 区块对应 `.wp-block-group`。某些区块可以选择退出此默认行为,只需在其 `block.json` 文件的 `supports` 部分通过 `__experimentalSelector` 键明确指定要使用的选择器即可。需要注意的是,区块必须在服务端注册,样式引擎才能使用 `__experimentalSelector` 字段。 + +{% codetabs %} +{% Input %} + +```json +{ + "version": 3, + "styles": { + "color": { + "text": "var(--wp--preset--color--primary)" + }, + "blocks": { + "core/paragraph": { + "color": { + "text": "var(--wp--preset--color--secondary)" + } + }, + "core/group": { + "color": { + "text": "var(--wp--preset--color--tertiary)" + } + } + } + } +} +``` + +{% Output %} + +```css +body { + color: var( --wp--preset--color--primary ); +} +p { /* core/paragraph 区块选择退出默认行为,使用 p 作为选择器。 */ + color: var( --wp--preset--color--secondary ); +} +.wp-block-group { + color: var( --wp--preset--color--tertiary ); +} +``` +{% end %} + +#### 引用样式 + +区块可以通过引用根层级样式来进行样式设置。此功能由 Gutenberg 支持。 +如果使用 `styles.color.background` 为根层级注册背景颜色: + +```JSON +"styles": { + "color": { + "background": "var(--wp--preset--color--primary)" + } + } +``` + +你可以使用 `ref: "styles.color.background"` 来为区块复用该样式: + +```JSON +{ + "color": { + "text": { "ref": "styles.color.background" } + } +} +``` + +#### 元素样式 + +除了顶层样式和区块层级样式外,还有一种元素的概念,可以在两个地方使用。它们是一个封闭集合: + +Gutenberg 支持的: + +- `button`:映射到 `wp-element-button` CSS 类。同时为了向后兼容,也映射到 `wp-block-button__link`。 +- `caption`:映射到 `.wp-element-caption, .wp-block-audio figcaption, .wp-block-embed figcaption, .wp-block-gallery figcaption, .wp-block-image figcaption, .wp-block-table figcaption, .wp-block-video figcaption` CSS 类。 +- `heading`:映射到所有标题,即 `h1 到 h6` CSS 选择器。 + +WordPress 支持的: + +- `h1`:映射到 `h1` CSS 选择器。 +- `h2`:映射到 `h2` CSS 选择器。 +- `h3`:映射到 `h3` CSS 选择器。 +- `h4`:映射到 `h4` CSS 选择器。 +- `h5`:映射到 `h5` CSS 选择器。 +- `h6`:映射到 `h6` CSS 选择器。 +- `link`:映射到 `a` CSS 选择器。 + +如果在顶层找到元素样式,将使用元素选择器。如果在区块内找到,使用的选择器将是该元素附加到对应区块的选择器。 + +{% codetabs %} +{% Input %} + +```json +{ + "version": 3, + "styles": { + "typography": { + "fontSize": "var(--wp--preset--font-size--normal)" + }, + "elements": { + "h1": { + "typography": { + "fontSize": "var(--wp--preset--font-size--huge)" + } + }, + "h2": { + "typography": { + "fontSize": "var(--wp--preset--font-size--big)" + } + }, + "h3": { + "typography": { + "fontSize": "var(--wp--preset--font-size--medium)" + } + } + }, + "blocks": { + "core/group": { + "elements": { + "h2": { + "typography": { + "fontSize": "var(--wp--preset--font-size--small)" + } + }, + "h3": { + "typography": { + "fontSize": "var(--wp--preset--font-size--smaller)" + } + } + } + } + } + } +} +``` + +{% Output %} + +```css +body { + font-size: var( --wp--preset--font-size--normal ); +} +h1 { + font-size: var( --wp--preset--font-size--huge ); +} +h2 { + font-size: var( --wp--preset--font-size--big ); +} +h3 { + font-size: var( --wp--preset--font-size--medium ); +} +.wp-block-group h2 { + font-size: var( --wp--preset--font-size--small ); +} +.wp-block-group h3 { + font-size: var( --wp--preset--font-size--smaller ); +} +``` +{% end %} + +### 版本说明 + +此字段用于描述 `theme.json` 文件的格式规范。最新版本为 WordPress 6.6 推出的[版本 3](https://developer.wordpress.org/block-editor/reference-guides/theme-json-reference/theme-json-living/)。 + +当需进行破坏性变更时,会发布新版本。这允许主题开发者自主选择何时启用破坏性变更,并将其 theme.json 文件迁移至新格式。 + +旧版 `theme.json` 均具有向后兼容性,可继续在新版 WordPress 和 Gutenberg 插件中正常使用。但新功能将基于最新版本进行开发。 + +请参照[版本迁移指南](https://developer.wordpress.org/block-editor/reference-guides/theme-json-reference/theme-json-migrations/)了解升级至最新版本的具体操作。 + +### 设置项 + +
    +Gutenberg 插件扩展了 WordPress 5.8 支持的设置项,使其可兼容其他 WordPress 版本。这些扩展功能需经过成熟度测试后才会被移植至核心代码。 + +下方选项卡分别展示 WordPress 5.8 支持的设置项与 Gutenberg 插件支持的设置项。 +
    + +设置项章节采用以下结构: + +{% codetabs %} +{% WordPress %} + +```json +{ + "version": 3, + "settings": { + "border": { + "radius": false, + "color": false, + "style": false, + "width": false + }, + "color": { + "custom": true, + "customDuotone": true, + "customGradient": true, + "duotone": [], + "gradients": [], + "link": false, + "palette": [], + "text": true, + "background": true, + "defaultGradients": true, + "defaultPalette": true + }, + "custom": {}, + "layout": { + "contentSize": "800px", + "wideSize": "1000px" + }, + "spacing": { + "margin": false, + "padding": false, + "blockGap": null, + "units": [ "px", "em", "rem", "vh", "vw" ] + }, + "typography": { + "customFontSize": true, + "lineHeight": false, + "dropCap": true, + "fluid": false, + "fontStyle": true, + "fontWeight": true, + "letterSpacing": true, + "textDecoration": true, + "textTransform": true, + "fontSizes": [], + "fontFamilies": [] + }, + "blocks": { + "core/paragraph": { + "color": {}, + "custom": {}, + "layout": {}, + "spacing": {}, + "typography": {} + }, + "core/heading": {}, + "etc": {} + } + } +} +``` + +{% Gutenberg %} + +```json +{ + "version": 3, + "settings": { + "appearanceTools": false, + "border": { + "color": false, + "radius": false, + "style": false, + "width": false + }, + "color": { + "background": true, + "custom": true, + "customDuotone": true, + "customGradient": true, + "defaultGradients": true, + "defaultPalette": true, + "duotone": [], + "gradients": [], + "link": false, + "palette": [], + "text": true + }, + "custom": {}, + "dimensions": { + "aspectRatio": false, + "minHeight": false, + }, + "layout": { + "contentSize": "800px", + "wideSize": "1000px" + }, + "spacing": { + "blockGap": null, + "margin": false, + "padding": false, + "customSpacingSize": true, + "units": [ "px", "em", "rem", "vh", "vw" ], + "spacingScale": { + "operator": "*", + "increment": 1.5, + "steps": 7, + "mediumStep": 1.5, + "unit": "rem" + }, + "spacingSizes": [] + }, + "typography": { + "customFontSize": true, + "dropCap": true, + "fluid": false, + "fontFamilies": [], + "fontSizes": [], + "fontStyle": true, + "fontWeight": true, + "letterSpacing": true, + "lineHeight": false, + "textAlign": true, + "textColumns": false, + "textDecoration": true, + "textTransform": true + }, + "blocks": { + "core/paragraph": { + "border": {}, + "color": {}, + "custom": {}, + "layout": {}, + "spacing": {}, + "typography": {} + }, + "core/heading": {}, + "etc": {} + } + } +} +``` +{% end %} + +每个区块均可独立配置这些设置项,实现比 `add_theme_support` 更精细的控制粒度。顶层声明的设置项将影响所有区块,除非特定区块对其进行覆盖。这种机制既提供了配置继承性,也支持一次性配置所有区块。 + +需要注意的是,并非所有设置项都适用于每个区块。设置项章节为主题提供启用/禁用机制,但具体功能支持仍需由区块自行实现。例如,若某区块未实现 `dropCap` 首字下沉功能,则主题无法通过 `theme.json` 为该区块启用此功能。 + +``` +{% end %} + +为保持向后兼容性,通过 `add_theme_support` 声明的预设也会生成 CSS 自定义属性。若 `theme.json` 包含任何预设,这些预设将优先于通过 `add_theme_support` 声明的预设。 + +预设类通过用户操作附加到文章内容。因此引擎会为这些类添加 `!important` 声明,因为用户样式应优先于主题样式。 + +#### 自定义配置 + +除了为预设创建 CSS 自定义属性外,`theme.json` 还允许主题创建自定义属性,无需单独排队加载。在 `custom` 字段内声明的任何值都将按以下命名模式转换为 CSS 自定义属性:`--wp--custom--<变量名称>`。 + +例如: + +{% codetabs %} +{% 输入 %} + +```json +{ + "version": 3, + "settings": { + "custom": { + "baseFont": 16, + "lineHeight": { + "small": 1.2, + "medium": 1.4, + "large": 1.8 + } + }, + "blocks": { + "core/group": { + "custom": { + "baseFont": 32 + } + } + } + } +} +``` + +{% 输出 %} + +```css +body { + --wp--custom--base-font: 16; + --wp--custom--line-height--small: 1.2; + --wp--custom--line-height--medium: 1.4; + --wp--custom--line-height--large: 1.8; +} +.wp-block-group { + --wp--custom--base-font: 32; +} +``` + +{% end %} + +请注意,变量名称通过在每个嵌套层级间添加 `--` 来创建,且 `camelCase` 格式的字段会转换为 `kebab-case` 格式。 + +#### 设置示例 + +- 仅对段落区块启用自定义颜色: + +```json +{ + "version": 3, + "settings": { + "color": { + "custom": false + }, + "blocks": { + "core/paragraph": { + "color": { + "custom": true + } + } + } + } +} +``` + +- 禁用按钮区块的边框圆角: + +```json +{ + "version": 3, + "settings": { + "blocks": { + "core/button": { + "border": { + "radius": false + } + } + } + } +} +``` + +- 为群组区块提供与其他区块不同的调色板: + +```json +{ + "version": 3, + "settings": { + "color": { + "palette": [ + { + "slug": "black", + "color": "#000000", + "name": "黑色" + }, + { + "slug": "white", + "color": "#FFFFFF", + "name": "白色" + }, + { + "slug": "red", + "color": "#FF0000", + "name": "红色" + }, + { + "slug": "green", + "color": "#00FF00", + "name": "绿色" + }, + { + "slug": "blue", + "color": "#0000FF", + "name": "蓝色" + } + ] + }, + "blocks": { + "core/group": { + "color": { + "palette": [ + { + "slug": "black", + "color": "#000000", + "name": "黑色" + }, + { + "slug": "white", + "color": "#FFF", + "name": "白色" + } + ] + } + } + } + } +} +``` + +### 样式 + +
    +Gutenberg 插件扩展了 WordPress 5.8 的可用样式,使其可与其他 WordPress 版本兼容,这些样式在移植到核心版本前需经过成熟度验证。 + +下方标签页分别展示 WordPress 5.8 支持的样式与 Gutenberg 插件支持的样式。 +
    + +每个区块通过[区块支持机制](/docs/reference-guides/block-api/block-supports.md)声明其暴露的样式属性。这些支持声明用于在编辑器中自动生成区块的 UI 控件。主题可通过 `theme.json` 为任何区块使用样式属性——但需由主题自行验证这些样式是否能根据区块标记等要素正常生效。 + +{% codetabs %} +{% WordPress %} + +```json +{ + "version": 3, + "styles": { + "border": { + "radius": "value", + "color": "value", + "style": "value", + "width": "value" + }, + "filter": { + "duotone": "value" + }, + "color": { + "background": "value", + "gradient": "value", + "text": "value" + }, + "spacing": { + "blockGap": "value", + "margin": { + "top": "value", + "right": "value", + "bottom": "value", + "left": "value", + }, + "padding": { + "top": "value", + "right": "value", + "bottom": "value", + "left": "value" + } + }, + "typography": { + "fontSize": "value", + "fontStyle": "value", + "fontWeight": "value", + "letterSpacing": "value", + "lineHeight": "value", + "textDecoration": "value", + "textTransform": "value" + }, + "elements": { + "link": { + "border": {}, + "color": {}, + "spacing": {}, + "typography": {} + }, + "h1": {}, + "h2": {}, + "h3": {}, + "h4": {}, + "h5": {}, + "h6": {} + }, + "blocks": { + "core/group": { + "border": {}, + "color": {}, + "spacing": {}, + "typography": {}, + "elements": { + "link": {}, + "h1": {}, + "h2": {}, + "h3": {}, + "h4": {}, + "h5": {}, + "h6": {} + } + }, + "etc": {} + } + } +} +``` +{% Gutenberg %} + +```json +{ + "version": 3, + "styles": { + "border": { + "color": "value", + "radius": "value", + "style": "value", + "width": "value" + }, + "color": { + "background": "value", + "gradient": "value", + "text": "value" + }, + "dimensions": { + "aspectRatio": "value", + "minHeight": "value" + }, + "filter": { + "duotone": "value" + }, + "spacing": { + "blockGap": "value", + "margin": { + "top": "value", + "right": "value", + "bottom": "value", + "left": "value" + }, + "padding": { + "top": "value", + "right": "value", + "bottom": "value", + "left": "value" + } + }, + "typography": { + "fontFamily": "value", + "fontSize": "value", + "fontStyle": "value", + "fontWeight": "value", + "letterSpacing": "value", + "lineHeight": "value", + "textColumns": "value", + "textDecoration": "value", + "textTransform": "value" + }, + "elements": { + "link": { + "border": {}, + "color": {}, + "spacing": {}, + "typography": {} + }, + "h1": {}, + "h2": {}, + "h3": {}, + "h4": {}, + "h5": {}, + "h6": {}, + "heading": {}, + "button": {}, + "caption": {} + }, + "blocks": { + "core/group": { + "border": {}, + "color": {}, + "dimensions": {}, + "spacing": {}, + "typography": {}, + "elements": { + "link": {}, + "h1": {}, + "h2": {}, + "h3": {}, + "h4": {}, + "h5": {}, + "h6": {} + } + }, + "etc": {} + } + } +} +``` +{% end %} + +### 启用UI控件选项 + +存在一个特殊的设置属性 `appearanceTools`,它是一个布尔值,默认值为 false。主题可以通过此设置启用以下选项: + +- 背景:backgroundImage、backgroundSize +- 边框:color、radius、style、width +- 颜色:link +- 尺寸:aspectRatio、minHeight +- 位置:sticky +- 间距:blockGap、margin、padding +- 排版:lineHeight + +#### 与 add_theme_support 的向后兼容性 + +为了保持向后兼容性,现有的用于配置区块编辑器的 `add_theme_support` 声明已被适配到顶层部分的适当类别中。例如,如果主题使用 `add_theme_support('disable-custom-colors')`,则等同于将 `settings.color.custom` 设置为 `false`。如果 `theme.json` 包含任何设置,这些设置将优先于通过 `add_theme_support` 声明的值。以下是完整的等效关系列表: + +| add_theme_support | theme.json 设置 | +| --------------------------- | ------------------------------------------------------------ | +| `custom-line-height` | 将 `typography.lineHeight` 设置为 `true`。 | +| `custom-spacing` | 将 `spacing.padding` 设置为 `true`。 | +| `custom-units` | 通过 `spacing.units` 提供单位列表。 | +| `disable-custom-colors` | 将 `color.custom` 设置为 `false`。 | +| `disable-custom-font-sizes` | 将 `typography.customFontSize` 设置为 `false`。 | +| `disable-custom-gradients` | 将 `color.customGradient` 设置为 `false`。 | +| `editor-color-palette` | 通过 `color.palette` 提供颜色列表。 | +| `editor-font-sizes` | 通过 `typography.fontSizes` 提供字体大小列表。 | +| `editor-gradient-presets` | 通过 `color.gradients` 提供渐变列表。 | +| `editor-spacing-sizes` | 通过 `spacing.spacingSizes` 提供间距尺寸列表。 | +| `appearance-tools` | 将 `appearanceTools` 设置为 `true`。 | +| `border` | 将 `border: color, radius, style, width` 设置为 `true`。 | +| `link-color ` | 将 `color.link` 设置为 `true`。 | + +#### 预设 + +预设是设置部分的一部分。它们是通过某些UI控件向用户显示的值。通过 `theme.json` 定义这些值,引擎可以为主题提供更多功能,例如自动翻译预设名称或排队相应的CSS类和自定义属性。 + +可以通过 `theme.json` 定义以下预设: + +- `color.duotone`:不生成类或自定义属性。 +- `color.gradients`:为每个预设值生成一个类和一个自定义属性。 +- `color.palette`: + - 为每个预设值生成3个类:color、background-color 和 border-color。 + - 为每个预设值生成一个自定义属性。 +- `spacing.spacingSizes`/`spacing.spacingScale`:为每个预设值生成一个自定义属性。 +- `typography.fontSizes`:为每个预设值生成一个类和一个自定义属性。 +- `typography.fontFamilies`:为每个预设值生成一个自定义属性。 + +类和自定义属性的命名规则如下: + +- 自定义属性:`--wp--preset--{preset-category}--{preset-slug}`,例如 `--wp--preset--color--black` +- 类:`.has-{preset-slug}-{preset-category}`,例如 `.has-black-color` + +{% codetabs %} +{% 输入 %} + +```json +{ + "version": 3, + "settings": { + "color": { + "duotone": [ + { + "colors": [ "#000", "#FFF" ], + "slug": "black-and-white", + "name": "Black and White" + } + ], + "gradients": [ + { + "slug": "blush-bordeaux", + "gradient": "linear-gradient(135deg,rgb(254,205,165) 0%,rgb(254,45,45) 50%,rgb(107,0,62) 100%)", + "name": "Blush bordeaux" + }, + { + "slug": "blush-light-purple", + "gradient": "linear-gradient(135deg,rgb(255,206,236) 0%,rgb(152,150,240) 100%)", + "name": "Blush light purple" + } + ], + "palette": [ + { + "slug": "strong-magenta", + "color": "#a156b4", + "name": "Strong magenta" + }, + { + "slug": "very-dark-grey", + "color": "rgb(131, 12, 8)", + "name": "Very dark grey" + } + ] + }, + "typography": { + "fontFamilies": [ + { + "fontFamily": "-apple-system,BlinkMacSystemFont,\"Segoe UI\",Roboto,Oxygen-Sans,Ubuntu,Cantarell, \"Helvetica Neue\",sans-serif", + "slug": "system-font", + "name": "System Font" + }, + { + "fontFamily": "Helvetica Neue, Helvetica, Arial, sans-serif", + "slug": "helvetica-arial", + "name": "Helvetica or Arial" + } + ], + "fontSizes": [ + { + "slug": "big", + "size": 32, + "name": "Big" + }, + { + "slug": "x-large", + "size": 46, + "name": "Large" + } + ] + }, + "spacing": { + "spacingScale": { + "operator": "*", + "increment": 1.5, + "steps": 7, + "mediumStep": 1.5, + "unit": "rem" + }, + "spacingSizes": [ + { + "slug": "40", + "size": "1rem", + "name": "Small" + }, + { + "slug": "50", + "size": "1.5rem", + "name": "Medium" + }, + { + "slug": "60", + "size": "2rem", + "name": "Large" + } + ] + }, + "blocks": { + "core/group": { + "color": { + "palette": [ + { + "slug": "black", + "color": "#000000", + "name": "Black" + }, + { + "slug": "white", + "color": "#ffffff", + "name": "White" + } + ] + } + } + } + } +} +``` + +{% 输出 %} + +```css +/* 顶层自定义属性 */ +body { + --wp--preset--color--strong-magenta: #a156b4; + --wp--preset--color--very-dark-grey: #444; + --wp--preset--gradient--blush-bordeaux: linear-gradient( 135deg, rgb( 254, 205, 165 ) 0%, rgb( 254, 45, 45 ) 50%, rgb( 107, 0, 62 ) 100% ); + --wp--preset--gradient--blush-light-purple: linear-gradient( 135deg, rgb( 255, 206, 236 ) 0%, rgb( 152, 150, 240 ) 100% ); + --wp--preset--font-size--x-large: 46; + --wp--preset--font-size--big: 32; + --wp--preset--font-family--helvetica-arial: Helvetica Neue, Helvetica, Arial, sans-serif; + --wp--preset--font-family--system: -apple-system,BlinkMacSystemFont,\"Segoe UI\",Roboto,Oxygen-Sans,Ubuntu,Cantarell, \"Helvetica Neue\",sans-serif; + --wp--preset--spacing--20: 0.44rem; + --wp--preset--spacing--30: 0.67rem; + --wp--preset--spacing--40: 1rem; + --wp--preset--spacing--50: 1.5rem; + --wp--preset--spacing--60: 2.25rem; + --wp--preset--spacing--70: 3.38rem; + --wp--preset--spacing--80: 5.06rem; +} + +/* 区块级自定义属性(绑定到群组区块) */ +.wp-block-group { + --wp--preset--color--black: #000000; + --wp--preset--color--white: #ffffff; +} + +/* 顶层类 */ +.has-strong-magenta-color { color: #a156b4 !important; } +.has-strong-magenta-background-color { background-color: #a156b4 !important; } +.has-strong-magenta-border-color { border-color: #a156b4 !important; } +.has-very-dark-grey-color { color: #444 !important; } +.has-very-dark-grey-background-color { background-color: #444 !important; } +.has-very-dark-grey-border-color { border-color: #444 !important; } +.has-blush-bordeaux-background { background: linear-gradient( 135deg, rgb( 254, 205, 165 ) 0%, rgb( 254, 45, 45 ) 50%, rgb( 107, 0, 62 ) 100% ) !important; } +.has-blush-light-purple-background { background: linear-gradient( 135deg, rgb( 255, 206, 236 ) 0%, rgb( 152, 150, 240 ) 100% ) !important; } +.has-big-font-size { font-size: 32; } +.has-normal-font-size { font-size: 16; } + +/* 区块级类(绑定到群组区块) */ +.wp-block-group.has-black-color { color: #a156b4 !important; } +.wp-block-group.has-black-background-color { background-color: #a156b4 !important; } +.wp-block-group.has-black-border-color { border-color: #a156b4 !important; } +.wp-block-group.has-white-color { color: #444 !important; } +.wp-block-group.has-white-background-color { background-color: #444 !important; } +.wp-block-group.has-white-border-color { border-color: #444 !important; } +``` \ No newline at end of file diff --git a/how-to-guides/themes/theme-support.md b/how-to-guides/themes/theme-support.md new file mode 100644 index 0000000..4b9c125 --- /dev/null +++ b/how-to-guides/themes/theme-support.md @@ -0,0 +1,511 @@ +## 链接颜色控制 + +链接颜色支持已在 WordPress 5.8 版本中趋于稳定。该功能默认处于关闭状态(`false`),主题可通过 [theme.json 文件](/docs/how-to-guides/curating-the-editor-experience/theme-json.md)启用: + +```json +{ + "settings": { + "color": { + "link": true + } + } +} +``` + +> 替代方案:若已启用 Gutenberg 插件,传统的实验性支持 `add_theme_support( 'experimental-link-color' )` 仍可生效。当 Gutenberg 插件要求最低 WordPress 版本为 5.9 时,此备用方案将被移除。 + +当用户设置区块的链接颜色时,系统将以如下形式添加新样式: + +```css +.wp-elements- a { + color: !important; +} +``` + +其中: +- `` 为随机生成的数字 +- `` 可以是 `var(--wp--preset--color--slug)`(当用户选择预设值时)或原始颜色值(当用户选择自定义值时) + +区块将被附加 `.wp-elements-` 类名。 + +## 外观工具 + +通过此设置启用以下全局样式配置项: + +- 边框:颜色、圆角、样式、宽度 +- 颜色:链接色彩 +- 间距:区块间隙、外边距、内边距 +- 排版:行高 +- 尺寸:宽高比、最小高度 +- 定位:粘性定位 + +```php +add_theme_support( 'appearance-tools' ); +``` + +## 边框 + +启用完整边框设置功能: + +```php +add_theme_support( 'border' ); +``` + +## 链接颜色 + +启用链接颜色设置功能: + +```php +add_theme_support( 'link-color' ); +``` + +## 区块化模板部件 + +区块化模板部件允许管理员使用区块编辑网站局部组件。此功能默认关闭,需要主题通过声明支持来启用: + +```php +add_theme_support( 'block-template-parts' ); +``` + +该功能仅对非区块主题有实际意义,因为区块主题已通过站点编辑器原生支持区块化模板部件。 + +独立模板部件编辑器不允许编辑者创建新模板部件或删除现有模板部件,这是因为主题需要手动在 PHP 模板中包含模板部件。 + +您可在[主题手册的区块模板与模板部件章节](https://developer.wordpress.org/themes/block-themes/templates-and-template-parts/#block-c5fa39a2-a27d-4bd2-98d0-dc6249a0801a)中了解更多关于区块化模板部件的信息。 + +# 主题支持 + +新版区块功能为所有主题提供了基础支持,同时包含可选的增强功能以及扩展和自定义能力。 + +构建主题时需要考虑以下几个新概念: + +- **编辑器调色板** - 系统提供默认颜色集,但主题可以注册自己的颜色,并可选地将用户选择限制在预设调色板范围内。 +- **编辑器字号面板** - 系统提供默认字号集,但主题可以注册自己的字号,并可选地将用户选择限制在预设字号范围内。 +- **响应式嵌入内容** - 主题需主动启用响应式嵌入功能。 +- **前端与编辑器样式** - 为充分发挥区块功能,主题开发者需确保核心样式正常显示并启用,或编写与自身主题最适配的自定义样式。 +- **区块工具** - 主题可选择启用多种区块工具,如行高设置、自定义单位等。 +- **核心区块模式** - 主题可选择禁用默认区块模式。 + +默认情况下,区块会提供自身样式以确保在未修改的主题中正常显示。同时它们还[提供可选的预设样式](#默认区块样式)。主题可以添加/覆盖这些样式,也可以完全不提供样式,完全依赖区块自带的样式。 + +某些高级区块功能需要主题本身启用支持,因为这些样式难以由区块直接提供,可能需要主题本身进行架构层面的调整才能完美适配。 + +要启用这些功能,请在主题的 `functions.php` 文件中调用 `add_theme_support`。例如: + +```php +function mytheme_setup_theme_supported_features() { + add_theme_support( 'editor-color-palette', array( + array( + 'name' => esc_attr__( '强品红色', 'themeLangDomain' ), + 'slug' => 'strong-magenta', + 'color' => '#a156b4', + ), + array( + 'name' => esc_attr__( '浅灰品红色', 'themeLangDomain' ), + 'slug' => 'light-grayish-magenta', + 'color' => '#d0a5db', + ), + array( + 'name' => esc_attr__( '极浅灰色', 'themeLangDomain' ), + 'slug' => 'very-light-gray', + 'color' => '#eee', + ), + array( + 'name' => esc_attr__( '深灰色', 'themeLangDomain' ), + 'slug' => 'very-dark-gray', + 'color' => '#444', + ), + ) ); +} + +add_action( 'after_setup_theme', 'mytheme_setup_theme_supported_features' ); +``` + +## 可选功能 + +## 默认区块样式 + +核心区块包含默认的结构样式。这些样式默认会在编辑器和前端同时加载。例如控制分栏区块的CSS规则,若缺少这些规则,该区块将呈现破碎布局且无法显示分栏效果。 + +### 预设区块样式 + +区块编辑器允许主题为前端启用更具设计主张的样式。例如引用区块左侧的默认彩色条。若要在经典主题中使用这些预设样式,请添加对 `wp-block-styles` 的主题支持: + +```php +add_theme_support( 'wp-block-styles' ); +``` + +您可以在[区块库主题文件](https://github.com/WordPress/gutenberg/blob/trunk/packages/block-library/src/theme.scss)中查看将被引入的CSS代码。 + +对于区块主题或提供 `theme.json` 文件的主题,不建议使用此主题支持功能。为确保全局样式规则与区块样式之间不存在样式冲突,请将所需的区块样式添加到主题的 `theme.json` 文件中。 + +### 宽对齐功能: + +某些区块(如图像区块)可通过向区块包装器添加对应类名(`alignwide` 或 `alignfull`)来定义“宽”或“全宽”对齐方式。主题可通过以下调用启用此功能: + +```php +add_theme_support( 'align-wide' ); +``` + +有关此功能的更多信息,请参阅[关于 `add_theme_support()` 的开发文档](https://developer.wordpress.org/reference/functions/add_theme_support/)。 + +### 支持自定义单位 + +除了像素单位外,用户还可使用其他单位定义尺寸、内边距等。可用单位包括:px、em、rem、vh、vw。主题可通过以下代码禁用此功能: + +```php +add_theme_support( 'custom-units', array() ); +``` + +主题还可对可用自定义单位进行筛选。 + +```php +add_theme_support( 'custom-units', 'rem', 'em' ); +``` + +### 禁用默认区块模式 + +WordPress 内置了多种区块模式,主题可通过以下代码选择不启用捆绑模式,转而提供自有模式集: + +```php +remove_theme_support( 'core-block-patterns' ); +``` + +## 编辑器样式 + +区块编辑器支持主题的[编辑器样式](https://codex.wordpress.org/Editor_Style),但其运作方式与经典编辑器略有不同。 + +在经典编辑器中,编辑器样式表会直接加载到所见即所得编辑器的iframe内,不做任何更改。而区块编辑器并不使用iframe。为确保样式仅应用于编辑器内容,系统会通过选择性重写或调整特定CSS选择器来自动转换编辑器样式。这也使得区块编辑器可以在区块变体预览中利用您的编辑器样式。 + +例如,如果您在编辑器样式中编写`body { ... }`,这将被重写为`.editor-styles-wrapper { ... }`。这也意味着您不应直接针对任何编辑器类名进行样式设定。 + +由于其运作方式不同,您需要在`add_editor_style`函数之外,额外在主题中添加以下代码片段来启用此功能: + +```php +add_theme_support( 'editor-styles' ); +``` + +您无需过多调整编辑器样式;大多数主题只需添加上述代码片段,即可在经典编辑器和区块编辑器中获得相似效果。 + +### 载入编辑器样式 + +使用`add_editor_style`函数在编辑器界面载入CSS样式。对于经典编辑器,这是为编辑器添加样式的唯一必要函数。对于新版区块编辑器,您需要先添加前述的`add_theme_support( 'editor-styles');`。 + +```php +add_editor_style( 'style-editor.css' ); +``` + +将此代码添加到`functions.php`文件后,会将`style-editor.css`样式表加入编辑器待加载样式表队列。 + +### 基础色彩 + +您可以像设置普通网页那样设置编辑器样式。以下是将背景色和字体颜色改为蓝色的方法: + +```css +/* 将此代码添加至`style-editor.css`文件 */ +body { + background-color: #d3ebf3; + color: #00005d; +} +``` + +### 调整编辑器宽度 + +要更改编辑器的主列宽,请将以下CSS添加至`style-editor.css`: + +```css +/* 主列宽度 */ +.wp-block { + max-width: 720px; +} + +/* "宽版"区块宽度 */ +.wp-block[data-align='wide'] { + max-width: 1080px; +} + +/* "全宽"区块宽度 */ +.wp-block[data-align='full'] { + max-width: none; +} +``` + +您可以使用这些编辑器宽度值来匹配主题设置。可使用任何CSS宽度单位,包括`%`或`px`。 + +扩展阅读:[使用样式表应用样式](/docs/how-to-guides/block-tutorial/applying-styles-with-stylesheets.md)。 + +## 响应式嵌入内容 + +嵌入区块会自动为嵌入内容应用样式,以反映在iFrame中嵌入内容的长宽比。应用长宽比响应式样式的区块显示效果如下: + +```html +
    ...
    +``` + +为使内容在保持长宽比的同时自适应尺寸,``元素需要具有`wp-embed-responsive`类。该样式类默认未设置,需要主题通过声明支持`responsive-embeds`功能来启用: + +```php +add_theme_support( 'responsive-embeds' ); +``` + +## 间距控制 + +部分区块可配备内边距控制功能。此功能默认关闭,需要主题通过声明支持来启用: + +```php +add_theme_support( 'custom-spacing' ); +``` + +### 区块字体大小 + +区块可能允许用户配置所使用的字体大小,例如段落区块。区块提供一组默认字体大小,但主题可以覆盖并提供自己的设置: + +```php +add_theme_support( 'editor-font-sizes', array( + array( + 'name' => esc_attr__( '小号', 'themeLangDomain' ), + 'size' => 12, + 'slug' => 'small' + ), + array( + 'name' => esc_attr__( '常规', 'themeLangDomain' ), + 'size' => 16, + 'slug' => 'regular' + ), + array( + 'name' => esc_attr__( '大号', 'themeLangDomain' ), + 'size' => 36, + 'slug' => 'large' + ), + array( + 'name' => esc_attr__( '超大号', 'themeLangDomain' ), + 'size' => 50, + 'slug' => 'huge' + ) +) ); +``` + +字体大小将按照主题提供的顺序显示在字体大小选择器中。 + +主题负责创建应用正确字体大小样式的类。类名通过添加 'has-',后接使用短横线命名法的字体大小名称,并以 `-font-size` 结尾来构建。 + +以常规字体大小为例,主题可提供以下类: + +```css +.has-regular-font-size { + font-size: 16px; +} +``` + +
    +注意:标识符 `default` 和 `custom` 为保留字,主题不可使用。 +
    + +自 WordPress 5.9 起,要覆盖核心定义的字体大小值,没有 `theme.json` 的主题必须通过 CSS 自定义属性设置其值,而非提供类。CSS 自定义属性使用以下命名方式 `--wp--preset--font-size--`。更多信息请参阅[此开发说明](https://make.wordpress.org/core/2022/01/08/updates-for-settings-styles-and-theme-json/)。例如: + +```css +:root { + --wp--preset--font-size--small: <新值>; + --wp--preset--font-size--large: <新值>; +} +``` + +### 禁用自定义字体大小 + +主题可通过以下代码禁用设置自定义字体大小的功能: + +```php +add_theme_support( 'disable-custom-font-sizes' ); +``` + +设置后,用户将被限制使用区块编辑器提供的默认大小或通过 `editor-font-sizes` 主题支持设置提供的大小。 + +### 禁用区块调色板中的自定义颜色 + +默认情况下,提供给区块的调色板允许用户选择与编辑器或主题默认颜色不同的自定义颜色。 + +主题可通过以下方式禁用此功能: + +```php +add_theme_support( 'disable-custom-colors' ); +``` + +此标志将确保用户只能从主题提供的 `editor-color-palette` 中选择颜色,或者如果主题未提供,则从编辑器的默认颜色中选择。 + +### 禁用自定义渐变 + +主题可通过以下代码禁用设置自定义渐变的功能: + +```php +add_theme_support( 'disable-custom-gradients' ); +``` + +设置后,用户将被限制使用区块编辑器提供的默认渐变或通过 `editor-gradient-presets` 主题支持设置提供的渐变。 + +### 禁用基础布局样式 + +_**注意:**自 WordPress 6.1 起。_ + +主题可以选择退出生成的区块布局样式,这些样式为核心区块(包括群组、列、按钮和社交图标)提供默认结构样式。通过使用以下代码,这些主题承诺提供自己的结构样式,因为使用此功能将导致核心区块在编辑器和站点前端显示不正确: + +```php +add_theme_support( 'disable-layout-styles' ); +``` + +对于希望自定义 `blockGap` 样式或区块间距的主题,请参阅[关于全局设置和样式的开发者文档](/docs/how-to-guides/themes/global-settings-and-styles.md#what-is-blockgap-and-how-can-i-use-it)。 + +### 支持自定义行高 + +某些区块(如段落和标题)支持自定义行高。主题可通过以下代码启用对此功能的支持: + +```php +add_theme_support( 'custom-line-height' ); +``` + +### 宽对齐与浮动元素 + +要创建一个能够同时适应宽幅图像、侧边栏、居中列以及限定在居中列内的浮动元素的响应式布局,可能会遇到一些困难。 + +区块编辑器为浮动图像添加了额外的标记,以便更轻松地设置样式。 + +以下是一个带标题的 `Image` 标记示例: + +```html +
    + +
    简短图片标题。
    +
    +``` + +以下是一个左浮动图像的标记示例: + +```html +
    +
    + +
    简短图片标题。
    +
    +
    +``` + +这里有一个使用上述标记实现响应式布局的 [codepen](https://codepen.io/joen/pen/zLWvrW) 示例,该布局包含侧边栏、宽幅图像以及带有限定标题的浮动元素。 + +### 区块调色板 + +不同区块可以自定义颜色。区块编辑器提供了默认调色板,但主题可以覆盖它并提供自己的调色板: + +```php +add_theme_support( 'editor-color-palette', array( + array( + 'name' => esc_attr__( '强品红色', 'themeLangDomain' ), + 'slug' => 'strong-magenta', + 'color' => '#a156b4', + ), + array( + 'name' => esc_attr__( '浅灰品红色', 'themeLangDomain' ), + 'slug' => 'light-grayish-magenta', + 'color' => '#d0a5db', + ), + array( + 'name' => esc_attr__( '极浅灰色', 'themeLangDomain' ), + 'slug' => 'very-light-gray', + 'color' => '#eee', + ), + array( + 'name' => esc_attr__( '极深灰色', 'themeLangDomain' ), + 'slug' => 'very-dark-gray', + 'color' => '#444', + ), +) ); +``` + +`name` 是一个易于理解的标签(如上所示),它会显示在工具提示中,并为用户提供颜色的有意义的描述。这对于依赖屏幕阅读器或难以感知颜色的用户尤其重要。`slug` 是颜色的唯一标识符,用于生成区块编辑器调色板的 CSS 类。`color` 是指定颜色的十六进制代码。 + +某些颜色会动态变化,例如“主色”和“辅色”,例如在 Twenty Nineteen 主题中,这些颜色无法通过编程方式描述。尽管如此,建议在适用时为至少默认值提供有意义的 `name`。 + +颜色将按顺序显示在调色板上,并且可以指定的颜色数量没有限制。 + +主题负责创建在不同上下文中应用颜色的类。核心区块使用“颜色”、“背景颜色”和“边框颜色”上下文。因此,为了正确地将“强品红色”应用于核心区块的所有上下文,主题应自行实现这些类。类名的构建方式是在“has-”后附加使用短横线命名法的类名,并以上下文名称结尾。 + +```css +.has-strong-magenta-color { + color: #a156b4; +} + +.has-strong-magenta-background-color { + background-color: #a156b4; +} + +.has-strong-magenta-border-color { + border-color: #a156b4; +} +``` + +从 WordPress 5.9 开始,要覆盖核心定义的颜色值,没有 `theme.json` 的主题必须通过 CSS 自定义属性设置其值,而不是提供类。CSS 自定义属性使用以下命名方式:`--wp--preset--color--`。更多信息请参阅 [此开发说明](https://make.wordpress.org/core/2022/01/08/updates-for-settings-styles-and-theme-json/)。例如: + +```css +:root { + --wp--preset--color--cyan-bluish-gray: <新值>; + --wp--preset--color--pale-pink: <新值>; +} +``` + +### 区块渐变预设 + +不同区块可以从预定义的渐变列表中选择。区块编辑器提供了默认的渐变预设,但主题可以覆盖它们并提供自己的预设: + +```php +add_theme_support( + 'editor-gradient-presets', + array( + array( + 'name' => esc_attr__( '鲜艳的蓝绿色到鲜艳的紫色', 'themeLangDomain' ), + 'gradient' => 'linear-gradient(135deg,rgba(6,147,227,1) 0%,rgb(155,81,224) 100%)', + 'slug' => 'vivid-cyan-blue-to-vivid-purple' + ), + array( + 'name' => esc_attr__( '鲜艳的绿青色到鲜艳的蓝绿色', 'themeLangDomain' ), + 'gradient' => 'linear-gradient(135deg,rgba(0,208,132,1) 0%,rgba(6,147,227,1) 100%)', + 'slug' => 'vivid-green-cyan-to-vivid-cyan-blue', + ), + array( + 'name' => esc_attr__( '浅绿青色到鲜艳的绿青色', 'themeLangDomain' ), + 'gradient' => 'linear-gradient(135deg,rgb(122,220,180) 0%,rgb(0,208,130) 100%)', + 'slug' => 'light-green-cyan-to-vivid-green-cyan', + ), + array( + 'name' => esc_attr__( '明亮鲜艳的琥珀色到明亮鲜艳的橙色', 'themeLangDomain' ), + 'gradient' => 'linear-gradient(135deg,rgba(252,185,0,1) 0%,rgba(255,105,0,1) 100%)', + 'slug' => 'luminous-vivid-amber-to-luminous-vivid-orange', + ), + array( + 'name' => esc_attr__( '明亮鲜艳的橙色到鲜艳的红色', 'themeLangDomain' ), + 'gradient' => 'linear-gradient(135deg,rgba(255,105,0,1) 0%,rgb(207,46,46) 100%)', + 'slug' => 'luminous-vivid-orange-to-vivid-red', + ), + ) +); +``` + +`name` 是一个易于理解的标签(如上所示),它会显示在工具提示中,并为用户提供渐变的有意义的描述。这对于依赖屏幕阅读器或难以感知颜色的用户尤其重要。`gradient` 是应用于区块背景图像的渐变的 CSS 值。有效渐变类型的详细信息可以在 [mozilla 文档](https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Images/Using_CSS_gradients) 中找到。`slug` 是渐变的唯一标识符,用于生成区块编辑器使用的 CSS 类。 + +主题负责创建应用渐变的类。因此,要正确应用“鲜艳的蓝绿色到鲜艳的紫色”,主题应实现以下类: + +```css +.has-vivid-cyan-blue-to-vivid-purple-gradient-background { + background: linear-gradient( + 135deg, + rgba( 6, 147, 227, 1 ) 0%, + rgb( 155, 81, 224 ) 100% + ); +} +``` + +从 WordPress 5.9 开始,要覆盖核心定义的渐变值,没有 `theme.json` 的主题必须通过 CSS 自定义属性设置其值,而不是提供类。CSS 自定义属性使用以下命名方式:`--wp--preset--gradient--`。更多信息请参阅 [此开发说明](https://make.wordpress.org/core/2022/01/08/updates-for-settings-styles-and-theme-json/)。例如: + +```css +:root { + --wp--preset--gradient--vivid-cyan-blue-to-vivid-purple: <新值>; + --wp--preset--gradient--light-green-cyan-to-vivid-green-cyan: <新值>; +} +``` \ No newline at end of file diff --git a/how-to-guides/thunks.md b/how-to-guides/thunks.md new file mode 100644 index 0000000..be8a16c --- /dev/null +++ b/how-to-guides/thunks.md @@ -0,0 +1,225 @@ +## Thunks API + +thunk 函数接收一个包含以下键的单一对象参数: + +### select + +一个包含与状态预绑定的存储选择器的对象,这意味着您无需提供状态,只需提供额外参数。`select` 会触发相关的解析器(如果存在),但不会等待其完成。即使当前值为空,它也会直接返回当前值。 + +如果某个选择器是公共 API 的一部分,它将以方法形式存在于 select 对象上: + +```js +const thunk = () => ( { select } ) => { + // select 是该存储选择器的对象,已预绑定到当前状态: + const temperature = select.getTemperature(); +} +``` + +由于并非所有选择器都在存储中公开,`select` 同时支持以函数形式传递选择器作为参数: + +```js +const thunk = () => ( { select } ) => { + // select 支持私有选择器: + const doubleTemperature = select( ( temperature ) => temperature * 2 ); +} +``` + +### resolveSelect + +`resolveSelect` 与 `select` 相同,但它返回一个 Promise,该 Promise 会通过相关解析器提供的值进行解析。 + +```js +const thunk = () => ( { resolveSelect } ) => { + const temperature = await resolveSelect.getTemperature(); +} +``` + +### dispatch + +一个包含存储操作的对象 + +如果某个操作是公共 API 的一部分,它将以方法形式存在于 `dispatch` 对象上: + +```js +const thunk = () => ( { dispatch } ) => { + // dispatch 是该存储操作的对象: + const temperature = await dispatch.retrieveTemperature(); +} +``` + +由于并非所有操作都在存储中公开,`dispatch` 同时支持以函数形式传递 Redux 操作作为参数: + +```js +const thunk = () => async ( { dispatch } ) => { + // dispatch 也是一个接受内联操作的函数: + dispatch({ type: 'SET_TEMPERATURE', temperature: result.value }); + + // thunk 可与操作互换使用 + dispatch( updateTemperature( 100 ) ); + + // thunk 也可以是异步的。当它们是异步时,dispatch 会返回一个 Promise + await dispatch( ( ) => window.fetch( /* ... */ ) ); +} +``` + +### registry + +注册表通过其 `dispatch`、`select` 和 `resolveSelect` 方法提供对其他存储的访问。 +这些方法与上述方法非常相似,但略有不同。调用 `registry.select( storeName )` 会返回一个函数,该函数返回来自 `storeName` 的选择器对象。当您需要与另一个存储交互时,这会非常方便。例如: + +```js +const thunk = () => ( { registry } ) => { + const error = registry.select( 'core' ).getLastEntitySaveError( 'root', 'menu', menuId ); + /* ... */ +} +``` + +# Core-Data 中的 Thunk 函数 + +[Gutenberg 11.6](https://github.com/WordPress/gutenberg/pull/27276) 新增了对 _thunk_ 的支持。您可以将 thunk 理解为可被调度的函数: + +```js +// actions.js +export const myThunkAction = () => ( { select, dispatch } ) => { + return "我是一个 thunk!我可以被调度,使用选择器,甚至调度其他动作。"; +}; +``` + +## Thunk 函数的优势何在? + +Thunk [拓展了 Redux 动作的定义边界](https://jsnajdr.wordpress.com/2021/10/04/motivation-for-thunks/)。在 thunk 出现之前,动作是纯函数化的,只能返回和生成数据。常见的应用场景(例如在动作中与存储库交互或请求 API 数据)需要使用独立的 [control](https://developer.wordpress.org/block-editor/reference-guides/packages/packages-data/#controls)。您经常会看到这样的代码: + +```js +export function* saveRecordAction( id ) { + const record = yield controls.select( 'current-store', 'getRecord', id ); + yield { type: 'BEFORE_SAVE', id, record }; + const results = yield controls.fetch({ url: 'https://...', method: 'POST', data: record }); + yield { type: 'AFTER_SAVE', id, results }; + return results; +} + +const controls = { + select: // ..., + fetch: // ..., +}; +``` + +像存储库操作和 fetch 函数这样的副作用需要在动作之外实现。Thunk 为这种方法提供了替代方案,允许您直接内联处理副作用: + +```js +export const saveRecordAction = ( id ) => async ({ select, dispatch }) => { + const record = select( 'current-store', 'getRecord', id ); + dispatch({ type: 'BEFORE_SAVE', id, record }); + const response = await fetch({ url: 'https://...', method: 'POST', data: record }); + const results = await response.json(); + dispatch({ type: 'AFTER_SAVE', id, results }); + return results; +} +``` + +这样就无需再单独实现 controls。 + +### Thunk 可访问存储库辅助工具 + +让我们看一个 Gutenberg 核心代码中的例子。在 thunk 出现之前,`@wordpress/interface` 包中的 `toggleFeature` 动作是这样实现的: + +```js +export function* toggleFeature( scope, featureName ) { + const currentValue = yield controls.select( + interfaceStoreName, + 'isFeatureActive', + scope, + featureName + ); + + yield controls.dispatch( + interfaceStoreName, + 'setFeatureValue', + scope, + featureName, + ! currentValue + ); +} +``` + +Controls 曾是唯一能调度动作和从存储库选择数据的方式。 + +通过 thunk,现在有了更简洁的实现方式。这是 `toggleFeature` 当前的实现: + +```js +export function toggleFeature( scope, featureName ) { + return function ( { select, dispatch } ) { + const currentValue = select.isFeatureActive( scope, featureName ); + dispatch.setFeatureValue( scope, featureName, ! currentValue ); + }; +} +``` + +借助 `select` 和 `dispatch` 参数,thunk 可以直接使用存储库,无需依赖生成器和 controls。 + +### Thunk 支持异步操作 + +假设有个简单的 React 应用,允许您设置恒温器温度。它只有一个输入框和一个按钮。点击按钮会调用携带输入值的 `saveTemperatureToAPI` 动作。 + +若使用 controls 保存温度,存储库定义如下所示: + +```js +const store = wp.data.createReduxStore( 'my-store', { + actions: { + saveTemperatureToAPI: function*( temperature ) { + const result = yield { type: 'FETCH_JSON', url: 'https://...', method: 'POST', data: { temperature } }; + return result; + } + }, + controls: { + async FETCH_JSON( action ) { + const response = await window.fetch( action.url, { + method: action.method, + body: JSON.stringify( action.data ), + } ); + return response.json(); + } + }, + // reducers, selectors, ... +} ); +``` + +虽然代码逻辑清晰,但存在一层间接调用。`saveTemperatureToAPI` 动作并不直接与 API 通信,而是需要通过 `FETCH_JSON` control 中转。 + +让我们看看如何用 thunk 消除这层间接调用: + +```js +const store = wp.data.createReduxStore( 'my-store', { + actions: { + saveTemperatureToAPI: ( temperature ) => async () => { + const response = await window.fetch( 'https://...', { + method: 'POST', + body: JSON.stringify( { temperature } ), + } ); + return await response.json(); + } + }, + // reducers, selectors, ... +} ); +``` + +这非常简洁!更棒的是,resolvers 也同样支持这种写法: + +```js +const store = wp.data.createReduxStore( 'my-store', { + // ... + selectors: { + getTemperature: ( state ) => state.temperature + }, + resolvers: { + getTemperature: () => async ( { dispatch } ) => { + const response = await window.fetch( 'https://...' ); + const result = await response.json(); + dispatch.receiveCurrentTemperature( result.temperature ); + } + }, + // ... +} ); +``` + +与(现已过时的)生成器和 controls 支持一样,所有数据存储库默认都包含对 thunk 的支持。 \ No newline at end of file diff --git a/how-to-guides/widgets/README.md b/how-to-guides/widgets/README.md new file mode 100644 index 0000000..27e4103 --- /dev/null +++ b/how-to-guides/widgets/README.md @@ -0,0 +1,9 @@ +# 小工具 + +Gutenberg插件将WP管理后台中的小工具编辑器界面替换为基于WordPress区块编辑器的全新界面。 + +**目录** + +- [小工具区块编辑器概述](/docs/how-to-guides/widgets/overview.md) +- [恢复旧版小工具编辑器](/docs/how-to-guides/widgets/opting-out.md) +- [确保与旧版小工具区块的兼容性](/docs/how-to-guides/widgets/legacy-widget-block.md) \ No newline at end of file diff --git a/how-to-guides/widgets/legacy-widget-block.md b/how-to-guides/widgets/legacy-widget-block.md new file mode 100644 index 0000000..6aed223 --- /dev/null +++ b/how-to-guides/widgets/legacy-widget-block.md @@ -0,0 +1,188 @@ +# 关于传统小工具区块 + +传统小工具区块允许用户添加、编辑和预览由插件注册的第三方小工具,以及使用经典小工具编辑器添加的小工具。 + +可通过区块插入器添加传统小工具区块,并从该区块的下拉菜单中选择小工具来添加第三方小工具。 + +也可通过在区块插入器中搜索小工具名称并选择该小工具来添加第三方小工具。系统将插入一个传统小工具区块的变体。 + +## 与传统小工具区块的兼容性 + +### `widget-added` 事件 + +传统小工具区块将以类似于定制器的方式显示小工具的表单,因此与大多数第三方小工具兼容。 + +如果小工具在其表单中使用 JavaScript,则必须在 `document` 上触发 `'widget-added'` jQuery 事件后,将事件添加到 DOM 中。 + +例如,当“更改密码”复选框被勾选时,小工具可能希望显示一个“密码”字段。 + +```js +( function ( $ ) { + $( document ).on( 'widget-added', function ( $event, $control ) { + $control.find( '.change-password' ).on( 'change', function () { + var isChecked = $( this ).prop( 'checked' ); + $control.find( '.password' ).toggleClass( 'hidden', ! isChecked ); + } ); + } ); +} )( jQuery ); +``` + +请注意,所有小工具的事件处理程序都在 `widget-added` 回调中添加。 + +### 显示“无预览可用” + +当未选择传统小工具区块时,该区块将显示小工具的预览。 + +当小工具的 `widget()` 函数未呈现任何内容或仅呈现空的 HTML 元素时,传统小工具区块会自动显示“无预览可用”的消息。 + +小工具可以通过在不应显示预览时从 `widget()` 提前返回来利用这一点。 + +```php +class ExampleWidget extends WP_Widget { + ... + public function widget( $instance ) { + if ( ! isset( $instance['name'] ) ) { + // 名称为必填项,如果没有则什么都不显示。 + return; + } + ?> +

    名称:

    + ... + true, + // ...其他选项 + ); + parent::__construct( 'example_widget', 'ExampleWidget', $widget_ops ); + } + ... +} +``` + +这允许区块编辑器和其他 REST API 客户端通过访问 REST API 响应中的 `instance.raw` 来查看您的小工具的实例数组。 + +请注意,[WordPress 5.8.0 之前的版本允许您通过在扩展 `WP_Widget` 的类中将 `$show_instance_in_rest` 设置为 `true`](https://core.trac.wordpress.org/ticket/53332) 来启用此功能。 + +```php +class ExampleWidget extends WP_Widget { + ... + public $show_instance_in_rest = true; + ... +} +``` + +现在已不推荐使用此方法,建议改用小工具选项方法。 + +#### 2) 添加区块转换 + +现在,我们可以定义一个[区块转换](https://developer.wordpress.org/block-editor/reference-guides/block-api/block-transforms/),告诉区块编辑器用什么替换包含您小工具的传统小工具区块。 + +这是通过向区块定义中添加 JavaScript 代码来实现的。在此示例中,我们定义了一个转换,将 ID 为 `'example_widget'` 的小工具转换为名称为 `'example/block'` 的区块。 + +```js +transforms: { + from: [ + { + type: 'block', + blocks: [ 'core/legacy-widget' ], + isMatch: ( { idBase, instance } ) => { + if ( ! instance?.raw ) { + // 如果原始实例未在 REST API 中显示,则无法转换。 + return false; + } + return idBase === 'example_widget'; + }, + transform: ( { instance } ) => { + return createBlock( 'example/block', { + name: instance.raw.name, + } ); + }, + }, + ] +}, +``` + +#### 3) 在传统小工具区块中隐藏小工具 + +最后,我们可以告诉传统小工具区块在“选择小工具”下拉列表和区块插入器中隐藏您的小工具。这鼓励用户使用替换您小工具的区块。 + +可以使用 `widget_types_to_hide_from_legacy_widget_block` 过滤器来实现这一点。 + +```php +function hide_example_widget( $widget_types ) { + $widget_types[] = 'example_widget'; + return $widget_types; +} +add_filter( 'widget_types_to_hide_from_legacy_widget_block', 'hide_example_widget' ); +``` + +## 在其他区块编辑器中使用传统小工具区块(高级) + +您可以选择允许在其他区块编辑器(例如 WordPress 文章编辑器)中使用传统小工具区块。默认情况下,此功能未启用。 + +首先,确保页面上加载了传统小工具所需的任何样式和脚本。一种便捷的方法是手动执行用户浏览小工具 WP 管理屏幕时通常运行的所有钩子。 + +```php +add_action( 'admin_print_styles', function() { + if ( get_current_screen()->is_block_editor() ) { + do_action( 'admin_print_styles-widgets.php' ); + } +} ); +add_action( 'admin_print_scripts', function() { + if ( get_current_screen()->is_block_editor() ) { + do_action( 'load-widgets.php' ); + do_action( 'widgets.php' ); + do_action( 'sidebar_admin_setup' ); + do_action( 'admin_print_scripts-widgets.php' ); + } +} ); +add_action( 'admin_print_footer_scripts', function() { + if ( get_current_screen()->is_block_editor() ) { + do_action( 'admin_print_footer_scripts-widgets.php' ); + } +} ); +add_action( 'admin_footer', function() { + if ( get_current_screen()->is_block_editor() ) { + do_action( 'admin_footer-widgets.php' ); + } +} ); +``` + +然后,使用 `@wordpress/widgets` 包中定义的 `registerLegacyWidgetBlock` 注册传统小工具区块。 + +```php +add_action( 'enqueue_block_editor_assets', function() { + wp_enqueue_script( 'wp-widgets' ); + wp_add_inline_script( 'wp-widgets', 'wp.widgets.registerLegacyWidgetBlock()' ); +} ); +``` \ No newline at end of file diff --git a/how-to-guides/widgets/opting-out.md b/how-to-guides/widgets/opting-out.md new file mode 100644 index 0000000..cd46919 --- /dev/null +++ b/how-to-guides/widgets/opting-out.md @@ -0,0 +1,44 @@ +# 恢复经典小工具编辑器 + +有多种方法可以禁用新的小工具区块编辑器。 + +## 使用 `remove_theme_support` + +主题可以通过调用 `remove_theme_support( 'widgets-block-editor' )` 来禁用小工具区块编辑器。 + +例如,主题可以在 `functions.php` 文件中添加以下 PHP 代码: + +```php +function example_theme_support() { + remove_theme_support( 'widgets-block-editor' ); +} +add_action( 'after_setup_theme', 'example_theme_support' ); +``` + +## 使用经典小工具插件 + +最终用户可以通过安装并激活[经典小工具插件](https://wordpress.org/plugins/classic-widgets/)来禁用小工具区块编辑器。 + +安装此插件后,可以通过停用和激活插件来切换小工具区块编辑器的开关状态。 + +## 使用过滤器 + +`use_widgets_block_editor` 过滤器用于控制是否启用小工具区块编辑器。 + +例如,网站管理员可以在 Must-Use 插件中添加以下 PHP 代码来禁用小工具区块编辑器: + +```php +add_filter( 'use_widgets_block_editor', '__return_false' ); +``` + +对于更高级的用法,您可以提供自定义函数。以下示例展示了如何为特定用户禁用小工具区块编辑器: + +```php +function example_use_widgets_block_editor( $use_widgets_block_editor ) { + if ( 123 === get_current_user_id() ) { + return false; + } + return $use_widgets_block_editor; +} +add_filter( 'use_widgets_block_editor', 'example_use_widgets_block_editor' ); +``` \ No newline at end of file diff --git a/how-to-guides/widgets/overview.md b/how-to-guides/widgets/overview.md new file mode 100644 index 0000000..27e52dd --- /dev/null +++ b/how-to-guides/widgets/overview.md @@ -0,0 +1,31 @@ +# 小工具区块编辑器概述 + +## 小工具区块编辑器 + +全新小工具编辑器是WordPress的一项功能升级,它将小工具区域升级为支持区块与小工具并存使用。这项功能通过大家熟悉的WordPress区块编辑器,提供了全新小工具管理体验。 + +您可以通过访问外观→小工具或外观→自定义→小工具并选择小工具区域,来使用全新小工具编辑器。 + +小工具区块编辑器允许您通过独立编辑器或自定义器,将区块和小工具插入当前启用主题定义的任意[小工具区域或侧边栏](https://developer.wordpress.org/themes/functionality/sidebars/)。 + +### 自定义器小工具区块编辑器 + +全新小工具编辑器同时取代了自定义器中的小工具版块,采用基于区块的全新编辑器。 + +您可以通过访问外观→自定义,选择小工具,然后选择小工具区域来使用自定义器小工具区块编辑器。 + +通过自定义器使用全新小工具编辑器不仅支持将区块和小工具插入选定的小工具区域,还能利用编辑器右侧的实时预览功能,以及所有其他自定义器专属功能(如变更排程与共享)。 + +## 兼容性 + +在新版小工具编辑器推出前已添加到小工具区域的传统小工具,将通过传统小工具区块继续正常工作。 + +传统小工具区块作为兼容机制,允许我们在基于区块的全新小工具编辑器中编辑和预览传统小工具的变更。 + +通过插件注册的第三方小工具仍可通过添加和设置传统小工具区块,插入到小工具区域中。 + +小工具编辑器使用用户不可见的底层“区块”小工具来存储区块数据。这意味着插件和主题将继续正常工作,且停用小工具区块编辑器不会造成任何数据丢失。 + +主题可通过`remove_theme_support( 'widgets-block-editor' )`代码禁用小工具区块编辑器。 + +用户可通过安装[经典小工具插件](https://wordpress.org/plugins/classic-widgets/)来禁用小工具区块编辑器。 \ No newline at end of file diff --git a/manifest.json b/manifest.json new file mode 100644 index 0000000..1a15556 --- /dev/null +++ b/manifest.json @@ -0,0 +1,2438 @@ +[ + { + "title": "Block Editor Handbook", + "slug": "handbook", + "markdown_source": "../docs/README.md", + "parent": null + }, + { + "title": "Getting Started", + "slug": "getting-started", + "markdown_source": "../docs/getting-started/README.md", + "parent": null + }, + { + "title": "Block Development Environment", + "slug": "devenv", + "markdown_source": "../docs/getting-started/devenv/README.md", + "parent": "getting-started" + }, + { + "title": "Node.js development environment", + "slug": "nodejs-development-environment", + "markdown_source": "../docs/getting-started/devenv/nodejs-development-environment.md", + "parent": "devenv" + }, + { + "title": "Get started with wp-env", + "slug": "get-started-with-wp-env", + "markdown_source": "../docs/getting-started/devenv/get-started-with-wp-env.md", + "parent": "devenv" + }, + { + "title": "Get started with create-block", + "slug": "get-started-with-create-block", + "markdown_source": "../docs/getting-started/devenv/get-started-with-create-block.md", + "parent": "devenv" + }, + { + "title": "Get started with wp-scripts", + "slug": "get-started-with-wp-scripts", + "markdown_source": "../docs/getting-started/devenv/get-started-with-wp-scripts.md", + "parent": "devenv" + }, + { + "title": "Quick Start Guide", + "slug": "quick-start-guide", + "markdown_source": "../docs/getting-started/quick-start-guide.md", + "parent": "getting-started" + }, + { + "title": "Tutorial: Build your first block", + "slug": "tutorial", + "markdown_source": "../docs/getting-started/tutorial.md", + "parent": "getting-started" + }, + { + "title": "Fundamentals of Block Development", + "slug": "fundamentals", + "markdown_source": "../docs/getting-started/fundamentals/README.md", + "parent": "getting-started" + }, + { + "title": "File structure of a block", + "slug": "file-structure-of-a-block", + "markdown_source": "../docs/getting-started/fundamentals/file-structure-of-a-block.md", + "parent": "fundamentals" + }, + { + "title": "block.json", + "slug": "block-json", + "markdown_source": "../docs/getting-started/fundamentals/block-json.md", + "parent": "fundamentals" + }, + { + "title": "Registration of a block", + "slug": "registration-of-a-block", + "markdown_source": "../docs/getting-started/fundamentals/registration-of-a-block.md", + "parent": "fundamentals" + }, + { + "title": "The block wrapper", + "slug": "block-wrapper", + "markdown_source": "../docs/getting-started/fundamentals/block-wrapper.md", + "parent": "fundamentals" + }, + { + "title": "The block in the Editor", + "slug": "block-in-the-editor", + "markdown_source": "../docs/getting-started/fundamentals/block-in-the-editor.md", + "parent": "fundamentals" + }, + { + "title": "Markup representation of a block", + "slug": "markup-representation-block", + "markdown_source": "../docs/getting-started/fundamentals/markup-representation-block.md", + "parent": "fundamentals" + }, + { + "title": "Static or Dynamic rendering of a block", + "slug": "static-dynamic-rendering", + "markdown_source": "../docs/getting-started/fundamentals/static-dynamic-rendering.md", + "parent": "fundamentals" + }, + { + "title": "Working with JavaScript for the Block Editor", + "slug": "javascript-in-the-block-editor", + "markdown_source": "../docs/getting-started/fundamentals/javascript-in-the-block-editor.md", + "parent": "fundamentals" + }, + { + "title": "Glossary", + "slug": "glossary", + "markdown_source": "../docs/getting-started/glossary.md", + "parent": "getting-started" + }, + { + "title": "Frequently Asked Questions", + "slug": "faq", + "markdown_source": "../docs/getting-started/faq.md", + "parent": "getting-started" + }, + { + "title": "How-to Guides", + "slug": "how-to-guides", + "markdown_source": "../docs/how-to-guides/README.md", + "parent": null + }, + { + "title": "Accessibility", + "slug": "accessibility", + "markdown_source": "../docs/how-to-guides/accessibility.md", + "parent": "how-to-guides" + }, + { + "title": "Blocks", + "slug": "block-tutorial", + "markdown_source": "../docs/how-to-guides/block-tutorial/README.md", + "parent": "how-to-guides" + }, + { + "title": "Use styles and stylesheets", + "slug": "applying-styles-with-stylesheets", + "markdown_source": "../docs/how-to-guides/block-tutorial/applying-styles-with-stylesheets.md", + "parent": "block-tutorial" + }, + { + "title": "Creating dynamic blocks", + "slug": "creating-dynamic-blocks", + "markdown_source": "../docs/how-to-guides/block-tutorial/creating-dynamic-blocks.md", + "parent": "block-tutorial" + }, + { + "title": "Nested Blocks: Using InnerBlocks", + "slug": "nested-blocks-inner-blocks", + "markdown_source": "../docs/how-to-guides/block-tutorial/nested-blocks-inner-blocks.md", + "parent": "block-tutorial" + }, + { + "title": "Extending the Query Loop block", + "slug": "extending-the-query-loop-block", + "markdown_source": "../docs/how-to-guides/block-tutorial/extending-the-query-loop-block.md", + "parent": "block-tutorial" + }, + { + "title": "Development Platform", + "slug": "platform", + "markdown_source": "../docs/how-to-guides/platform/README.md", + "parent": "how-to-guides" + }, + { + "title": "Building a custom block editor", + "slug": "custom-block-editor", + "markdown_source": "../docs/how-to-guides/platform/custom-block-editor.md", + "parent": "platform" + }, + { + "title": "Create your First App with Gutenberg Data", + "slug": "data-basics", + "markdown_source": "../docs/how-to-guides/data-basics/README.md", + "parent": "how-to-guides" + }, + { + "title": "Setup", + "slug": "1-data-basics-setup", + "markdown_source": "../docs/how-to-guides/data-basics/1-data-basics-setup.md", + "parent": "data-basics" + }, + { + "title": "Building a list of pages", + "slug": "2-building-a-list-of-pages", + "markdown_source": "../docs/how-to-guides/data-basics/2-building-a-list-of-pages.md", + "parent": "data-basics" + }, + { + "title": "Building an edit form", + "slug": "3-building-an-edit-form", + "markdown_source": "../docs/how-to-guides/data-basics/3-building-an-edit-form.md", + "parent": "data-basics" + }, + { + "title": "Building a Create page form", + "slug": "4-building-a-create-page-form", + "markdown_source": "../docs/how-to-guides/data-basics/4-building-a-create-page-form.md", + "parent": "data-basics" + }, + { + "title": "Adding a delete button", + "slug": "5-adding-a-delete-button", + "markdown_source": "../docs/how-to-guides/data-basics/5-adding-a-delete-button.md", + "parent": "data-basics" + }, + { + "title": "Curating the Editor Experience", + "slug": "curating-the-editor-experience", + "markdown_source": "../docs/how-to-guides/curating-the-editor-experience/README.md", + "parent": "how-to-guides" + }, + { + "title": "Block Locking API", + "slug": "block-locking", + "markdown_source": "../docs/how-to-guides/curating-the-editor-experience/block-locking.md", + "parent": "curating-the-editor-experience" + }, + { + "title": "Patterns", + "slug": "patterns", + "markdown_source": "../docs/how-to-guides/curating-the-editor-experience/patterns.md", + "parent": "curating-the-editor-experience" + }, + { + "title": "theme.json", + "slug": "theme-json", + "markdown_source": "../docs/how-to-guides/curating-the-editor-experience/theme-json.md", + "parent": "curating-the-editor-experience" + }, + { + "title": "Filters and hooks", + "slug": "filters-and-hooks", + "markdown_source": "../docs/how-to-guides/curating-the-editor-experience/filters-and-hooks.md", + "parent": "curating-the-editor-experience" + }, + { + "title": "Disable Editor functionality", + "slug": "disable-editor-functionality", + "markdown_source": "../docs/how-to-guides/curating-the-editor-experience/disable-editor-functionality.md", + "parent": "curating-the-editor-experience" + }, + { + "title": "Enqueueing assets in the Editor", + "slug": "enqueueing-assets-in-the-editor", + "markdown_source": "../docs/how-to-guides/enqueueing-assets-in-the-editor.md", + "parent": "how-to-guides" + }, + { + "title": "Feature Flags", + "slug": "feature-flags", + "markdown_source": "../docs/how-to-guides/feature-flags.md", + "parent": "how-to-guides" + }, + { + "title": "Formatting Toolbar API", + "slug": "format-api", + "markdown_source": "../docs/how-to-guides/format-api.md", + "parent": "how-to-guides" + }, + { + "title": "Internationalization", + "slug": "internationalization", + "markdown_source": "../docs/how-to-guides/internationalization.md", + "parent": "how-to-guides" + }, + { + "title": "Meta Boxes", + "slug": "metabox", + "markdown_source": "../docs/how-to-guides/metabox.md", + "parent": "how-to-guides" + }, + { + "title": "Notices", + "slug": "notices", + "markdown_source": "../docs/how-to-guides/notices/README.md", + "parent": "how-to-guides" + }, + { + "title": "Plugin Sidebar", + "slug": "plugin-sidebar-0", + "markdown_source": "../docs/how-to-guides/plugin-sidebar-0.md", + "parent": "how-to-guides" + }, + { + "title": "Propagating updates for block types", + "slug": "propagating-updates", + "markdown_source": "../docs/how-to-guides/propagating-updates.md", + "parent": "how-to-guides" + }, + { + "title": "Themes", + "slug": "themes", + "markdown_source": "../docs/how-to-guides/themes/README.md", + "parent": "how-to-guides" + }, + { + "title": "Global Settings & Styles (theme.json)", + "slug": "global-settings-and-styles", + "markdown_source": "../docs/how-to-guides/themes/global-settings-and-styles.md", + "parent": "themes" + }, + { + "title": "Theme Support", + "slug": "theme-support", + "markdown_source": "../docs/how-to-guides/themes/theme-support.md", + "parent": "themes" + }, + { + "title": "Thunks in Core-Data", + "slug": "thunks", + "markdown_source": "../docs/how-to-guides/thunks.md", + "parent": "how-to-guides" + }, + { + "title": "Widgets", + "slug": "widgets", + "markdown_source": "../docs/how-to-guides/widgets/README.md", + "parent": "how-to-guides" + }, + { + "title": "Widgets Block Editor overview", + "slug": "overview", + "markdown_source": "../docs/how-to-guides/widgets/overview.md", + "parent": "widgets" + }, + { + "title": "Restoring the classic Widgets Editor", + "slug": "opting-out", + "markdown_source": "../docs/how-to-guides/widgets/opting-out.md", + "parent": "widgets" + }, + { + "title": "About the Legacy Widget block", + "slug": "legacy-widget-block", + "markdown_source": "../docs/how-to-guides/widgets/legacy-widget-block.md", + "parent": "widgets" + }, + { + "title": "Reference Guides", + "slug": "reference-guides", + "markdown_source": "../docs/reference-guides/README.md", + "parent": null + }, + { + "title": "Block API Reference", + "slug": "block-api", + "markdown_source": "../docs/reference-guides/block-api/README.md", + "parent": "reference-guides" + }, + { + "title": "Annotations", + "slug": "block-annotations", + "markdown_source": "../docs/reference-guides/block-api/block-annotations.md", + "parent": "block-api" + }, + { + "title": "API Versions", + "slug": "block-api-versions", + "markdown_source": "../docs/reference-guides/block-api/block-api-versions.md", + "parent": "block-api" + }, + { + "title": "Attributes", + "slug": "block-attributes", + "markdown_source": "../docs/reference-guides/block-api/block-attributes.md", + "parent": "block-api" + }, + { + "title": "Bindings", + "slug": "block-bindings", + "markdown_source": "../docs/reference-guides/block-api/block-bindings.md", + "parent": "block-api" + }, + { + "title": "Context", + "slug": "block-context", + "markdown_source": "../docs/reference-guides/block-api/block-context.md", + "parent": "block-api" + }, + { + "title": "Deprecation", + "slug": "block-deprecation", + "markdown_source": "../docs/reference-guides/block-api/block-deprecation.md", + "parent": "block-api" + }, + { + "title": "Edit and Save", + "slug": "block-edit-save", + "markdown_source": "../docs/reference-guides/block-api/block-edit-save.md", + "parent": "block-api" + }, + { + "title": "Metadata in block.json", + "slug": "block-metadata", + "markdown_source": "../docs/reference-guides/block-api/block-metadata.md", + "parent": "block-api" + }, + { + "title": "Patterns", + "slug": "block-patterns", + "markdown_source": "../docs/reference-guides/block-api/block-patterns.md", + "parent": "block-api" + }, + { + "title": "Registration", + "slug": "block-registration", + "markdown_source": "../docs/reference-guides/block-api/block-registration.md", + "parent": "block-api" + }, + { + "title": "Selectors", + "slug": "block-selectors", + "markdown_source": "../docs/reference-guides/block-api/block-selectors.md", + "parent": "block-api" + }, + { + "title": "Styles", + "slug": "block-styles", + "markdown_source": "../docs/reference-guides/block-api/block-styles.md", + "parent": "block-api" + }, + { + "title": "Supports", + "slug": "block-supports", + "markdown_source": "../docs/reference-guides/block-api/block-supports.md", + "parent": "block-api" + }, + { + "title": "Templates", + "slug": "block-templates", + "markdown_source": "../docs/reference-guides/block-api/block-templates.md", + "parent": "block-api" + }, + { + "title": "Transforms", + "slug": "block-transforms", + "markdown_source": "../docs/reference-guides/block-api/block-transforms.md", + "parent": "block-api" + }, + { + "title": "Variations", + "slug": "block-variations", + "markdown_source": "../docs/reference-guides/block-api/block-variations.md", + "parent": "block-api" + }, + { + "title": "Core Blocks Reference", + "slug": "core-blocks", + "markdown_source": "../docs/reference-guides/core-blocks.md", + "parent": "reference-guides" + }, + { + "title": "Hooks Reference", + "slug": "filters", + "markdown_source": "../docs/reference-guides/filters/README.md", + "parent": "reference-guides" + }, + { + "title": "Block Filters", + "slug": "block-filters", + "markdown_source": "../docs/reference-guides/filters/block-filters.md", + "parent": "filters" + }, + { + "title": "Editor Hooks", + "slug": "editor-filters", + "markdown_source": "../docs/reference-guides/filters/editor-filters.md", + "parent": "filters" + }, + { + "title": "i18n Filters", + "slug": "i18n-filters", + "markdown_source": "../docs/reference-guides/filters/i18n-filters.md", + "parent": "filters" + }, + { + "title": "Parser Filters", + "slug": "parser-filters", + "markdown_source": "../docs/reference-guides/filters/parser-filters.md", + "parent": "filters" + }, + { + "title": "Autocomplete", + "slug": "autocomplete-filters", + "markdown_source": "../docs/reference-guides/filters/autocomplete-filters.md", + "parent": "filters" + }, + { + "title": "Global Styles Filters", + "slug": "global-styles-filters", + "markdown_source": "../docs/reference-guides/filters/global-styles-filters.md", + "parent": "filters" + }, + { + "title": "Interactivity API Reference", + "slug": "interactivity-api", + "markdown_source": "../docs/reference-guides/interactivity-api/README.md", + "parent": "reference-guides" + }, + { + "title": "Core Concepts", + "slug": "core-concepts", + "markdown_source": "../docs/reference-guides/interactivity-api/core-concepts/README.md", + "parent": "interactivity-api" + }, + { + "title": "The Reactive and Declarative mindset", + "slug": "the-reactive-and-declarative-mindset", + "markdown_source": "../docs/reference-guides/interactivity-api/core-concepts/the-reactive-and-declarative-mindset.md", + "parent": "core-concepts" + }, + { + "title": "Understanding global state, local context and derived state", + "slug": "undestanding-global-state-local-context-and-derived-state", + "markdown_source": "../docs/reference-guides/interactivity-api/core-concepts/undestanding-global-state-local-context-and-derived-state.md", + "parent": "core-concepts" + }, + { + "title": "Server-side rendering: Processing directives on the server", + "slug": "server-side-rendering", + "markdown_source": "../docs/reference-guides/interactivity-api/core-concepts/server-side-rendering.md", + "parent": "core-concepts" + }, + { + "title": "Using TypeScript", + "slug": "using-typescript", + "markdown_source": "../docs/reference-guides/interactivity-api/core-concepts/using-typescript.md", + "parent": "core-concepts" + }, + { + "title": "Quick start guide", + "slug": "iapi-quick-start-guide", + "markdown_source": "../docs/reference-guides/interactivity-api/iapi-quick-start-guide.md", + "parent": "interactivity-api" + }, + { + "title": "API Reference", + "slug": "api-reference", + "markdown_source": "../docs/reference-guides/interactivity-api/api-reference.md", + "parent": "interactivity-api" + }, + { + "title": "About the Interactivity API", + "slug": "iapi-about", + "markdown_source": "../docs/reference-guides/interactivity-api/iapi-about.md", + "parent": "interactivity-api" + }, + { + "title": "Frequently Asked Questions", + "slug": "iapi-faq", + "markdown_source": "../docs/reference-guides/interactivity-api/iapi-faq.md", + "parent": "interactivity-api" + }, + { + "title": "SlotFills Reference", + "slug": "slotfills", + "markdown_source": "../docs/reference-guides/slotfills/README.md", + "parent": "reference-guides" + }, + { + "title": "MainDashboardButton", + "slug": "main-dashboard-button", + "markdown_source": "../docs/reference-guides/slotfills/main-dashboard-button.md", + "parent": "slotfills" + }, + { + "title": "PluginBlockSettingsMenuItem", + "slug": "plugin-block-settings-menu-item", + "markdown_source": "../docs/reference-guides/slotfills/plugin-block-settings-menu-item.md", + "parent": "slotfills" + }, + { + "title": "PluginDocumentSettingPanel", + "slug": "plugin-document-setting-panel", + "markdown_source": "../docs/reference-guides/slotfills/plugin-document-setting-panel.md", + "parent": "slotfills" + }, + { + "title": "PluginMoreMenuItem", + "slug": "plugin-more-menu-item", + "markdown_source": "../docs/reference-guides/slotfills/plugin-more-menu-item.md", + "parent": "slotfills" + }, + { + "title": "PluginPostPublishPanel", + "slug": "plugin-post-publish-panel", + "markdown_source": "../docs/reference-guides/slotfills/plugin-post-publish-panel.md", + "parent": "slotfills" + }, + { + "title": "PluginPostStatusInfo", + "slug": "plugin-post-status-info", + "markdown_source": "../docs/reference-guides/slotfills/plugin-post-status-info.md", + "parent": "slotfills" + }, + { + "title": "PluginPrePublishPanel", + "slug": "plugin-pre-publish-panel", + "markdown_source": "../docs/reference-guides/slotfills/plugin-pre-publish-panel.md", + "parent": "slotfills" + }, + { + "title": "PluginSidebar", + "slug": "plugin-sidebar", + "markdown_source": "../docs/reference-guides/slotfills/plugin-sidebar.md", + "parent": "slotfills" + }, + { + "title": "PluginSidebarMoreMenuItem", + "slug": "plugin-sidebar-more-menu-item", + "markdown_source": "../docs/reference-guides/slotfills/plugin-sidebar-more-menu-item.md", + "parent": "slotfills" + }, + { + "title": "RichText Reference", + "slug": "richtext", + "markdown_source": "../docs/reference-guides/richtext.md", + "parent": "reference-guides" + }, + { + "title": "Theme.json Reference", + "slug": "theme-json-reference", + "markdown_source": "../docs/reference-guides/theme-json-reference/README.md", + "parent": "reference-guides" + }, + { + "title": "Theme.json Version 3 Reference (latest)", + "slug": "theme-json-living", + "markdown_source": "../docs/reference-guides/theme-json-reference/theme-json-living.md", + "parent": "theme-json-reference" + }, + { + "title": "Theme.json Version 1 Reference", + "slug": "theme-json-v1", + "markdown_source": "../docs/reference-guides/theme-json-reference/theme-json-v1.md", + "parent": "theme-json-reference" + }, + { + "title": "Theme.json Version 2 Reference", + "slug": "theme-json-v2", + "markdown_source": "../docs/reference-guides/theme-json-reference/theme-json-v2.md", + "parent": "theme-json-reference" + }, + { + "title": "Migrating Theme.json to Newer Versions", + "slug": "theme-json-migrations", + "markdown_source": "../docs/reference-guides/theme-json-reference/theme-json-migrations.md", + "parent": "theme-json-reference" + }, + { + "title": "Available Styles Options", + "slug": "styles-versions", + "markdown_source": "../docs/reference-guides/theme-json-reference/styles-versions.md", + "parent": "theme-json-reference" + }, + { + "title": "Component Reference", + "slug": "components", + "markdown_source": "../packages/components/README.md", + "parent": "reference-guides" + }, + { + "title": "AlignmentMatrixControl", + "slug": "alignment-matrix-control", + "markdown_source": "../packages/components/src/alignment-matrix-control/README.md", + "parent": "components" + }, + { + "title": "AnglePickerControl", + "slug": "angle-picker-control", + "markdown_source": "../packages/components/src/angle-picker-control/README.md", + "parent": "components" + }, + { + "title": "Animate", + "slug": "animate", + "markdown_source": "../packages/components/src/animate/README.md", + "parent": "components" + }, + { + "title": "Autocomplete", + "slug": "autocomplete", + "markdown_source": "../packages/components/src/autocomplete/README.md", + "parent": "components" + }, + { + "title": "BaseControl", + "slug": "base-control", + "markdown_source": "../packages/components/src/base-control/README.md", + "parent": "components" + }, + { + "title": "BorderBoxControl", + "slug": "border-box-control", + "markdown_source": "../packages/components/src/border-box-control/border-box-control/README.md", + "parent": "components" + }, + { + "title": "BorderControl", + "slug": "border-control", + "markdown_source": "../packages/components/src/border-control/border-control/README.md", + "parent": "components" + }, + { + "title": "BoxControl", + "slug": "box-control", + "markdown_source": "../packages/components/src/box-control/README.md", + "parent": "components" + }, + { + "title": "ButtonGroup", + "slug": "button-group", + "markdown_source": "../packages/components/src/button-group/README.md", + "parent": "components" + }, + { + "title": "Button", + "slug": "button", + "markdown_source": "../packages/components/src/button/README.md", + "parent": "components" + }, + { + "title": "DateCalendar", + "slug": "date-calendar", + "markdown_source": "../packages/components/src/calendar/date-calendar/README.md", + "parent": "components" + }, + { + "title": "DateRangeCalendar", + "slug": "date-range-calendar", + "markdown_source": "../packages/components/src/calendar/date-range-calendar/README.md", + "parent": "components" + }, + { + "title": "CardBody", + "slug": "card-body", + "markdown_source": "../packages/components/src/card/card-body/README.md", + "parent": "components" + }, + { + "title": "CardDivider", + "slug": "card-divider", + "markdown_source": "../packages/components/src/card/card-divider/README.md", + "parent": "components" + }, + { + "title": "CardFooter", + "slug": "card-footer", + "markdown_source": "../packages/components/src/card/card-footer/README.md", + "parent": "components" + }, + { + "title": "CardHeader", + "slug": "card-header", + "markdown_source": "../packages/components/src/card/card-header/README.md", + "parent": "components" + }, + { + "title": "CardMedia", + "slug": "card-media", + "markdown_source": "../packages/components/src/card/card-media/README.md", + "parent": "components" + }, + { + "title": "Card", + "slug": "card", + "markdown_source": "../packages/components/src/card/card/README.md", + "parent": "components" + }, + { + "title": "CheckboxControl", + "slug": "checkbox-control", + "markdown_source": "../packages/components/src/checkbox-control/README.md", + "parent": "components" + }, + { + "title": "CircularOptionPicker", + "slug": "circular-option-picker", + "markdown_source": "../packages/components/src/circular-option-picker/README.md", + "parent": "components" + }, + { + "title": "ClipboardButton", + "slug": "clipboard-button", + "markdown_source": "../packages/components/src/clipboard-button/README.md", + "parent": "components" + }, + { + "title": "ColorIndicator", + "slug": "color-indicator", + "markdown_source": "../packages/components/src/color-indicator/README.md", + "parent": "components" + }, + { + "title": "ColorPalette", + "slug": "color-palette", + "markdown_source": "../packages/components/src/color-palette/README.md", + "parent": "components" + }, + { + "title": "ColorPicker", + "slug": "color-picker", + "markdown_source": "../packages/components/src/color-picker/README.md", + "parent": "components" + }, + { + "title": "ComboboxControl", + "slug": "combobox-control", + "markdown_source": "../packages/components/src/combobox-control/README.md", + "parent": "components" + }, + { + "title": "Composite", + "slug": "composite", + "markdown_source": "../packages/components/src/composite/README.md", + "parent": "components" + }, + { + "title": "ConfirmDialog", + "slug": "confirm-dialog", + "markdown_source": "../packages/components/src/confirm-dialog/README.md", + "parent": "components" + }, + { + "title": "CustomSelectControl", + "slug": "custom-select-control", + "markdown_source": "../packages/components/src/custom-select-control/README.md", + "parent": "components" + }, + { + "title": "Dashicon", + "slug": "dashicon", + "markdown_source": "../packages/components/src/dashicon/README.md", + "parent": "components" + }, + { + "title": "DateTime", + "slug": "date-time", + "markdown_source": "../packages/components/src/date-time/README.md", + "parent": "components" + }, + { + "title": "DimensionControl", + "slug": "dimension-control", + "markdown_source": "../packages/components/src/dimension-control/README.md", + "parent": "components" + }, + { + "title": "Disabled", + "slug": "disabled", + "markdown_source": "../packages/components/src/disabled/README.md", + "parent": "components" + }, + { + "title": "Divider", + "slug": "divider", + "markdown_source": "../packages/components/src/divider/README.md", + "parent": "components" + }, + { + "title": "Draggable", + "slug": "draggable", + "markdown_source": "../packages/components/src/draggable/README.md", + "parent": "components" + }, + { + "title": "DropZone", + "slug": "drop-zone", + "markdown_source": "../packages/components/src/drop-zone/README.md", + "parent": "components" + }, + { + "title": "DropdownMenu", + "slug": "dropdown-menu", + "markdown_source": "../packages/components/src/dropdown-menu/README.md", + "parent": "components" + }, + { + "title": "Dropdown", + "slug": "dropdown", + "markdown_source": "../packages/components/src/dropdown/README.md", + "parent": "components" + }, + { + "title": "DuotonePicker", + "slug": "duotone-picker", + "markdown_source": "../packages/components/src/duotone-picker/README.md", + "parent": "components" + }, + { + "title": "Elevation", + "slug": "elevation", + "markdown_source": "../packages/components/src/elevation/README.md", + "parent": "components" + }, + { + "title": "ExternalLink", + "slug": "external-link", + "markdown_source": "../packages/components/src/external-link/README.md", + "parent": "components" + }, + { + "title": "FlexBlock", + "slug": "flex-block", + "markdown_source": "../packages/components/src/flex/flex-block/README.md", + "parent": "components" + }, + { + "title": "FlexItem", + "slug": "flex-item", + "markdown_source": "../packages/components/src/flex/flex-item/README.md", + "parent": "components" + }, + { + "title": "Flex", + "slug": "flex", + "markdown_source": "../packages/components/src/flex/flex/README.md", + "parent": "components" + }, + { + "title": "FocalPointPicker", + "slug": "focal-point-picker", + "markdown_source": "../packages/components/src/focal-point-picker/README.md", + "parent": "components" + }, + { + "title": "FocusableIframe", + "slug": "focusable-iframe", + "markdown_source": "../packages/components/src/focusable-iframe/README.md", + "parent": "components" + }, + { + "title": "FontSizePicker", + "slug": "font-size-picker", + "markdown_source": "../packages/components/src/font-size-picker/README.md", + "parent": "components" + }, + { + "title": "FormFileUpload", + "slug": "form-file-upload", + "markdown_source": "../packages/components/src/form-file-upload/README.md", + "parent": "components" + }, + { + "title": "FormToggle", + "slug": "form-toggle", + "markdown_source": "../packages/components/src/form-toggle/README.md", + "parent": "components" + }, + { + "title": "FormTokenField", + "slug": "form-token-field", + "markdown_source": "../packages/components/src/form-token-field/README.md", + "parent": "components" + }, + { + "title": "GradientPicker", + "slug": "gradient-picker", + "markdown_source": "../packages/components/src/gradient-picker/README.md", + "parent": "components" + }, + { + "title": "Grid", + "slug": "grid", + "markdown_source": "../packages/components/src/grid/README.md", + "parent": "components" + }, + { + "title": "Guide", + "slug": "guide", + "markdown_source": "../packages/components/src/guide/README.md", + "parent": "components" + }, + { + "title": "HStack", + "slug": "h-stack", + "markdown_source": "../packages/components/src/h-stack/README.md", + "parent": "components" + }, + { + "title": "Heading", + "slug": "heading", + "markdown_source": "../packages/components/src/heading/README.md", + "parent": "components" + }, + { + "title": "NavigateRegions", + "slug": "navigate-regions", + "markdown_source": "../packages/components/src/higher-order/navigate-regions/README.md", + "parent": "components" + }, + { + "title": "HigherOrder", + "slug": "higher-order", + "markdown_source": "../packages/components/src/higher-order/README.md", + "parent": "components" + }, + { + "title": "WithConstrainedTabbing", + "slug": "with-constrained-tabbing", + "markdown_source": "../packages/components/src/higher-order/with-constrained-tabbing/README.md", + "parent": "components" + }, + { + "title": "WithFallbackStyles", + "slug": "with-fallback-styles", + "markdown_source": "../packages/components/src/higher-order/with-fallback-styles/README.md", + "parent": "components" + }, + { + "title": "WithFilters", + "slug": "with-filters", + "markdown_source": "../packages/components/src/higher-order/with-filters/README.md", + "parent": "components" + }, + { + "title": "WithFocusOutside", + "slug": "with-focus-outside", + "markdown_source": "../packages/components/src/higher-order/with-focus-outside/README.md", + "parent": "components" + }, + { + "title": "WithFocusReturn", + "slug": "with-focus-return", + "markdown_source": "../packages/components/src/higher-order/with-focus-return/README.md", + "parent": "components" + }, + { + "title": "WithNotices", + "slug": "with-notices", + "markdown_source": "../packages/components/src/higher-order/with-notices/README.md", + "parent": "components" + }, + { + "title": "WithSpokenMessages", + "slug": "with-spoken-messages", + "markdown_source": "../packages/components/src/higher-order/with-spoken-messages/README.md", + "parent": "components" + }, + { + "title": "Icon", + "slug": "icon", + "markdown_source": "../packages/components/src/icon/README.md", + "parent": "components" + }, + { + "title": "InputControl", + "slug": "input-control", + "markdown_source": "../packages/components/src/input-control/README.md", + "parent": "components" + }, + { + "title": "IsolatedEventContainer", + "slug": "isolated-event-container", + "markdown_source": "../packages/components/src/isolated-event-container/README.md", + "parent": "components" + }, + { + "title": "ItemGroup", + "slug": "item-group", + "markdown_source": "../packages/components/src/item-group/item-group/README.md", + "parent": "components" + }, + { + "title": "Item", + "slug": "item", + "markdown_source": "../packages/components/src/item-group/item/README.md", + "parent": "components" + }, + { + "title": "KeyboardShortcuts", + "slug": "keyboard-shortcuts", + "markdown_source": "../packages/components/src/keyboard-shortcuts/README.md", + "parent": "components" + }, + { + "title": "MenuGroup", + "slug": "menu-group", + "markdown_source": "../packages/components/src/menu-group/README.md", + "parent": "components" + }, + { + "title": "MenuItem", + "slug": "menu-item", + "markdown_source": "../packages/components/src/menu-item/README.md", + "parent": "components" + }, + { + "title": "MenuItemsChoice", + "slug": "menu-items-choice", + "markdown_source": "../packages/components/src/menu-items-choice/README.md", + "parent": "components" + }, + { + "title": "Modal", + "slug": "modal", + "markdown_source": "../packages/components/src/modal/README.md", + "parent": "components" + }, + { + "title": "NavigableContainer", + "slug": "navigable-container", + "markdown_source": "../packages/components/src/navigable-container/README.md", + "parent": "components" + }, + { + "title": "Navigation", + "slug": "navigation", + "markdown_source": "../packages/components/src/navigation/README.md", + "parent": "components" + }, + { + "title": "Navigator", + "slug": "navigator", + "markdown_source": "../packages/components/src/navigator/README.md", + "parent": "components" + }, + { + "title": "Notice", + "slug": "notice", + "markdown_source": "../packages/components/src/notice/README.md", + "parent": "components" + }, + { + "title": "NumberControl", + "slug": "number-control", + "markdown_source": "../packages/components/src/number-control/README.md", + "parent": "components" + }, + { + "title": "Panel", + "slug": "panel", + "markdown_source": "../packages/components/src/panel/README.md", + "parent": "components" + }, + { + "title": "Placeholder", + "slug": "placeholder", + "markdown_source": "../packages/components/src/placeholder/README.md", + "parent": "components" + }, + { + "title": "Popover", + "slug": "popover", + "markdown_source": "../packages/components/src/popover/README.md", + "parent": "components" + }, + { + "title": "ProgressBar", + "slug": "progress-bar", + "markdown_source": "../packages/components/src/progress-bar/README.md", + "parent": "components" + }, + { + "title": "QueryControls", + "slug": "query-controls", + "markdown_source": "../packages/components/src/query-controls/README.md", + "parent": "components" + }, + { + "title": "RadioControl", + "slug": "radio-control", + "markdown_source": "../packages/components/src/radio-control/README.md", + "parent": "components" + }, + { + "title": "RadioGroup", + "slug": "radio-group", + "markdown_source": "../packages/components/src/radio-group/README.md", + "parent": "components" + }, + { + "title": "RangeControl", + "slug": "range-control", + "markdown_source": "../packages/components/src/range-control/README.md", + "parent": "components" + }, + { + "title": "ResizableBox", + "slug": "resizable-box", + "markdown_source": "../packages/components/src/resizable-box/README.md", + "parent": "components" + }, + { + "title": "ResizeTooltip", + "slug": "resize-tooltip", + "markdown_source": "../packages/components/src/resizable-box/resize-tooltip/README.md", + "parent": "components" + }, + { + "title": "ResponsiveWrapper", + "slug": "responsive-wrapper", + "markdown_source": "../packages/components/src/responsive-wrapper/README.md", + "parent": "components" + }, + { + "title": "Sandbox", + "slug": "sandbox", + "markdown_source": "../packages/components/src/sandbox/README.md", + "parent": "components" + }, + { + "title": "ScrollLock", + "slug": "scroll-lock", + "markdown_source": "../packages/components/src/scroll-lock/README.md", + "parent": "components" + }, + { + "title": "Scrollable", + "slug": "scrollable", + "markdown_source": "../packages/components/src/scrollable/README.md", + "parent": "components" + }, + { + "title": "SearchControl", + "slug": "search-control", + "markdown_source": "../packages/components/src/search-control/README.md", + "parent": "components" + }, + { + "title": "SelectControl", + "slug": "select-control", + "markdown_source": "../packages/components/src/select-control/README.md", + "parent": "components" + }, + { + "title": "SlotFill", + "slug": "slot-fill", + "markdown_source": "../packages/components/src/slot-fill/README.md", + "parent": "components" + }, + { + "title": "Snackbar", + "slug": "snackbar", + "markdown_source": "../packages/components/src/snackbar/README.md", + "parent": "components" + }, + { + "title": "Spacer", + "slug": "spacer", + "markdown_source": "../packages/components/src/spacer/README.md", + "parent": "components" + }, + { + "title": "Spinner", + "slug": "spinner", + "markdown_source": "../packages/components/src/spinner/README.md", + "parent": "components" + }, + { + "title": "Surface", + "slug": "surface", + "markdown_source": "../packages/components/src/surface/README.md", + "parent": "components" + }, + { + "title": "TabPanel", + "slug": "tab-panel", + "markdown_source": "../packages/components/src/tab-panel/README.md", + "parent": "components" + }, + { + "title": "TextControl", + "slug": "text-control", + "markdown_source": "../packages/components/src/text-control/README.md", + "parent": "components" + }, + { + "title": "TextHighlight", + "slug": "text-highlight", + "markdown_source": "../packages/components/src/text-highlight/README.md", + "parent": "components" + }, + { + "title": "Text", + "slug": "text", + "markdown_source": "../packages/components/src/text/README.md", + "parent": "components" + }, + { + "title": "TextareaControl", + "slug": "textarea-control", + "markdown_source": "../packages/components/src/textarea-control/README.md", + "parent": "components" + }, + { + "title": "ToggleControl", + "slug": "toggle-control", + "markdown_source": "../packages/components/src/toggle-control/README.md", + "parent": "components" + }, + { + "title": "ToggleGroupControlOptionBase", + "slug": "toggle-group-control-option-base", + "markdown_source": "../packages/components/src/toggle-group-control/toggle-group-control-option-base/README.md", + "parent": "components" + }, + { + "title": "ToggleGroupControlOptionIcon", + "slug": "toggle-group-control-option-icon", + "markdown_source": "../packages/components/src/toggle-group-control/toggle-group-control-option-icon/README.md", + "parent": "components" + }, + { + "title": "ToggleGroupControlOption", + "slug": "toggle-group-control-option", + "markdown_source": "../packages/components/src/toggle-group-control/toggle-group-control-option/README.md", + "parent": "components" + }, + { + "title": "ToggleGroupControl", + "slug": "toggle-group-control", + "markdown_source": "../packages/components/src/toggle-group-control/toggle-group-control/README.md", + "parent": "components" + }, + { + "title": "ToolbarButton", + "slug": "toolbar-button", + "markdown_source": "../packages/components/src/toolbar/toolbar-button/README.md", + "parent": "components" + }, + { + "title": "ToolbarDropdownMenu", + "slug": "toolbar-dropdown-menu", + "markdown_source": "../packages/components/src/toolbar/toolbar-dropdown-menu/README.md", + "parent": "components" + }, + { + "title": "ToolbarGroup", + "slug": "toolbar-group", + "markdown_source": "../packages/components/src/toolbar/toolbar-group/README.md", + "parent": "components" + }, + { + "title": "ToolbarItem", + "slug": "toolbar-item", + "markdown_source": "../packages/components/src/toolbar/toolbar-item/README.md", + "parent": "components" + }, + { + "title": "Toolbar", + "slug": "toolbar", + "markdown_source": "../packages/components/src/toolbar/toolbar/README.md", + "parent": "components" + }, + { + "title": "ToolsPanelHeader", + "slug": "tools-panel-header", + "markdown_source": "../packages/components/src/tools-panel/tools-panel-header/README.md", + "parent": "components" + }, + { + "title": "ToolsPanelItem", + "slug": "tools-panel-item", + "markdown_source": "../packages/components/src/tools-panel/tools-panel-item/README.md", + "parent": "components" + }, + { + "title": "ToolsPanel", + "slug": "tools-panel", + "markdown_source": "../packages/components/src/tools-panel/tools-panel/README.md", + "parent": "components" + }, + { + "title": "Tooltip", + "slug": "tooltip", + "markdown_source": "../packages/components/src/tooltip/README.md", + "parent": "components" + }, + { + "title": "TreeGrid", + "slug": "tree-grid", + "markdown_source": "../packages/components/src/tree-grid/README.md", + "parent": "components" + }, + { + "title": "TreeSelect", + "slug": "tree-select", + "markdown_source": "../packages/components/src/tree-select/README.md", + "parent": "components" + }, + { + "title": "Truncate", + "slug": "truncate", + "markdown_source": "../packages/components/src/truncate/README.md", + "parent": "components" + }, + { + "title": "UnitControl", + "slug": "unit-control", + "markdown_source": "../packages/components/src/unit-control/README.md", + "parent": "components" + }, + { + "title": "VStack", + "slug": "v-stack", + "markdown_source": "../packages/components/src/v-stack/README.md", + "parent": "components" + }, + { + "title": "VisuallyHidden", + "slug": "visually-hidden", + "markdown_source": "../packages/components/src/visually-hidden/README.md", + "parent": "components" + }, + { + "title": "ZStack", + "slug": "z-stack", + "markdown_source": "../packages/components/src/z-stack/README.md", + "parent": "components" + }, + { + "title": "Package Reference", + "slug": "packages", + "markdown_source": "../docs/reference-guides/packages.md", + "parent": "reference-guides" + }, + { + "title": "@wordpress/a11y", + "slug": "packages-a11y", + "markdown_source": "../packages/a11y/README.md", + "parent": "packages" + }, + { + "title": "@wordpress/admin-ui", + "slug": "packages-admin-ui", + "markdown_source": "../packages/admin-ui/README.md", + "parent": "packages" + }, + { + "title": "@wordpress/annotations", + "slug": "packages-annotations", + "markdown_source": "../packages/annotations/README.md", + "parent": "packages" + }, + { + "title": "@wordpress/api-fetch", + "slug": "packages-api-fetch", + "markdown_source": "../packages/api-fetch/README.md", + "parent": "packages" + }, + { + "title": "@wordpress/autop", + "slug": "packages-autop", + "markdown_source": "../packages/autop/README.md", + "parent": "packages" + }, + { + "title": "@wordpress/babel-plugin-import-jsx-pragma", + "slug": "packages-babel-plugin-import-jsx-pragma", + "markdown_source": "../packages/babel-plugin-import-jsx-pragma/README.md", + "parent": "packages" + }, + { + "title": "@wordpress/babel-plugin-makepot", + "slug": "packages-babel-plugin-makepot", + "markdown_source": "../packages/babel-plugin-makepot/README.md", + "parent": "packages" + }, + { + "title": "@wordpress/babel-preset-default", + "slug": "packages-babel-preset-default", + "markdown_source": "../packages/babel-preset-default/README.md", + "parent": "packages" + }, + { + "title": "@wordpress/base-styles", + "slug": "packages-base-styles", + "markdown_source": "../packages/base-styles/README.md", + "parent": "packages" + }, + { + "title": "@wordpress/blob", + "slug": "packages-blob", + "markdown_source": "../packages/blob/README.md", + "parent": "packages" + }, + { + "title": "@wordpress/block-directory", + "slug": "packages-block-directory", + "markdown_source": "../packages/block-directory/README.md", + "parent": "packages" + }, + { + "title": "@wordpress/block-editor", + "slug": "packages-block-editor", + "markdown_source": "../packages/block-editor/README.md", + "parent": "packages" + }, + { + "title": "@wordpress/block-library", + "slug": "packages-block-library", + "markdown_source": "../packages/block-library/README.md", + "parent": "packages" + }, + { + "title": "@wordpress/block-serialization-default-parser", + "slug": "packages-block-serialization-default-parser", + "markdown_source": "../packages/block-serialization-default-parser/README.md", + "parent": "packages" + }, + { + "title": "@wordpress/block-serialization-spec-parser", + "slug": "packages-block-serialization-spec-parser", + "markdown_source": "../packages/block-serialization-spec-parser/README.md", + "parent": "packages" + }, + { + "title": "@wordpress/blocks", + "slug": "packages-blocks", + "markdown_source": "../packages/blocks/README.md", + "parent": "packages" + }, + { + "title": "@wordpress/browserslist-config", + "slug": "packages-browserslist-config", + "markdown_source": "../packages/browserslist-config/README.md", + "parent": "packages" + }, + { + "title": "@wordpress/commands", + "slug": "packages-commands", + "markdown_source": "../packages/commands/README.md", + "parent": "packages" + }, + { + "title": "@wordpress/components", + "slug": "packages-components", + "markdown_source": "../packages/components/README.md", + "parent": "packages" + }, + { + "title": "@wordpress/compose", + "slug": "packages-compose", + "markdown_source": "../packages/compose/README.md", + "parent": "packages" + }, + { + "title": "@wordpress/core-commands", + "slug": "packages-core-commands", + "markdown_source": "../packages/core-commands/README.md", + "parent": "packages" + }, + { + "title": "@wordpress/core-data", + "slug": "packages-core-data", + "markdown_source": "../packages/core-data/README.md", + "parent": "packages" + }, + { + "title": "@wordpress/create-block-interactive-template", + "slug": "packages-create-block-interactive-template", + "markdown_source": "../packages/create-block-interactive-template/README.md", + "parent": "packages" + }, + { + "title": "@wordpress/create-block-tutorial-template", + "slug": "packages-create-block-tutorial-template", + "markdown_source": "../packages/create-block-tutorial-template/README.md", + "parent": "packages" + }, + { + "title": "@wordpress/create-block", + "slug": "packages-create-block", + "markdown_source": "../packages/create-block/README.md", + "parent": "packages" + }, + { + "title": "External Project Templates", + "slug": "packages-create-block-external-template", + "markdown_source": "../packages/create-block/docs/external-template.md", + "parent": "packages-create-block" + }, + { + "title": "@wordpress/customize-widgets", + "slug": "packages-customize-widgets", + "markdown_source": "../packages/customize-widgets/README.md", + "parent": "packages" + }, + { + "title": "@wordpress/data-controls", + "slug": "packages-data-controls", + "markdown_source": "../packages/data-controls/README.md", + "parent": "packages" + }, + { + "title": "@wordpress/data", + "slug": "packages-data", + "markdown_source": "../packages/data/README.md", + "parent": "packages" + }, + { + "title": "@wordpress/dataviews", + "slug": "packages-dataviews", + "markdown_source": "../packages/dataviews/README.md", + "parent": "packages" + }, + { + "title": "@wordpress/date", + "slug": "packages-date", + "markdown_source": "../packages/date/README.md", + "parent": "packages" + }, + { + "title": "@wordpress/dependency-extraction-webpack-plugin", + "slug": "packages-dependency-extraction-webpack-plugin", + "markdown_source": "../packages/dependency-extraction-webpack-plugin/README.md", + "parent": "packages" + }, + { + "title": "@wordpress/deprecated", + "slug": "packages-deprecated", + "markdown_source": "../packages/deprecated/README.md", + "parent": "packages" + }, + { + "title": "@wordpress/docgen", + "slug": "packages-docgen", + "markdown_source": "../packages/docgen/README.md", + "parent": "packages" + }, + { + "title": "@wordpress/dom-ready", + "slug": "packages-dom-ready", + "markdown_source": "../packages/dom-ready/README.md", + "parent": "packages" + }, + { + "title": "@wordpress/dom", + "slug": "packages-dom", + "markdown_source": "../packages/dom/README.md", + "parent": "packages" + }, + { + "title": "@wordpress/e2e-test-utils-playwright", + "slug": "packages-e2e-test-utils-playwright", + "markdown_source": "../packages/e2e-test-utils-playwright/README.md", + "parent": "packages" + }, + { + "title": "@wordpress/e2e-test-utils", + "slug": "packages-e2e-test-utils", + "markdown_source": "../packages/e2e-test-utils/README.md", + "parent": "packages" + }, + { + "title": "@wordpress/e2e-tests", + "slug": "packages-e2e-tests", + "markdown_source": "../packages/e2e-tests/README.md", + "parent": "packages" + }, + { + "title": "@wordpress/edit-post", + "slug": "packages-edit-post", + "markdown_source": "../packages/edit-post/README.md", + "parent": "packages" + }, + { + "title": "@wordpress/edit-site", + "slug": "packages-edit-site", + "markdown_source": "../packages/edit-site/README.md", + "parent": "packages" + }, + { + "title": "@wordpress/edit-widgets", + "slug": "packages-edit-widgets", + "markdown_source": "../packages/edit-widgets/README.md", + "parent": "packages" + }, + { + "title": "@wordpress/editor", + "slug": "packages-editor", + "markdown_source": "../packages/editor/README.md", + "parent": "packages" + }, + { + "title": "@wordpress/element", + "slug": "packages-element", + "markdown_source": "../packages/element/README.md", + "parent": "packages" + }, + { + "title": "@wordpress/env", + "slug": "packages-env", + "markdown_source": "../packages/env/README.md", + "parent": "packages" + }, + { + "title": "@wordpress/escape-html", + "slug": "packages-escape-html", + "markdown_source": "../packages/escape-html/README.md", + "parent": "packages" + }, + { + "title": "@wordpress/eslint-plugin", + "slug": "packages-eslint-plugin", + "markdown_source": "../packages/eslint-plugin/README.md", + "parent": "packages" + }, + { + "title": "@wordpress/fields", + "slug": "packages-fields", + "markdown_source": "../packages/fields/README.md", + "parent": "packages" + }, + { + "title": "@wordpress/format-library", + "slug": "packages-format-library", + "markdown_source": "../packages/format-library/README.md", + "parent": "packages" + }, + { + "title": "@wordpress/hooks", + "slug": "packages-hooks", + "markdown_source": "../packages/hooks/README.md", + "parent": "packages" + }, + { + "title": "@wordpress/html-entities", + "slug": "packages-html-entities", + "markdown_source": "../packages/html-entities/README.md", + "parent": "packages" + }, + { + "title": "@wordpress/i18n", + "slug": "packages-i18n", + "markdown_source": "../packages/i18n/README.md", + "parent": "packages" + }, + { + "title": "@wordpress/icons", + "slug": "packages-icons", + "markdown_source": "../packages/icons/README.md", + "parent": "packages" + }, + { + "title": "@wordpress/interactivity-router", + "slug": "packages-interactivity-router", + "markdown_source": "../packages/interactivity-router/README.md", + "parent": "packages" + }, + { + "title": "@wordpress/interactivity", + "slug": "packages-interactivity", + "markdown_source": "../packages/interactivity/README.md", + "parent": "packages" + }, + { + "title": "@wordpress/interface", + "slug": "packages-interface", + "markdown_source": "../packages/interface/README.md", + "parent": "packages" + }, + { + "title": "@wordpress/is-shallow-equal", + "slug": "packages-is-shallow-equal", + "markdown_source": "../packages/is-shallow-equal/README.md", + "parent": "packages" + }, + { + "title": "@wordpress/jest-console", + "slug": "packages-jest-console", + "markdown_source": "../packages/jest-console/README.md", + "parent": "packages" + }, + { + "title": "@wordpress/jest-preset-default", + "slug": "packages-jest-preset-default", + "markdown_source": "../packages/jest-preset-default/README.md", + "parent": "packages" + }, + { + "title": "@wordpress/jest-puppeteer-axe", + "slug": "packages-jest-puppeteer-axe", + "markdown_source": "../packages/jest-puppeteer-axe/README.md", + "parent": "packages" + }, + { + "title": "@wordpress/keyboard-shortcuts", + "slug": "packages-keyboard-shortcuts", + "markdown_source": "../packages/keyboard-shortcuts/README.md", + "parent": "packages" + }, + { + "title": "@wordpress/keycodes", + "slug": "packages-keycodes", + "markdown_source": "../packages/keycodes/README.md", + "parent": "packages" + }, + { + "title": "@wordpress/latex-to-mathml", + "slug": "packages-latex-to-mathml", + "markdown_source": "../packages/latex-to-mathml/README.md", + "parent": "packages" + }, + { + "title": "@wordpress/lazy-import", + "slug": "packages-lazy-import", + "markdown_source": "../packages/lazy-import/README.md", + "parent": "packages" + }, + { + "title": "@wordpress/list-reusable-blocks", + "slug": "packages-list-reusable-blocks", + "markdown_source": "../packages/list-reusable-blocks/README.md", + "parent": "packages" + }, + { + "title": "@wordpress/media-utils", + "slug": "packages-media-utils", + "markdown_source": "../packages/media-utils/README.md", + "parent": "packages" + }, + { + "title": "@wordpress/notices", + "slug": "packages-notices", + "markdown_source": "../packages/notices/README.md", + "parent": "packages" + }, + { + "title": "@wordpress/npm-package-json-lint-config", + "slug": "packages-npm-package-json-lint-config", + "markdown_source": "../packages/npm-package-json-lint-config/README.md", + "parent": "packages" + }, + { + "title": "@wordpress/nux", + "slug": "packages-nux", + "markdown_source": "../packages/nux/README.md", + "parent": "packages" + }, + { + "title": "@wordpress/patterns", + "slug": "packages-patterns", + "markdown_source": "../packages/patterns/README.md", + "parent": "packages" + }, + { + "title": "@wordpress/plugins", + "slug": "packages-plugins", + "markdown_source": "../packages/plugins/README.md", + "parent": "packages" + }, + { + "title": "@wordpress/postcss-plugins-preset", + "slug": "packages-postcss-plugins-preset", + "markdown_source": "../packages/postcss-plugins-preset/README.md", + "parent": "packages" + }, + { + "title": "@wordpress/postcss-themes", + "slug": "packages-postcss-themes", + "markdown_source": "../packages/postcss-themes/README.md", + "parent": "packages" + }, + { + "title": "@wordpress/preferences-persistence", + "slug": "packages-preferences-persistence", + "markdown_source": "../packages/preferences-persistence/README.md", + "parent": "packages" + }, + { + "title": "@wordpress/preferences", + "slug": "packages-preferences", + "markdown_source": "../packages/preferences/README.md", + "parent": "packages" + }, + { + "title": "@wordpress/prettier-config", + "slug": "packages-prettier-config", + "markdown_source": "../packages/prettier-config/README.md", + "parent": "packages" + }, + { + "title": "@wordpress/primitives", + "slug": "packages-primitives", + "markdown_source": "../packages/primitives/README.md", + "parent": "packages" + }, + { + "title": "@wordpress/priority-queue", + "slug": "packages-priority-queue", + "markdown_source": "../packages/priority-queue/README.md", + "parent": "packages" + }, + { + "title": "@wordpress/private-apis", + "slug": "packages-private-apis", + "markdown_source": "../packages/private-apis/README.md", + "parent": "packages" + }, + { + "title": "@wordpress/project-management-automation", + "slug": "packages-project-management-automation", + "markdown_source": "../packages/project-management-automation/README.md", + "parent": "packages" + }, + { + "title": "@wordpress/react-i18n", + "slug": "packages-react-i18n", + "markdown_source": "../packages/react-i18n/README.md", + "parent": "packages" + }, + { + "title": "@wordpress/readable-js-assets-webpack-plugin", + "slug": "packages-readable-js-assets-webpack-plugin", + "markdown_source": "../packages/readable-js-assets-webpack-plugin/README.md", + "parent": "packages" + }, + { + "title": "@wordpress/redux-routine", + "slug": "packages-redux-routine", + "markdown_source": "../packages/redux-routine/README.md", + "parent": "packages" + }, + { + "title": "@wordpress/reusable-blocks", + "slug": "packages-reusable-blocks", + "markdown_source": "../packages/reusable-blocks/README.md", + "parent": "packages" + }, + { + "title": "@wordpress/rich-text", + "slug": "packages-rich-text", + "markdown_source": "../packages/rich-text/README.md", + "parent": "packages" + }, + { + "title": "@wordpress/router", + "slug": "packages-router", + "markdown_source": "../packages/router/README.md", + "parent": "packages" + }, + { + "title": "@wordpress/scripts", + "slug": "packages-scripts", + "markdown_source": "../packages/scripts/README.md", + "parent": "packages" + }, + { + "title": "@wordpress/server-side-render", + "slug": "packages-server-side-render", + "markdown_source": "../packages/server-side-render/README.md", + "parent": "packages" + }, + { + "title": "@wordpress/shortcode", + "slug": "packages-shortcode", + "markdown_source": "../packages/shortcode/README.md", + "parent": "packages" + }, + { + "title": "@wordpress/style-engine", + "slug": "packages-style-engine", + "markdown_source": "../packages/style-engine/README.md", + "parent": "packages" + }, + { + "title": "@wordpress/style-engine Using the Style Engine to generate block supports styles", + "slug": "using-the-style-engine-with-block-supports", + "markdown_source": "../packages/style-engine/docs/using-the-style-engine-with-block-supports.md", + "parent": "packages-style-engine" + }, + { + "title": "@wordpress/stylelint-config", + "slug": "packages-stylelint-config", + "markdown_source": "../packages/stylelint-config/README.md", + "parent": "packages" + }, + { + "title": "@wordpress/sync", + "slug": "packages-sync", + "markdown_source": "../packages/sync/README.md", + "parent": "packages" + }, + { + "title": "@wordpress/token-list", + "slug": "packages-token-list", + "markdown_source": "../packages/token-list/README.md", + "parent": "packages" + }, + { + "title": "@wordpress/undo-manager", + "slug": "packages-undo-manager", + "markdown_source": "../packages/undo-manager/README.md", + "parent": "packages" + }, + { + "title": "@wordpress/upload-media", + "slug": "packages-upload-media", + "markdown_source": "../packages/upload-media/README.md", + "parent": "packages" + }, + { + "title": "@wordpress/url", + "slug": "packages-url", + "markdown_source": "../packages/url/README.md", + "parent": "packages" + }, + { + "title": "@wordpress/viewport", + "slug": "packages-viewport", + "markdown_source": "../packages/viewport/README.md", + "parent": "packages" + }, + { + "title": "@wordpress/views", + "slug": "packages-views", + "markdown_source": "../packages/views/README.md", + "parent": "packages" + }, + { + "title": "@wordpress/warning", + "slug": "packages-warning", + "markdown_source": "../packages/warning/README.md", + "parent": "packages" + }, + { + "title": "@wordpress/widgets", + "slug": "packages-widgets", + "markdown_source": "../packages/widgets/README.md", + "parent": "packages" + }, + { + "title": "@wordpress/wordcount", + "slug": "packages-wordcount", + "markdown_source": "../packages/wordcount/README.md", + "parent": "packages" + }, + { + "title": "Data Module Reference", + "slug": "data", + "markdown_source": "../docs/reference-guides/data/README.md", + "parent": "reference-guides" + }, + { + "title": "WordPress Core Data", + "slug": "data-core", + "markdown_source": "../docs/reference-guides/data/data-core.md", + "parent": "data" + }, + { + "title": "Annotations", + "slug": "data-core-annotations", + "markdown_source": "../docs/reference-guides/data/data-core-annotations.md", + "parent": "data" + }, + { + "title": "Block directory", + "slug": "data-core-block-directory", + "markdown_source": "../docs/reference-guides/data/data-core-block-directory.md", + "parent": "data" + }, + { + "title": "The Block Editor’s Data", + "slug": "data-core-block-editor", + "markdown_source": "../docs/reference-guides/data/data-core-block-editor.md", + "parent": "data" + }, + { + "title": "Block Types Data", + "slug": "data-core-blocks", + "markdown_source": "../docs/reference-guides/data/data-core-blocks.md", + "parent": "data" + }, + { + "title": "The Commands Data", + "slug": "data-core-commands", + "markdown_source": "../docs/reference-guides/data/data-core-commands.md", + "parent": "data" + }, + { + "title": "Customize Widgets", + "slug": "data-core-customize-widgets", + "markdown_source": "../docs/reference-guides/data/data-core-customize-widgets.md", + "parent": "data" + }, + { + "title": "The Editor’s UI Data", + "slug": "data-core-edit-post", + "markdown_source": "../docs/reference-guides/data/data-core-edit-post.md", + "parent": "data" + }, + { + "title": "Edit Site", + "slug": "data-core-edit-site", + "markdown_source": "../docs/reference-guides/data/data-core-edit-site.md", + "parent": "data" + }, + { + "title": "Edit Widgets", + "slug": "data-core-edit-widgets", + "markdown_source": "../docs/reference-guides/data/data-core-edit-widgets.md", + "parent": "data" + }, + { + "title": "The Post Editor’s Data", + "slug": "data-core-editor", + "markdown_source": "../docs/reference-guides/data/data-core-editor.md", + "parent": "data" + }, + { + "title": "The Keyboard Shortcuts Data", + "slug": "data-core-keyboard-shortcuts", + "markdown_source": "../docs/reference-guides/data/data-core-keyboard-shortcuts.md", + "parent": "data" + }, + { + "title": "Notices Data", + "slug": "data-core-notices", + "markdown_source": "../docs/reference-guides/data/data-core-notices.md", + "parent": "data" + }, + { + "title": "The NUX (New User Experience) Data", + "slug": "data-core-nux", + "markdown_source": "../docs/reference-guides/data/data-core-nux.md", + "parent": "data" + }, + { + "title": "Preferences", + "slug": "data-core-preferences", + "markdown_source": "../docs/reference-guides/data/data-core-preferences.md", + "parent": "data" + }, + { + "title": "Reusable blocks", + "slug": "data-core-reusable-blocks", + "markdown_source": "../docs/reference-guides/data/data-core-reusable-blocks.md", + "parent": "data" + }, + { + "title": "Rich Text", + "slug": "data-core-rich-text", + "markdown_source": "../docs/reference-guides/data/data-core-rich-text.md", + "parent": "data" + }, + { + "title": "The Viewport Data", + "slug": "data-core-viewport", + "markdown_source": "../docs/reference-guides/data/data-core-viewport.md", + "parent": "data" + }, + { + "title": "Explanations", + "slug": "explanations", + "markdown_source": "../docs/explanations/README.md", + "parent": null + }, + { + "title": "Architecture", + "slug": "architecture", + "markdown_source": "../docs/explanations/architecture/README.md", + "parent": "explanations" + }, + { + "title": "Key Concepts", + "slug": "key-concepts", + "markdown_source": "../docs/explanations/architecture/key-concepts.md", + "parent": "architecture" + }, + { + "title": "Data Flow and Data Format", + "slug": "data-flow", + "markdown_source": "../docs/explanations/architecture/data-flow.md", + "parent": "architecture" + }, + { + "title": "Entities and Undo/Redo", + "slug": "entities", + "markdown_source": "../docs/explanations/architecture/entities.md", + "parent": "architecture" + }, + { + "title": "Modularity", + "slug": "modularity", + "markdown_source": "../docs/explanations/architecture/modularity.md", + "parent": "architecture" + }, + { + "title": "Performance", + "slug": "performance", + "markdown_source": "../docs/explanations/architecture/performance.md", + "parent": "architecture" + }, + { + "title": "Automated Testing", + "slug": "automated-testing", + "markdown_source": "../docs/explanations/architecture/automated-testing.md", + "parent": "architecture" + }, + { + "title": "Site Editing Templates", + "slug": "full-site-editing-templates", + "markdown_source": "../docs/explanations/architecture/full-site-editing-templates.md", + "parent": "architecture" + }, + { + "title": "Styles in the Editor", + "slug": "styles", + "markdown_source": "../docs/explanations/architecture/styles.md", + "parent": "architecture" + }, + { + "title": "User Interface", + "slug": "user-interface", + "markdown_source": "../docs/explanations/user-interface/README.md", + "parent": "explanations" + }, + { + "title": "Block Design", + "slug": "block-design", + "markdown_source": "../docs/explanations/user-interface/block-design.md", + "parent": "user-interface" + }, + { + "title": "Animation", + "slug": "animation", + "markdown_source": "../docs/explanations/user-interface/animation.md", + "parent": "user-interface" + }, + { + "title": "Resources", + "slug": "design-resources", + "markdown_source": "../docs/explanations/user-interface/design-resources.md", + "parent": "user-interface" + }, + { + "title": "History", + "slug": "history", + "markdown_source": "../docs/explanations/history.md", + "parent": "explanations" + }, + { + "title": "Contributor Guide", + "slug": "contributors", + "markdown_source": "../docs/contributors/README.md", + "parent": null + }, + { + "title": "Code Contributions", + "slug": "code", + "markdown_source": "../docs/contributors/code/README.md", + "parent": "contributors" + }, + { + "title": "Getting Started With Code Contribution", + "slug": "getting-started-with-code-contribution", + "markdown_source": "../docs/contributors/code/getting-started-with-code-contribution.md", + "parent": "code" + }, + { + "title": "Git Workflow", + "slug": "git-workflow", + "markdown_source": "../docs/contributors/code/git-workflow.md", + "parent": "code" + }, + { + "title": "Coding Guidelines", + "slug": "coding-guidelines", + "markdown_source": "../docs/contributors/code/coding-guidelines.md", + "parent": "code" + }, + { + "title": "Testing Overview", + "slug": "testing-overview", + "markdown_source": "../docs/contributors/code/testing-overview.md", + "parent": "code" + }, + { + "title": "End-to-End Testing", + "slug": "e2e", + "markdown_source": "../docs/contributors/code/e2e/README.md", + "parent": "testing-overview" + }, + { + "title": "Migration guide", + "slug": "migration", + "markdown_source": "../docs/contributors/code/e2e/migration.md", + "parent": "e2e" + }, + { + "title": "Overusing snapshots", + "slug": "overusing-snapshots", + "markdown_source": "../docs/contributors/code/e2e/overusing-snapshots.md", + "parent": "e2e" + }, + { + "title": "Scripts", + "slug": "scripts", + "markdown_source": "../docs/contributors/code/scripts.md", + "parent": "code" + }, + { + "title": "Managing Packages", + "slug": "managing-packages", + "markdown_source": "../docs/contributors/code/managing-packages.md", + "parent": "code" + }, + { + "title": "Gutenberg Release Process", + "slug": "release", + "markdown_source": "../docs/contributors/code/release/README.md", + "parent": "code" + }, + { + "title": "Gutenberg plugin releases", + "slug": "plugin-release", + "markdown_source": "../docs/contributors/code/release/plugin-release.md", + "parent": "release" + }, + { + "title": "Packages releases to NPM and WordPress Core updates", + "slug": "package-release-and-core-updates", + "markdown_source": "../docs/contributors/code/release/package-release-and-core-updates.md", + "parent": "release" + }, + { + "title": "Cherry-picking automation", + "slug": "auto-cherry-picking", + "markdown_source": "../docs/contributors/code/auto-cherry-picking.md", + "parent": "release" + }, + { + "title": "React Native mobile editor", + "slug": "react-native", + "markdown_source": "../docs/contributors/code/react-native/README.md", + "parent": "code" + }, + { + "title": "Getting Started for the React Native based Mobile Gutenberg", + "slug": "getting-started-react-native", + "markdown_source": "../docs/contributors/code/react-native/getting-started-react-native.md", + "parent": "react-native" + }, + { + "title": "Setup guide for React Native development (macOS)", + "slug": "osx-setup-guide", + "markdown_source": "../docs/contributors/code/react-native/osx-setup-guide.md", + "parent": "react-native" + }, + { + "title": "React Native Integration Test Guide", + "slug": "integration-test-guide", + "markdown_source": "../docs/contributors/code/react-native/integration-test-guide.md", + "parent": "react-native" + }, + { + "title": "React Native Internationalization Guide", + "slug": "internationalization-guide", + "markdown_source": "../docs/contributors/code/react-native/internationalization-guide.md", + "parent": "react-native" + }, + { + "title": "Backward Compatibility", + "slug": "backward-compatibility", + "markdown_source": "../docs/contributors/code/backward-compatibility.md", + "parent": "code" + }, + { + "title": "Deprecations", + "slug": "deprecations", + "markdown_source": "../docs/contributors/code/deprecations.md", + "parent": "code" + }, + { + "title": "How To Get Your Pull Request Reviewed?", + "slug": "how-to-get-your-pull-request-reviewed", + "markdown_source": "../docs/contributors/code/how-to-get-your-pull-request-reviewed.md", + "parent": "code" + }, + { + "title": "Design Contributions", + "slug": "design", + "markdown_source": "../docs/contributors/design/README.md", + "parent": "contributors" + }, + { + "title": "Blocks are the Interface", + "slug": "the-block", + "markdown_source": "../docs/contributors/design/the-block.md", + "parent": "design" + }, + { + "title": "Documentation Contributions", + "slug": "documentation", + "markdown_source": "../docs/contributors/documentation/README.md", + "parent": "contributors" + }, + { + "title": "Copy Guidelines", + "slug": "copy-guide", + "markdown_source": "../docs/contributors/documentation/copy-guide.md", + "parent": "documentation" + }, + { + "title": "Triage", + "slug": "triage", + "markdown_source": "../docs/contributors/triage.md", + "parent": "contributors" + }, + { + "title": "Localizing Gutenberg", + "slug": "localizing", + "markdown_source": "../docs/contributors/localizing.md", + "parent": "contributors" + }, + { + "title": "Accessibility Testing", + "slug": "accessibility-testing", + "markdown_source": "../docs/contributors/accessibility-testing.md", + "parent": "contributors" + }, + { + "title": "Repository Management", + "slug": "repository-management", + "markdown_source": "../docs/contributors/repository-management.md", + "parent": "contributors" + }, + { + "title": "Folder Structure", + "slug": "folder-structure", + "markdown_source": "../docs/contributors/folder-structure.md", + "parent": "contributors" + }, + { + "title": "Gutenberg versions in WordPress", + "slug": "versions-in-wordpress", + "markdown_source": "../docs/contributors/versions-in-wordpress.md", + "parent": "contributors" + } +] diff --git a/private-apis.md b/private-apis.md new file mode 100644 index 0000000..206b7d6 --- /dev/null +++ b/private-apis.md @@ -0,0 +1,340 @@ +### `core/edit-post` 存储库 + +私有选择器: +- `getEditedPostTemplateId` + +## edit-site + +### `core/edit-site` 存储库 + +私有操作: +- `registerRoute` +- `setEditorCanvasContainerView` + +私有选择器: +- `getRoutes` +- `getEditorCanvasContainerView` + +# Gutenberg 私有 API + +本文概述了 Gutenberg 包暴露的私有 API。这些 API 用于实现 Gutenberg 编辑器(文章编辑器、站点编辑器、核心区块及其他功能)的组成部分,但未向插件和主题开发者或自定义 Gutenberg 集成的开发者公开。 + +本文档旨在展示我们拥有多少私有 API,以及如何利用 `@wordpress/*` 系列包提供的库和框架来构建 Gutenberg 编辑器应用。 + +## data + +注册器包含两个私有方法: +- `privateActionsOf` +- `privateSelectorsOf` + +每个存储库均提供用于注册私有选择器/操作的私有 API: +- `privateActions` +- `registerPrivateActions` +- `privateSelectors` +- `registerPrivateSelectors` + +## blocks + +### `core/blocks` 存储库 + +私有操作: +- `addBlockBindingsSource` +- `removeBlockBindingsSource` +- `addBootstrappedBlockType` +- `addUnprocessedBlockType` + +私有选择器: +- `getAllBlockBindingsSources` +- `getBlockBindingsSource` +- `getBootstrappedBlockType` +- `getSupportedStyles` +- `getUnprocessedBlockTypes` +- `hasContentRoleAttribute` + +## components + +私有导出项: +- `__experimentalPopoverLegacyPositionToPlacement` +- `ComponentsContext` +- `Tabs` +- `Theme` +- `Menu` +- `kebabCase` + +## commands + +私有导出项: +- `useCommandContext`(2023 年 5 月通过 #50543 添加) + +### `core/commands` 存储库 + +私有操作: +- `setContext`(与 `useCommandContext` 同时添加) + +## preferences + +私有导出项(2024 年 1 月通过 #57639 添加): +- `PreferenceBaseOption` +- `PreferenceToggleControl` +- `PreferencesModal` +- `PreferencesModalSection` +- `PreferencesModalTabs` + +仅有一个公开导出的组件! +- `PreferenceToggleMenuItem` + +## block-editor + +私有导出项: +- `AdvancedPanel` +- `BackgroundPanel` +- `BorderPanel` +- `ColorPanel` +- `DimensionsPanel` +- `FiltersPanel` +- `GlobalStylesContext` +- `ImageSettingsPanel` +- `TypographyPanel` +- `areGlobalStyleConfigsEqual` +- `getBlockCSSSelector` +- `getBlockSelectors` +- `getGlobalStylesChanges` +- `getLayoutStyles` +- `toStyles` +- `useGlobalSetting` +- `useGlobalStyle` +- `useGlobalStylesOutput` +- `useGlobalStylesOutputWithConfig` +- `useGlobalStylesReset` +- `useHasBackgroundPanel` +- `useHasBorderPanel` +- `useHasBorderPanelControls` +- `useHasColorPanel` +- `useHasDimensionsPanel` +- `useHasFiltersPanel` +- `useHasImageSettingsPanel` +- `useHasTypographyPanel` +- `useSettingsForBlockElement` +- `ExperimentalBlockCanvas`:公共 `BlockCanvas` 的变体,包含多个额外属性:`contentRef`、`shouldIframe`、`iframeProps`。 +- `ExperimentalBlockEditorProvider`:公共 `BlockEditorProvider` 的变体,过滤掉若干私有/实验性设置。另请参阅 `__experimentalUpdateSettings`。 +- `getDuotoneFilter` +- `getRichTextValues` +- `PrivateQuickInserter` +- `extractWords` +- `getNormalizedSearchTerms` +- `normalizeString` +- `PrivateListView` +- `ResizableBoxPopover` +- `BlockInfo` +- `useHasBlockToolbar` +- `cleanEmptyObject` +- `BlockQuickNavigation` +- `LayoutStyle` +- `BlockRemovalWarningModal` +- `useLayoutClasses` +- `useLayoutStyles` +- `DimensionsTool` +- `ResolutionTool` +- `TabbedSidebar` +- `TextAlignmentControl` +- `usesContextKey` +- `useFlashEditableBlocks` +- `useZoomOut` +- `globalStylesDataKey` +- `globalStylesLinksDataKey` +- `selectBlockPatternsKey` +- `requiresWrapperOnCopy` +- `PrivateRichText`:额外属性 `readOnly` 于 #58916 和 #60327(2024 年 2 月和 3 月)添加。 +- `PrivateInserterLibrary`:额外属性 `onPatternCategorySelection` 于 #62130(2024 年 5 月)添加。 +- `reusableBlocksSelectKey` +- `PrivateBlockPopover`:包含两个额外属性 `__unstableContentRef` 和 `__unstablePopoverSlot`。 +- `PrivatePublishDateTimePicker`:公共 `PublishDateTimePicker` 的变体,包含两个额外属性:`isCompact` 和 `showPopoverHeaderActions`。 +- `useSpacingSizes` +- `useBlockDisplayTitle` +- `__unstableBlockStyleVariationOverridesWithConfig` +- `setBackgroundStyleDefaults` +- `sectionRootClientIdKey` +- `__unstableCommentIconFill` +- `__unstableCommentIconToolbarFill` + +### `core/block-editor` 存储库 + +私有操作: +- `__experimentalUpdateSettings`:公共 `updateSettings` 操作的实验版本,用于过滤部分私有/实验性设置。 +- `clearBlockRemovalPrompt`:清除区块移除提示 +- `deleteStyleOverride`:删除样式覆盖 +- `ensureDefaultBlock`:确保默认区块存在 +- `expandBlock`:展开区块 +- `hideBlockInterface`:隐藏区块界面 +- `modifyContentLockBlock`:修改内容锁定区块 +- `privateRemoveBlocks`:私有移除区块 +- `resetZoomLevel`:重置缩放级别 +- `setBlockRemovalRules`:设置区块移除规则 +- `setInsertionPoint`:设置插入点 +- `setLastFocus`:设置最后焦点 +- `setOpenedBlockSettingsMenu`:设置已打开的区块设置菜单 +- `setStyleOverride`:设置样式覆盖 +- `setZoomLevel`:设置缩放级别 +- `showBlockInterface`:显示区块界面 +- `startDragging`:开始拖拽 +- `stopDragging`:停止拖拽 +- `stopEditingAsBlocks`:停止以区块形式编辑 + +私有选择器: +- `getAllPatterns`:获取所有模式 +- `getBlockRemovalRules`:获取区块移除规则 +- `getBlockSettings`:获取区块设置 +- `getBlockStyles`:获取区块样式 +- `getBlockWithoutAttributes`:获取无属性区块 +- `getClosestAllowedInsertionPoint`:获取最近允许插入点 +- `getClosestAllowedInsertionPointForPattern`:获取模式最近允许插入点 +- `getContentLockingParent`:获取内容锁定父级 +- `getEnabledBlockParents`:获取启用区块父级 +- `getEnabledClientIdsTree`:获取启用的客户端ID树 +- `getExpandedBlock`:获取已展开区块 +- `getInserterMediaCategories`:获取插入器媒体分类 +- `getInsertionPoint`:获取插入点 +- `getLastFocus`:获取最后焦点 +- `getLastInsertedBlocksClientIds`:获取最后插入区块客户端ID +- `getOpenedBlockSettingsMenu`:获取已打开区块设置菜单 +- `getParentSectionBlock`:获取父级区块区域 +- `getPatternBySlug`:通过别名获取模式 +- `getRegisteredInserterMediaCategories`:获取已注册插入器媒体分类 +- `getRemovalPromptData`:获取移除提示数据 +- `getReusableBlocks`:获取可重用区块 +- `getSectionRootClientId`:获取区域根客户端ID +- `getStyleOverrides`:获取样式覆盖 +- `getTemporarilyEditingAsBlocks`:获取临时以区块形式编辑状态 +- `getTemporarilyEditingFocusModeToRevert`:获取待恢复的临时编辑焦点模式 +- `getZoomLevel`:获取缩放级别 +- `hasAllowedPatterns`:检查是否存在允许模式 +- `isBlockInterfaceHidden`:检查区块界面是否隐藏 +- `isBlockSubtreeDisabled`:检查区块子树是否禁用 +- `isDragging`:检查是否正在拖拽 +- `isResolvingPatterns`:检查是否正在解析模式 +- `isSectionBlock`:检查是否为区域区块 +- `isZoomOut`:检查是否处于缩小状态 + +## core-data + +私有导出: +- `useEntityRecordsWithPermissions`:带权限的实体记录钩子 + +### `core` 存储库 + +私有操作: +- `receiveRegisteredPostMeta`:接收已注册文章元数据 +- `editMediaEntity`:编辑媒体实体 + +私有选择器: +- `getBlockPatternsForPostType`:获取文章类型区块模式 +- `getEntityRecordPermissions`:获取实体记录权限 +- `getEntityRecordsPermissions`:获取实体记录集权限 +- `getNavigationFallbackId`:获取导航回退ID +- `getRegisteredPostMeta`:获取已注册文章元数据 +- `getUndoManager`:获取撤销管理器 + +## patterns(2023年8月创建的包,无公开导出,所有内容均为私有) + +私有导出: +- `OverridesPanel`:覆盖面板 +- `CreatePatternModal`:创建模式模态框 +- `CreatePatternModalContents`:创建模式模态框内容 +- `DuplicatePatternModal`:复制模式模态框 +- `isOverridableBlock`:检查是否为可覆盖区块 +- `hasOverridableBlocks`:检查是否存在可覆盖区块 +- `useDuplicatePatternProps`:复制模式属性钩子 +- `RenamePatternModal`:重命名模式模态框 +- `PatternsMenuItems`:模式菜单项 +- `RenamePatternCategoryModal`:重命名模式分类模态框 +- `PatternOverridesControls`:模式覆盖控件 +- `ResetOverridesControl`:重置覆盖控件 +- `PatternOverridesBlockControls`:模式覆盖区块控件 +- `useAddPatternCategory`:添加模式分类钩子 +- `PATTERN_TYPES`:模式类型 +- `PATTERN_DEFAULT_CATEGORY`:模式默认分类 +- `PATTERN_USER_CATEGORY`:模式用户分类 +- `EXCLUDED_PATTERN_SOURCES`:排除的模式来源 +- `PATTERN_SYNC_TYPES`:模式同步类型 +- `PARTIAL_SYNCING_SUPPORTED_BLOCKS`:支持部分同步的区块 + +### `core/patterns` 存储库 + +私有操作: +- `convertSyncedPatternToStatic`:将同步模式转换为静态 +- `createPattern`:创建模式 +- `createPatternFromFile`:从文件创建模式 +- `setEditingPattern`:设置编辑模式 + +私有选择器: +- `isEditingPattern`:检查是否正在编辑模式 + +## block-library + +私有导出: +- `BlockKeyboardShortcuts`:区块键盘快捷键 + +## router(仅私有导出) + +私有导出: +- `useHistory`:历史记录钩子 +- `useLocation`:位置钩子 +- `RouterProvider`:路由提供者 + +## core-commands(仅私有导出) + +私有导出: +- `useCommands`:命令钩子 + +## editor + +私有导出: +- `CreateTemplatePartModal`:创建模板部件模态框 +- `BackButton`:返回按钮 +- `EntitiesSavedStatesExtensible`:可扩展实体保存状态 +- `Editor`:编辑器 +- `EditorContentSlotFill`:编辑器内容插槽填充 +- `GlobalStylesProvider`:全局样式提供者 +- `mergeBaseAndUserConfigs`:合并基础与用户配置 +- `PluginPostExcerpt`:插件文章摘要 +- `PostCardPanel`:文章卡片面板 +- `PreferencesModal`:偏好设置模态框 +- `usePostActions`:文章操作钩子 +- `ToolsMoreMenuGroup`:工具更多菜单组 +- `ViewMoreMenuGroup`:视图更多菜单组 +- `ResizableEditor`:可调整大小编辑器 +- `registerCoreBlockBindingsSources`:注册核心区块绑定源 +- `interfaceStore`:界面存储库 +- `ActionItem`:操作项 +- `ComplementaryArea`:补充区域 +- `ComplementaryAreaMoreMenuItem`:补充区域更多菜单项 +- `FullscreenMode`:全屏模式 +- `InterfaceSkeleton`:界面骨架 +- `PinnedItems`:固定项目 + +### `core/editor` 存储库 + +私有操作: +- `createTemplate`:创建模板 +- `hideBlockTypes`:隐藏区块类型 +- `registerEntityAction`:注册实体操作 +- `registerPostTypeActions`:注册文章类型操作 +- `removeTemplates`:移除模板 +- `revertTemplate`:恢复模板 +- `saveDirtyEntities`:保存未保存实体 +- `setCurrentTemplateId`:设置当前模板ID +- `setIsReady`:设置就绪状态 +- `showBlockTypes`:显示区块类型 +- `unregisterEntityAction`:取消注册实体操作 + +私有选择器: +- `getEntityActions`:获取实体操作 +- `getInserter`:获取插入器 +- `getInserterSidebarToggleRef`:获取插入器侧边栏切换引用 +- `getListViewToggleRef`:获取列表视图切换引用 +- `getPostBlocksByName`:通过名称获取文章区块 +- `getPostIcon`:获取文章图标 +- `hasPostMetaChanges`:检查文章元数据是否有变更 +- `isEntityReady`:检查实体是否就绪 + +## edit-post \ No newline at end of file diff --git a/reference-guides/README.md b/reference-guides/README.md new file mode 100644 index 0000000..d579e7d --- /dev/null +++ b/reference-guides/README.md @@ -0,0 +1,71 @@ +# 参考指南 + +## [区块 API 参考](/docs/reference-guides/block-api/README.md) + +- [注释](/docs/reference-guides/block-api/block-annotations.md) +- [API 版本](/docs/reference-guides/block-api/block-api-versions.md) +- [属性](/docs/reference-guides/block-api/block-attributes.md) +- [上下文](/docs/reference-guides/block-api/block-context.md) +- [弃用说明](/docs/reference-guides/block-api/block-deprecation.md) +- [编辑与保存](/docs/reference-guides/block-api/block-edit-save.md) +- [模式](/docs/reference-guides/block-api/block-patterns.md) +- [注册](/docs/reference-guides/block-api/block-registration.md) +- [功能支持](/docs/reference-guides/block-api/block-supports.md) +- [模板](/docs/reference-guides/block-api/block-templates.md) +- [转换功能](/docs/reference-guides/block-api/block-transforms.md) +- [元数据](/docs/reference-guides/block-api/block-metadata.md) +- [变体](/docs/reference-guides/block-api/block-variations.md) + +## [钩子参考](/docs/reference-guides/filters/README.md) + +- [区块过滤器](/docs/reference-guides/filters/block-filters.md) +- [编辑器钩子](/docs/reference-guides/filters/editor-filters.md) +- [国际化钩子](/docs/reference-guides/filters/i18n-filters.md) +- [解析器钩子](/docs/reference-guides/filters/parser-filters.md) +- [自动补全](/docs/reference-guides/filters/autocomplete-filters.md) +- [全局样式钩子](/docs/reference-guides/filters/global-styles-filters.md) + +## [插槽填充参考](/docs/reference-guides/slotfills/README.md) + +- [主仪表板按钮](/docs/reference-guides/slotfills/main-dashboard-button.md) +- [插件区块设置菜单项](/docs/reference-guides/slotfills/plugin-block-settings-menu-item.md) +- [插件文档设置面板](/docs/reference-guides/slotfills/plugin-document-setting-panel.md) +- [插件更多菜单项](/docs/reference-guides/slotfills/plugin-more-menu-item.md) +- [插件文章发布后面板](/docs/reference-guides/slotfills/plugin-post-publish-panel.md) +- [插件文章状态信息](/docs/reference-guides/slotfills/plugin-post-status-info.md) +- [插件文章发布前面板](/docs/reference-guides/slotfills/plugin-pre-publish-panel.md) +- [插件侧边栏](/docs/reference-guides/slotfills/plugin-sidebar.md) +- [插件侧边栏更多菜单项](/docs/reference-guides/slotfills/plugin-sidebar-more-menu-item.md) + +## [主题配置参考](/docs/reference-guides/theme-json-reference/README.md) + +- [版本 3(最新)](/docs/reference-guides/theme-json-reference/theme-json-living.md) +- [版本 2](/docs/reference-guides/theme-json-reference/theme-json-v2.md) +- [版本 1](/docs/reference-guides/theme-json-reference/theme-json-v1.md) +- [迁移至新版本](/docs/reference-guides/theme-json-reference/theme-json-migrations.md) + +## [富文本参考](/docs/reference-guides/richtext.md) + +## [组件参考](/packages/components/README.md) + +## [包参考](/docs/reference-guides/packages.md) + +## [数据模块参考](/docs/reference-guides/data/README.md) + +- [**core**:WordPress 核心数据](/docs/reference-guides/data/data-core.md) + - [**core/annotations**:注释](/docs/reference-guides/data/data-core-annotations.md) + - [**core/block-directory**:区块目录](/docs/reference-guides/data/data-core-block-directory.md) + - [**core/block-editor**:区块编辑器数据](/docs/reference-guides/data/data-core-block-editor.md) + - [**core/blocks**:区块类型数据](/docs/reference-guides/data/data-core-blocks.md) + - [**core/customize-widgets**:自定义小工具](/docs/reference-guides/data/data-core-customize-widgets.md) + - [**core/edit-post**:编辑器界面数据](/docs/reference-guides/data/data-core-edit-post.md) + - [**core/edit-site**:站点编辑](/docs/reference-guides/data/data-core-edit-site.md) + - [**core/edit-widgets**:小工具编辑](/docs/reference-guides/data/data-core-edit-widgets.md) + - [**core/editor**:文章编辑器数据](/docs/reference-guides/data/data-core-editor.md) + - [**core/keyboard-shortcuts**:键盘快捷键数据](/docs/reference-guides/data/data-core-keyboard-shortcuts.md) + - [**core/notices**:通知数据](/docs/reference-guides/data/data-core-notices.md) + - [**core/nux**:新用户体验数据](/docs/reference-guides/data/data-core-nux.md) + - [**core/preferences**:偏好设置](/docs/reference-guides/data/data-core-preferences.md) + - [**core/reusable-blocks**:可复用区块](/docs/reference-guides/data/data-core-reusable-blocks.md) + - [**core/rich-text**:富文本](/docs/reference-guides/data/data-core-rich-text.md) + - [**core/viewport**:视口数据](/docs/reference-guides/data/data-core-viewport.md) \ No newline at end of file diff --git a/reference-guides/block-api/README.md b/reference-guides/block-api/README.md new file mode 100644 index 0000000..3f1351b --- /dev/null +++ b/reference-guides/block-api/README.md @@ -0,0 +1,22 @@ +# 区块 API 参考 + +区块是编辑器的基本组成单元,也是插件和主题注册自身功能、扩展编辑器能力的主要方式。 + +以下章节将引导您了解现有的区块 API: + +- [注释](/docs/reference-guides/block-api/block-annotations.md) +- [API 版本](/docs/reference-guides/block-api/block-api-versions.md) +- [属性](/docs/reference-guides/block-api/block-attributes.md) +- [绑定](/docs/reference-guides/block-api/block-bindings.md) +- [上下文](/docs/reference-guides/block-api/block-context.md) +- [弃用说明](/docs/reference-guides/block-api/block-deprecation.md) +- [编辑与保存](/docs/reference-guides/block-api/block-edit-save.md) +- [block.json 中的元数据](/docs/reference-guides/block-api/block-metadata.md) +- [模式](/docs/reference-guides/block-api/block-patterns.md) +- [注册](/docs/reference-guides/block-api/block-registration.md) +- [选择器](/docs/reference-guides/block-api/block-selectors.md) +- [样式](/docs/reference-guides/block-api/block-styles.md) +- [支持特性](/docs/reference-guides/block-api/block-supports.md) +- [转换功能](/docs/reference-guides/block-api/block-transforms.md) +- [模板](/docs/reference-guides/block-api/block-templates.md) +- [变体](/docs/reference-guides/block-api/block-variations.md) \ No newline at end of file diff --git a/reference-guides/block-api/block-annotations.md b/reference-guides/block-api/block-annotations.md new file mode 100644 index 0000000..bb1c211 --- /dev/null +++ b/reference-guides/block-api/block-annotations.md @@ -0,0 +1,61 @@ +# 标注功能 + +
    +注意: 此 API 为实验性功能,这意味着在未来的任何版本中可能会发生不向后兼容的变更或被移除。 +
    + +标注是一种高亮区块编辑器中创建的文章特定片段的方式。典型应用包括文本批注与拼写检查,两者均可通过标注 API 对文本片段进行标记。 + +## API + +要直观了解 API,最简便的方式是创建一个至少包含 200 个无格式字符的区块,并在控制台中执行以下代码: + +```js +wp.data.dispatch( 'core/annotations' ).addAnnotation( { + source: 'my-annotations-plugin', + blockClientId: wp.data.select( 'core/block-editor' ).getBlockOrder()[ 0 ], + richTextIdentifier: 'content', + range: { + start: 50, + end: 100, + }, +} ); +``` + +范围起始点与结束点的计算应仅基于相应 `RichText` 的纯文本内容。例如在以下 HTML 中,位置 0 将指向大写字母 S 之前的位置: + +```html +加粗文本 +``` + +为帮助确定正确位置,可使用 `wp.richText.create` 方法。该方法会将 HTML 片段拆分为文本内容与格式标记。 + +所有可用属性均可在 `addAnnotation` 操作的 API 文档中查阅。 + +`richTextIdentifier` 属性用于指定标注所应用的富文本实例标识符。由于区块可能包含多个用于管理不同属性数据的富文本实例,必须传入此参数才能确保在正确的实例中高亮文本。 + +例如段落区块仅包含单个标识符为 `content` 的富文本实例。引述区块类型则拥有 2 个富文本实例,若需在引注部分高亮文本,添加标注时需将 `citation` 作为 `richTextIdentifier` 传入;若需定位引述内容,则需使用标识符 `value`。具体标识符请参考区块类型的源代码。 + +## 区块标注 + +也可对完整区块进行标注。此时只需提供值为 `block` 的 `selector` 属性。默认的 `selector` 为 `range`,适用于文本标注场景。 + +```js +wp.data.dispatch( 'core/annotations' ).addAnnotation( { + source: 'my-annotations-plugin', + blockClientId: wp.data.select( 'core/block-editor' ).getBlockOrder()[ 0 ], + selector: 'block', +} ); +``` + +此操作不会自动提供样式支持,因此需要额外添加 CSS 以确保标注可见: + +```css +.is-annotated-by-my-annotations-plugin { + outline: 1px solid black; +} +``` + +## 文本标注 + +文本标注通过 `start` 与 `end` 属性进行控制。由于简单的起止位置属性无法直接适用于 HTML 环境,这两个属性被设计为 `rich-text` 内部结构中的偏移量。为简化理解,可将其视为去除所有 HTML 标记后,在纯文本环境中计算的标注起止位置。 \ No newline at end of file diff --git a/reference-guides/block-api/block-api-versions.md b/reference-guides/block-api/block-api-versions.md new file mode 100644 index 0000000..0a3021b --- /dev/null +++ b/reference-guides/block-api/block-api-versions.md @@ -0,0 +1,16 @@ +# API 版本说明 + +本文档记录了不同 API 版本之间的变更内容。 + +## 版本 3(适用于 WordPress 6.3 及以上版本) +- 当所有已注册区块均采用区块 API 第 3 版或更高版本时,文章编辑器将采用 iframe 内嵌模式。支持第 3 版意味着区块应能在 iframe 内正常工作,但若存在不支持第 3 版的区块,该区块仍可能在 iframe 外渲染。 +- 请参阅[本文](https://make.wordpress.org/core/2021/06/29/blocks-in-an-iframed-template-editor/)获取迁移指南,了解如何将 API 版本升级至第 3 版并确保在 iframe 编辑器中正常运行。 + +## 版本 2(适用于 WordPress 5.6 及以上版本) + +- 区块开发者必须使用 `useBlockProps()` 钩子函数来渲染区块 `edit` 实现的包装元素。 +- 在处理 `save` 功能时,生成的类名和样式不再自动添加到静态区块的保存标记中。如需保留这些样式,区块开发者必须显式调用 `useBlockProps.save()` 并将其添加到区块包装器中。 + +## 版本 1 + +初始版本。 \ No newline at end of file diff --git a/reference-guides/block-api/block-attributes.md b/reference-guides/block-api/block-attributes.md new file mode 100644 index 0000000..ae2810f --- /dev/null +++ b/reference-guides/block-api/block-attributes.md @@ -0,0 +1,495 @@ +# 属性 + +区块属性提供了关于区块存储数据的信息。例如:富文本内容、图片URL列表、背景色或按钮标题。 + +一个区块可以包含任意数量的属性,这些属性通过`attributes`字段指定——该对象中的每个键都是属性名称,值则是属性定义。 + +属性定义至少包含`type`或`enum`之一,也可能包含其他字段。 + +*示例*:定义三个属性(`url`、`title`和`size`)的属性对象。 + +```js +{ + url: { + type: 'string', + source: 'attribute', + selector: 'img', + attribute: 'src', + }, + title: { + type: 'string', + }, + size: { + enum: [ 'large', 'small' ], + }, +} +``` + +当区块被解析时,此定义将用于从区块内容中提取数据。所有匹配项都会通过`attributes`属性提供给您的区块。 + +该解析过程可概括为: + +1. 从`source`中提取值 +2. 检查值是否匹配`type`,或是`enum`值之一 + +*示例*:使用上述属性定义,在`edit`函数中可用的属性。 + +```js +function YourBlockEdit( { attributes } ) { + return ( +

    URL是{ attributes.url },标题是{ attributes.title },尺寸是{ attributes.size }。

    + ) +} +``` + +区块需负责使用`save`函数确保所有带`source`字段的属性按照属性定义保存。此过程非自动执行。 + +没有`source`的属性将自动保存在区块[注释分隔符](/docs/explanations/architecture/key-concepts.md#data-attributes)中。 + +例如,使用上述属性定义时,您需要确保`save`函数包含与`url`属性对应的img标签。`title`和`size`属性将保存在注释分隔符中。 + +*示例*:包含`url`属性的`save`函数示例 + +```js +function YourBlockSave( { attributes } ) { + return ( + + ) +} +``` + +保存的HTML将在注释分隔符中包含`title`和`size`,在`img`节点中包含`url`。 + +```html + + + +``` + +若属性随时间变化,可通过[区块弃用](/docs/reference-guides/block-api/block-deprecation.md)来迁移旧属性或完全移除。 + +## 类型验证 + +`type`指明属性存储的数据类型。它不表示数据存储位置(由`source`字段定义)。 + +除非提供`enum`,否则`type`是必需的。`type`可与`enum`同时使用。 + +`type`字段必须是以下之一: + +- `null` +- `boolean` +- `object` +- `array` +- `string` +- `integer` +- `number`(与`integer`相同) + +注意:`object`的有效性由您的`source`决定。示例可参阅下文的`query`详情。 + +## 枚举验证 + +属性可定义为固定值集合中的一个值。通过包含允许值数组的`enum`来指定: + +*示例*:`enum`示例。 + +```js +{ + size: { + enum: [ 'large', 'small', 'tiny' ] + } +} +``` + +## 值来源 + +属性来源用于定义如何从已保存的文章内容中提取属性值。它们提供了一种从已保存标记到区块JavaScript表示的映射机制。 + +可用的`source`值包括: +- `(无值)`- 未指定`source`时,数据存储在区块的[注释分隔符](/docs/explanations/architecture/key-concepts.md#data-attributes)中 +- `attribute`- 数据存储在HTML元素属性中 +- `text`- 数据存储在HTML文本中 +- `html`- 数据以HTML形式存储(通常由`RichText`使用) +- `query`- 数据以对象数组形式存储 +- `meta`- 数据存储在文章元数据中(已弃用) + +`source`字段通常与`selector`字段结合使用。若未指定选择器参数,源定义将针对区块根节点运行。若指定选择器参数,则将针对区块内的匹配元素运行。 + +`selector`可以是HTML标签,或任何可通过[querySelector](https://developer.mozilla.org/en-US/docs/Web/API/Document/querySelector)查询的内容,例如类或id属性。下方提供了示例。 + +例如,`img`选择器将匹配`img`元素,而`img.class`将匹配具有`class`类的`img`元素。 + +在底层实现中,属性来源是[hpq](https://github.com/aduth/hpq)库功能的超集,该小型库用于将HTML标记解析和查询为对象形态。 + +总结而言,`source`决定数据在内容中的存储位置,而`type`决定数据的类型。为减少存储数据量,通常建议尽可能将数据存储在HTML中而非注释分隔符内的属性中。 + +### 元数据源(已弃用) + +
    +虽然属性可从文章的元数据中获取,但元数据属性源已被视为弃用;应改用 EntityProvider 及相关钩子 API,如 创建元数据块指南 中所示。 +
    + +属性可从文章的元数据中获取,而非从已保存文章内容中的块表示中获取。为此,需要为属性在 `meta` 键下指定其对应的元数据键。 + +属性定义: +```js +{ + author: { + type: 'string', + source: 'meta', + meta: 'author' + }, +}, +``` + +从此处开始,元数据属性可以通过与任何属性相同的接口由块读取和写入: + +```js +edit( { attributes, setAttributes } ) { + function onChange( event ) { + setAttributes( { author: event.target.value } ); + } + + return ; +}, +``` + +#### 注意事项 + +默认情况下,元字段将从文章对象的元数据中排除。可以通过显式使字段可见来规避此问题: + +```php +function gutenberg_my_block_init() { + register_post_meta( 'post', 'author', array( + 'show_in_rest' => true, + ) ); +} +add_action( 'init', 'gutenberg_my_block_init' ); +``` + +此外,请注意 WordPress 默认行为为: + +- 不将元数据视为唯一,而是返回值的数组; +- 将该数据视为字符串。 + +如果不希望出现上述任一行为,可以在相同的 `register_post_meta` 调用中补充 `single` 和/或 `type` 参数,如下所示: + +```php +function gutenberg_my_block_init() { + register_post_meta( 'post', 'author_count', array( + 'show_in_rest' => true, + 'single' => true, + 'type' => 'integer', + ) ); +} +add_action( 'init', 'gutenberg_my_block_init' ); +``` + +如果希望在属性中使用对象或数组,可以注册一个 `string` 属性类型并使用 JSON 作为中间格式。在保存之前将结构化数据序列化为 JSON,然后在服务器上反序列化 JSON 字符串。请注意,您需要负责数据的完整性;确保正确清理数据、处理缺失数据等。 + +最后,请确保在设置属性时尊重数据的类型,因为框架不会自动执行元数据的类型转换。块属性中的类型错误将导致文章即使在保存后仍保持“脏”状态(参见 `isEditedPostDirty`、`hasEditedAttributes`)。例如,如果 `authorCount` 是整数类型,请记住事件处理程序可能会传递不同类型的数据,因此应显式转换值: + +```js +function onChange( event ) { + props.setAttributes( { authorCount: Number( event.target.value ) } ); +} +``` + +## 默认值 + +块属性可以包含默认值,当 `type` 和 `source` 与块内容中的任何内容不匹配时,将使用该值。 + +该值由 `default` 字段提供,并且该值应与属性的预期格式匹配。 + +_示例_:`default` 值的示例。 + +```js +{ + type: 'string', + default: 'hello world' +} +``` + +```js +{ + type: 'array', + default: [ + { "url": "https://lorempixel.com/1200/800/", "alt": "大图" }, + { "url": "https://lorempixel.com/50/50/", "alt": "小图" } + ] +} +``` + +```js +{ + type: 'object', + default: { + width: 100, + title: '标题' + } +} +``` + +## 角色 + +`role` 属性将属性指定为特定概念类型。此属性可应用于任何属性,以提供关于应如何处理该属性的语义含义。 + +使用 `content` 将属性指定为用户可编辑的内容。在特殊情况下(例如仅内容锁定),标记为 `content` 的块可能被启用为特权编辑。 +使用 `local` 将属性标记为临时且不可持久化的。标记为 `local` 的属性会被块序列化器忽略,并且永远不会保存到文章内容中。 + +_示例_:段落块使用的 `content` 角色 + +```js +{ + content: { + type: 'string', + source: 'html', + selector: 'p', + role: 'content', + } +} +``` + +_示例_:用于临时数据的 `local` 角色。 + +```js +{ + blob: { + type: 'string', + role: 'local', + } +} +``` + +更多信息请参阅 [WordPress 6.7 开发说明](https://make.wordpress.org/core/2024/10/20/miscellaneous-block-editor-changes-in-wordpress-6-7/#stabilized-role-property-for-block-attributes)。 + +### `attribute` 数据源 + +使用 `attribute` 数据源可从标记中提取属性值。通过必须提供的 `attribute` 字段指定需要提取的属性。 + +_示例_:从区块标记中的图片元素提取 `src` 属性。 + +保存内容: +```html +
    + 区块内容 + + +
    +``` + +属性定义: +```js +{ + url: { + type: 'string', + source: 'attribute', + selector: 'img', + attribute: 'src', + } +} +``` + +区块中可用的属性: +```js +{ "url": "https://lorempixel.com/1200/800/" } +``` + +大多数标记中的属性都是字符串类型。HTML中的数值属性仍会以字符串形式存储,且不会自动转换。 + +_示例_:从区块标记中的图片元素提取 `width` 属性。 + +保存内容: +```html +
    + 区块内容 + + +
    +``` + +属性定义: +```js +{ + width: { + type: 'string', + source: 'attribute', + selector: 'img', + attribute: 'width', + } +} +``` + +区块中可用的属性: +```js +{ "width": "50" } +``` + +唯一例外是检查属性是否存在的情况(例如检查 `button` 上的 `disabled` 属性)。此时可使用 `boolean` 类型,存储值将转换为布尔值。 + +_示例_:从区块标记中的按钮元素提取 `disabled` 属性。 + +保存内容: +```html +
    + 区块内容 + + +
    +``` + +属性定义: +```js +{ + disabled: { + type: 'boolean', + source: 'attribute', + selector: 'button', + attribute: 'disabled', + } +} +``` + +区块中可用的属性: +```js +{ "disabled": true } +``` + +### `text` 数据源 + +使用 `text` 可从标记中提取内部文本。注意返回的HTML内容遵循 [`textContent`](https://developer.mozilla.org/zh-CN/docs/Web/API/Node/textContent) 规则。 + +_示例_:从区块标记中的 figcaption 元素提取 `content` 属性。 + +保存内容: +```html +
    + + +
    figcaption 元素的内部文本
    +
    +``` + +属性定义: +```js +{ + content: { + type: 'string', + source: 'text', + selector: 'figcaption', + } +} +``` + +区块中可用的属性: +```js +{ "content": "figcaption 元素的内部文本" } +``` + +另一个使用 `text` 数据源,通过 `.my-content` 类选择器提取文本的示例: + +_示例_:从区块标记中具有 `.my-content` 类的元素提取 `content` 属性。 + +保存内容: +```html +
    + + +

    .my-content 类的内部文本

    +
    +``` + +属性定义: +```js +{ + content: { + type: 'string', + source: 'text', + selector: '.my-content', + } +} +``` + +区块中可用的属性: +```js +{ "content": ".my-content 类的内部文本" } +``` + +### `html` 数据源 + +使用 `html` 可从标记中提取内部HTML。注意返回的文本内容遵循 [`innerHTML`](https://developer.mozilla.org/zh-CN/docs/Web/API/Element/innerHTML) 规则。 + +_示例_:从区块标记中的 figcaption 元素提取 `content` 属性。 + +保存内容: +```html +
    + + +
    figcaption 元素的内部文本
    +
    +``` + +属性定义: +```js +{ + content: { + type: 'string', + source: 'html', + selector: 'figcaption', + } +} +``` + +区块中可用的属性: +```js +{ "content": "figcaption 元素的内部文本" } +``` + +### `query` 数据源 + +使用 `query` 可从标记中提取值数组。数组条目由 `selector` 参数决定,区块内每个匹配元素都会对应一个条目,其结构由第二个参数(属性源对象)定义。 + +`query` 字段实际上是嵌套的区块属性定义。虽然可以进一步嵌套(但不一定推荐这样做)。 + +_示例_:从区块标记中的每个图片元素提取 `src` 和 `alt` 属性。 + +保存内容: +```html +
    + 大图 + 小图 +
    +``` + +属性定义: +```js +{ + images: { + type: 'array', + source: 'query', + selector: 'img', + query: { + url: { + type: 'string', + source: 'attribute', + attribute: 'src', + }, + alt: { + type: 'string', + source: 'attribute', + attribute: 'alt', + }, + } + } +} +``` + +区块中可用的属性: +```js +{ + "images": [ + { "url": "https://lorempixel.com/1200/800/", "alt": "大图" }, + { "url": "https://lorempixel.com/50/50/", "alt": "小图" } + ] +} +``` \ No newline at end of file diff --git a/reference-guides/block-api/block-bindings.md b/reference-guides/block-api/block-bindings.md new file mode 100644 index 0000000..444fd87 --- /dev/null +++ b/reference-guides/block-api/block-bindings.md @@ -0,0 +1,327 @@ +#### setValues 函数 + +`setValues` 函数用于更新绑定区块源的所有值。它接收一个包含以下属性的 `object` 作为参数: + +- `bindings` 返回特定源的绑定对象。该对象必须以属性作为键,值可以是 `string` 或包含参数的 `object`。此对象包含一个 `newValue` 属性,用于存储用户的输入。 +- `clientId` 返回一个 `string`,表示当前区块的客户端 ID。 +- `context` 返回一个 `object`,表示当前区块的上下文,定义在 `usesContext` 属性中。[关于区块上下文的更多信息](https://developer.wordpress.org/block-editor/reference-guides/block-api/block-context/)。 +- `dispatch` 返回一个 `object`,包含存储的操作创建器。[关于 dispatch 的更多信息](https://developer.wordpress.org/block-editor/reference-guides/packages/packages-data/#dispatch)。 +- `select` 返回一个 `object`,包含给定存储的选择器。[更多信息请参阅文档](https://developer.wordpress.org/block-editor/reference-guides/packages/packages-data/#select)。 + +#### 编辑器注册核心示例 + +核心中有几个示例可供参考: + +- 文章元数据。[源代码](https://github.com/WordPress/gutenberg/blob/5afd6c27bfba2be2e06b502257753fbfff1ae9f0/packages/editor/src/bindings/post-meta.js#L74-L146) +- 模式覆盖。[源代码](https://github.com/WordPress/gutenberg/blob/5afd6c27bfba2be2e06b502257753fbfff1ae9f0/packages/editor/src/bindings/pattern-overrides.js#L8-L100) + +## 注销源 + +_**注意:**自 WordPress 6.7 起。_ + +`unregisterBlockBindingsSource` 通过提供名称注销一个区块绑定源。 + +```js +import { unregisterBlockBindingsSource } from '@wordpress/blocks'; + +unregisterBlockBindingsSource( 'plugin/my-custom-source' ); +``` + +## 获取所有源 + +_**注意:**自 WordPress 6.7 起。_ + +`getBlockBindingsSources` 返回所有已注册的区块绑定源。 + +```js +import { getBlockBindingsSources } from '@wordpress/blocks'; + +const registeredSources = getBlockBindingsSources(); +``` + +## 获取特定源 + +_**注意:**自 WordPress 6.7 起。_ + +`getBlockBindingsSource` 通过名称返回特定的区块绑定源。 + +```js +import { getBlockBindingsSource } from '@wordpress/blocks'; + +const blockBindingsSource = getBlockBindingsSource( 'plugin/my-custom-source' ); +``` + +## 区块绑定工具 + +_**注意:**自 WordPress 6.7 起。_ + +`useBlockBindingUtils` 是一个包含两个辅助函数的钩子,允许开发者轻松编辑 `metadata.bindings` 属性。 + +它接受一个 `clientId` 字符串作为参数,如果未设置,函数将使用上下文中的当前区块客户端 ID。 + +示例: + +```js +import { useBlockBindingsUtils } from '@wordpress/block-editor'; + +const { updateBlockBindings } = useBlockBindingsUtils('my-block-client-id-12345'); +... +``` + +### updateBlockBindings + +`updateBlockBindings` 的工作方式类似于 `updateBlockAttributes`,可用于创建、更新或删除特定连接。 + +```js +import { useBlockBindingsUtils } from '@wordpress/block-editor'; + +const { updateBlockBindings } = useBlockBindingsUtils(); + +function updateBlockBindingsURLSource( url ) { + updateBlockBindings({ + url: { + source: 'myplugin/new-source', + } + }) +} + +// 从 url 属性中移除绑定。 +function removeBlockBindingsURLSource() { + updateBlockBindings( { url: undefined } ); +} +``` + +### removeAllBlockBindings + +`removeAllBlockBindings` 将通过移除 `metadata.bindings` 属性来删除区块中的所有现有连接。 + +```js +import { useBlockBindingsUtils } from '@wordpress/block-editor'; + +const { removeAllBlockBindings } = useBlockBindingsUtils(); + +function clearBlockBindings() { + removeAllBlockBindings(); +} +``` + +#### 服务器注册核心示例 + +核心代码库中有几个可用作参考的示例: + +- 文章元数据。[源代码](https://github.com/WordPress/wordpress-develop/blob/trunk/src/wp-includes/block-bindings/post-meta.php) +- 模式覆盖。[源代码](https://github.com/WordPress/wordpress-develop/blob/trunk/src/wp-includes/block-bindings/pattern-overrides.php) +- 二十五主题。[源代码](https://github.com/WordPress/wordpress-develop/blob/trunk/src/wp-content/themes/twentytwentyfive/functions.php) + +### 编辑器注册 + +_**注意:** 自WordPress 6.7起支持_ + +客户端编辑器注册允许定义绑定区块在获取值或编辑值时的行为。 + +注册自定义源的函数是 `registerBlockBindingsSource( args )`: + +- `args`:包含以下结构的`对象`: + - `name`:具有唯一性且机器可读名称的`字符串` + - `label`:自定义源的人类可读名称`字符串`。若已在服务器端定义,服务器标签将被此标签覆盖,此时不建议在此重复定义(可选) + - `usesContext`:自定义源可能需要的区块上下文`数组`。若已在服务器端定义,则不应在此重复定义(可选) + - `getValues`:从源获取值的`函数`(可选) + - `setValues`:允许更新与源连接值的`函数`(可选) + - `canUserEditValue`:判断用户是否可以编辑值的`函数`。默认情况下用户无法编辑(可选) + +此示例将在编辑器中显示自定义文章元数据日期,若不存在则显示当前日期。用户可以编辑日期值。(注意:此示例未将用户输入格式化为日期——仅用于教学目的) + +```js +import { + registerBlockBindingsSource, +} from '@wordpress/blocks'; +import { __ } from '@wordpress/i18n'; +import { store as coreDataStore } from '@wordpress/core-data'; + +registerBlockBindingsSource( { + name: 'wpmovies/visualization-date', + label: __( '观影日期', 'custom-bindings' ), // 可跳过标签定义,因已在前面示例的服务器端定义 + usesContext: [ 'postType' ], // 可跳过postId定义,因已在前面示例的服务器端定义 + getValues( { select, context } ) { + let wpMoviesVisualizationDate; + const { getEditedEntityRecord } = select( coreDataStore ); + if ( context?.postType && context?.postId ) { + wpMoviesVisualizationDate = getEditedEntityRecord( + 'postType', + context?.postType, + context?.postId + ).meta?.wp_movies_visualization_date; + } + if ( wpMoviesVisualizationDate ) { + return { + content: wpMoviesVisualizationDate, + }; + } + + return { + content: new Date().toLocaleDateString( 'zh-CN' ), + }; + }, + setValues( { select, dispatch, context, bindings } ) { + dispatch( coreDataStore ).editEntityRecord( + 'postType', + context?.postType, + context?.postId, + { + meta: { + wp_movies_visualization_date: bindings?.content?.newValue, + }, + } + ); + }, + canUserEditValue( { select, context } ) { + return true; + }, +} ); +``` + +#### getValues函数 + +`getValues`函数在区块加载时从源获取值。它接收包含以下属性的`对象`作为参数: + +- `bindings` 返回特定源的绑定对象。必须以属性为键,值可以是`字符串`或带参数的`对象` +- `clientId` 返回当前区块客户端ID的`字符串` +- `context` 返回在`usesContext`属性中定义的当前区块上下文`对象`。[关于区块上下文的更多信息](https://developer.wordpress.org/block-editor/reference-guides/block-api/block-context/) +- `select` 返回给定存储库选择器的`对象`。[详细文档](https://developer.wordpress.org/block-editor/reference-guides/packages/packages-data/#select) + +该函数必须返回具有以下结构的`对象`: +`{ '区块属性': 值 }` + +# 数据绑定 + +
    +区块绑定 API 仅适用于 WordPress 6.5 及以上版本。 +
    + +区块绑定 API 允许您将动态数据"绑定"到区块的属性上,这些数据最终会反映在输出到前端浏览器的 HTML 标记中。 + +例如,可以将图片区块的 `url` 属性连接到从外部 API 返回随机图片的函数。 + +```html + +``` + +## 兼容区块及其属性 + +目前并非所有区块属性都支持数据绑定。虽然正在努力提升兼容性,但当前支持的列表如下: + +| 支持区块 | 支持属性 | +| ---------------- | -------------------- | +| 段落 | content | +| 标题 | content | +| 图片 | id, url, title, alt | +| 按钮 | text, url, linkTarget, rel | + +## 注册自定义数据源 + +注册数据源需要至少定义 `name`(名称)、`label`(标签)和 `callback`(回调函数),该函数从数据源获取值并传递给区块属性。 + +数据源注册后,任何支持的区块都可以通过配置 `metadata.bindings` 属性来读取该数据源的值。 + +注册可以通过 PHP 在服务器端完成,也可以通过 JavaScript 在编辑器端完成,两者可以共存。 + +服务器端注册定义的标签将被编辑器端定义的标签覆盖。 + +### 服务器端注册 + +服务器端注册允许应用一个回调函数,该函数将在前端为绑定属性执行。 + +注册自定义数据源的函数是 `register_block_bindings_source($name, $args)`: + +- `name`:`string`,设置自定义数据源的唯一 ID。 +- `args`:`array`,包含: + - `label`:`string`,自定义数据源的可读名称。 + - `uses_context`:`array`,传递给回调函数的区块上下文(可选)。 + - `get_value_callback`:`function`,在绑定区块的渲染函数中运行。它接受三个参数:`source_args`、`block_instance` 和 `attribute_name`。此值可以通过 `block_bindings_source_value` 过滤器进行修改。 + +注意,`register_block_bindings_source()` 应通过附加到 `init` 钩子的处理程序调用。 + +以下是一个示例: + +```php +add_action( + 'init', + function () { + register_block_bindings_source( + 'wpmovies/visualization-date', + array( + 'label' => __( 'Visualization Date', 'custom-bindings' ), + 'get_value_callback' => function ( array $source_args, $block_instance ) { + $post_id = $block_instance->context['postId']; + if ( isset( $source_args['key'] ) ) { + return get_post_meta( $post_id, $source_args['key'], true ); + } + }, + 'uses_context' => array( 'postId' ), + ) + ); + } +); +``` + +此示例需要注册一个 `post_meta`,并且可以使用过滤器返回默认的 `$visualization_date` 值,该值将在下一个标题中显示。 + +```php +add_action( + 'init', + function () { + register_meta( + 'post', + 'wp_movies_visualization_date', + array( + 'show_in_rest' => true, + 'single' => true, + 'type' => 'string', + 'label' => __( 'Movie visualization date', 'custom-bindings' ), + ) + ); + } +); +``` + +
    +注意:以下划线开头的文章元键(例如 `_example_key`)是受保护的,不能用于区块绑定。此外,文章元必须通过 `show_in_rest = true` 注册才能在区块绑定 API 中使用。 +
    + +#### 区块绑定数据源值过滤器 + +_**注意:**自 WordPress 6.7 起。_ + +`get_value_callback` 返回的值可以通过 `block_bindings_source_value` 过滤器进行修改。 +该过滤器具有以下参数: + +- `value`:要过滤的值。 +- `name`:数据源的名称。 +- `source_args`:包含数据源参数的 `array`。 +- `block_instance`:区块实例对象。 +- `attribute_name`:属性名称。 + +示例: + +```php +function wpmovies_format_visualization_date( $value, $name ) { + // 防止此过滤器应用于其他数据源。 + if ( $name !== 'wpmovies/visualization-date' ) { + return $value; + } + if ( ! $value ) { + return date( 'm/d/Y' ); + } + return date( 'm/d/Y', strtotime( $value ) ); +} + +add_filter( 'block_bindings_source_value', 'wpmovies_format_visualization_date', 10, 2 ); +``` \ No newline at end of file diff --git a/reference-guides/block-api/block-context.md b/reference-guides/block-api/block-context.md new file mode 100644 index 0000000..f57c9b1 --- /dev/null +++ b/reference-guides/block-api/block-context.md @@ -0,0 +1,168 @@ +# 上下文 + +区块上下文是一项功能,允许祖先区块提供可供其自身层次结构内的后代区块使用的值。这些后代区块可以继承这些值,而无需依赖硬编码值,也无需明确知晓提供这些值的区块。 + +这在全站编辑中尤其有用。例如,某个区块的内容可能取决于显示它的文章上下文。博客目录模板可能显示许多不同文章的摘要。通过使用区块上下文,仍然可以有一个单一的“文章摘要”区块,根据继承的文章ID显示文章内容。 + +如果您熟悉 [React Context](https://react.dev/learn/passing-data-deeply-with-context),区块上下文采用了许多相同的理念。实际上,客户端区块编辑器对区块上下文的实现是 React Context 的一个非常简单的应用。区块上下文在服务器端 `render_callback` 实现中也受支持,如下面的示例所示。 + +## 定义区块上下文 + +区块上下文在区块的注册设置中定义。一个区块可以提供一个上下文值,或者使用它希望继承的值。 + +### 提供区块上下文 + +区块可以通过在其注册设置中分配一个 `providesContext` 属性来提供上下文值。这是一个将上下文名称映射到区块自身某个属性的对象。该属性值对应的值将提供给后代区块,并可以通过相同的上下文名称引用。目前,区块上下文仅支持源自区块自身属性的值。未来可能会增强以支持其他上下文值来源。 + +```js + attributes: { + recordId: { + type: 'number', + }, + }, + + providesContext: { + 'my-plugin/recordId': 'recordId', + }, +``` + +有关完整示例,请参阅下面的部分。 + +#### 包含命名空间 + +如上例所示,建议您在上下文键的名称中包含命名空间,以避免与其他插件或 WordPress 提供的默认上下文值发生潜在冲突。上下文命名空间应特定于您的插件,并且在大多数情况下可以与区块本身的名称相同。 + +### 使用区块上下文 + +区块可以通过在其注册设置中分配一个 `usesContext` 属性来从祖先提供者继承上下文值。这应分配为区块希望继承的上下文名称数组。 + +```js +registerBlockType('my-plugin/record-title', { + title: 'Record Title', + category: 'widgets', + + usesContext: ['my-plugin/recordId'], + +``` + +## 使用区块上下文 + +一旦区块定义了它希望继承的上下文,就可以在 `edit`(JavaScript)和 `render_callback`(PHP)的实现中访问它。它作为上下文值的对象(JavaScript)或关联数组(PHP)提供,这些值已为区块定义。请注意,只有在区块明确定义了希望继承该值的情况下,上下文值才会可用。 + +注意:区块上下文不适用于 `save`。 + +### JavaScript + +```js +registerBlockType('my-plugin/record-title', { + + edit({ context }) { + return 'The record ID: ' + context['my-plugin/recordId']; + }, + +``` + +### PHP + +区块的上下文值可以从 `$block` 参数的 `context` 属性中获取,该参数作为第三个参数传递给 `render_callback` 函数。 + +```php +register_block_type( 'my-plugin/record-title', array( + 'render_callback' => function( $attributes, $content, $block ) { + return 'The current record ID is: ' . $block->context['my-plugin/recordId']; + }, +) ); +``` + +## 示例 + +1. 创建一个 `record` 区块。 + +``` +npm init @wordpress/block --namespace my-plugin record +cd record +``` + +2. 编辑 `src/index.js`。在 `registerBlockType` 函数中插入 `recordId` 属性和 `providesContext` 属性,并在底部添加 `record-title` 区块的注册: + +```js +registerBlockType( 'my-plugin/record', { + // ... 省略 ... + + attributes: { + recordId: { + type: 'number', + }, + }, + + providesContext: { + 'my-plugin/recordId': 'recordId', + }, + + /** + * @see ./edit.js + */ + edit: Edit, + + /** + * @see ./save.js + */ + save, +} ); + +registerBlockType( 'my-plugin/record-title', { + title: 'Record Title', + category: 'widgets', + + usesContext: [ 'my-plugin/recordId' ], + + edit( { context } ) { + return 'The record ID: ' + context[ 'my-plugin/recordId' ]; + }, + + save() { + return null; + }, +} ); +``` + +3. 编辑 `record` 区块的 `src/edit.js`。将 `Edit` 函数替换为以下代码: + +```js +import { TextControl } from '@wordpress/components'; +import { InnerBlocks } from '@wordpress/block-editor'; + +export default function Edit( props ) { + const MY_TEMPLATE = [ [ 'my-plugin/record-title', {} ] ]; + const { + attributes: { recordId }, + setAttributes, + } = props; + return ( +
    + + setAttributes( { recordId: Number( val ) } ) + } + /> + +
    + ); +} +``` + +4. 编辑 `record` 区块的 `src/save.js`。将 `save` 函数替换为以下代码: + +```js +export default function save( props ) { + return

    The record ID: { props.attributes.recordId }

    ; +} +``` + +5. 创建一篇新文章并添加 `record` 区块。如果您在文本框中输入一个数字,您会看到其下方的 `record-title` 区块中显示相同的数字。 + +![区块上下文示例](https://user-images.githubusercontent.com/8876600/93000215-c8570380-f561-11ea-9bd0-0b2bd0ca1752.png) \ No newline at end of file diff --git a/reference-guides/block-api/block-deprecation.md b/reference-guides/block-api/block-deprecation.md new file mode 100644 index 0000000..d18bb7d --- /dev/null +++ b/reference-guides/block-api/block-deprecation.md @@ -0,0 +1,203 @@ +## 修改属性集 + +有时需要更新属性集,以重命名或修改旧属性。 + +**示例** + +```js +const { registerBlockType } = wp.blocks; + +registerBlockType( 'gutenberg/block-with-deprecated-version', { + // ... 其他区块属性 + + attributes: { + content: { + type: 'string', + default: '随机值', + }, + }, + + save( props ) { + return
    { props.attributes.content }
    ; + }, + + deprecated: [ + { + attributes: { + text: { + type: 'string', + default: '随机值', + }, + }, + + migrate( { text } ) { + return { + content: text, + }; + }, + + save( props ) { + return

    { props.attributes.text }

    ; + }, + }, + ], +} ); +``` + +在上方示例中,我们将区块的标记从 `p` 更新为 `div`,并将 `text` 属性重命名为 `content`。 + +## 修改内部区块 + +某些情况下,在迁移区块时可能需要添加或移除内部区块。 +例如:某个区块需要将标题属性迁移至段落内部区块。 + +**示例** + +```js +const { registerBlockType } = wp.blocks; + +registerBlockType( 'gutenberg/block-with-deprecated-version', { + // ... 区块属性 + + save( props ) { + return

    { props.attributes.title }

    ; + }, + + deprecated: [ + { + attributes: { + title: { + type: 'string', + source: 'html', + selector: 'p', + }, + }, + + migrate( attributes, innerBlocks ) { + const { title, ...restAttributes } = attributes; + + return [ + restAttributes, + [ + createBlock( 'core/paragraph', { + content: attributes.title, + fontSize: 'large', + } ), + ...innerBlocks, + ], + ]; + }, + + save( props ) { + return

    { props.attributes.title }

    ; + }, + }, + ], +} ); +``` + +在上方示例中,我们将区块更新为使用带标题的内部段落区块,而非标题属性。 + +_以上是区块弃用的示例案例。如需查看真实场景的示例,请参阅[核心区块库](https://github.com/WordPress/gutenberg/tree/HEAD/packages/block-library/src)中的弃用实现。核心区块在版本迭代中持续更新,包含简单与复杂的弃用方案。_ + +# 弃用说明 + +> 本页面提供了关于弃用API原理与使用的完整指南。入门教程请参阅[开发者博客](https://developer.wordpress.org/news/)上的[区块弃用基础教程](https://developer.wordpress.org/news/2023/03/block-deprecation-a-tutorial/)。 + +当更新静态区块标记与属性时,区块开发者需要考虑使用旧版区块的现有文章。为提供平滑的升级路径,您可选择以下策略之一: + +- 不弃用原区块,创建新区块(使用不同名称) +- 提供区块的"弃用"版本,允许用户在区块编辑器中打开旧版内容时使用更新后的区块进行编辑。 + +一个区块可包含多个弃用版本。当解析后的区块当前状态无效,或弃用版本定义了返回true的`isEligible`函数时,将尝试执行弃用流程。 + +弃用机制并非像数据库迁移等软件数据更新那样以链式更新方式运作。初看容易认为每个弃用版本会对数据执行必要修改,然后将新形态的区块传递给下一个弃用版本继续处理。但实际流程是: + +1. 若当前`save`方法无法生成有效区块,弃用数组中的第一个弃用版本将接收原始保存内容 +2. 若该版本的`save`方法能生成有效内容,则使用此弃用版本来解析区块属性。若其包含`migrate`方法,系统将使用弃用版本解析的属性运行该方法 +3. 若首个弃用版本的`save`方法无法生成有效区块,则依次尝试数组中后续弃用版本,直到找到能生成有效区块的版本 +4. 首个生成有效区块的弃用版本对应的属性及innerBlocks,将被传回当前`save`方法以生成新的有效区块内容 +5. 此时当前区块应处于有效状态,弃用工作流程随即终止 + +需特别注意:若弃用版本的`save`方法无法生成有效区块,则其`migrate`方法也会被完全跳过——即使`isEligible`函数对给定属性返回true。这意味着若区块存在多个弃用版本且需执行新迁移(如将内容移至`InnerBlocks`),可能需要更新多个弃用版本中的`migrate`方法,以确保所有历史版本都能应用必要更改。 + +另需注意:若弃用版本的`save`方法从其他文件导入额外函数,对这些文件的修改可能意外改变弃用行为。建议将这些函数的快照副本添加到弃用文件中,而非直接导入,以避免意外破坏弃用功能。 + +对于含多个弃用版本的区块,建议将每个弃用版本保存为对应区块版本的常量,再将其添加到区块的`deprecated`数组。数组中的弃用版本应按时间倒序排列,这样区块编辑器会优先尝试应用最新且最可能的弃用版本,避免不必要的性能损耗。 + +**示例** + +```js +const v1 = {}; +const v2 = {}; +const v3 = {}; +const deprecated = [ v3, v2, v1 ]; +``` + +同时建议维护包含不同版本区块内容的[测试固件](https://github.com/WordPress/gutenberg/blob/HEAD/test/integration/fixtures/blocks/README.md),以便轻松验证新弃用版本和迁移功能在所有历史版本中的运行情况。 + +弃用定义位于区块类型的`deprecated`属性中,这是一个弃用对象数组,每个对象包含以下形式: + +- `attributes` (对象):区块弃用版本的[属性定义](/docs/reference-guides/block-api/block-attributes.md) +- `supports` (对象):区块弃用版本的[支持定义](/docs/reference-guides/block-api/block-registration.md) +- `save` (函数):区块弃用版本的[save实现](/docs/reference-guides/block-api/block-edit-save.md) +- `migrate`:(函数,可选)。接收旧属性与内部区块后,应返回新属性或与区块兼容的属性元组数组。如前所述,若弃用版本的`save`函数未返回有效区块,其`migrate`将不会运行,因此需确保迁移功能在所有相关弃用版本中均可用。 + - _参数_ + - `attributes`: 区块旧属性 + - `innerBlocks`: 区块旧内部区块 + - _返回值_ + - `Object | Array`: 更新后的区块属性或元组数组`[attributes, innerBlocks]` +- `isEligible`:(函数,可选)。当函数返回`true`时,即使区块有效仍可由该弃用版本处理迁移。这在区块技术上有效但仍需更新属性或内部区块时特别有用。**注意**:当所有先前弃用版本的save函数结果均无效时,此函数不会被调用。 + - _参数_ + - `attributes`: 从序列化HTML解析得到的原始区块属性(在应用区块类型代码之前) + - `innerBlocks`: 区块当前内部区块 + - `data`: 包含代表区块节点及其结果区块对象属性的对象 + - `data.blockNode`: 解析序列化HTML后得到的区块原始形态 + - `data.block`: 对`blockNode`应用区块类型后得到的区块对象 + - _返回值_ + - `boolean`: 指示此有效区块是否符合该弃用版本的迁移条件 + +
    +重要提示:由于attributessupportssave会影响区块的解析与序列化,它们不会自动从当前版本继承,必须在弃用对象中明确定义才能在迁移过程中被处理。 +
    + +**示例** + +```js +const { registerBlockType } = wp.blocks; +const attributes = { + text: { + type: 'string', + default: 'some random value', + }, +}; +const supports = { + className: false, +}; + +registerBlockType( 'gutenberg/block-with-deprecated-version', { + // ... 其他区块属性 + + attributes, + + supports, + + save( props ) { + return
    { props.attributes.text }
    ; + }, + + deprecated: [ + { + attributes, + + supports, + + save( props ) { + return

    { props.attributes.text }

    ; + }, + }, + ], +} ); +``` + +以上示例中,我们将区块标记从`p`标签更新为`div`标签。 \ No newline at end of file diff --git a/reference-guides/block-api/block-edit-save.md b/reference-guides/block-api/block-edit-save.md new file mode 100644 index 0000000..d0bd72d --- /dev/null +++ b/reference-guides/block-api/block-edit-save.md @@ -0,0 +1,328 @@ +### 验证常见问题解答 + +**区块如何变为无效状态?** + +导致区块失效最常见的两种原因是: + +1. 区块代码存在缺陷,导致内容被意外修改。插件开发者可参阅下文了解如何调试区块失效问题。 +2. 您或外部编辑器对区块的HTML标记进行了更改,导致其不再符合规范。 + +**作为插件开发者,应如何调试区块被标记为无效的问题?** + +开始调试前,请务必先了解上文所述的验证步骤,该步骤记录了检测区块是否无效的流程。当区块重新生成的标记与文章内容中保存的标记不匹配时,该区块即被视为无效,这通常是由于区块属性未能从已保存内容中正确解析所致。 + +若您正在使用[属性源](/docs/reference-guides/block-api/block-attributes.md),请确保从标记中提取的属性完全符合预期,且类型正确(通常应为`'字符串'`或`'数字'`类型)。 + +当检测到区块无效时,浏览器开发者工具控制台会记录警告信息。该警告会包含标记差异具体位置的详细信息。请仔细比对预期标记与实际标记的差异,定位问题根源。 + +**更改区块的`save`行为后,旧内容中出现无效区块应如何修复?** + +请参阅[过时区块处理指南](/docs/reference-guides/block-api/block-deprecation.md),了解如何在主动调整标记时兼容历史内容。 + +## 示例 + +以下是结合使用属性、编辑和保存功能的几个示例。 + +### 将属性保存至子元素 + +```jsx +attributes: { + content: { + type: 'string', + source: 'html', + selector: 'div' + } +}, + +edit: ( { attributes, setAttributes } ) => { + const blockProps = useBlockProps(); + const updateFieldValue = ( val ) => { + setAttributes( { content: val } ); + } + return ( +
    + +
    + ); +}, + +save: ( { attributes } ) => { + const blockProps = useBlockProps.save(); + + return
    { attributes.content }
    ; +}, +``` + +### 通过序列化保存属性 + +理想情况下,已保存的属性应包含在标记中。但有时这并不现实,因此如果未指定属性来源,该属性会被序列化并保存到块的注释分隔符中。 + +此示例适用于动态块(例如[最新文章块](https://github.com/WordPress/gutenberg/blob/HEAD/packages/block-library/src/latest-posts/index.js)),其标记在服务器端渲染。虽然仍需保存函数,但此时仅返回 null,因为该块并不保存编辑器中的内容。 + +```jsx +attributes: { + postsToShow: { + type: 'number', + } +}, + +edit: ( { attributes, setAttributes } ) => { + const blockProps = useBlockProps(); + + return ( +
    + { + setAttributes( { postsToShow: parseInt( val ) } ); + }} + /> +
    + ); +}, + +save: () => { + return null; +} +``` + +## 验证 + +当编辑器加载时,会对文章内容中的所有块进行验证以确定其准确性,从而防止内容丢失。这与块的保存实现密切相关,因为如果编辑器无法正确恢复块,用户可能会无意中删除或修改其内容。在编辑器初始化期间,使用从文章内容解析出的属性重新生成每个块的已保存标记。如果新生成的标记与文章内容中已存储的标记不匹配,则该块将被标记为无效。这是因为我们假设除非用户进行编辑,否则标记应与已保存内容保持一致。 + +如果检测到某个块无效,系统将提示用户选择如何处理无效情况: + +![无效块提示](https://user-images.githubusercontent.com/7753001/88754471-4cf7e900-d191-11ea-9123-3cee20719d10.png) + +点击**尝试恢复块**按钮将尽可能执行恢复操作。 + +点击块侧边的"三点"菜单会显示三个选项: + +- **解决**:打开解决块对话框,其中包含两个按钮: + - **转换为 HTML**:保护已保存文章内容中的原始标记,并将块从其原始类型转换为 HTML 块类型,使用户能够直接修改 HTML 标记。 + - **转换为块**:保护已保存文章内容中的原始标记,并将块从其原始类型转换为经过验证的块类型。 +- **转换为 HTML**:保护已保存文章内容中的原始标记,并将块从其原始类型转换为 HTML 块类型,使用户能够直接修改 HTML 标记。 +- **转换为经典块**:将已保存文章内容中的原始标记视为正确。由于块将从其原始类型转换为经典块类型,因此无法再使用原始块类型的控件来编辑内容。 + +## 保存函数 + +`save` 函数定义了如何将不同属性组合成最终标记,随后将其序列化到 `post_content` 中。 + +```jsx +save: () => { + const blockProps = useBlockProps.save(); + + return
    您的区块内容
    ; +}; +``` + +对于大多数区块而言,`save` 的返回值应是一个 [WordPress 元素实例](/packages/element/README.md),用于表征区块在前端站点的呈现效果。 + +_注意:_ 虽然可以从 `save` 返回字符串值,但该值*会被转义处理*。若字符串包含 HTML 标记,这些标记将在前端站点以字面形式显示,而不会解析为等效的 HTML 节点内容。如必须从 `save` 返回原始 HTML,请使用 `wp.element.RawHTML`。但正如其名所示,这种方式易受[跨站脚本攻击](https://en.wikipedia.org/wiki/Cross-site_scripting),因此建议尽可能使用 WordPress 元素层级结构。 + +_注意:_ 保存函数应是纯函数且无状态,仅依赖于调用时传入的属性。它不应使用任何类似 `useState` 或 `useEffect` 的 API,也不应从其他来源获取信息;例如,无法在函数内部使用数据模块 - `select( store ).selector( ... )`。 +这是因为当外部信息发生变化时,后续编辑文章时可能会导致区块被标记为无效状态([了解更多验证信息](#validation))。 + +若需要将其他信息纳入保存过程,开发者可考虑以下两种方案: + +- 使用[动态区块](/docs/how-to-guides/block-tutorial/creating-dynamic-blocks.md)并在服务器端动态获取所需信息 +- 将外部值存储为属性,并在区块的 `edit` 函数中随变更动态更新 + +对于[动态区块](/docs/how-to-guides/block-tutorial/creating-dynamic-blocks.md),`save` 的返回值可表征区块内容的缓存副本,仅当实现区块的插件被禁用时显示。 + +若未明确指定,默认实现将不会在文章内容中保存动态区块的标记,而是始终在区块前端显示时实时计算。 + +### 区块包装器属性 + +与 `edit` 函数类似,在渲染静态区块时,必须将 `useBlockProps.save()` 返回的区块属性添加到区块的包装元素。这能确保区块类名正确渲染,同时包含由区块支持 API 注入的所有 HTML 属性。 + +### 属性参数 + +与 `edit` 相同,`save` 函数也会接收包含属性的对象参数,这些属性可插入到标记中。 + +```jsx +save: ( { attributes } ) => { + const blockProps = useBlockProps.save(); + + return
    { attributes.content }
    ; +}; +``` + +保存区块时,需按照属性源定义规定的相同格式保存属性。若未指定属性源,属性将保存至区块的注释分隔符。详见[区块属性文档](/docs/reference-guides/block-api/block-attributes.md)获取更多信息。 + +### 内部区块 + +传递给 `save` 函数的参数中还存在第二个属性 `innerBlocks`。该属性通常用于内部操作,实际需要使用的情况极为罕见。 + +初始化时,`innerBlocks` 是一个包含嵌套区块对象表征的数组。在极少数需要使用此属性的场景中,它可帮助您调整区块的渲染方式。例如,您可以根据嵌套区块的数量或特定区块类型的存在情况来差异化渲染区块。 + +```jsx +save: ( { attributes, innerBlocks } ) => { + const { className, ...rest } = useBlockProps.save(); + + // 初始化期间 innerBlocks 也可能是对象 - React 元素 + const numberOfInnerBlocks = innerBlocks?.length; + if ( numberOfInnerBlocks > 1 ) { + className = className + ( className ? ' ' : '' ) + 'more-than-one'; + }; + const blockProps = { ...rest, className }; + + return
    { attributes.content }
    ; +}; +``` + +此示例中,当内部区块数量超过一个时,会为区块添加额外类名,从而实现区块的差异化样式设计。 + +# 编辑与保存 + +当在客户端使用 JavaScript 注册区块时,`edit` 和 `save` 函数定义了区块在编辑器中的渲染方式、操作逻辑以及保存机制。 + +## 编辑函数 + +`edit` 函数描述了区块在编辑器上下文中的结构,决定了编辑器在使用该区块时的渲染内容。 + +```jsx +import { useBlockProps } from '@wordpress/block-editor'; + +// ... +const blockSettings = { + apiVersion: 3, + + // ... + + edit: () => { + const blockProps = useBlockProps(); + + return
    您的区块内容
    ; + }, +}; +``` + +### 区块包装器属性 + +首先需要注意的是在区块包装元素上使用的 `useBlockProps` React 钩子。上例中区块包装器在编辑器内渲染为「div」元素,但为了让 Gutenberg 编辑器能够操作区块、添加必要的额外类名...区块包装元素必须应用从 `useBlockProps` React 钩子调用中获取的属性。区块包装元素应是原生 DOM 元素(如 `
    ` 和 ``),或是能将附加属性传递至原生 DOM 元素的 React 组件。而使用 `` 或 `` 等组件则不符合要求。 + +若包装元素需要自定义 HTML 属性,需将其作为参数传递给 `useBlockProps` 钩子。例如要为包装器添加 `my-random-classname` 类名: + +```jsx +import { useBlockProps } from '@wordpress/block-editor'; + +// ... +const blockSettings = { + apiVersion: 3, + + // ... + + edit: () => { + const blockProps = useBlockProps( { + className: 'my-random-classname', + } ); + + return
    您的区块内容
    ; + }, +}; +``` + +### 属性参数 + +`edit` 函数通过对象参数接收多个属性,可用于调整区块行为。 + +`attributes` 属性会呈现所有可用属性及其对应值,这些属性在区块类型注册时通过 `attributes` 属性定义(详见[属性文档](/docs/reference-guides/block-api/block-attributes.md))。假设在区块注册时定义了 `content` 属性,即可在编辑函数中调用该值: + +```js +edit: ( { attributes } ) => { + const blockProps = useBlockProps(); + + return
    { attributes.content }
    ; +}; +``` + +当区块插入编辑器时,`attributes.content` 的值将显示在 `div` 中。 + +### 选中状态 + +isSelected 属性为布尔值,用于标识区块当前是否被选中。 + +```jsx +edit: ( { attributes, isSelected } ) => { + const blockProps = useBlockProps(); + + return ( +
    + 您的区块内容 + { isSelected && ( + 仅当区块被选中时显示 + ) } +
    + ); +}; +``` + +### 属性设置 + +setAttributes 函数允许区块根据用户交互更新单个属性。 + +```jsx +edit: ( { attributes, setAttributes, isSelected } ) => { + const blockProps = useBlockProps(); + + // 简化属性访问 + const { content, mySetting } = attributes; + + // 用户点击按钮时切换设置 + const toggleSetting = () => setAttributes( { mySetting: ! mySetting } ); + return ( +
    + { content } + { isSelected && ( + + ) } +
    + ); +}; +``` + +使用对象或数组类型的属性时,建议在更新前先进行复制或克隆: + +```js +// 正确做法 - 基于旧列表属性创建新数组并添加新项: +const { list } = attributes; +const addListItem = ( newListItem ) => + setAttributes( { list: [ ...list, newListItem ] } ); + +// 错误做法 - 直接修改现有属性中的列表: +const { list } = attributes; +const addListItem = ( newListItem ) => { + list.push( newListItem ); + setAttributes( { list } ); +}; +``` + +这样做的原因在于:JavaScript 中数组和对象通过引用传递,此做法可确保修改不会影响其他持有相同数据引用的代码。此外,Gutenberg 项目遵循 Redux 库的[状态不可变原则](https://redux.js.org/faq/immutable-data#what-are-the-benefits-of-immutability)——不应直接修改数据,而是创建包含变更的新数据版本。 + +`setAttribute` 也支持以更新函数作为参数。该函数必须是纯函数,接收当前属性作为唯一参数并返回更新后的属性。此方法适用于需要基于先前状态更新值,或处理对象和数组的场景。 + +_**注意:** 自 WordPress 6.9 起支持。_ + +```js +// 用户点击按钮时切换设置 +const toggleSetting = () => + setAttributes( ( currentAttr ) => ( { + mySetting: ! currentAttr.mySetting, + } ) ); + +// 向列表添加项目 +const addListItem = ( newListItem ) => + setAttributes( ( currentAttr ) => ( { + list: [ ...currentAttr.list, newListItem ], + } ) ); +``` \ No newline at end of file diff --git a/reference-guides/block-api/block-metadata.md b/reference-guides/block-api/block-metadata.md new file mode 100644 index 0000000..249cc9e --- /dev/null +++ b/reference-guides/block-api/block-metadata.md @@ -0,0 +1,838 @@ +### 前端资源加载优化 + +自 WordPress 5.8 版本起,开发者可配置仅在前端渲染时加载指定区块类型的脚本与样式。此功能适用于 `block.json` 文件中的以下资源字段: + +- `script` +- `viewScript` +- `style` +- `viewStyle`(WordPress 6.5.0 版本新增) + +## 国际化支持 + +WordPress 字符串发现系统可自动翻译本文档中标记为可翻译的字段。首先需在提供区块元数据的 `block.json` 文件中设置 `textdomain` 属性。 + +**示例:** + +```json +{ + "title": "我的区块", + "description": "我的区块非常出色", + "keywords": [ "出色" ], + "textdomain": "我的插件" +} +``` + +### PHP 实现 + +在 PHP 中,当执行 `register_block_type` 时,本地化属性将在 WordPress 后端自动包裹在 `_x` 函数调用中。这些翻译会以内联脚本形式添加到插件的脚本句柄或 WordPress 核心的 `wp-block-library` 脚本句柄。 + +`register_block_type` 处理可翻译值的方式大致等效于以下代码段: + +```php + _x( '我的区块', '区块标题', '我的插件' ), + 'description' => _x( '我的区块非常出色!', '区块描述', '我的插件' ), + 'keywords' => array( _x( '出色', '区块关键词', '我的插件' ) ), +); +``` + +该实现遵循现有的 [get_plugin_data](https://developer.wordpress.org/reference/functions/get_plugin_data) 函数逻辑,通过解析插件内容获取元数据,并动态应用翻译。 + +### JavaScript 实现 + +在 JavaScript 中,可通过 `@wordpress/blocks` 包的 `registerBlockType` 方法,传入从 `block.json` 加载的元数据对象作为首参数。所有本地化属性会自动包裹在 `_x`(来自 `@wordpress/i18n` 包)函数调用中,其运作机制与 PHP 端类似。 + +**示例:** + +```js +import { registerBlockType } from '@wordpress/blocks'; +import Edit from './edit'; +import metadata from './block.json'; + +registerBlockType( metadata, { + edit: Edit, + // ...其他客户端设置 +} ); +``` + +## 向后兼容性 + +现有注册机制(服务端与前端)将继续生效,它将作为基于 `block.json` 注册方式的底层实现细节。 + +待所有细节完善后,核心区块将进行迭代迁移,第三方区块将在控制台中收到警告提示,建议重构现有区块注册 API。 + +以下属性将继续在客户端保持向后兼容,部分属性未来可能被替代 API 取代: + +- `edit` - 详见[编辑与保存](/docs/reference-guides/block-api/block-edit-save.md)文档 +- `save` - 详见[编辑与保存](/docs/reference-guides/block-api/block-edit-save.md)文档 +- `transforms` - 详见[转换器](/docs/reference-guides/block-api/block-registration.md#transforms-optional)文档 +- `deprecated` - 详见[废弃区块](/docs/reference-guides/block-api/block-deprecation.md)文档 +- `merge` - 当前未收录文档,用于处理多区块合并功能 +- `getEditWrapperProps` - 同样未收录文档,用于向区块编辑组件包装器注入额外属性 + +**示例**: + +```js +import { registerBlockType } from '@wordpress/blocks'; + +registerBlockType( '我的插件/区块名称', { + edit: function () { + // 编辑定义 + }, + save: function () { + // 保存定义 + }, + getEditWrapperProps: function () { + // 实现逻辑 + }, +} ); +``` + +对于 WordPress 支持的[动态区块](/docs/how-to-guides/block-tutorial/creating-dynamic-blocks.md),仍可通过服务端的 [`register_block_type`](https://developer.wordpress.org/reference/functions/register_block_type/) 函数注册 `render_callback` 属性。 + +# 在 block.json 中定义元数据 + +自 WordPress 5.8 版本起,我们建议使用 `block.json` 元数据文件作为通过 PHP(服务端)和 JavaScript(客户端)注册区块类型的标准方式。以下是一个示例 `block.json` 文件,用于为插件创建通知区块定义元数据。 + +**示例:** + +```json +{ + "$schema": "https://schemas.wp.org/trunk/block.json", + "apiVersion": 3, + "name": "my-plugin/notice", + "title": "通知", + "category": "text", + "parent": [ "core/group" ], + "icon": "star", + "description": "显示警告、错误或成功通知...", + "keywords": [ "警示", "消息" ], + "version": "1.0.3", + "textdomain": "my-plugin", + "attributes": { + "message": { + "type": "string", + "source": "html", + "selector": ".message" + } + }, + "providesContext": { + "my-plugin/message": "message" + }, + "usesContext": [ "groupId" ], + "selectors": { + "root": ".wp-block-my-plugin-notice" + }, + "supports": { + "align": true + }, + "styles": [ + { "name": "default", "label": "默认", "isDefault": true }, + { "name": "other", "label": "其他" } + ], + "example": { + "attributes": { + "message": "这是一个通知!" + } + }, + "variations": [ + { + "name": "example", + "title": "示例", + "attributes": { + "message": "这是一个示例!" + } + } + ], + "editorScript": "file:./index.js", + "script": "file:./script.js", + "viewScript": [ "file:./view.js", "example-shared-view-script" ], + "editorStyle": "file:./index.css", + "style": [ "file:./style.css", "example-shared-style" ], + "viewStyle": [ "file:./view.css", "example-view-style" ], + "render": "file:./render.php" +} +``` + +## 使用元数据文件的优势 + +通过 JSON 格式存储区块类型时,区块定义支持在 JavaScript、PHP 和其他语言之间共享代码。而使用 `block.json` 元数据文件注册区块还能带来额外优势。 + +从性能角度来看,当主题支持资源懒加载时,通过 `block.json` 注册的区块将自动实现资源队列优化。在 `style` 或 `script` 属性中列出的前端 CSS 和 JavaScript 资源仅当区块出现在页面上时才会加入队列,从而减小页面体积。 + +此外,由于[区块类型 REST API 接口](https://developer.wordpress.org/rest-api/reference/block-types/)仅能列出在服务端注册的区块,因此建议在服务端注册区块;使用 `block.json` 文件可简化此注册过程。 + +[WordPress 插件目录](https://wordpress.org/plugins/)能够检测 `block.json` 文件,高亮显示插件中包含的区块,并提取其元数据。若希望将区块提交至区块目录,则插件中包含的所有区块都必须具备 `block.json` 文件,以便区块目录识别它们。 + +通过使用预定义的架构描述文件可改善开发体验。支持的编辑器能够提供工具提示、自动补全和架构验证等辅助功能。要使用此架构,请在 `block.json` 文件顶部添加以下内容: + +```json +"$schema": "https://schemas.wp.org/trunk/block.json" +``` + +
    +请查阅区块注册文档了解如何使用元数据注册区块。 +
    + +## 区块 API + +本节描述所有可添加到 `block.json` 文件中用于定义区块类型行为和元数据的属性。 + +### API 版本 + +- 类型:`number` +- 可选性:可选 +- 本地化:不支持 +- 属性:`apiVersion` +- 默认值:`1` + +```json +{ "apiVersion": 3 } +``` + +区块所使用的 Block API 版本。最新版本为 `3`,随 WordPress 6.3 引入。 + +详见 [API 版本文档](/docs/reference-guides/block-api/block-api-versions.md) 获取更多详细信息。 + +### 编辑器脚本 + +- 类型:`WPDefinedAsset`|`WPDefinedAsset[]`([了解更多](#wpdefinedasset)) +- 可选 +- 是否本地化:否 +- 属性:`editorScript` + +```json +{ "editorScript": "file:./index.js" } +``` + +区块类型的编辑器脚本定义。这些脚本仅会在编辑器环境中加入队列。 + +可以传递使用 [`wp_register_script`](https://developer.wordpress.org/reference/functions/wp_register_script/) 函数注册的脚本句柄、相对于 `block.json` 文件的 JavaScript 文件路径,或包含两者混合的列表([了解更多](#wpdefinedasset))。 + +_注意:从 WordPress `6.1.0` 版本开始,支持传递编辑器脚本数组。_ + +### 脚本 + +- 类型:`WPDefinedAsset`|`WPDefinedAsset[]`([了解更多](#wpdefinedasset)) +- 可选 +- 是否本地化:否 +- 属性:`script` + +```json +{ "script": "file:./script.js" } +``` + +区块类型的前端和编辑器脚本定义。这些脚本会在编辑器环境和网站前端查看内容时同时加入队列。 + +可以传递使用 [`wp_register_script`](https://developer.wordpress.org/reference/functions/wp_register_script/) 函数注册的脚本句柄、相对于 `block.json` 文件的 JavaScript 文件路径,或包含两者混合的列表([了解更多](#wpdefinedasset))。 + +_注意:从 WordPress `6.1.0` 版本开始,支持传递脚本数组。_ + +### 视图脚本 + +- 类型:`WPDefinedAsset`|`WPDefinedAsset[]`([了解更多](#wpdefinedasset)) +- 可选 +- 是否本地化:否 +- 属性:`viewScript` +- 自:`WordPress 5.9.0` + +```json +{ "viewScript": [ "file:./view.js", "example-shared-view-script" ] } +``` + +区块类型的前端脚本定义。这些脚本仅会在网站前端查看内容时加入队列。 + +可以传递使用 [`wp_register_script`](https://developer.wordpress.org/reference/functions/wp_register_script/) 函数注册的脚本句柄、相对于 `block.json` 文件的 JavaScript 文件路径,或包含两者混合的列表([了解更多](#wpdefinedasset))。 + +_注意:从 WordPress `6.1.0` 版本开始,支持传递视图脚本数组。_ + +### 视图脚本模块 + +- 类型:`WPDefinedAsset`|`WPDefinedAsset[]`([了解更多](#wpdefinedasset)) +- 可选 +- 是否本地化:否 +- 属性:`viewScriptModule` +- 自:`WordPress 6.5.0` + +```json +{ "viewScriptModule": [ "file:./view.js", "example-shared-script-module-id" ] } +``` + +区块类型的前端脚本模块定义。这些模块仅会在网站前端查看内容时加入队列。 + +可以传递使用 [`wp_register_script_module`](https://developer.wordpress.org/reference/functions/wp_register_script_module/) 函数注册的脚本模块 ID、相对于 `block.json` 文件的 JavaScript 模块路径,或包含两者混合的列表([了解更多](#wpdefinedasset))。 + +目前 WordPress 脚本与 WordPress 脚本模块不兼容。如果前端视图资源依赖于 WordPress 脚本,应使用 `viewScript`;如果依赖于 WordPress 脚本模块(当前为交互性 API),则应使用 `viewScriptModule`。脚本模块的[更多功能](https://core.trac.wordpress.org/ticket/60647)将逐步开放。 + +_注意:此功能自 WordPress `6.5.0` 版本起可用。_ + +### 编辑器样式 + +- 类型:`WPDefinedAsset`|`WPDefinedAsset[]`([了解更多](#wpdefinedasset)) +- 可选 +- 是否本地化:否 +- 属性:`editorStyle` + +```json +{ "editorStyle": "file:./index.css" } +``` + +区块类型的编辑器样式定义。这些样式仅会在编辑器环境中加入队列。 + +可以传递使用 [`wp_register_style`](https://developer.wordpress.org/reference/functions/wp_register_style/) 函数注册的样式句柄、相对于 `block.json` 文件的 CSS 文件路径,或包含两者混合的列表([了解更多](#wpdefinedasset))。 + +_注意:从 WordPress `5.9.0` 版本开始,支持传递编辑器样式数组。_ + +### 名称 + +- 类型:`字符串` +- 必需 +- 是否本地化:否 +- 属性:`name` + +```json +{ "name": "core/heading" } +``` + +区块名称是用于标识区块的唯一字符串。名称必须遵循 `命名空间/区块名称` 的结构,其中命名空间是插件或主题的名称。 + +**注意:** 区块名称只能包含小写字母数字字符、连字符,且最多只能包含一个正斜杠用于表示插件唯一的命名空间前缀。名称必须以字母开头。 + +**注意:** 此名称在注释分隔符中使用,格式为 ``。属于 `core` 命名空间的区块类型在序列化时不包含命名空间。 + +### 标题 + +- 类型:`字符串` +- 必需 +- 是否本地化:是 +- 属性:`title` + +```json +{ "title": "标题" } +``` + +这是区块的显示标题,可以通过翻译函数进行翻译。标题将显示在插入器及编辑器的其他区域中。 + +**注意:** 为了确保区块标题在用户界面中易于阅读和访问,请尽量避免使用过长的标题。 + +### 分类 + +- 类型:`字符串` +- 可选 +- 是否本地化:否 +- 属性:`category` + +```json +{ "category": "文本" } +``` + +区块按分类进行分组,以帮助用户浏览和发现它们。 + +核心提供的分类包括: + +- 文本 +- 媒体 +- 设计 +- 小工具 +- 主题 +- 嵌入 + +插件和主题还可以注册[自定义区块分类](/docs/reference-guides/filters/block-filters.md#managing-block-categories)。 + +实现应预期并容忍未知分类,并提供合理的回退行为(例如使用“文本”分类)。 + +### 父级 + +- 类型:`字符串数组` +- 可选 +- 是否本地化:否 +- 属性:`parent` + +```json +{ "parent": [ "my-block/product" ] } +``` + +设置 `parent` 可以让一个区块仅在嵌套于指定区块内时才可用。例如,可以允许“加入购物车”区块仅在“产品”区块内可用。 + +### 祖先 + +- 类型:`字符串数组` +- 可选 +- 是否本地化:否 +- 属性:`ancestor` +- 自:`WordPress 6.0.0` + +```json +{ "ancestor": [ "my-block/product" ] } +``` + +`ancestor` 属性使区块可以在指定区块类型的子树中的任何位置使用。例如,可以在“列”区块中放置“评论内容”区块,只要“列”区块位于“评论模板”区块的子树中。与 `parent` 属性相比,指定了 `ancestor` 的区块可以放置在子树的任何位置,而指定了 `parent` 的区块必须是直接子级。 + +### 允许的区块 + +- 类型:`字符串数组` +- 可选 +- 是否本地化:否 +- 属性:`allowedBlocks` +- 自:`WordPress 6.5.0` + +```json +{ "allowedBlocks": [ "my-block/product" ] } +``` + +`allowedBlocks` 指定哪些区块类型可以作为该区块的直接子级。例如,“列表”区块可以仅允许“列表项”区块作为子级。 + +### 图标 + +- 类型:`字符串` +- 可选 +- 是否本地化:否 +- 属性:`icon` + +```json +{ "icon": "smile" } +``` + +应指定图标属性以便更容易识别区块。图标可以是 [WordPress 的 Dashicons](https://developer.wordpress.org/resource/dashicons/) 中的任意图标(在非 JavaScript 环境中,图标别名也可作为回退)。 + +**注意:** 也可以通过客户端使用 SVG 元素的源来覆盖此属性。此外,此属性可以通过 JavaScript 定义为包含背景色和前景色的对象。这些颜色将在适用时与图标一起显示,例如在插入器中。自定义 SVG 图标会自动包装在 [wp.primitives.SVG](/packages/primitives/README.md) 组件中,以添加可访问性属性(aria-hidden、role 和 focusable)。 + +### 描述 + +- 类型:`字符串` +- 可选 +- 是否本地化:是 +- 属性:`description` + +```json +{ + "description": "引入新板块并组织内容,以帮助访客更好地浏览" +} +``` + +这是区块的简短描述,可以通过翻译函数进行翻译。此描述将显示在区块检查器中。 + +### 关键词 + +- 类型:`string[]` +- 可选 +- 支持本地化:是 +- 属性:`keywords` +- 默认值:`[]` + +```json +{ "keywords": [ "keyword1", "keyword2" ] } +``` + +有时,一个区块可以设置别名,帮助用户在搜索时发现它。例如,图片区块可能还希望通过“照片”一词被搜索到。您可以通过提供不限数量的术语数组(这些术语会被翻译)来实现此功能。 + +### 版本 + +- 类型:`string` +- 可选 +- 支持本地化:否 +- 属性:`version` +- 自:`WordPress 5.8.0` + +```json +{ "version": "1.0.3" } +``` + +区块的当前版本号,例如 1.0 或 1.0.3。这与插件版本管理类似。此字段可能与区块资源一起用于控制缓存失效,如果区块作者未提供此字段,则使用已安装的 WordPress 版本号代替。 + +### 文本域 + +- 类型:`string` +- 可选 +- 支持本地化:否 +- 属性:`textdomain` +- 自:`WordPress 5.7.0` + +```json +{ "textdomain": "my-plugin" } +``` + +插件/区块的 [gettext](https://www.gnu.org/software/gettext/) 文本域。更多信息请参见[如何国际化您的插件](https://developer.wordpress.org/plugins/internationalization/how-to-internationalize-your-plugin/)页面中的[文本域](https://developer.wordpress.org/plugins/internationalization/how-to-internationalize-your-plugin/#text-domains)部分。 + +### 属性 + +- 类型:`object` +- 可选 +- 支持本地化:否 +- 属性:`attributes` +- 默认值:`{}` + +```json +{ + "attributes": { + "cover": { + "type": "string", + "source": "attribute", + "selector": "img", + "attribute": "src" + }, + "author": { + "type": "string", + "source": "html", + "selector": ".book-author" + } + } +} +``` + +属性提供了区块的结构化数据需求。它们在序列化时可以以不同形式存在,但在通用接口下统一声明。 + +更多详情请参阅[属性文档](/docs/reference-guides/block-api/block-attributes.md)。 + +### 提供上下文 + +- 类型:`object` +- 可选 +- 支持本地化:否 +- 属性:`providesContext` +- 默认值:`{}` + +以对象形式提供上下文,将上下文名称映射到区块自身的某个属性,供该类型区块的子级访问。 + +更多详情请参阅[区块上下文文档](/docs/reference-guides/block-api/block-context.md)。 + +```json +{ + "providesContext": { + "my-plugin/recordId": "recordId" + } +} +``` + +### 上下文 + +- 类型:`string[]` +- 可选 +- 支持本地化:否 +- 属性:`usesContext` +- 默认值:`[]` + +要从祖先提供者继承的上下文值名称数组。 + +更多详情请参阅[区块上下文文档](/docs/reference-guides/block-api/block-context.md)。 + +```json +{ + "usesContext": [ "message" ] +} +``` + +### 选择器 + +- 类型:`object` +- 可选 +- 支持本地化:否 +- 属性:`selectors` +- 默认值:`{}` +- 自:`WordPress 6.3.0` + +在生成 theme.json(全局样式)样式表的区块样式时使用的任何自定义 CSS 选择器,按 `root`、功能或子功能作为键。提供自定义选择器可以更精细地控制样式应用于哪些区块元素,例如,仅将排版样式应用于内部标题,而颜色样式仍应用于外部区块包装器等。 + +更多详情请参阅[选择器文档](/docs/reference-guides/block-api/block-selectors.md)。 + +```json +{ + "selectors": { + "root": ".my-custom-block-selector", + "color": { + "text": ".my-custom-block-selector p" + }, + "typography": { + "root": ".my-custom-block-selector > h2", + "text-decoration": ".my-custom-block-selector > h2 span" + } + } +} +``` + +### 支持功能 + +- 类型:`object` +- 可选 +- 支持本地化:否 +- 属性:`supports` +- 默认值:`{}` + +包含一组用于控制编辑器中使用的功能的选项。更多详情请参阅[支持功能文档](/docs/reference-guides/block-api/block-supports.md)。 + +### 区块样式 + +- 类型:`数组` +- 可选 +- 支持本地化:是(仅限 `label` 字段) +- 属性:`styles` +- 默认值:`[]` + +```json +{ + "styles": [ + { "name": "default", "label": "默认", "isDefault": true }, + { "name": "other", "label": "其他" } + ] +} +``` + +区块样式可用于为区块提供替代样式,其实现原理是通过在区块包装器上添加类名。主题开发者可以利用 CSS 针对已选定的区块样式类名进行样式定制。 + +插件和主题还可以为现有区块注册[自定义区块样式](/docs/reference-guides/block-api/block-styles.md)。 + +### 示例配置 + +- 类型:`对象` +- 可选 +- 支持本地化:否 +- 属性:`example` + +```json +{ + "example": { + "attributes": { + "message": "这是一个通知!" + } + } +} +``` + +该配置用于为区块提供结构化的示例数据。当用户将鼠标悬停在区块上时,这些数据将用于在检查器帮助面板中构建区块预览效果。 + +更多详细信息请参阅[示例配置文档](/docs/reference-guides/block-api/block-registration.md#example-optional)。 + +### 区块变体 + +- 类型:`对象数组|WP预定义路径`([了解更多](#wpdefinedpath)) +- 可选 +- 支持本地化:是(仅限各变体的 `title`、`description` 和 `keywords` 字段) +- 属性:`variations` +- 版本要求:`WordPress 5.9.0` 及以上 + +```json +{ + "variations": [ + { + "name": "example", + "title": "示例", + "attributes": { + "level": 2, + "message": "这是一个示例!" + }, + "scope": [ "block" ], + "isActive": [ "level" ] + } + ] +} +``` + +区块变体 API 允许一个区块拥有多个相似版本,这些版本共享某些通用功能。每个区块变体通过设置不同的初始属性或内部区块来相互区分。当插入区块时,这些属性和/或内部区块会被自动应用。 + +*注意:在 JavaScript 中可为 `isActive` 属性提供函数,为 `icon` 属性提供 React 元素。而在 `block.json` 文件中,这两个属性仅支持字符串类型* + +从 6.7 版本开始,可以在 `block.json` 中指定一个 PHP 文件来在服务端生成区块变体列表: + +```json +{ "variations": "file:./variations.php" } +``` + +该 PHP 文件需要返回包含区块变体的数组。从 PHP 文件返回的变体中的字符串不会自动本地化,需照常使用 `__()` 函数进行处理。 + +例如: + +```php + true, + 'name' => 'wordpress', + 'title' => 'WordPress', + 'icon' => 'wordpress', + 'attributes' => array( + 'service' => 'wordpress', + ), + 'isActive' => array( 'service' ) + ), + array( + 'name' => 'mail', + 'title' => __( '邮件' ), + 'keywords' => array( + __( '邮箱' ), + __( '电子邮件' ) + ), + 'icon' => 'mail', + 'attributes' => array( + 'service' => 'mail', + ), + 'isActive' => array( 'mail' ) + ), +); + +``` + +更多详细信息请参阅[变体文档](/docs/reference-guides/block-api/block-variations.md)。 + +### 区块钩子 + +- 类型:`对象` +- 可选 +- 属性:`blockHooks` +- 版本要求:`WordPress 6.4.0` 及以上 + +```json +{ + "blockHooks": { + "my-plugin/banner": "after" + } +} +``` + +区块钩子 API 允许区块自动插入到指定区块类型的所有实例旁,其相对位置也由被"挂钩"的区块指定。即区块可以选择插入到指定区块类型的前后,或作为其首个子区块/最后一个子区块(即分别在其子区块列表的开头或末尾添加)。被挂钩的区块将同时在前端和编辑器中显示(以便用户进行自定义)。 + +配置中的键是要挂钩的区块名称(`字符串`),值是要挂钩的位置(`字符串`)。请参阅[区块钩子文档](/docs/reference-guides/block-api/block-registration.md#block-hooks-optional)了解更多可用配置信息。 + +### 样式 + +- 类型:`WPDefinedAsset` | `WPDefinedAsset[]`([了解更多](#wpdefinedasset)) +- 可选 +- 是否本地化:否 +- 属性:`style` + +```json +{ "style": [ "file:./style.css", "example-shared-style" ] } +``` + +区块类型的前端和编辑器样式定义。这些样式将在编辑器和前端查看内容时同时加载。 + +可以传递通过 [`wp_register_style`](https://developer.wordpress.org/reference/functions/wp_register_style/) 函数注册的样式句柄、相对于 `block.json` 文件的 CSS 文件路径,或包含两者的混合列表([了解更多](#wpdefinedasset))。 + +*注意:从 WordPress `5.9.0` 版本开始,支持传递样式数组。* + +### 查看样式 + +- 类型:`WPDefinedAsset` | `WPDefinedAsset[]`([了解更多](#wpdefinedasset)) +- 可选 +- 是否本地化:否 +- 属性:`viewStyle` +- 自版本:`WordPress 6.5.0` + +```json +{ "viewStyle": [ "file:./view.css", "example-view-style" ] } +``` + +区块类型的前端样式定义。这些样式仅在前端查看内容时加载。 + +可以传递通过 [`wp_register_style`](https://developer.wordpress.org/reference/functions/wp_register_style/) 函数注册的样式句柄、相对于 `block.json` 文件的 CSS 文件路径,或包含两者的混合列表([了解更多](#wpdefinedasset))。 + +仅前端样式特别适用于交互式区块,用于样式化仅在用户执行某些操作后可见的部分,且这些样式在编辑器中永远不需要。你可以先使用 `style` 属性将所有通用样式放在一个样式表中。仅当需要特定于编辑器的样式或特定于前端的样式时,可以扩展使用 `editorStyle` 和 `viewStyle`,但仍将样式的主要通用部分保留在主样式表中。 + +### 渲染 + +- 类型:`WPDefinedPath`([了解更多](#wpdefinedpath)) +- 可选 +- 是否本地化:否 +- 属性:`render` +- 自版本:`WordPress 6.1.0` + +```json +{ "render": "file:./render.php" } +``` + +在服务器上渲染区块类型以在前端显示时使用的 PHP 文件。该文件暴露以下变量: + +- `$attributes`(`array`):区块属性。 +- `$content`(`string`):区块默认内容。 +- `$block`(`WP_Block`):区块实例。 + +使用 `render` 定义的 `render.php` 文件的示例实现可能如下: + +```php +
    > + +
    +``` + +*注意:在服务器上渲染页面 HTML 时,此文件会为每个区块类型的实例加载。在文件中声明函数或类时,必须考虑到这一点。避免错误风险的最简单方法是从另一个文件调用共享逻辑。* + +## 资源 + +### `WPDefinedPath` + +`WPDefinedPath` 类型是字符串的子类型,其值表示相对于 `block.json` 文件位置的 JavaScript、CSS 或 PHP 文件路径。提供的路径必须以 `file:` 为前缀。此方法基于 npm 处理包的[本地路径](https://docs.npmjs.com/files/package.json#local-paths)的方式。 + +**示例:** + +在 `block.json` 中: + +```json +{ + "render": "file:./render.php" +} +``` + +### `WPDefinedAsset` + +它扩展了 `WPDefinedPath`,适用于 JavaScript 和 CSS 文件。文件路径的替代方案可以是脚本句柄、脚本模块 ID 或样式句柄,引用使用 WordPress 辅助函数已注册的资源。 + +**示例:** + +在 `block.json` 中: + +```json +{ + "editorScript": "file:./index.js", + "script": "file:./script.js", + "viewScriptModule": [ + "file:./view.js", + "example-registered-script-module-id" + ], + "editorStyle": "file:./index.css", + "style": [ "file:./style.css", "example-shared-style" ], + "viewStyle": [ "file:./view.css", "example-view-style" ] +} +``` + +在 WordPress 环境中,当使用 PHP 注册区块时,它将自动注册在 `block.json` 文件中找到的所有脚本、脚本模块和样式,并使用文件路径而非资源句柄。 + +这就是为什么 `WPDefinedAsset` 类型必须提供一种方式来镜像使用 [`wp_register_script`](https://developer.wordpress.org/reference/functions/wp_register_script/)、[`wp_register_script_module`](https://developer.wordpress.org/reference/functions/wp_register_script_module/) 和 [`wp_register_style`](https://developer.wordpress.org/reference/functions/wp_register_style/) 注册脚本、脚本模块和样式所需的参数,然后将这些参数作为与区块关联的句柄或脚本模块 ID。 + +可以提供具有以下结构的对象: + +- `handle`(`string`)- 脚本的名称。如果省略,将自动生成。 +- `dependencies`(`string[]` | `{ id: string, import?: 'dynamic'|'static' }[]`)- 此脚本依赖的已注册脚本句柄数组。脚本模块可以使用简单字符串表示静态依赖项,或使用对象形式表示动态依赖项。动态依赖项是可能在运行时使用或不使用的依赖项,通常与动态 `import('module-id')` 语法一起使用。默认值:`[]`。 +- `version`(`string` | `false` | `null`)- 指定脚本版本号的字符串(如果有),将作为查询字符串添加到 URL 中以实现缓存清除。如果版本设置为 `false`,则自动添加等于当前安装的 WordPress 版本的版本号。如果设置为 `null`,则不添加版本。默认值:`false`。 + +该定义存储在一个单独的 PHP 文件中,该文件以 `.asset.php` 结尾,并位于 `block.json` 中列出的 JS/CSS 文件旁边。WordPress 将通过模式匹配自动检测此文件。此选项是首选,因为预计它将与 `@wordpress/scripts` 包一起自动生成这些资源文件。 + +**示例:** + +``` +build/ +├─ block.json +├─ index.js +└─ index.asset.php +``` + +在 `block.json` 中: + +```json +{ "editorScript": "file:./index.js" } +``` + +在 `build/index.asset.php` 中: + +```php + array( + 'react', + 'wp-blocks', + 'wp-i18n', + ), + 'version' => '3be55b05081a63d8f9d0ecb466c42cfd', +); +``` \ No newline at end of file diff --git a/reference-guides/block-api/block-patterns.md b/reference-guides/block-api/block-patterns.md new file mode 100644 index 0000000..bd96ec4 --- /dev/null +++ b/reference-guides/block-api/block-patterns.md @@ -0,0 +1,206 @@ +## 语义化区块模式 + +在区块主题中,您还可以将区块模式标记为"页眉"或"页脚"模式(模板部件区域)。我们称之为"语义化区块模式"。当用户插入或替换页眉/页脚模板部件时,这些模式将会显示给用户。 + +示例: + +```php + __( '我的页眉', 'my-plugin' ), + 'categories' => array( 'header' ), + // 将该模式指定至"header"区域 + 'blockTypes' => array( 'core/template-part/header' ), + 'content' => '我的区块模式内容', + ) +); +``` + +# 区块模式 + +区块模式是从区块插入器的模式选项卡中可用的预定义区块布局。一旦插入到内容中,这些区块即可用于添加或修改内容及配置。 + +## 区块模式 + +### register_block_pattern + +编辑器自带若干核心区块模式。主题和插件作者可以使用 `register_block_pattern` 辅助函数来注册额外的自定义区块模式。 + +`register_block_pattern` 辅助函数接收两个参数: +- `title`:一个遵循 `命名空间/标题` 命名约定的机器可读标题。 +- `properties`:描述模式属性的数组。 + +区块模式可用的属性包括: + +- `title`(必需):模式的人类可读标题。 +- `content`(必需):模式的区块 HTML 标记。 +- `description`(可选):在插入器中用于描述模式的视觉隐藏文本。描述是可选的,但当标题未能完全说明模式功能时,强烈建议提供。描述将帮助用户在搜索时发现该模式。 +- `categories`(可选):用于对区块模式进行分组的已注册模式类别数组。区块模式可显示在多个类别中。类别必须单独注册才能在此处使用。 +- `keywords`(可选):帮助用户在搜索时发现模式的别名或关键词数组。 +- `viewportWidth`(可选):指定模式预期宽度的整数,以便在插入器中显示模式的缩放预览。 +- `blockTypes`(可选):模式预期与之配合使用的区块类型数组。每个值需为已声明区块的 `name`。 +- `postTypes`(可选):模式限制与之配合使用的文章类型数组。该模式仅在编辑数组中传递的某一文章类型时可用。对于所有其他文章类型,该模式完全不可用。 +- `templateTypes`(可选):模式适用的模板类型数组,例如,如果模式用于 404 页面,则为 `404`;如果模式用于显示单篇文章,则为 `single-post`。 +- `inserter`(可选):默认情况下,所有模式都会出现在插入器中。若要隐藏模式,使其只能通过编程方式插入,请将 `inserter` 设置为 `false`。 +- `source`(可选):表示模式来源的字符串。对于注册模式的插件,传递字符串 `plugin`;对于主题,传递字符串 `theme`。 + +以下代码示例注册了一个名为 `my-plugin/my-awesome-pattern` 的区块模式: + +```php +register_block_pattern( + 'my-plugin/my-awesome-pattern', + array( + 'title' => __( '两个按钮', 'my-plugin' ), + 'description' => _x( '两个水平按钮,左侧按钮为实心填充,右侧按钮为轮廓样式。', '区块模式描述', 'my-plugin' ), + 'content' => "\n\n", + ) +); +``` + +请注意,`register_block_pattern()` 应从附加到 `init` 钩子的处理程序中调用。 + +```php +function my_plugin_register_my_patterns() { + register_block_pattern( ... ); +} + +add_action( 'init', 'my_plugin_register_my_patterns' ); +``` + +## 取消注册区块模式 + +### unregister_block_pattern + +`unregister_block_pattern` 辅助函数允许从主题或插件中取消注册先前已注册的区块模式,它接收一个参数。 + +- `title`:要取消注册的区块模式的名称。 + +以下代码示例取消了名为 `my-plugin/my-awesome-pattern` 的区块模式的注册: + +```php +unregister_block_pattern( 'my-plugin/my-awesome-pattern' ); +``` + +_注意:_ + +`unregister_block_pattern()` 应在附加到 init 钩子的处理程序中调用。 + +```php +function my_plugin_unregister_my_patterns() { + unregister_block_pattern( ... ); +} + +add_action( 'init', 'my_plugin_unregister_my_patterns' ); +``` + +## 区块模式分类 + +区块模式可以使用分类进行分组。区块编辑器自带了一些捆绑的分类,您可以在自定义区块模式中使用。您也可以注册自己的区块模式分类。 + +### register_block_pattern_category + +`register_block_pattern_category` 辅助函数接收两个参数。 + +- `title`:区块模式分类的机器可读标题。 +- `properties`:描述模式分类属性的数组。 + +模式分类的属性包括: + +- `label`(必需):模式分类的人类可读标签。 + +以下代码示例注册了名为 `hero` 的分类: + +```php +register_block_pattern_category( + 'hero', + array( 'label' => __( 'Hero', 'my-plugin' ) ) +); +``` + +_注意:_ + +`register_block_pattern_category()` 应在附加到 init 钩子的处理程序中调用。 + +除非有模式被分配到此分类,否则该分类不会显示在“模式”下。 + +```php +function my_plugin_register_my_pattern_categories() { + register_block_pattern_category( ... ); +} + +add_action( 'init', 'my_plugin_register_my_pattern_categories' ); +``` + +### unregister_block_pattern_category + +`unregister_block_pattern_category` 辅助函数允许从主题或插件中取消注册先前已注册的区块模式分类,它接收一个参数。 + +- `title`:要取消注册的区块模式分类的名称。 + +以下代码示例取消了名为 `hero` 的分类的注册: + +```php +unregister_block_pattern_category( 'hero' ); +``` + +_注意:_ + +`unregister_block_pattern_category()` 应在附加到 init 钩子的处理程序中调用。 + +```php +function my_plugin_unregister_my_pattern_categories() { + unregister_block_pattern_category( ... ); +} + +add_action( 'init', 'my_plugin_unregister_my_pattern_categories' ); +``` + +## 特定于区块类型的区块模式和模式转换 + +可以将一个区块模式附加到一个或多个区块类型。这会将该区块模式添加为该区块类型的可用转换。 + +目前,这些转换仅适用于简单区块(没有内部块的区块)。为了建议某个模式,**所选中的每个块都必须存在于该区块模式中**。 + +例如: + +```php +register_block_pattern( + 'my-plugin/powered-by-wordpress', + array( + 'title' => __( 'Powered by WordPress', 'my-plugin' ), + 'blockTypes' => array( 'core/paragraph' ), + 'content' => ' +

    Powered by WordPress

    + ', + ) +); +``` + +上面的代码注册了一个名为 `my-plugin/powered-by-wordpress` 的区块模式,并在段落块的“转换菜单”中显示该模式。转换结果将保留段落的现有内容并应用其他属性——在本例中为背景色和文本颜色。 + +如上所述,如果我们选择了多个块,并且存在与这些块匹配的上下文模式,那么简单区块的模式转换也可以工作。让我们看一个附加了两个区块类型的模式示例。 + +```php +register_block_pattern( + 'my-plugin/powered-by-wordpress', + array( + 'title' => __( 'Powered by WordPress', 'my-plugin' ), + 'blockTypes' => array( 'core/paragraph', 'core/heading' ), + 'content' => ' +
    + +

    Hi everyone

    + + +

    Powered by WordPress

    + +
    ', + ) +); +``` + +在上面的示例中,如果我们选择**两个区块类型中的任意一个**,无论是段落块还是标题块,此模式都将通过转换所选块的内容来建议,并且还会添加模式中剩余的块。另一方面,如果我们多选一个段落块和一个标题块,则这两个块都将被转换。 + +区块也可以在其他地方使用这些上下文区块模式。例如,当插入新的查询循环块时,会向用户提供附加到该块的所有模式的列表。 \ No newline at end of file diff --git a/reference-guides/block-api/block-registration.md b/reference-guides/block-api/block-registration.md new file mode 100644 index 0000000..1008ae1 --- /dev/null +++ b/reference-guides/block-api/block-registration.md @@ -0,0 +1,371 @@ +## 区块集合 + +## `registerBlockCollection` + +- **类型:** `函数` + +区块可被添加至集合中,将同一来源的所有区块归组管理。 + +`registerBlockCollection` 接收两个参数:`namespace` 和一个包含 `title` 与 `icon` 的设置对象。 + +### 命名空间 + +- **类型:** `字符串` + +此命名空间需与区块名称中声明的命名空间一致;即您的插件或主题的名称。 + +### 设置 + +#### 标题 + +- **类型:** `字符串` + +此标题将在区块插入器的部分显示,列出此集合中的所有区块。 + +#### 图标 + +- **类型:** `对象` + +(可选)在区块插入器中与标题一同显示的图标。 + +```js +// 注册一个区块集合 +registerBlockCollection( 'my-plugin', { title: '我的插件' } ); +``` + +# 注册 + +区块注册 API 参考文档。 + +
    +您可以使用本文档记录的函数仅在客户端通过 JavaScript 注册区块,但推荐的方式是同时使用服务端的 PHP 通过 `block.json` 元数据文件注册新区块类型。请参阅元数据文档获取完整信息 +
    +了解如何创建您的第一个区块。从搭建开发环境、熟悉工具到适应新的开发模式,本教程涵盖了开始创建区块所需掌握的所有知识。 +
    + +## `registerBlockType` + +- **类型:** `函数` + +每个区块都从注册新的区块类型定义开始。要注册区块,您需要使用 [`wp-blocks` 包](/packages/blocks/README.md#registerBlockType) 中的 `registerBlockType` 函数。该函数接收两个参数:区块 `name` 和区块配置对象。 + +### 区块名称 + +- **类型:** `字符串` + +区块名称是用于标识区块的唯一字符串。名称必须采用 `命名空间/区块名称` 的结构,其中命名空间是您的插件或主题名称。 + +```js +// 使用唯一名称注册我的区块 +registerBlockType( 'my-plugin/book', {} ); +``` + +_注意:_ 区块名称只能包含小写字母数字字符和连字符,且必须以字母开头。 + +_注意:_ 此名称在注释分隔符中使用,格式为 ``。核心提供的区块在序列化时不包含命名空间。 + +#### 重要提示:请谨慎选择命名空间 + +区块名称一旦确定后续将无法随意更改。区块名称会存储在使用该区块的每篇文章的内容中,因此更改名称需要编辑所有受影响的文章或运行数据库脚本。 + +#### 命名空间最佳实践 + +- 使用实际的插件/主题名称:`my-awesome-plugin/block-name` +- 避免使用通用名称,如 `editorial/`、`block/` 或 `create-block/` +- 为插件/主题中的所有区块使用相同的命名空间 +- 确保命名空间唯一性,防止与其他插件冲突 + +```js +// 优秀示例 +registerBlockType( 'my-company-blocks/hero', {} ); +registerBlockType( 'awesome-gallery-plugin/slideshow', {} ); + +// 避免以下示例 +registerBlockType( 'create-block/example', {} ); // 过于通用 +registerBlockType( 'block/content', {} ); // 过于通用 +``` + +_注意:_ `registerBlockCollection()` 仅适用于单一命名空间下的区块。 + +### 区块配置 + +- **类型:** `对象` [ `{ 键: 值 }` ] + +区块需要指定若干属性才能成功注册。这些属性通过配置对象定义,包含以下内容: + +#### title + +- **类型:** `字符串` + +这是区块的显示标题,可通过翻译函数进行本地化。标题将显示在插入器及编辑器的其他区域。 + +```js +// 数据对象示例 +title: __( '书籍' ); +``` + +_注意:_ 为确保区块标题在界面中易于阅读和访问,请尽量避免使用过长的标题。 + +#### description(可选) + +- **类型:** `字符串` + +这是区块的简短描述,可通过翻译函数进行本地化。该描述将显示在设置侧边栏的区块选项卡中。 + +```js +description: __( '显示书籍卡片的区块。' ); +``` + +#### category + +- **类型:** `字符串` [ text | media | design | widgets | theme | embed ] + +区块按类别分组,以帮助用户浏览和发现。 + +核心提供的类别包括: + +- text(文本) +- media(媒体) +- design(设计) +- widgets(小工具) +- theme(主题) +- embed(嵌入) + +```js +// 分配到 'widgets' 类别 +category: 'widgets', +``` + +插件和主题也可以注册[自定义区块类别](/docs/reference-guides/filters/block-filters.md#managing-block-categories)。 + +#### variations(可选) + +- **类型:** `Object[]` +- **始于:** `WordPress 5.9.0` + +与声明块样式的方式类似,块类型可以定义用户可供选择的块变体。不同之处在于,此字段不仅改变视觉外观,还提供在插入块时应用初始自定义属性和内部块的方法。更多详情请参阅[块变体 API](/docs/reference-guides/block-api/block-variations.md)。 + +#### supports(可选) + +- **类型:** `Object` + +`supports` 包含一组用于控制编辑器中使用的功能的选项。更多详情请参阅 [`supports` 文档](/docs/reference-guides/block-api/block-supports.md)。 + +#### transforms(可选) + +- **类型:** `Object` + +`transforms` 提供了块可以从哪些内容转换而来以及可以转换为哪些内容的规则。块可以从另一个块、短代码、正则表达式、文件或原始 DOM 节点转换而来。有关每个可用转换的更多信息,请参阅[块转换 API](/docs/reference-guides/block-api/block-transforms.md)。 + +#### parent(可选) + +- **类型:** `Array` + +块可以插入到使用 [`InnerBlocks`](https://github.com/WordPress/gutenberg/blob/HEAD/packages/block-editor/src/components/inner-blocks/README.md) 作为嵌套内容的块中。有时,限制块仅作为嵌套块可用是有用的。例如,您可能希望仅允许“加入购物车”块在“产品”块内使用。 + +设置 `parent` 可以让块要求仅在嵌套在指定块中时才可用。 + +```js +// 仅允许此块嵌套在 Columns 块中 +parent: [ 'core/columns' ], +``` + +#### ancestor(可选) + +- **类型:** `Array` +- **始于:** `WordPress 6.0.0` + +`ancestor` 属性使块可以在指定块类型的子树中的任何位置使用。例如,只要“列”块位于“评论模板”块内的某个位置,就可以将“评论内容”块放置在“列”块中。与 `parent` 属性相比,指定了 `ancestor` 的块可以放置在子树的任何位置,而指定了 `parent` 的块必须是直接子级。 + +```js +// 仅允许此块在 Columns 块的任意层级中嵌套 +ancestor: [ 'core/columns' ], +``` + +#### allowedBlocks(可选) + +- **类型:** `Array` +- **始于:** `WordPress 6.5.0` + +设置 `allowedBlocks` 属性将限制哪些块类型可以作为该块的直接子级嵌套。 + +```js +// 仅允许 Columns 块作为此块的直接子级嵌套 +allowedBlocks: [ 'core/columns' ], +``` + +#### blockHooks(可选) + +- **类型:** `Object` +- **始于:** `WordPress 6.4.0` + +块钩子是一个 API,允许块自动插入到给定块类型的所有实例旁边,其相对位置也由“被钩入”的块指定。也就是说,块可以选择插入到给定块类型之前或之后,或者作为其第一个或最后一个子级(即分别在其子块列表的开头或末尾添加)。被钩入的块将同时显示在前端和编辑器中(以允许用户进行自定义)。 + +键是要钩入的块的名称(`string`),值是要钩入的位置(`string`)。允许的目标值包括: + +- `before` – 在目标块之前注入。 +- `after` – 在目标块之后注入。 +- `firstChild` – 在目标容器块的第一个内部块之前注入。 +- `lastChild` – 在目标容器块的最后一个内部块之后注入。 + +```js +{ + blockHooks: { + 'core/verse': 'before', + 'core/spacer': 'after', + 'core/column': 'firstChild', + 'core/group': 'lastChild', + } +} +``` + +#### 图标(可选) + +- **类型:** `字符串` | `对象` + +应指定图标属性以便更轻松地识别区块。图标可以是任意 [WordPress Dashicon](https://developer.wordpress.org/resource/dashicons/),或自定义的 `svg` 元素。 + +```js +// 为区块指定 Dashicon +icon: 'book-alt', + +// 为区块指定自定义 SVG +icon: , +``` + +**注意:** 自定义 SVG 图标会自动包裹在 [`wp.primitives.SVG` 组件](/packages/primitives/README.md) 中,以添加可访问性属性(`aria-hidden`、`role` 和 `focusable`)。 + +也可以将对象作为图标传递,此时,如上所述的图标应包含在 `src` 属性中。 + +除了 `src`,对象还可以包含背景色和前景色,这些颜色将在适用时与图标一起显示,例如:在插入器中。 + +```js +icon: { + // 指定与图标一起显示的背景色,例如:在插入器中。 + background: '#7e70af', + // 指定图标颜色(可选:如果未设置,将自动定义易读的颜色) + foreground: '#fff', + // 为区块指定图标 + src: , +} , +``` + +#### 关键词(可选) + +- **类型:** `数组` + +有时,区块可能有别名,以帮助用户在搜索时发现它。例如,`image` 区块可能也希望通过 `photo` 被发现。您可以通过提供一组术语(可翻译)来实现。 + +```js +// 通过关键词别名更轻松地发现区块。 +// 这些关键词可以本地化,因此您的关键词可以在不同语言环境中使用。 +keywords: [ __( 'image' ), __( 'photo' ), __( 'pics' ) ], +``` + +#### 样式(可选) + +- **类型:** `数组` + +区块样式可用于为区块提供替代样式。它通过向区块包装器添加类名来实现。使用 CSS,主题开发者可以在选中区块样式时定位该类名。 + +```js +// 注册区块样式。 +styles: [ + // 将样式标记为默认。 + { + name: 'default', + label: __( 'Rounded' ), + isDefault: true + }, + { + name: 'outline', + label: __( 'Outline' ) + }, + { + name: 'squared', + label: __( 'Squared' ) + }, +], +``` + +插件和主题还可以为现有区块注册 [自定义区块样式](/docs/reference-guides/block-api/block-styles.md)。 + +#### 属性(可选) + +- **类型:** `对象` + +属性提供了区块所需的结构化数据。它们在序列化时可以以不同形式存在,但它们在通用接口下一起声明。 + +```js +// 指定区块属性 +attributes: { + cover: { + type: 'string', + source: 'attribute', + selector: 'img', + attribute: 'src', + }, + author: { + type: 'string', + source: 'html', + selector: '.book-author', + }, + pages: { + type: 'number', + }, +}, +``` + +- **参见:[属性](/docs/reference-guides/block-api/block-attributes.md)。** + +#### 示例(可选) + +- **类型:** `对象` + +示例为区块提供了结构化的示例数据。这些数据用于构建区块的预览,当用户将鼠标悬停在区块上时,预览将显示在检查器帮助面板中;当选中区块时,预览将显示在样式面板中。 + +示例对象中提供的数据应与定义的属性匹配。例如: + +```js +example: { + attributes: { + cover: 'https://example.com/image.jpg', + author: 'William Shakespeare', + pages: 500 + }, +}, +``` + +如果未定义 `example`,则不会显示预览。因此,即使未定义任何属性,设置空的示例对象 `example: {}` 也会触发预览显示。 + +还可以通过 `innerBlocks` 使用内部区块扩展区块预览。例如: + +```js +example: { + attributes: { + cover: 'https://example.com/image.jpg', + }, + innerBlocks: [ + { + name: 'core/paragraph', + attributes: { + /* 翻译示例文本。 */ + content: __( + 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Praesent et eros eu felis.' + ), + }, + }, + ], +}, +``` + +还可以通过 `viewportWidth` 以像素为单位定义预览容器的宽度。例如: + +```js +example: { + attributes: { + cover: 'https://example.com/image.jpg', + }, + viewportWidth: 800 +}, +``` \ No newline at end of file diff --git a/reference-guides/block-api/block-selectors.md b/reference-guides/block-api/block-selectors.md new file mode 100644 index 0000000..fd52827 --- /dev/null +++ b/reference-guides/block-api/block-selectors.md @@ -0,0 +1,97 @@ +# 选择器 + +区块选择器是允许区块自定义生成样式时使用的 CSS 选择器的 API。 + +区块可在三个层级自定义其 CSS 选择器:根级、功能级和子功能级。 + +## 根选择器 + +根选择器是区块的主 CSS 选择器。 + +所有区块都需要一个主 CSS 选择器来包含其样式声明。如果未通过区块选择器 API 提供,将默认生成格式为 `.wp-block-<名称>` 的选择器。 + +### 示例 + +```json +{ + ... + "selectors": { + "root": ".my-custom-block-selector" + } +} +``` + +## 功能选择器 + +功能选择器关联区块支持的样式,例如边框、颜色、排版等。 + +区块可能希望将特定功能的样式应用到区块内的不同元素。例如在区块包装器上使用颜色样式,但仅将排版样式应用到内部标题。 + +### 示例 + +```json +{ + ... + "selectors": { + "root": ".my-custom-block-selector", + "color": ".my-custom-block-selector", + "typography": ".my-custom-block-selector > h2" + } +} +``` + +## 子功能选择器 + +这些选择器关联区块支持提供的独立样式,例如 `background-color`。 + +子功能可以在其独有的选择器下生成样式。当某个区块支持子功能无法与应用了该支持其他子功能的元素相同时,这尤其有用。 + +`text-decoration` 就是一个很好的例子。网页浏览器对此样式的渲染方式不同,如果将其添加到包装元素,则很难被覆盖。通过为 `text-decoration` 分配自定义选择器,其样式可以仅定位到应应用的元素。 + +### 示例 + +```json +{ + ... + "selectors": { + "root": ".my-custom-block-selector", + "color": ".my-custom-block-selector", + "typography": { + "root": ".my-custom-block-selector > h2", + "text-decoration": ".my-custom-block-selector > h2 span" + } + } +} +``` + +## 简写形式 + +您可以为相关功能设置单个选择器作为字符串值,而不必为每个子功能指定 CSS 选择器。这是上文示例中 `color` 功能所演示的方法。 + +## 回退机制 + +未针对特定功能配置的选择器将回退到区块的根选择器。类似地,如果子功能未设置自定义选择器,它将回退到其父功能的选择器,如果父功能选择器也不可用,将进一步回退到区块的根选择器。 + +您可以将通用选择器设置为父功能的 `root` 选择器,并仅为不同的子功能定义独特选择器,而不必为多个子功能重复设置选择器。 + +### 示例 + +```json +{ + ... + "selectors": { + "root": ".my-custom-block-selector", + "color": { + "text": ".my-custom-block-selector p" + }, + "typography": { + "root": ".my-custom-block-selector > h2", + "text-decoration": ".my-custom-block-selector > h2 span" + } + } +} +``` + +在上面的示例中,`color.background-color` 子功能未显式设置。由于 `color` 功能也未定义 `root` 选择器,`color.background-color` 将被包含在区块的主根选择器 `.my-custom-block-selector` 下。 + +对于诸如 `typography.font-size` 的子功能,由于存在父功能选择器,它将回退到父功能的选择器,即 `.my-custom-block-selector > h2`。 \ No newline at end of file diff --git a/reference-guides/block-api/block-styles.md b/reference-guides/block-api/block-styles.md new file mode 100644 index 0000000..b2e29e8 --- /dev/null +++ b/reference-guides/block-api/block-styles.md @@ -0,0 +1,142 @@ +# 样式 + +区块样式允许为现有区块应用替代样式。其实现原理是向区块包装器添加 className。当用户选中某个区块样式时,该 className 可用于为区块提供替代样式。关于如何为区块应用样式的完整示例,请参阅[使用样式和样式表](/docs/how-to-guides/block-tutorial/applying-styles-with-stylesheets.md)。 + +_示例:_ + +```js +wp.blocks.registerBlockStyle( 'core/quote', { + name: 'fancy-quote', + label: '花式引用', +} ); +``` + +以上示例为 `core/quote` 区块注册了名为 `fancy-quote` 的区块样式。当用户从样式选择器中选中此区块样式时,区块包装器将添加 `is-style-fancy-quote` 的 className。 + +通过设置 `isDefault: true`,可将注册的区块样式标记为未提供自定义类名时的默认激活样式。这也意味着标记为默认的样式不会在 HTML 输出中添加自定义类名。 + +如需移除区块样式,请使用 `wp.blocks.unregisterBlockStyle()`。 + +_示例:_ + +```js +wp.blocks.unregisterBlockStyle( 'core/quote', 'large' ); +``` + +以上代码将从 `core/quote` 区块中移除名为 `large` 的区块样式。 + +**重要提示:** 在取消注册区块样式时,可能存在[竞态条件](https://en.wikipedia.org/wiki/Race_condition)——注册样式和取消注册样式的代码执行顺序不确定。为确保取消注册代码最后执行,需将注册样式的组件(本例中为 `wp-edit-post`)指定为依赖项。此外,使用 `wp.domReady()` 可确保取消注册代码在 DOM 加载完成后执行。 + +通过以下 PHP 代码加载你的 JavaScript: + +```php +function myguten_enqueue() { + wp_enqueue_script( + 'myguten-script', + plugins_url( 'myguten.js', __FILE__ ), + array( 'wp-blocks', 'wp-dom-ready', 'wp-edit-post' ), + filemtime( plugin_dir_path( __FILE__ ) . '/myguten.js' ) + ); +} +add_action( 'enqueue_block_editor_assets', 'myguten_enqueue' ); +``` + +`myguten.js` 中的 JavaScript 代码: + +```js +wp.domReady( function () { + wp.blocks.unregisterBlockStyle( 'core/quote', 'large' ); +} ); +``` + +## 服务端注册辅助函数 + +虽然提供的示例能完全控制区块样式,但需要编写大量代码。 + +为简化注册和取消注册区块样式的流程,还提供了两个服务端函数:`register_block_style` 和 `unregister_block_style`。 + +### register_block_style + +`register_block_style` 函数的第一个参数接收区块名称,第二个参数接收描述样式属性的数组。 + +样式数组的属性必须包含 `name` 和 `label`: + +- `name`:用于计算 CSS 类的样式标识符 +- `label`:用户可读的样式标签 + +除了这两个必需属性外,样式属性数组还应包含 `inline_style`、`style_handle` 或 `style_data` 属性之一: + +- `inline_style`:包含注册样式所需 CSS 类的内联 CSS 代码 +- `style_handle`:包含已注册样式句柄,该样式将在需要区块样式的地方被加载 +- `style_data`:包含 theme.json 风格的样式属性数组(自 WordPress 6.6 起可用) + +还可以将 `is_default` 属性设置为 `true`,将某个区块样式标记为默认样式。 + +以下代码示例为引用区块注册名为“蓝色引用”的样式,并提供内联样式使采用“蓝色引用”样式的引用区块显示为蓝色: + +```php +register_block_style( + 'core/quote', + array( + 'name' => 'blue-quote', + 'label' => __( '蓝色引用', 'textdomain' ), + 'inline_style' => '.wp-block-quote.is-style-blue-quote { color: blue; }', + ) +); +``` + +如果已注册包含区块样式 CSS 的样式表,只需传递样式表句柄,`register_block_style` 函数将确保其被加载: + +```php +wp_register_style( 'myguten-style', get_template_directory_uri() . '/custom-style.css' ); + +// ... + +register_block_style( + 'core/quote', + array( + 'name' => 'fancy-quote', + 'label' => __( '花式引用', 'textdomain' ), + 'style_handle' => 'myguten-style', + ) +); +``` + +另一种方式是使用 `style_data` 属性(如下例),为图片区块添加带橙色边框和微圆角的样式: + +```php +register_block_style( + array( 'core/image' ), + array( + 'name' => 'orange-border', + 'label' => __( '橙色边框', 'pauli' ), + 'style_data'=> array( + 'border' => array( + 'color' => '#f5bc42', + 'style' => 'solid', + 'width' => '4px', + 'radius' => '15px' + ) + ) + ) +); +``` + +使用 `style_data` 属性允许用户通过**编辑器 > 样式**中的全局样式界面进行修改。该属性自 WordPress 6.6 版本开始提供。 + +更多信息请参阅 WordPress 6.6 开发说明:[区块样式章节](https://make.wordpress.org/core/2024/06/24/section-styles/)。 +另见 WordPress 开发者博客:[在 WordPress 6.6 中使用区块样式变体为区块区域、嵌套元素等设置样式](https://developer.wordpress.org/news/2024/06/styling-sections-nested-elements-and-more-with-block-style-variations-in-wordpress-6-6/)。 + +### unregister_block_style + +`unregister_block_style` 用于取消注册之前通过 `register_block_style` 在服务端注册的区块样式。 + +函数的第一个参数是区块的注册名称,第二个参数是样式名称。 + +以下代码示例从引用区块中取消注册名为 'fancy-quote' 的样式: + +```php +unregister_block_style( 'core/quote', 'fancy-quote' ); +``` + +**重要说明:** `unregister_block_style` 函数仅取消注册通过 `register_block_style` 在服务端注册的样式,不会取消注册通过客户端代码注册的样式。 \ No newline at end of file diff --git a/reference-guides/block-api/block-supports.md b/reference-guides/block-api/block-supports.md new file mode 100644 index 0000000..2d75b40 --- /dev/null +++ b/reference-guides/block-api/block-supports.md @@ -0,0 +1,1166 @@ +## 拆分功能 + +当设置为 `true` 时,按下 `Enter` 键会将当前区块分割为两个独立区块。请注意,此功能仅适用于简单文本区块(例如包含单个 `RichText` 字段的段落和标题)。在 `edit` 函数中的 RichText 组件_必须_包含与文本属性键相匹配的 `identifier` 属性,这样才能正确更新选区定位,并确保系统能准确识别拆分位置。 + +# 支持功能 + +区块支持功能是一套 API,允许区块声明对特定功能的支持。 + +启用这些功能中的任意一项,都会在区块上注册额外属性,并提供操作这些属性的用户界面。 + +为了让属性应用到区块上,生成的属性会被添加到区块的包装元素中。这些属性会被添加到通过 `useBlockProps` 钩子函数返回的对象中。 + +`BlockEdit` 函数: + +```js +function BlockEdit() { + const blockProps = useBlockProps(); + + return
    Hello World!
    ; +} +``` + +`save` 函数: + +```js +function BlockEdit() { + const blockProps = useBlockProps.save(); + + return
    Hello World!
    ; +} +``` + +对于通过 PHP 中的 `render_callback` 渲染的动态区块,可以使用 `get_block_wrapper_attributes()` 函数。该函数返回一个包含所有生成属性的字符串,需要输出到区块包装元素的开始标签中。 + +`render_callback` 函数: + +```php +function render_block() { + $wrapper_attributes = get_block_wrapper_attributes(); + + return sprintf( + '
    %2$s
    ', + $wrapper_attributes, + 'Hello World!' + ); +} +``` + +## allowedBlocks + +_**注意:** 自 WordPress 6.9 起可用。_ + +- 类型:`boolean` +- 默认值:`false` + +此属性添加用户界面控件,使用户能够为区块容器选择允许的子区块。要使用此功能,请在 `useInnerBlocksProps` 的选项对象中将 `attributes.allowedBlocks` 作为 `allowedBlocks` 属性传递。 + +```js +supports: { + allowedBlocks: true +} +``` + +```jsx +import { useBlockProps, useInnerBlocksProps } from '@wordpress/block-editor'; + +function Edit( { attributes } ) { + const { allowedBlocks } = attributes; + const blockProps = useBlockProps(); + const innerBlocksProps = useInnerBlocksProps( blockProps, { + allowedBlocks + } ); + return
    ; +} +``` + +## anchor + +- 类型:`boolean` +- 默认值:`false` + +锚点功能允许您直接链接到页面上的特定区块。此属性添加一个用于定义区块 ID 的字段和一个复制直接链接的按钮。_重要提示:目前尚不适用于动态区块。_ + +```js +// 声明支持锚点链接。 +supports: { + anchor: true +} +``` + +## auto_register + +- 类型:`boolean` +- 默认值:`false` + +启用[仅限 PHP 的区块](/docs/getting-started/fundamentals/registration-of-a-block.md#php-only-blocks-with-auto-registration)自动出现在区块编辑器中,无需进行 JavaScript 注册。当设置为 `true` 时,在服务器上通过 `render_callback` 注册的区块将自动在编辑器中注册并使用 `ServerSideRender`。这些区块默认使用区块 API 版本 3,如果使用旧版本,则会自动升级。 + +```php +register_block_type( 'my-plugin/server-block', array( + 'render_callback' => function( $attributes ) { + $wrapper_attributes = get_block_wrapper_attributes(); + + return sprintf( + '
    服务器内容
    ', + $wrapper_attributes + ); + }, + 'supports' => array( + 'auto_register' => true, + ), +) ); +``` + +## align + +- 类型:`boolean` 或 `array` +- 默认值:`false` + +此属性添加区块控件,支持更改区块的对齐方式。 + +```js +supports: { + // 声明支持区块对齐。 + // 这将支持所有选项: + // 左对齐、居中对齐、右对齐、宽对齐和全宽对齐。 + align: true +} +``` + +```js +supports: { + // 声明支持特定的对齐选项。 + align: [ 'left', 'right', 'full' ] +} +``` + +当区块声明支持 `align` 时,属性定义会扩展为包含一个 `string` 类型的 align 属性。默认情况下,不分配任何对齐方式。区块可以通过指定自己的 `align` 属性并设置默认值来应用默认对齐方式。例如: + +```js +attributes: { + align: { + type: 'string', + default: 'right' + } +} +``` + +## alignWide(宽对齐) + +- 类型:`boolean`(布尔值) +- 默认值:`true` + +此属性可为您的主题启用[宽对齐](/docs/how-to-guides/themes/theme-support.md#wide-alignment)功能。若需对单个区块禁用此功能,请将该标志设为 `false`。 + +```js +supports: { + // 移除宽对齐功能支持 + alignWide: false +} +``` + +## ariaLabel(无障碍标签) + +- 类型:`boolean`(布尔值) +- 默认值:`false` + +ARIA标签可用于为元素定义无障碍访问标签。此属性支持为区块定义aria-label,且无需暴露用户界面字段。 + +```js +supports: { + // 启用无障碍标签支持 + ariaLabel: true +} +``` + +## background(背景) + +_**注意:** 自WordPress 6.5版本起支持。* + +- 类型:`Object`(对象) +- 默认值:`null` +- 子属性说明 + - `backgroundImage`:类型为 `boolean`,默认值 `false` + - `backgroundSize`:类型为 `boolean`,默认值 `false` + +此值表示区块支持部分与背景相关的CSS样式属性。当启用时,若[主题声明支持](/docs/how-to-guides/themes/global-settings-and-styles.md#opt-in-into-ui-controls),区块编辑器将显示用户界面控件供用户设置数值。 + +`backgroundImage` 添加用户界面控件,允许用户选择背景图像。 +`backgroundSize` 添加焦点选择器用于选择背景图像位置,并允许用户选择背景尺寸(覆盖、包含、固定)。 + +```js +supports: { + background: { + backgroundImage: true, // 启用背景图像控制 + backgroundSize: true // 启用背景图像+尺寸控制 + } +} +``` + +当区块声明支持特定背景属性时,其属性定义将扩展至包含 `style` 属性。 + +当选定背景图像时,图像数据将存储在 `style.background.backgroundImage` 中。 + +当选定背景图像并调整其位置或尺寸时,背景位置将存储在 `style.background.backgroundPosition` 中,背景尺寸则存储在 `style.background.backgroundSize` 属性中。 + +- `style`:类型为 `object` 的属性,无默认值。当声明 `backgroundImage` 或 `backgroundSize` 支持时添加,用于存储用户设置的自定义值。 + - `background`:类型为 `object` 的属性。 + - `backgroundImage`:类型为 `object` 的属性,包含所选图像的信息 + - `url`:类型为 `string`,图像URL地址 + - `id`:类型为 `int`,媒体附件ID + - `source`:类型为 `string`,目前唯一值为 `file` + - `title`:类型为 `string`,媒体附件标题 + - `backgroundPosition`:类型为 `string` 的属性,定义背景图像位置,由焦点选择器选定并作为 [`background-position`](https://developer.mozilla.org/en-US/docs/Web/CSS/background-position) 值在CSS中使用 + - `backgroundSize`:类型为 `string` 的属性,定义CSS [`background-size`](https://developer.mozilla.org/en-US/docs/Web/CSS/background-size) 值 + +区块可通过指定带默认值的自身属性来应用默认背景图像、位置和尺寸。例如: + +```js +attributes: { + style: { + background: { + backgroundImage: { + "url":"图像URL" + }, + backgroundPosition:"50% 50%", + backgroundSize: "cover" + } + } +} +``` + +## className(类名) + +- 类型:`boolean`(布尔值) +- 默认值:`true` + +默认情况下,类名 `.wp-block-your-block-name` 会添加到已保存标记的根元素中。这有助于为主题和插件所依赖的区块样式提供一致的机制。若因故不需要在标记中添加类名,可禁用此功能。 + +```js +supports: { + // 移除生成类名的支持 + className: false +} +``` + +## html + +- 类型:`boolean` +- 默认值:`true` + +默认情况下,区块的标记可以单独编辑。要禁用此行为,请将 `html` 设置为 `false`。 + +```js +supports: { + // 移除对 HTML 模式的支持。 + html: false +} +``` + +## inserter + +- 类型:`boolean` +- 默认值:`true` + +默认情况下,所有区块都会出现在插入器、区块转换菜单、样式手册等界面中。若要将某个区块从用户界面的所有部分隐藏,使其只能通过编程方式插入,请将 `inserter` 设置为 `false`。 + +```js +supports: { + // 在插入器中隐藏此区块。 + inserter: false +} +``` + +## interactivity + +- 类型:`boolean` 或 `object` +- 默认值:`false` +- 子属性: + - `clientNavigation`:类型 `boolean`,默认值 `false` + - `interactive`:类型 `boolean`,默认值 `false` + +指示区块是否使用交互性 API 功能。 + +`clientNavigation` 子属性指示区块是否与交互性 API 的客户端导航兼容。 +仅当区块为非交互式,或使用交互性 API 实现交互时,才将其设置为 true。若区块具有交互性但使用原生 JS、jQuery 或其他非交互性 API 的 JS 框架/库,则设置为 false。 + +`interactive` 子属性指示区块是否使用交互性 API 指令。 + +## layout + +- 类型:`boolean` 或 `Object` +- 默认值:null +- 子属性: + - `default`:类型 `Object`,默认值 null + - `allowSwitching`:类型 `boolean`,默认值 `false` + - `allowEditing`:类型 `boolean`,默认值 `true` + - `allowInheriting`:类型 `boolean`,默认值 `true` + - `allowSizingOnChildren`:类型 `boolean`,默认值 `false` + - `allowVerticalAlignment`:类型 `boolean`,默认值 `true` + - `allowJustification`:类型 `boolean`,默认值 `true` + - `allowOrientation`:类型 `boolean`,默认值 `true` + - `allowCustomContentAndWideSize`:类型 `boolean`,默认值 `true` + +此值仅适用于作为内部区块容器的区块。若设置为 `true`,布局类型将为 `flow`。对于其他布局类型,需要在 `default` 对象内显式设置 `type`。 + +### layout.default + +- 类型:`Object` +- 默认值:null + +允许设置 `type` 属性以定义区块的默认布局类型,以及该布局类型固有属性的默认值。例如,对于 `flex` 布局,可以为 `flexWrap` 设置默认值。 + +### layout.allowSwitching + +- 类型:`boolean` +- 默认值:`false` + +暴露一个切换器控件,允许在所有现有布局类型之间切换。 + +### layout.allowEditing + +- 类型:`boolean` +- 默认值:`true` + +决定区块侧边栏中布局控件的显示。若设置为 false,布局控件将被隐藏。 + +### layout.allowInheriting + +- 类型:`boolean` +- 默认值:`true` + +仅适用于 `flow` 布局类型,决定“内部区块使用内容宽度”切换开关的显示。 + +### layout.allowSizingOnChildren + +- 类型:`boolean` +- 默认值:`false` + +仅适用于 `flex` 布局类型,决定在弹性布局区块的所有子区块上显示尺寸控件(适应/填充/固定)。 + +### layout.allowVerticalAlignment + +- 类型:`boolean` +- 默认值:`true` + +仅适用于 `flex` 布局类型,决定区块工具栏中垂直对齐控件的显示。 + +### layout.allowJustification + +- 类型:`boolean` +- 默认值:`true` + +对于 `flex` 布局类型,决定区块工具栏和区块侧边栏中对齐控件的显示。对于 `constrained` 布局类型,决定区块侧边栏中对齐控件的显示。 + +### layout.allowOrientation + +- 类型:`boolean` +- 默认值:`true` + +仅适用于 `flex` 布局类型,决定区块工具栏中方向控件的显示。 + +## color(颜色) + +- 类型:`Object` +- 默认值:null +- 子属性: + - `background`:类型为 `boolean`,默认值 `true` + - `button`:类型为 `boolean`,默认值 `false` + - `enableContrastChecker`:类型为 `boolean`,默认值 `true` + - `gradients`:类型为 `boolean`,默认值 `false` + - `heading`:类型为 `boolean`,默认值 `false` + - `link`:类型为 `boolean`,默认值 `false` + - `text`:类型为 `boolean`,默认值 `true` + +该值表示区块支持与颜色相关的部分属性。当存在此值时,区块编辑器将显示用户界面控件供用户设置这些属性的值。 + +请注意,`background` 和 `text` 键的默认值为 `true`,因此如果存在 `color` 属性,它们也会被视为启用状态: + +```js +supports: { + color: { + // 同时启用文本和背景 UI 控件 + gradients: true // 启用渐变 UI 控件 + } +} +``` + +也可以单独禁用它们: + +```js +supports: { + color: { // 文本 UI 控件处于启用状态 + background: false, // 禁用背景 UI 控件 + gradients: true // 启用渐变 UI 控件 + } +} +``` + +### color.background + +此属性添加用户界面控件,允许用户为区块设置纯背景颜色。 + +当声明颜色支持时,此属性默认启用(与文本颜色同时启用),因此仅设置 color 就会启用背景颜色支持。 + +```js +supports: { + color: true // 启用背景和文本颜色支持 +} +``` + +若要在保持其他颜色支持启用的同时禁用背景支持,请设置为 `false`。 + +```js +supports: { + color: { + // 禁用背景支持,文本颜色支持仍处于启用状态 + background: false + } +} +``` + +当区块声明支持 `color.background` 时,属性定义会扩展以包含两个新属性:`backgroundColor` 和 `style`: + +- `backgroundColor`:一个 `string` 类型的属性,未分配默认值。 + + 当用户从预设背景颜色列表中选择时,预设别名会存储在 `backgroundColor` 属性中。 + + 背景颜色预设来源于 `editor-color-palette` [主题支持](/docs/how-to-guides/themes/theme-support.md#block-color-palettes)。 + + 区块可以通过指定带有默认值的自身属性来应用默认的预设背景颜色。例如: + + ```js + attributes: { + backgroundColor: { + type: 'string', + default: 'some-preset-background-slug', + } + } + ``` + +- `style`:一个 `object` 类型的属性,未分配默认值。 + + 当选择自定义背景颜色时(即使用自定义颜色选择器),自定义颜色值会存储在 `style.color.background` 属性中。 + + 区块可以通过指定带有默认值的自身属性来应用默认的自定义背景颜色。例如: + + ```js + attributes: { + style: { + type: 'object', + default: { + color: { + background: '#aabbcc', + } + } + } + } + ``` + +### color.button + +_**注意:** 自 WordPress 6.5 起可用。_ + +此属性添加区块控件,允许用户在区块中设置按钮颜色(文本、背景)。按钮颜色默认处于禁用状态。 + +要启用按钮颜色支持,请将 `color.button` 设置为 `true`。 + +```js +supports: { + color: { + button: true + } +} +``` + +按钮颜色预设来源于 `editor-color-palette` [主题支持](/docs/how-to-guides/themes/theme-support.md#block-color-palettes)。 + +当区块声明支持 `color.button` 时,属性定义会扩展以包含 `style` 属性: + +- `style`:一个 `object` 类型的属性,未分配默认值。 + + 当选择按钮颜色时,颜色值会存储在 `style.elements.button.color.text` 和 `style.elements.button.color.background` 属性中。 + + 区块可以通过指定带有默认值的自身属性来应用默认的按钮颜色。例如: + + ```js + attributes: { + style: { + type: 'object', + default: { + elements: { + button: { + color: { + text: 'var:preset|color|contrast', + background: '#000000', + } + } + } + } + } + } + ``` + +## 版式设置 + +- 类型:`对象` +- 默认值:`null` +- 子属性: + - `fontSize`:类型为 `布尔值`,默认值 `false` + - `lineHeight`:类型为 `布尔值`,默认值 `false` + - `textAlign`:类型为 `布尔值` 或 `数组`,默认值 `false` + +此对象的存在表示区块支持某些与版式相关的属性。当支持时,区块编辑器将显示版式设置界面,允许用户控制这些属性的值。 + +```js +supports: { + typography: { + // 启用字体大小的支持和界面控制。 + fontSize: true, + // 启用行高的支持和界面控制。 + lineHeight: true, + // 启用文本对齐的支持和界面控制。 + textAlign: true, + }, +} +``` + +### typography.fontSize + +- 类型:`布尔值` +- 默认值:`false` + +此值表示区块支持 CSS 样式属性 `font-size`。当支持时,区块编辑器将显示一个界面控件,供用户设置其值。 + +该控件中显示的值是由主题通过 `editor-font-sizes` [主题支持](/docs/how-to-guides/themes/theme-support.md#block-font-sizes)声明的,如果未提供任何值,则使用默认值。 + +```js +supports: { + typography: { + // 启用字体大小的支持和界面控制。 + fontSize: true, + }, +} +``` + +当区块声明支持 `fontSize` 时,属性定义将扩展为包含两个新属性:`fontSize` 和 `style`: + +- `fontSize`:一个 `字符串` 类型的属性,未分配默认值。它存储用户选择的任何预设值。区块可以通过指定自己的 `fontSize` 属性并设置默认值来应用默认字体大小。例如: + +```js +attributes: { + fontSize: { + type: 'string', + default: 'some-value', + } +} +``` + +- `style`:一个 `对象` 类型的属性,未分配默认值。它存储用户设置的自定义值,并与其他区块支持(如颜色)共享。区块可以通过指定自己的 `style` 属性并设置默认值来应用默认样式。例如: + +```js +attributes: { + style: { + type: 'object', + default: { + typography: { + fontSize: 'value' + } + } + } +} +``` + +### typography.lineHeight + +- 类型:`布尔值` +- 默认值:`false` + +此值表示区块支持 CSS 样式属性 `line-height`。当支持时,区块编辑器将显示一个界面控件,供用户设置其值(前提是[主题声明支持](/docs/how-to-guides/themes/theme-support.md#supporting-custom-line-heights))。 + +```js +supports: { + typography: { + // 启用行高的支持和界面控制。 + lineHeight: true, + }, +} +``` + +当区块声明支持 `lineHeight` 时,属性定义将扩展为包含一个新的 `style` 属性,类型为 `对象`,未分配默认值。它存储用户设置的自定义值。区块可以通过指定自己的 `style` 属性并设置默认值来应用默认样式。例如: + +```js +attributes: { + style: { + type: 'object', + default: { + typography: { + lineHeight: 'value' + } + } + } +} +``` + +### typography.textAlign + +_**注意:**自 WordPress 6.6 起。_ + +- 类型:`布尔值` 或 `数组` +- 默认值:`false` + +此属性添加区块工具栏控件,允许更改区块的文本对齐方式。 + +```js +supports: { + typography: { + // 声明支持区块的文本对齐。 + // 这将支持所有选项: + // 左对齐、居中对齐、右对齐。 + textAlign: true + } +} +``` + +```js +supports: { + typography: { + // 声明支持特定的文本对齐选项。 + textAlign: [ 'left', 'right' ] + } +} +``` + +当区块声明支持 `textAlign` 时,属性定义将扩展为包含一个新的 `style` 属性,类型为 `对象`,未分配默认值。它存储用户设置的自定义值。区块可以通过指定自己的 `style` 属性并设置默认值来应用默认样式。例如: + +```js +attributes: { + style: { + type: 'object', + default: { + typography: { + textAlign: 'value' + } + } + } +} +``` + +### color.text + +该属性用于添加区块控件,允许用户在区块中设置文本颜色。 + +当声明支持颜色功能时,此属性会默认启用(同时启用背景色),因此仅需设置颜色属性即可启用文本颜色功能。 + +```js +supports: { + color: true // 同时启用背景色和文本颜色,但不启用链接颜色 +} +``` + +若需在保持其他颜色支持功能的情况下禁用文本颜色支持,可将 `color.text` 设为 `false`。 + +```js +supports: { + color: { + // 禁用文本颜色支持 + text: false + } +} +``` + +文本颜色预设值来源于 `editor-color-palette` [主题支持功能](/docs/how-to-guides/themes/theme-support.md#block-color-palettes)。 + +当区块声明支持 `color.text` 时,属性定义将扩展包含两个新属性:`textColor` 和 `style`: + +- `textColor`:未设默认值的字符串类型属性。 + + 当用户从预设文本颜色列表中选择时,预设别名将存储在 `textColor` 属性中。 + + 区块可通过指定带默认值的自定义属性来应用预设文本颜色。例如: + + ```js + attributes: { + textColor: { + type: 'string', + default: '某个预设文本颜色别名', + } + } + ``` + +- `style`:未设默认值的对象类型属性。 + + 当选择自定义文本颜色时(即使用自定义颜色选择器),自定义颜色值将存储在 `style.color.text` 属性中。 + + 区块可通过指定带默认值的自定义属性来应用默认自定义文本颜色。例如: + + ```js + attributes: { + style: { + type: 'object', + default: { + color: { + text: '#aabbcc', + } + } + } + } + ``` + +## customClassName + +- 类型:`boolean` +- 默认值:`true` + +该属性用于添加自定义 className 字段以定义区块包装器类名。 + +```js +supports: { + // 移除对自定义 className 的支持 + customClassName: false +} +``` + +## dimensions + +_**注意:** 自 WordPress 6.2 起可用。_ + +- 类型:`Object` +- 默认值:null +- 子属性: + - `minHeight`:类型 `boolean`,默认值 `false` + +该值表示区块支持部分与尺寸相关的 CSS 样式属性。当启用时,若[主题声明支持](/docs/how-to-guides/themes/global-settings-and-styles.md#opt-in-into-ui-controls),区块编辑器将显示用户界面控件供用户设置数值。 + +```js +supports: { + dimensions: { + aspectRatio: true // 启用宽高比控件 + minHeight: true // 启用最小高度控件 + } +} +``` + +当区块声明支持特定尺寸属性时,其属性定义将扩展包含 `style` 属性: + +- `style`:未设默认值的对象类型属性。该属性在声明支持 `aspectRatio` 或 `minHeight` 时添加,用于存储用户设置的自定义值。例如: + +```js +attributes: { + style: { + dimensions: { + aspectRatio: "16/9", + minHeight: "50vh" + } + } +} +``` + +## filter + +- 类型:`Object` +- 默认值:null +- 子属性: + - `duotone`:类型 `boolean`,默认值 `false` + +该值表示区块支持部分与滤镜相关的属性。当启用时,区块编辑器将显示用户界面控件供用户设置数值。 + +### filter.duotone + +该属性用于添加用户界面控件,允许用户对区块或区块局部应用双色调滤镜。 + +```js +supports: { + filter: { + // 启用双色调支持 + duotone: true + } +}, +selectors: { + filter: { + // 对图片区块内的 img 元素应用滤镜 + duotone: '.wp-block-image img' + } +} +``` + +通过设置 `selectors.filter.duotone` 选择器,可将滤镜应用于区块内部的元素。 + +双色调预设值来源于 [theme.json](/docs/how-to-guides/themes/global-settings-and-styles.md) 中的 `color.duotone`。 + +当区块声明支持 `filter.duotone` 时,属性定义将扩展包含 `style` 属性: + +- `style`:未设默认值的对象类型属性。 + + 区块可通过指定带默认值的自定义属性来应用默认双色调。例如: + + ```js + attributes: { + style: { + type: 'object', + default: { + color: { + duotone: [ + '#FFF', + '#000' + ] + } + } + } + } + ``` + +### color.enableContrastChecker + +_**注意:** 自 WordPress 6.5 起可用。_ + +决定对比度检查器小部件是否在区块编辑器界面中显示。 + +仅当区块声明支持颜色时,对比度检查器才会显示。它会测试颜色组合的可读性,并在存在潜在问题时发出警告。该属性默认启用。设置为 `false` 可显式禁用: + +```js +supports: { + color: { + enableContrastChecker: false + } +} +``` + +### color.__experimentalDuotone + +_**注意:** 自 WordPress 6.3 起已弃用。_ + +该属性已被 [`filter.duotone`](#filterduotone) 替代。 + +### color.gradients + +此属性添加用户界面控件,允许用户为区块应用渐变背景。 + +```js +supports: { + color: { + gradients: true, + // 如果不想将默认值与渐变一起使用,则必须禁用它们。 + background: false, + text: false + } +} +``` + +渐变预设来源于 `editor-gradient-presets` [主题支持](/docs/how-to-guides/themes/theme-support.md#block-gradient-presets)。 + +当区块声明支持 `color.gradient` 时,属性定义会扩展为包含两个新属性:`gradient` 和 `style`: + +- `gradient`:一个 `string` 类型的属性,未分配默认值。 + + 当用户从预设渐变列表中选择时,预设别名会存储在 `gradient` 属性中。 + + 区块可以通过指定带有默认值的自身属性来应用默认预设渐变。例如: + + ```js + attributes: { + gradient: { + type: 'string', + default: 'some-preset-gradient-slug', + } + } + ``` + +- `style`:一个 `object` 类型的属性,未分配默认值。 + + 当选择自定义渐变(即使用自定义渐变选择器)时,自定义渐变值会存储在 `style.color.gradient` 属性中。 + + 区块可以通过指定带有默认值的自身属性来应用默认自定义渐变。例如: + + ```js + attributes: { + style: { + type: 'object', + default: { + color: { + gradient: 'linear-gradient(135deg,rgb(170,187,204) 0%,rgb(17,34,51) 100%)', + } + } + } + } + ``` + +### color.heading + +_**注意:** 自 WordPress 6.5 起可用。_ + +此属性添加区块控件,允许用户在区块中设置标题颜色。标题颜色默认禁用。 + +要启用标题颜色支持,请将 `color.heading` 设置为 `true`。 + +```js +supports: { + color: { + // 启用标题颜色支持。 + heading: true + } +} +``` + +标题颜色预设来源于 `editor-color-palette` [主题支持](/docs/how-to-guides/themes/theme-support.md#block-color-palettes)。 + +当区块声明支持 `color.heading` 时,属性定义会扩展为包含 `style` 属性: + +- `style`:一个 `object` 类型的属性,未分配默认值。 + + 当选择标题颜色时,颜色值会存储在 `style.elements.heading.color.text` 和 `style.elements.heading.color.background` 属性中。 + + 区块可以通过指定带有默认值的自身属性来应用默认标题颜色。例如: + + ```js + attributes: { + style: { + type: 'object', + default: { + elements: { + heading: { + color: { + text: 'var:preset|color|contrast', + background: '#000000', + } + } + } + } + } + } + ``` + +### color.link + +此属性添加区块控件,允许用户在区块中设置链接颜色。链接颜色默认禁用。 + +要启用链接颜色支持,请将 `color.link` 设置为 `true`。 + +```js +supports: { + color: { + link: true + } +} +``` + +链接颜色预设来源于 `editor-color-palette` [主题支持](/docs/how-to-guides/themes/theme-support.md#block-color-palettes)。 + +当区块声明支持 `color.link` 时,属性定义会扩展为包含 `style` 属性: + +- `style`:一个 `object` 类型的属性,未分配默认值。 + + 当选择链接颜色时,颜色值会存储在 `style.elements.link.color.text` 和 `style.elements.link.:hover.color.text` 属性中。 + + 区块可以通过指定带有默认值的自身属性来应用默认链接颜色。例如: + + ```js + attributes: { + style: { + type: 'object', + default: { + elements: { + link: { + color: { + text: 'var:preset|color|contrast', + }, + ":hover": { + color: { + text: "#000000" + } + } + } + } + } + } + } + ``` + +### layout.allowCustomContentAndWideSize + +- 类型:`boolean` +- 默认值:`true` + +仅适用于 `constrained` 布局类型,用于控制区块侧边栏中自定义内容与宽尺寸控件的显示。 + +## lock + +- 类型:`boolean` +- 默认值:`true` + +区块可能需要禁用锁定状态的切换功能。默认情况下,用户可通过区块“选项”下拉菜单锁定/解锁区块。若要禁用此行为,可将 `lock` 设为 `false`。 + +```js +supports: { + // 移除对锁定UI的支持 + lock: false +} +``` + +## multiple + +- 类型:`boolean` +- 默认值:`true` + +非多重区块在每个文章中只能插入一次。例如,内置的“更多”区块若已存在于正在编辑的文章中,则无法再次插入。非多重区块的图标会自动变灰(不可点击)以防止多次插入。 + +```js +supports: { + // 每篇文章仅使用该区块一次 + multiple: false +} +``` + +## position + +_**注意:** 自 WordPress 6.2 起可用。_ + +- 类型:`Object` +- 默认值:null +- 子属性: + - `sticky`:类型为 `boolean`,默认值 `false` + +此值表示区块支持部分与定位相关的 CSS 样式属性。若声明支持,且[主题声明支持](/docs/how-to-guides/themes/global-settings-and-styles.md#opt-in-into-ui-controls),区块编辑器将显示 UI 控件供用户设置这些值。 + +注意:粘性定位控件目前仅适用于文档根层级的区块。将区块设为 `sticky` 位置后,当用户滚动页面时,该区块会粘附至其直接父级。 + +```js +supports: { + position: { + sticky: true // 启用选择粘性定位 + } +} +``` + +当区块声明支持特定定位属性时,其属性定义会扩展以包含 `style` 属性。 + +- `style`:类型为 `object` 的属性,无默认值。当声明支持 `sticky` 时添加。它存储用户设置的自定义值。例如: + +```js +attributes: { + style: { + position: { + type: "sticky", + top: "0px" + } + } +} +``` + +## renaming + +_**注意:** 自 WordPress 6.5 起可用。_ + +- 类型:`boolean` +- 默认值:`true` + +默认情况下,用户可通过区块“选项”下拉菜单或“高级”面板重命名区块。若要禁用此行为,可将 `renaming` 设为 false。 + +```js +supports: { + // 禁止在编辑器中重命名该区块 + renaming: false, +} +``` + +## reusable + +- 类型:`boolean` +- 默认值:`true` + +区块可能需要禁用转换为可重用区块的功能。默认情况下,所有区块均可转换为可重用区块。若将 `reusable` 支持设为 false,则不会显示将区块转换为可重用区块的选项。 + +```js +supports: { + // 禁止将该区块转换为可重用区块 + reusable: false, +} +``` + +## shadow + +_**注意:** 自 WordPress 6.5 起可用。_ + +- 类型:`boolean` +- 默认值:`false` + +此属性添加区块控件,允许用户为区块设置盒子阴影。默认情况下阴影功能是禁用的。 + +```js +supports: { + shadow: true // 启用盒子阴影选择器 +} +``` + +阴影预设来源于 `theme.json` 中定义的阴影预设。 + +当区块声明支持 `shadow` 时,其属性定义会扩展以包含 `style` 属性: + +- `style`:类型为 `object` 的属性,无默认值。 + + 当选择阴影时,颜色值会存储在 `style.shadow` 中。 + + 区块可通过指定带有默认值的自身属性来应用默认阴影。例如: + + ```js + attributes: { + style: { + type: 'object', + default: { + shadow: "var:preset|shadow|deep" + } + } + } + ``` + +## spacing + +- 类型:`Object` +- 默认值:null +- 子属性: + - `margin`:类型为 `boolean` 或 `array`,默认值 `false` + - `padding`:类型为 `boolean` 或 `array`,默认值 `false` + - `blockGap`:类型为 `boolean` 或 `array`,默认值 `false` + +此值表示区块支持部分与间距相关的 CSS 样式属性。若声明支持,且[主题声明支持](/docs/how-to-guides/themes/theme-support.md#cover-block-padding),区块编辑器将显示 UI 控件供用户设置这些值。 + +```js +supports: { + spacing: { + margin: true, // 启用边距 UI 控件 + padding: true, // 启用内边距 UI 控件 + blockGap: true, // 对同样使用 `layout` 的区块启用区块间距 UI 控件 + } +} +``` + +当区块声明支持特定间距属性时,其属性定义会扩展以包含 `style` 属性。 + +- `style`:类型为 `object` 的属性,无默认值。当声明支持 `margin` 或 `padding` 时添加。它存储用户设置的自定义值。例如: + +```js +attributes: { + style: { + margin: 'value', + padding: { + top: 'value', + } + } +} +``` + +间距属性可定义可配置的允许边——'top'、'right'、'bottom'、'left'。当定义了此类任意边时,仅显示这些边的 UI 控件。 + +轴向边使用 `vertical` 和 `horizontal` 术语定义,并为每个轴向对显示单个 UI 控件(例如,`vertical` 同时控制顶部和底部边)。间距属性可以支持任意单个边 **或** 轴向边,但不能混合使用两者。 + +注意:`blockGap` 接受 `vertical` 和 `horizontal` 轴向边,用于调整间距的列和行值。`blockGap` 不支持任意边。 + +```js +supports: { + spacing: { + margin: [ 'top', 'bottom' ], // 为任意边启用边距 + padding: true, // 为所有边启用内边距 + blockGap: [ 'horizontal', 'vertical' ], // 启用轴向(列/行)区块间距控件 + } +} +``` \ No newline at end of file diff --git a/reference-guides/block-api/block-templates.md b/reference-guides/block-api/block-templates.md new file mode 100644 index 0000000..49a7367 --- /dev/null +++ b/reference-guides/block-api/block-templates.md @@ -0,0 +1,184 @@ +# 模板 + +区块模板被定义为一组区块项目的列表。这类区块可以包含预定义的属性、占位符内容,并且可以是静态或动态的。区块模板允许为编辑器会话指定默认的初始状态。 + +模板的适用范围包括: + +- 在客户端动态设置默认状态(例如 `defaultBlock`) +- 注册为特定文章类型的默认模板 + +计划新增功能: + +- 保存并作为“页面模板”分配给页面 +- 在 `template.php` 文件中定义,或从站点特定的自定义文章类型(`wp_templates`)中拉取 +- 作为主题层级的等效方案 + +## API + +模板可以在 JS 或 PHP 中声明为一个 blockTypes 数组(区块名称和可选属性)。 + +以下第一个 PHP 示例为文章创建了一个包含图片区块的模板,您可以根据需要向模板中添加任意数量的区块。 + +PHP 示例: + +```php +template = array( + array( 'core/image' ), + ); +} +add_action( 'init', 'myplugin_register_template' ); +``` + +以下 JavaScript 示例使用 [InnerBlocks](https://github.com/WordPress/gutenberg/blob/HEAD/packages/block-editor/src/components/inner-blocks/README.md) 和模板创建了一个新区块,插入时会基于模板生成一组区块。 + +```js +const el = React.createElement; +const { registerBlockType } = wp.blocks; +const { InnerBlocks } = wp.blockEditor; + +const BLOCKS_TEMPLATE = [ + [ 'core/image', {} ], + [ 'core/paragraph', { placeholder: '图片详情' } ], +]; + +registerBlockType( 'myplugin/template', { + title: '我的模板区块', + category: 'widgets', + edit: ( props ) => { + return el( InnerBlocks, { + template: BLOCKS_TEMPLATE, + templateLock: false, + } ); + }, + save: ( props ) => { + return el( InnerBlocks.Content, {} ); + }, +} ); +``` + +有关实际使用模板的完整示例,请参阅 [元区块教程](/docs/how-to-guides/metabox.md#step-4-finishing-touches)。 + +## 区块属性 + +要查找可在模板中定义的所有区块属性的完整列表,请查阅区块的 `block.json` 文件,并查看 `attributes` 和 `supports` 值。 + +例如,[packages/block-library/src/heading/block.json](https://github.com/WordPress/gutenberg/blob/c62ccd80c7c6abb85740cf8745439029bf0f4d35/packages/block-library/src/heading/block.json#L5-L25) 显示该区块具有 `level` 属性,并支持 `anchor` 参数。 + +如果您没有安装 Gutenberg 插件,可以在 `wp-includes/blocks/heading/block.json` 中找到 `block.json` 文件。 + +## 自定义文章类型 + +自定义文章类型可以在注册过程中注册自己的模板: + +```php +function myplugin_register_book_post_type() { + $args = array( + 'public' => true, + 'label' => '书籍', + 'show_in_rest' => true, + 'template' => array( + array( 'core/image', array( + 'align' => 'left', + ) ), + array( 'core/heading', array( + 'placeholder' => '添加作者...', + ) ), + array( 'core/paragraph', array( + 'placeholder' => '添加描述...', + ) ), + ), + ); + register_post_type( 'book', $args ); +} +add_action( 'init', 'myplugin_register_book_post_type' ); +``` + +## 锁定 + +有时可能需要在用户界面锁定模板,使得呈现的区块无法被操作。这可以通过 `template_lock` 属性实现。 + +```php +function myplugin_register_template() { + $post_type_object = get_post_type_object( 'post' ); + $post_type_object->template = array( + array( 'core/paragraph', array( + 'placeholder' => '添加描述...', + ) ), + ); + $post_type_object->template_lock = 'all'; +} +add_action( 'init', 'myplugin_register_template' ); +``` + +_选项:_ + +- `contentOnly` — 阻止所有操作。此外,没有内容的区块类型会从列表视图中隐藏,并且无法在区块列表中获得焦点。与其他锁定类型不同,此设置不能被子级覆盖。 +- `all` — 阻止所有操作。无法插入新区块、移动现有区块或删除区块。 +- `insert` — 阻止插入或移除区块,但允许移动现有区块。 + +锁定设置可以由 InnerBlocks 继承。如果未在 InnerBlocks 区域中设置 `templateLock`,则使用父级 InnerBlocks 区域的锁定设置。如果区块是顶级区块,则使用当前文章类型的锁定配置。 + +## 单个区块锁定 + +除了模板级锁定之外,您还可以锁定单个区块;您可以在属性级别使用 `lock` 属性来实现这一点。区块级锁定的优先级高于 `templateLock` 功能。目前,您可以锁定移动和删除区块的操作。 + +```js +attributes: { + // 防止区块被移动或移除。 + lock: { + remove: true, + move: true, + } +} +``` + +_选项:_ + +- `remove` — 锁定区块,防止其被移除。 +- `move` — 锁定区块,防止其被移动。 + +您可以将此功能与 `templateLock` 结合使用,通过将 `remove` 或 `move` 设置为 `false` 来锁定除单个区块之外的所有区块。 + +```php +$template = array( + array( 'core/image', array( + 'align' => 'left', + ) ), + array( 'core/heading', array( + 'placeholder' => '添加作者...', + ) ), + // 允许移动或移除段落区块。 + array( 'core/paragraph', array( + 'placeholder' => '添加描述...', + 'lock' => array( + 'move' => false, + 'remove' => false, + ), + ) ), +); +``` + +## 嵌套模板 + +像列区块这样的容器区块也支持模板。这是通过为区块分配嵌套模板来实现的。 + +```php +$template = array( + array( 'core/paragraph', array( + 'placeholder' => '添加根级段落', + ) ), + array( 'core/columns', array(), array( + array( 'core/column', array(), array( + array( 'core/image', array() ), + ) ), + array( 'core/column', array(), array( + array( 'core/paragraph', array( + 'placeholder' => '添加内部段落' + ) ), + ) ), + ) ) +); +``` \ No newline at end of file diff --git a/reference-guides/block-api/block-transforms.md b/reference-guides/block-api/block-transforms.md new file mode 100644 index 0000000..c92e652 --- /dev/null +++ b/reference-guides/block-api/block-transforms.md @@ -0,0 +1,355 @@ +## `ungroup` 区块解组功能 + +通过区块配置中可选的 `transforms` 键,区块可使用 `ungroup` 子键定义用于替换当前处理区块的新区块。这些新区块通常是现有内部区块的子集,但也可包含全新区块。 + +若某区块配置了 `ungroup` 转换功能,即具备解组资格,无需作为默认分组区块即可使用。通过此 API 解组区块的用户界面与默认分组区块所用的界面相同。需满足以下条件才会显示解组按钮:必须选中单个分组区块,且该区块需包含若干内部区块。 + +**ungroup** 是一个回调函数,接收被处理区块的属性及内部区块数据,需返回区块对象数组。 + +示例: + +```js +export const settings = { + title: '我的分组区块标题', + description: '我的分组区块描述', + /* ... */ + transforms: { + ungroup: ( attributes, innerBlocks ) => + innerBlocks.flatMap( ( innerBlock ) => innerBlock.innerBlocks ), + }, +}; +``` + +当我们成功匹配此内容时,除 `data-post-id` 外所有HTML属性都将被剥离。如果给定 `div` 内存在其他HTML排列方式,则无法匹配转换器。同理,若其中出现的是 `

    ` 而非 `

    `,匹配也会失败。 + +在处理包含非短语内容(如带 `` 的 `
    `)的HTML片段时,模式定义至关重要。若未声明自定义模式,编辑器在尝试通过任何区块转换前会跳过这些特殊结构。 + +### 短代码 + +此类转换支持单向转换(从短代码创建区块),作为 `raw` 转换流程的组成部分。 + +`shortcode` 类型转换对象包含以下参数: + +- **type** _(字符串)_:固定值 `shortcode` +- **tag** _(字符串|数组)_:可转换的短代码标签或别名列表 +- **transform** _(函数,可选)_:接收短代码属性(首参数)与 [WPShortcodeMatch](/packages/shortcode/README.md#next)(次参数)的回调函数,应返回区块对象或区块对象数组。定义此参数时将优先于 `attributes` 参数 +- **attributes** _(对象,可选)_:根据[区块配置对象](/docs/reference-guides/block-api/block-registration.md)定义的属性结构,指定区块属性来源。若某属性包含 `shortcode` 键,则应为接收短代码属性(首参数)与 [WPShortcodeMatch](/packages/shortcode/README.md#next)(次参数)的函数,并返回将存入区块注释的属性值 +- **isMatch** _(函数,可选)_:根据 [Shortcode API](https://codex.wordpress.org/Shortcode_API) 接收短代码属性的回调函数,应返回布尔值。返回 `false` 将阻止该短代码转换为此区块 +- **priority** _(数字,可选)_:控制转换应用优先级,数值较低者优先,工作机制类似 [WordPress 钩子](https://developer.wordpress.org/reference/#Hook_to_WordPress)。如未设置,默认优先级为 `10` + +**示例:通过 `transform` 从短代码转换为区块** + +使用 `transform` 方法可将现有短代码转换为其对应区块: + +```js +transforms: { + from: [ + { + type: 'shortcode', + tag: 'video', + transform( { named: { src } } ) { + return createBlock( 'core/video', { src } ); + }, + // 当短代码不具备正确ID时 + // 阻止其转换为此区块 + isMatch( { named: { id } } ) { + return id === 'my-id'; + }, + }, + ], +}, +``` + +**示例:通过 `attributes` 从短代码转换为区块** + +使用 `attributes` 参数可将现有短代码转换为其对应区块: + +```js +transforms: { + from: [ + { + type: 'shortcode', + tag: 'youtube', + attributes: { + url: { + type: 'string', + source: 'attribute', + attribute: 'src', + selector: 'img', + }, + align: { + type: 'string', + // 短代码函数将提取短代码属性 + // 转换为可存入区块注释的值 + shortcode: ( { named: { align = 'alignnone' } } ) => { + return align.replace( 'align', '' ); + }, + }, + }, + // 当短代码不具备正确ID时 + // 阻止其转换为此区块 + isMatch( { named: { id } } ) { + return id === 'my-id'; + }, + }, + ] +}, +``` + +# 转换功能 + +区块转换是一套API,允许区块与其他区块进行相互转换,同时也支持从其他实体转换为区块。与此API兼容的现有实体包括短代码、文件、正则表达式和原始DOM节点。 + +## 转换方向:`to` 与 `from` + +区块通过配置项中可选的 `transforms` 键来声明支持的转换类型,其子键 `to` 和 `from` 分别存储不同方向的转换数组。例如: + +```js +export const settings = { + title: '我的区块标题', + description: '我的区块描述', + /* ... */ + transforms: { + from: [ + /* 支持的来源转换 */ + ], + to: [ + /* 支持的目标转换 */ + ], + }, +}; +``` + +## 转换类型 + +本节将介绍区块支持的现有转换类型: + +- block(区块) +- enter(回车) +- files(文件) +- prefix(前缀) +- raw(原始) +- shortcode(短代码) + +### 区块转换 + +此类转换支持双向转换,允许区块转换为其他类型的区块。在区块工具栏中有对应的UI控件。 + +`block` 类型的转换对象包含以下参数: + +- **type**(字符串):值为 `block` +- **blocks**(数组):已知区块类型列表。支持通配符值(`"*"`),表示该转换适用于所有区块类型(例如:所有区块都能转换为 `core/group`) +- **transform**(函数):接收被处理区块属性及内部区块的回调函数,应返回区块对象或区块对象数组 +- **isMatch**(函数,可选):接收区块属性(第一参数)和区块对象(第二参数)的回调函数,应返回布尔值。返回 `false` 将隐藏该转换选项 +- **isMultiBlock**(布尔值,可选):是否支持多选区块转换。为true时,`transform` 函数的首个参数为属性数组,第二参数为内部区块数组。默认为false +- **priority**(数字,可选):控制转换优先级,数值越低优先级越高。行为类似[WordPress钩子](https://developer.wordpress.org/reference/#Hook_to_WordPress),默认优先级为 `10` + +**示例:从段落区块转换为标题区块** + +在标题区块配置中添加以下代码(使用 [`wp-blocks` 包](/packages/blocks/README.md#createBlock) 的 `createBlock` 函数): + +```js +transforms: { + from: [ + { + type: 'block', + blocks: [ 'core/paragraph' ], + transform: ( { content } ) => { + return createBlock( 'core/heading', { + content, + } ); + }, + }, + ] +}, +``` + +**示例:含内部区块的转换** + +具有InnerBlocks的区块可与其他含内部区块的区块相互转换: + +```js +transforms: { + to: [ + { + type: 'block', + blocks: [ 'some/block-with-innerblocks' ], + transform: ( attributes, innerBlocks ) => { + return createBlock( + 'some/other-block-with-innerblocks', + attributes, + innerBlocks + ); + }, + }, + ], +}, +``` + +### 回车转换 + +此类转换仅支持来源转换,允许根据用户输入内容创建区块。当用户输入内容后按回车键时,将在新行中触发转换。 + +`enter` 类型的转换对象包含以下参数: + +- **type**(字符串):值为 `enter` +- **regExp**(正则表达式):用于匹配的正则表达式,匹配成功则触发转换 +- **transform**(函数):接收包含输入内容的回调函数,应返回区块对象或区块对象数组 +- **priority**(数字,可选):控制转换优先级,数值越低优先级越高。行为类似[WordPress钩子](https://developer.wordpress.org/reference/#Hook_to_WordPress),默认优先级为 `10` + +**示例:通过 --- 创建分隔符区块** + +当用户输入三个连字符后按回车键时创建分隔符区块: + +```js +transforms = { + from: [ + { + type: 'enter', + regExp: /^-{3,}$/, + transform: () => createBlock( 'core/separator' ), + }, + ], +}; +``` + +### 文件类型转换 + +此类转换支持 _from_ 方向,允许通过将文件拖拽至编辑器来创建区块。 + +`files` 类型的转换是一个包含以下参数的对象: + +- **type**(字符串):取值为 `files` +- **transform**(函数):接收正在处理的文件数组的回调函数,应返回区块对象或区块对象数组 +- **isMatch**(函数,可选):接收正在处理的文件数组的回调函数,应返回布尔值。返回 `false` 将阻止应用此转换 +- **priority**(数字,可选):控制转换应用的优先级,数值较低的优先级高于数值较高的优先级。其行为类似于 [WordPress 钩子](https://developer.wordpress.org/reference/#Hook_to_WordPress)。与钩子类似,未设置时的默认优先级为 `10` + +**示例:从文件创建文件区块** + +当用户将文件拖拽至编辑器时,可通过以下代码创建文件区块: + +```js +transforms: { + from: [ + { + type: 'files', + isMatch: ( files ) => files.length === 1, + // 通过定义低于默认值10的优先级, + // 使其在没有找到其他转换时作为备用方案创建文件区块 + priority: 15, + transform: ( files ) => { + const file = files[ 0 ]; + const blobURL = createBlobURL( file ); + // 文件将在 componentDidMount() 中上传 + return createBlock( 'core/file', { + href: blobURL, + fileName: file.name, + textLinkHref: blobURL, + } ); + }, + }, + ]; +} +``` + +### 前缀类型转换 + +此类转换支持 _from_ 方向,允许根据用户输入的文本创建区块。当用户在新区块行中输入文本并添加尾随空格时触发。 + +`prefix` 类型的转换是一个包含以下参数的对象: + +- **type**(字符串):取值为 `prefix` +- **prefix**(字符串):匹配此转换的字符或字符序列 +- **transform**(函数):接收输入内容的回调函数,应返回区块对象或区块对象数组 +- **priority**(数字,可选):控制转换应用的优先级,数值较低的优先级高于数值较高的优先级。其行为类似于 [WordPress 钩子](https://developer.wordpress.org/reference/#Hook_to_WordPress)。与钩子类似,未设置时的默认优先级为 `10` + +**示例:从文本创建自定义区块** + +若要在用户输入问号时创建自定义区块,可使用以下代码: + +```js +transforms: { + from: [ + { + type: 'prefix', + prefix: '?', + transform( content ) { + return createBlock( 'my-plugin/question', { + content, + } ); + }, + }, + ]; +} +``` + +### 原始内容类型转换 + +此类转换支持 _from_ 方向,允许根据原始 HTML 节点创建区块。当用户执行区块设置界面菜单中的"转换为区块"操作,或向编辑器粘贴/拖拽内容时触发。 + +`raw` 类型的转换是一个包含以下参数的对象: + +- **type**(字符串):取值为 `raw` +- **transform**(函数,可选):接收正在处理节点的回调函数,应返回区块对象或区块对象数组 +- **schema**(对象|函数,可选):定义用于检测和处理粘贴内容的 [HTML 内容模型](https://html.spec.whatwg.org/multipage/dom.html#content-models),详见[下文](#内容模型与架构) +- **selector**(字符串,可选):用于根据 [element.matches](https://developer.mozilla.org/en-US/docs/Web/API/Element/matches) 方法判断元素是否匹配的 CSS 选择器字符串。若元素不匹配则不会执行转换。这是 `isMatch` 的简写替代方案,若同时存在则优先使用 `isMatch` +- **isMatch**(函数,可选):接收正在处理节点的回调函数,应返回布尔值。返回 `false` 将阻止应用此转换 +- **priority**(数字,可选):控制转换应用的优先级,数值较低的优先级高于数值较高的优先级。其行为类似于 [WordPress 钩子](https://developer.wordpress.org/reference/#Hook_to_WordPress)。与钩子类似,未设置时的默认优先级为 `10` + +**示例:从 URL 创建嵌入区块** + +若要在用户向编辑器粘贴 URL 时创建嵌入区块,可使用以下代码: + +```js +transforms: { + from: [ + { + type: 'raw', + isMatch: ( node ) => + node.nodeName === 'P' && + /^\s*(https?:\/\/\S+)\s*$/i.test( node.textContent ), + transform: ( node ) => { + return createBlock( 'core/embed', { + url: node.textContent.trim(), + } ); + }, + }, + ], +} +``` + +

    内容模型与架构

    + +在粘贴内容时,可以定义用于验证和处理粘贴内容的[内容模型](https://html.spec.whatwg.org/multipage/dom.html#content-models)。粘贴到编辑器中的 HTML 通常包含应当转移和不应转移的混合元素。例如考虑将 `12:04 pm` 粘贴到编辑器中的情况:我们需要复制 `12:04 pm` 并忽略 `` 及其 `class` 属性,因为这些元素在复制后不再具有原有的含义和结构。 + +在编写 `raw` 转换时,可通过提供描述允许内容的 `schema` 来控制此行为,该架构将在尝试与区块匹配之前对粘贴内容进行清理。这些架构会传入 [`@wordpress/dom` 中的 `cleanNodeList`](https://github.com/wordpress/gutenberg/blob/trunk/packages/dom/src/dom/clean-node-list.js);请查阅该处获取[架构的完整描述](https://github.com/wordpress/gutenberg/blob/trunk/packages/dom/src/phrasing-content.js)。 + +```js +schema = { span: { children: { '#text': {} } } }; +``` + +**示例:自定义内容模型** + +假设我们需要匹配以下 HTML 片段并将其转换为某种自定义文章预览区块: + +```html +
    +

    文章标题

    +

    一段精彩内容

    +
    +``` + +我们需要通过提供以下架构来告知编辑器允许内部的 `h2` 和 `p` 元素。此示例中使用函数形式,该函数接收包含 `phrasingContentSchema` 的参数(以及指示转换操作是否以粘贴文本开始的布尔值 `isPaste`)。`phrasingContentSchema` 预定义为匹配 HTML 短语元素,如 ``、`` 和 ``。任何期望使用 `` 组件的地方都适合允许短语内容,否则在转换过程中将丢失所有文本格式。 + +```js +schema = ({ phrasingContentSchema }) => { + div: { + required: true, + attributes: [ 'data-post-id' ], + children: { + h2: { children: phrasingContentSchema }, + p: { children: phrasingContentSchema } + } + } +} +``` \ No newline at end of file diff --git a/reference-guides/block-api/block-variations.md b/reference-guides/block-api/block-variations.md new file mode 100644 index 0000000..176424d --- /dev/null +++ b/reference-guides/block-api/block-variations.md @@ -0,0 +1,248 @@ +# 区块变体 + +区块变体API允许您为区块定义多个版本(变体)。区块变体通过一组初始属性或内部区块与原始区块区分开来。当您将区块变体插入编辑器时,这些属性和/或内部区块会被应用。 + +变体是在不从头构建全新区块的情况下,创建现有区块迭代版本的绝佳方式。 + +为了更好地理解此API,请以嵌入区块为例。该区块包含多种可嵌入内容类型(WordPress、YouTube等)的众多变体。每个嵌入区块变体共享相同的底层功能,如编辑、保存等。除了名称和描述信息外,主要区别在于`providerNameSlug`属性。以下是嵌入区块变体的简化示例。完整规范请查看[源代码](https://github.com/WordPress/gutenberg/blob/trunk/packages/block-library/src/embed/variations.js)。 + +```js +variations: [ + { + name: 'wordpress', + title: 'WordPress', + description: __( '嵌入WordPress文章。' ), + attributes: { providerNameSlug: 'wordpress' }, + }, + { + name: 'youtube', + title: 'YouTube', + description: __( '嵌入YouTube视频。' ), + attributes: { providerNameSlug: 'youtube' }, + }, +], +``` + +## 定义区块变体 + +区块变体通过包含以下字段的对象定义: + +- `name`(类型`string`)– 唯一且机器可读的名称 +- `title`(可选,类型`string`)– 人类可读的变体标题 +- `description`(可选,类型`string`)– 人类可读的变体描述 +- `category`(可选,类型`string`)- 在搜索界面中用于按分类排列区块类型的分类标识 +- `keywords`(可选,类型`string[]`)- 帮助用户在搜索时发现变体的术语数组(可翻译) +- `icon`(可选,类型`string` | `Object`)– 帮助可视化变体的图标,可与区块类型保持相同样式 +- `attributes`(可选,类型`Object`)– 覆盖区块属性的值 +- `innerBlocks`(可选,类型`Array[]`)– 嵌套区块的初始配置 +- `example`(可选,类型`Object`)– 为区块预览提供结构化数据。设置为`undefined`可禁用预览。详见[区块注册API](/docs/reference-guides/block-api/block-registration.md#example-optional) +- `scope`(可选,类型`WPBlockVariationScope[]`)- 默认为`block`和`inserter`。变体适用的作用域列表,可用选项包括: + - `block` - 被区块用于筛选特定区块变体。`Columns`和`Query`区块包含此类变体,这些变体会传递给[实验性BlockVariationPicker](https://github.com/WordPress/gutenberg/blob/HEAD/packages/block-editor/src/components/block-variation-picker/README.md)组件,该组件负责显示变体并允许用户选择 + - `inserter` - 在插入器中显示区块变体 + - `transform` - 在变体转换组件中显示区块变体 +- `isDefault`(可选,类型`boolean`)– 默认为`false`。指示当前变体是否为默认变体(详见下文) +- `isActive`(可选,类型`Function|string[]`)- 用于确定选中区块时变体是否处于活动状态的函数或区块属性数组。该函数接受`blockAttributes`和`variationAttributes`参数(详见下文) + +
    + 从技术上讲,您可以创建没有唯一name的区块变体,但不推荐这样做。唯一的name允许编辑器区分您的变体与其他可能存在的变体,还允许在需要时注销您的变体,并对isDefault设置产生影响(详见下文)。 +
    + +## 创建区块变体 + +在区块注册过程中,可通过为`variations`键提供规范的变体对象数组来声明区块变体,如上例所示。更多详细信息请参阅[区块注册API](/docs/reference-guide/block-api/block-registration.md)。 + +若要为现有区块(如核心区块)创建变体,请使用`wp.blocks.registerBlockVariation()`。该函数接受区块名称和定义变体的对象。 + +```js +wp.blocks.registerBlockVariation( 'core/embed', { + name: 'custom-embed', + attributes: { providerNameSlug: 'custom' }, +} ); +``` + +## 通过PHP注册区块变体 + +区块变体也可使用`get_block_type_variations`过滤器钩子在PHP中注册。当需要基于已注册的文章类型、分类法或其他WordPress数据动态生成变体时,这种方法特别有用。 + +以下是为`core/image`区块注册自定义变体的示例: + +```php +function my_custom_image_variation( $variations, $block_type ) { + // 仅修改图片区块的变体 + if ( 'core/image' !== $block_type->name ) { + return $variations; + } + + // 添加自定义变体 + $variations[] = array( + 'name' => 'wide-image', + 'title' => __( '宽幅图片', 'textdomain' ), + 'description' => __( '宽幅图片', 'textdomain' ), + 'scope' => array( 'inserter' ), + 'isDefault' => false, + 'attributes' => array( + 'align' => 'wide', // 标识为自定义链接类型 + ), + ); + + return $variations; +} +add_filter( 'get_block_type_variations', 'my_custom_image_variation', 10, 2 ); +``` + +当请求区块类型的变体时会调用`get_block_type_variations`过滤器。它接收两个参数: +- `$variations`:当前已注册的区块类型变体数组 +- `$block_type`:完整的区块类型对象 + +注意:通过PHP注册的变体将与使用`registerBlockVariation()`通过JavaScript注册的任何变体合并。 + +
    查看如何使用PHP注册区块变体博客文章获取更多信息
    + +## 移除区块变体 + +区块变体也可以轻松移除。为此,请使用`wp.blocks.unregisterBlockVariation()`。该函数接受区块名称和需要注销的变体`name`。 + +```js +wp.blocks.unregisterBlockVariation( 'core/embed', 'youtube' ); +``` + +## 区块变体与区块样式 + +区块样式与区块变体的主要区别在于:区块样式仅向区块应用CSS类,从而能以替代方式进行样式设计。更多详细信息请参阅[区块样式API](/docs/reference-guides/block-api/block-styles.md)。 + +若要设置初始属性或内部区块,则属于区块变体范畴。在定义区块变体时,也可以通过`className`属性覆盖默认区块样式。 + +```js +variations: [ + { + name: 'blue', + title: __( '蓝色引用' ), + isDefault: true, + attributes: { + color: 'blue', + className: 'is-style-blue-quote' + }, + icon: 'format-quote', + isActive: ( blockAttributes, variationAttributes ) => + blockAttributes.color === variationAttributes.color + }, +], +``` + +## 使用`isDefault`标志 + +默认情况下,所有变体都会在插入器中与原始区块类型项共同显示。但为列出的任何变体设置`isDefault`标志将覆盖插入器中的常规区块类型。这是根据特定需求定制编辑器体验的强大工具。 + +例如,若希望媒体与文本区块默认在右侧显示图像,可创建如下变体: + +```js +wp.blocks.registerBlockVariation( 'core/media-text', { + name: 'media-text-media-right', + title: __( '媒体与文本' ), + isDefault: true, + attributes: { + mediaPosition: 'right', + }, +} ); +``` + +### 使用 `isDefault` 的注意事项 + +虽然 `isDefault` 在覆盖没有现有变体的区块时效果很好,但当存在其他变体时,您可能会遇到问题。 + +如果同一区块的另一个变体使用了 `isDefault`,您的变体不一定会成为默认变体。编辑器会尊重第一个注册的带有 `isDefault` 的变体,这可能不是您的变体。 + +解决方案是在注册带有 `isDefault` 的变体之前,先取消注册其他变体。这一注意事项进一步强调了始终为变体提供唯一 `name` 的建议。否则,该变体将无法被取消注册。 + +## 使用 `isActive` + +虽然 `isActive` 属性是可选的,但建议使用它。区块编辑器使用此 API 来检查哪个变体处于活动状态,并在编辑器中选择该变体的实例时显示正确的变体标题、图标和描述。 + +如果未设置 `isActive`,编辑器将无法区分原始区块的实例和您的变体,因此将显示原始区块的信息。 + +该属性可以设置为字符串数组(`string[]`)或函数。建议尽可能使用字符串数组版本。 + +`string[]` 版本用于声明应将区块实例的哪些属性与给定变体的属性进行比较。每个属性都将被检查,如果所有属性都匹配,则该变体将处于活动状态。 + +例如,在核心的 Embed 区块中,`providerNameSlug` 属性用于确定嵌入提供程序(例如 “youtube” 或 “twitter”)。变体可以这样声明: + +```js +const variations = [ + { + name: 'twitter', + title: 'Twitter', + icon: embedTwitterIcon, + keywords: [ 'tweet', __( 'social' ) ], + description: __( '嵌入推文。' ), + patterns: [ /^https?:\/\/(www\.)?twitter\.com\/.+/i ], + attributes: { providerNameSlug: 'twitter', responsive: true }, + }, + { + name: 'youtube', + title: 'YouTube', + icon: embedYouTubeIcon, + keywords: [ __( '音乐' ), __( '视频' ) ], + description: __( '嵌入 YouTube 视频。' ), + patterns: [ + /^https?:\/\/((m|www)\.)?youtube\.com\/.+/i, + /^https?:\/\/youtu\.be\/.+/i, + ], + attributes: { providerNameSlug: 'youtube', responsive: true }, + }, + // ... +]; +``` + +那么 `isActive` 属性将如下所示: + +```js +isActive: [ 'providerNameSlug' ]; +``` + +这将导致区块实例的 `providerNameSlug` 值与变体声明中的值(上述代码片段中的值)进行比较,以确定哪个嵌入变体处于活动状态。 + +自 WordPress `6.6.0` 起,还支持嵌套对象路径。例如,假设一个区块变体具有 `query` 对象作为属性。可以仅基于该对象的 `postType` 属性(同时忽略其所有其他属性)来确定变体是否处于活动状态: + +```js +isActive: [ 'query.postType' ]; +``` + +该属性的函数版本接受区块实例的 `blockAttributes` 作为第一个参数,以及为变体声明的 `variationAttributes` 作为第二个参数。可以通过比较这些参数来确定变体是否处于活动状态,并返回 `true` 或 `false`(表示此变体是否对此区块实例处于活动状态)。 + +以嵌入区块的相同示例为例,函数版本将如下所示: + +```js +isActive: ( blockAttributes, variationAttributes ) => + blockAttributes.providerNameSlug === variationAttributes.providerNameSlug, +``` + +### `isActive` 匹配的特定性 + +*注意:自 WordPress `6.6.0` 起改进了处理方式。* + +如果有多个变体的 `isActive` 检查匹配给定的区块实例,并且所有变体都是字符串数组,则将选择*特定性*最高的变体。考虑以下示例: + +```js +wp.blocks.registerBlockVariation( 'core/paragraph', { + name: 'paragraph-red', + title: '红色段落', + attributes: { + textColor: 'vivid-red', + }, + isActive: [ 'textColor' ], +} ); + +wp.blocks.registerBlockVariation( 'core/paragraph', { + name: 'paragraph-red-grey', + title: '红/灰段落', + attributes: { + textColor: 'vivid-red', + backgroundColor: 'cyan-bluish-gray', + }, + isActive: [ 'textColor', 'backgroundColor' ], +} ); +``` + +如果某个区块实例具有属性 `textColor: vivid-red` 和 `backgroundColor: cyan-bluish-gray`,则两个变体的 `isActive` 条件都将匹配该区块实例。在这种情况下,将确定*特定性*更高的匹配为活动变体,其中特定性计算为每个 `isActive` 数组的长度。这意味着 `红/灰段落` 将显示为活动变体。 + +请注意,如果匹配变体的 `isActive` 属性是函数而不是 `string[]`,则无法确定其特定性。在这种情况下,第一个匹配的变体将被确定为活动变体。因此,通常建议为 `isActive` 属性使用 `string[]` 而不是 `function`。 \ No newline at end of file diff --git a/reference-guides/core-blocks.md b/reference-guides/core-blocks.md new file mode 100644 index 0000000..7a888d6 --- /dev/null +++ b/reference-guides/core-blocks.md @@ -0,0 +1,1066 @@ +## 视频 + +从媒体库嵌入视频或上传新视频。([来源](https://github.com/WordPress/gutenberg/tree/trunk/packages/block-library/src/video)) + +- **名称:** core/video +- **分类:** 媒体 +- **支持功能:** 对齐方式、锚点、交互性(客户端导航)、间距(边距、内边距) +- **属性:** 自动播放、二进制数据块、字幕、控制条、ID、循环播放、静音、内联播放、海报帧、预加载、源文件、音轨 + + + +## 标签云 + +由热门关键词组成的云图,每个关键词的大小根据出现频率而定。([来源](https://github.com/WordPress/gutenberg/tree/trunk/packages/block-library/src/tag-cloud)) + +- **名称:** core/tag-cloud +- **分类:** 小工具 +- **支持:** 对齐方式、交互性(客户端导航)、间距(外边距、内边距)、排版(行高)、~~HTML~~ +- **属性:** 最大字体尺寸、标签数量、显示标签计数、最小字体尺寸、分类法 + +## 模板部件 + +编辑网站的不同全局区域,如页眉、页脚、侧边栏,或创建您自己的区域。([来源](https://github.com/WordPress/gutenberg/tree/trunk/packages/block-library/src/template-part)) + +- **名称:** core/template-part +- **分类:** 主题 +- **支持:** 对齐方式、交互性(客户端导航)、~~HTML~~、~~重命名~~、~~可复用~~ +- **属性:** 区域、别名、标签名称、主题 + +## 术语计数 + +显示分类法术语的文章计数。([来源](https://github.com/WordPress/gutenberg/tree/trunk/packages/block-library/src/term-count)) + +- **名称:** core/term-count +- **分类:** 主题 +- **支持:** 颜色(背景、渐变、文本)、交互性(客户端导航)、间距(内边距)、排版(字体大小、行高)、~~HTML~~ +- **属性:** 括号类型 + +## 术语描述 + +在查看归档时显示分类、标签和自定义分类法的描述。([来源](https://github.com/WordPress/gutenberg/tree/trunk/packages/block-library/src/term-description)) + +- **名称:** core/term-description +- **分类:** 主题 +- **支持:** 对齐方式(全宽、宽)、颜色(背景、链接、文本)、交互性(客户端导航)、间距(外边距、内边距)、排版(字体大小、行高)、~~HTML~~ +- **属性:** 文本对齐 + +## 术语名称 + +显示分类法术语的名称。([来源](https://github.com/WordPress/gutenberg/tree/trunk/packages/block-library/src/term-name)) + +- **名称:** core/term-name +- **分类:** 主题 +- **支持:** 对齐方式(全宽、宽)、颜色(背景、渐变、链接、文本)、交互性(客户端导航)、间距(内边距)、排版(字体大小、行高)、~~HTML~~ +- **属性:** 是否为链接、层级、文本对齐 + +## 术语模板 + +包含用于渲染分类法术语的块元素,如名称、描述等。([来源](https://github.com/WordPress/gutenberg/tree/trunk/packages/block-library/src/term-template)) + +- **名称:** core/term-template +- **分类:** 主题 +- **祖先块:** core/terms-query +- **支持:** 对齐方式(全宽、宽)、颜色(背景、渐变、链接、文本)、交互性(客户端导航)、布局、间距(块间距、外边距、内边距)、排版(字体大小、行高)、~~HTML~~、~~可复用~~ + +## 术语查询 + +一个高级块,允许根据不同的查询参数和视觉配置显示分类法术语。([来源](https://github.com/WordPress/gutenberg/tree/trunk/packages/block-library/src/terms-query)) + +- **名称:** core/terms-query +- **分类:** 主题 +- **支持:** 对齐方式(全宽、宽)、交互性、布局、~~HTML~~ +- **属性:** 标签名称、术语查询 + +## 文本列(已弃用) + +此块已弃用。请改用“列”块。([来源](https://github.com/WordPress/gutenberg/tree/trunk/packages/block-library/src/text-columns)) + +- **名称:** core/text-columns +- **分类:** 设计 +- **支持:** 交互性(客户端导航)、~~插入器~~ +- **属性:** 列数、内容、宽度 + +## 诗歌 + +插入诗歌。使用特殊的间距格式。或引用歌词。([来源](https://github.com/WordPress/gutenberg/tree/trunk/packages/block-library/src/verse)) + +- **名称:** core/verse +- **分类:** 文本 +- **支持:** 锚点、背景(背景图片、背景尺寸)、颜色(背景、渐变、链接、文本)、尺寸(最小高度)、交互性(客户端导航)、间距(外边距、内边距)、排版(字体大小、行高) +- **属性:** 内容、文本对齐 + +## 评论下一页 + +显示下一页评论的链接。([源码](https://github.com/WordPress/gutenberg/tree/trunk/packages/block-library/src/comments-pagination-next)) + +- **名称:** core/comments-pagination-next +- **分类:** theme +- **父级:** core/comments-pagination +- **支持:** color(background、gradients、~~text~~)、interactivity(clientNavigation)、typography(fontSize、lineHeight)、~~html~~、~~reusable~~ +- **属性:** label + +## 评论页码 + +显示评论分页的页码列表。([源码](https://github.com/WordPress/gutenberg/tree/trunk/packages/block-library/src/comments-pagination-numbers)) + +- **名称:** core/comments-pagination-numbers +- **分类:** theme +- **父级:** core/comments-pagination +- **支持:** color(background、gradients、~~text~~)、interactivity(clientNavigation)、typography(fontSize、lineHeight)、~~html~~、~~reusable~~ + +## 评论上一页 + +显示上一页评论的链接。([源码](https://github.com/WordPress/gutenberg/tree/trunk/packages/block-library/src/comments-pagination-previous)) + +- **名称:** core/comments-pagination-previous +- **分类:** theme +- **父级:** core/comments-pagination +- **支持:** color(background、gradients、~~text~~)、interactivity(clientNavigation)、typography(fontSize、lineHeight)、~~html~~、~~reusable~~ +- **属性:** label + +## 评论标题 + +显示带有评论数量的标题。([源码](https://github.com/WordPress/gutenberg/tree/trunk/packages/block-library/src/comments-title)) + +- **名称:** core/comments-title +- **分类:** theme +- **祖先:** core/comments +- **支持:** align、color(background、gradients、text)、interactivity(clientNavigation)、spacing(margin、padding)、typography(fontSize、lineHeight)、~~anchor~~、~~html~~ +- **属性:** level、levelOptions、showCommentsCount、showPostTitle、textAlign + +## 封面 + +添加带有文本覆盖的图像或视频。([源码](https://github.com/WordPress/gutenberg/tree/trunk/packages/block-library/src/cover)) + +- **名称:** core/cover +- **分类:** media +- **支持:** align、allowedBlocks、anchor、color(heading、text、~~background~~、~~enableContrastChecker~~)、dimensions(aspectRatio)、filter(duotone)、interactivity(clientNavigation)、layout(~~allowJustification~~)、shadow、spacing(blockGap、margin、padding)、typography(fontSize、lineHeight)、~~html~~ +- **属性:** alt、backgroundType、contentPosition、customGradient、customOverlayColor、dimRatio、focalPoint、gradient、hasParallax、id、isDark、isRepeated、isUserOverlayColor、minHeight、minHeightUnit、overlayColor、poster、sizeSlug、tagName、templateLock、url、useFeaturedImage + +## 详情 + +隐藏和显示额外内容。([源码](https://github.com/WordPress/gutenberg/tree/trunk/packages/block-library/src/details)) + +- **名称:** core/details +- **分类:** text +- **支持:** align(full、wide)、allowedBlocks、anchor、color(background、gradients、link、text)、interactivity(clientNavigation)、layout(~~allowEditing~~)、spacing(blockGap、margin、padding)、typography(fontSize、lineHeight)、~~html~~ +- **属性:** name、placeholder、showContent、summary + +## 嵌入 + +添加一个显示从其他网站(如 Twitter 或 YouTube)拉取内容的块。([源码](https://github.com/WordPress/gutenberg/tree/trunk/packages/block-library/src/embed)) + +- **名称:** core/embed +- **分类:** embed +- **支持:** align、interactivity(clientNavigation)、spacing(margin) +- **属性:** allowResponsive、caption、previewable、providerNameSlug、responsive、type、url + +## 文件 + +添加可下载文件的链接。([源码](https://github.com/WordPress/gutenberg/tree/trunk/packages/block-library/src/file)) + +- **名称:** core/file +- **分类:** media +- **支持:** align、anchor、color(background、gradients、link、~~text~~)、interactivity、spacing(margin、padding) +- **属性:** blob、displayPreview、downloadButtonText、fileId、fileName、href、id、previewHeight、showDownloadButton、textLinkHref、textLinkTarget + +## 阅读时长 + +显示阅读文章所需的分钟数,也可显示字数统计。([源码](https://github.com/WordPress/gutenberg/tree/trunk/packages/block-library/src/post-time-to-read)) + +- **名称:** core/post-time-to-read +- **分类:** 主题 +- **支持功能:** 颜色(背景、渐变、文字)、交互性(客户端导航)、间距(边距、内边距)、排版(字体大小、行高)、~~HTML~~ +- **属性:** 平均阅读速度、范围显示模式、显示方式、文字对齐 + +## 标题 + +显示文章、页面或任何其他内容类型的标题。([源码](https://github.com/WordPress/gutenberg/tree/trunk/packages/block-library/src/post-title)) + +- **名称:** core/post-title +- **分类:** 主题 +- **支持功能:** 对齐(全宽、宽屏)、颜色(背景、渐变、链接、文字)、交互性(客户端导航)、间距(边距、内边距)、排版(字体大小、行高)、~~HTML~~ +- **属性:** 链接模式、标题级别、级别选项、链接目标、关联关系、文字对齐 + +## 预格式化文本 + +添加保留空格和制表符的文本,同时支持样式设置。([源码](https://github.com/WordPress/gutenberg/tree/trunk/packages/block-library/src/preformatted)) + +- **名称:** core/preformatted +- **分类:** 文本 +- **支持功能:** 锚点、颜色(背景、渐变、文字)、交互性(客户端导航)、间距(边距、内边距)、排版(字体大小、行高) +- **属性:** 内容 + +## 引述块 + +为文本中的引用内容赋予特殊的视觉强调效果。([源码](https://github.com/WordPress/gutenberg/tree/trunk/packages/block-library/src/pullquote)) + +- **名称:** core/pullquote +- **分类:** 文本 +- **支持功能:** 对齐(全宽、左、右、宽屏)、锚点、背景(背景图、背景尺寸)、颜色(背景、渐变、链接、文字)、尺寸(最小高度)、交互性(客户端导航)、间距(边距、内边距)、排版(字体大小、行高) +- **属性:** 引用来源、文字对齐、内容值 + +## 循环查询 + +高级功能块,支持根据不同查询参数和可视化配置来显示文章类型。([源码](https://github.com/WordPress/gutenberg/tree/trunk/packages/block-library/src/query)) + +- **名称:** core/query +- **分类:** 主题 +- **支持功能:** 对齐(全宽、宽屏)、内容角色、交互性、布局、~~HTML~~ +- **属性:** 增强分页、命名空间、查询参数、查询ID、标签名称 + +## 无结果 + +包含在未找到查询结果时用于渲染内容的区块元素。([源码](https://github.com/WordPress/gutenberg/tree/trunk/packages/block-library/src/query-no-results)) + +- **名称:** core/query-no-results +- **分类:** 主题 +- **祖先区块:** core/query +- **支持功能:** 对齐、颜色(背景、渐变、链接、文字)、交互性(客户端导航)、排版(字体大小、行高)、~~HTML~~、~~可复用~~ + +## 分页 + +在适用时显示指向下一组/上一组文章的分页导航。([源码](https://github.com/WordPress/gutenberg/tree/trunk/packages/block-library/src/query-pagination)) + +- **名称:** core/query-pagination +- **分类:** 主题 +- **祖先区块:** core/query +- **允许子区块:** core/query-pagination-previous(上一页)、core/query-pagination-numbers(页码)、core/query-pagination-next(下一页) +- **支持功能:** 对齐、颜色(背景、渐变、链接、文字)、交互性(客户端导航)、布局(默认、~~允许继承~~、~~允许切换~~)、排版(字体大小、行高)、~~HTML~~、~~可复用~~ +- **属性:** 分页箭头、显示标签 + +## 下一页 + +显示下一篇文章列表页的链接。([源码](https://github.com/WordPress/gutenberg/tree/trunk/packages/block-library/src/query-pagination-next)) + +- **名称:** core/query-pagination-next +- **分类:** 主题 +- **父级区块:** core/query-pagination +- **支持功能:** 颜色(背景、渐变、~~文字~~)、交互性(客户端导航)、排版(字体大小、行高)、~~HTML~~、~~可复用~~ +- **属性:** 标签文字 + +## 首页链接 + +创建始终指向网站首页的链接。若页眉中已存在网站标题链接,通常无需使用此功能。([来源](https://github.com/WordPress/gutenberg/tree/trunk/packages/block-library/src/home-link)) + +- **名称:** core/home-link +- **分类:** 设计 +- **父级:** core/navigation +- **支持:** 交互性(客户端导航)、版式(字体大小、行高)、~~HTML~~、~~可复用~~ +- **属性:** 标签 + +## 自定义 HTML + +添加自定义 HTML 代码并在编辑时预览效果。([来源](https://github.com/WordPress/gutenberg/tree/trunk/packages/block-library/src/html)) + +- **名称:** core/html +- **分类:** 小工具 +- **支持:** 交互性(客户端导航)、~~类名~~、~~自定义类名~~、~~HTML~~ +- **属性:** 内容 + +## 图像 + +插入图像以呈现视觉表达。([来源](https://github.com/WordPress/gutenberg/tree/trunk/packages/block-library/src/image)) + +- **名称:** core/image +- **分类:** 媒体 +- **支持:** 对齐(居中、全宽、左对齐、右对齐、宽屏)、锚点、颜色(~~背景~~、~~文字~~)、滤镜(双色调)、交互性、阴影()、间距(边距) +- **属性:** 替代文本、宽高比、二进制数据、说明文字、高度、链接地址、ID、灯箱效果、链接类、链接目标类型、链接打开方式、关联关系、缩放比例、尺寸标识、标题、URL、宽度 + +## 最新评论 + +显示您最近的评论列表。([来源](https://github.com/WordPress/gutenberg/tree/trunk/packages/block-library/src/latest-comments)) + +- **名称:** core/latest-comments +- **分类:** 小工具 +- **支持:** 对齐、颜色(背景、渐变、链接、文字)、交互性(客户端导航)、间距(边距、内边距)、版式(字体大小、行高)、~~HTML~~ +- **属性:** 显示评论数、显示头像、显示日期、显示摘要 + +## 最新文章 + +显示您最近发布的文章列表。([来源](https://github.com/WordPress/gutenberg/tree/trunk/packages/block-library/src/latest-posts)) + +- **名称:** core/latest-posts +- **分类:** 小工具 +- **支持:** 对齐、颜色(背景、渐变、链接、文字)、交互性(客户端导航)、间距(边距、内边距)、版式(字体大小、行高)、~~HTML~~ +- **属性:** 为特色图片添加链接、分类目录、列数、显示作者、显示特色图片、显示文章内容、显示文章内容选项、显示发布日期、摘要长度、特色图片对齐方式、特色图片高度尺寸、特色图片尺寸标识、特色图片宽度尺寸、排序方式、排序依据、文章布局、显示文章数、选定作者 + +## 列表 + +按特定顺序显示的有序项目集合。([来源](https://github.com/WordPress/gutenberg/tree/trunk/packages/block-library/src/list)) + +- **名称:** core/list +- **分类:** 文本 +- **允许的子块:** core/list-item +- **支持:** 不稳定粘贴文本内联、锚点、颜色(背景、渐变、链接、文字)、交互性(客户端导航)、间距(边距、内边距)、版式(字体大小、行高)、~~HTML~~ +- **属性:** 有序列表、占位符、反向排序、起始数值、类型、值 + +## 列表项 + +列表中的单个项目。([来源](https://github.com/WordPress/gutenberg/tree/trunk/packages/block-library/src/list-item)) + +- **名称:** core/list-item +- **分类:** 文本 +- **父级:** core/list +- **允许的子块:** core/list +- **支持:** 锚点、颜色(背景、渐变、链接、文字)、交互性(客户端导航)、间距(边距、内边距)、拆分功能、版式(字体大小、行高)、~~类名~~ +- **属性:** 内容、占位符 + +## 登录/退出 + +显示登录和退出链接。([来源](https://github.com/WordPress/gutenberg/tree/trunk/packages/block-library/src/loginout)) + +- **名称:** core/loginout +- **分类:** 主题 +- **支持:** 类名、颜色(背景、渐变、链接、~~文字~~)、交互性(客户端导航)、间距(边距、内边距)、版式(字体大小、行高) +- **属性:** 以表单形式显示登录、重定向至当前页面 + +# 核心区块参考 + +本文档列出了 block-library 包中包含的区块。 + +- 带删除线(~~删除线~~)标记的项目已被明确禁用。 +- 标记为 **Experimental:** true 的区块仅在 Gutenberg 激活时可用。 +- 标记为 **Experimental:** fse 的区块仅在全站编辑器中可用。 + + + +## 手风琴 + +显示一组手风琴标题及关联的可展开内容。([源码](https://github.com/WordPress/gutenberg/tree/trunk/packages/block-library/src/accordion)) + +- **名称:** core/accordion +- **分类:** 设计 +- **允许的区块:** core/accordion-item +- **支持:** 对齐(全宽,宽),锚点,ariaLabel,背景(背景图片,背景尺寸),颜色(背景,渐变,文本),内容角色,交互性,布局,阴影,间距(区块间距,外边距,内边距),排版(字体大小,行高),~~html~~ +- **属性:** 自动关闭,标题级别,图标位置,级别选项,显示图标 + +## 手风琴标题 + +显示手风琴标题。([源码](https://github.com/WordPress/gutenberg/tree/trunk/packages/block-library/src/accordion-heading)) + +- **名称:** core/accordion-heading +- **分类:** 设计 +- **父级:** core/accordion-item +- **支持:** 锚点,颜色(背景,渐变,文本),交互性,阴影,间距(内边距),排版(字体大小),~~对齐~~,~~区块可见性~~,~~锁定~~ +- **属性:** 图标位置,级别,默认展开,显示图标,标题 + +## 手风琴项目 + +显示手风琴中的内容部分,包括标题和可展开内容。([源码](https://github.com/WordPress/gutenberg/tree/trunk/packages/block-library/src/accordion-item)) + +- **名称:** core/accordion-item +- **分类:** 设计 +- **父级:** core/accordion +- **允许的区块:** core/accordion-heading, core/accordion-panel +- **支持:** 颜色(背景,渐变,文本),内容角色,交互性,布局(~~允许编辑~~),阴影,间距(区块间距,外边距),排版(字体大小,行高),~~html~~ +- **属性:** 默认展开 + +## 手风琴面板 + +显示手风琴面板。([源码](https://github.com/WordPress/gutenberg/tree/trunk/packages/block-library/src/accordion-panel)) + +- **名称:** core/accordion-panel +- **分类:** 设计 +- **父级:** core/accordion-item +- **支持:** 允许的区块,颜色(背景,渐变,文本),内容角色,交互性,布局(~~允许编辑~~),阴影,间距(区块间距,内边距),排版(字体大小,行高),~~区块可见性~~,~~html~~,~~锁定~~ +- **属性:** 是否选中,默认展开,模板锁定 + +## 文章归档 + +显示按日期归档的文章列表。([源码](https://github.com/WordPress/gutenberg/tree/trunk/packages/block-library/src/archives)) + +- **名称:** core/archives +- **分类:** 小工具 +- **支持:** 对齐,颜色(背景,渐变,链接,文本),交互性(客户端导航),间距(外边距,内边距),排版(字体大小,行高),~~html~~ +- **属性:** 以下拉菜单显示,显示标签,显示文章计数,类型 + +## 音频 + +嵌入简易音频播放器。([源码](https://github.com/WordPress/gutenberg/tree/trunk/packages/block-library/src/audio)) + +- **名称:** core/audio +- **分类:** 媒体 +- **支持:** 对齐,锚点,交互性(客户端导航),间距(外边距,内边距) +- **属性:** 自动播放,二进制数据,说明文字,ID,循环播放,预加载,源文件 + +## 头像 + +添加用户头像。([源码](https://github.com/WordPress/gutenberg/tree/trunk/packages/block-library/src/avatar)) + +- **名称:** core/avatar +- **分类:** 主题 +- **支持:** 对齐,颜色(~~背景~~,~~文本~~),滤镜(双色调),交互性(客户端导航),间距(外边距,内边距),~~宽对齐~~,~~html~~ +- **属性:** 是否为链接,链接目标,尺寸,用户ID + +## 区块模式 + +在站点中复用此设计。([源码](https://github.com/WordPress/gutenberg/tree/trunk/packages/block-library/src/block)) + +- **名称:** core/block +- **分类:** 可复用 +- **支持:** 交互性(客户端导航),~~自定义类名~~,~~html~~,~~插入器~~,~~重命名~~ +- **属性:** 内容,引用 + +## 数学公式 + +使用 LaTeX 显示数学符号。([源码](https://github.com/WordPress/gutenberg/tree/trunk/packages/block-library/src/math)) + +- **名称:** core/math +- **分类:** 文本 +- **属性:** latex、mathML + +## 媒体与文本 + +将媒体内容与文字并排显示,实现更丰富的版式布局。([源码](https://github.com/WordPress/gutenberg/tree/trunk/packages/block-library/src/media-text)) + +- **名称:** core/media-text +- **分类:** 媒体 +- **支持功能:** 对齐(全宽、宽屏)、允许嵌套块、锚点、色彩(背景、渐变、标题、链接、文字)、交互功能(客户端导航)、间距(边距、内边距)、排版(字体大小、行高)、~~HTML~~ +- **属性:** align、focalPoint、href、imageFill、isStackedOnMobile、linkClass、linkDestination、linkTarget、mediaAlt、mediaId、mediaLink、mediaPosition、mediaSizeSlug、mediaType、mediaUrl、mediaWidth、rel、useFeaturedImage、verticalAlignment + +## 不受支持 + +您的站点不支持此区块。([源码](https://github.com/WordPress/gutenberg/tree/trunk/packages/block-library/src/missing)) + +- **名称:** core/missing +- **分类:** 文本 +- **支持功能:** 交互功能(客户端导航)、~~className~~、~~customClassName~~、~~html~~、~~inserter~~、~~reusable~~ +- **属性:** originalContent、originalName、originalUndelimitedContent + +## 摘要分隔符 + +此区块前的内容将显示在归档页面的摘要中。([源码](https://github.com/WordPress/gutenberg/tree/trunk/packages/block-library/src/more)) + +- **名称:** core/more +- **分类:** 设计 +- **支持功能:** 交互功能(客户端导航)、~~className~~、~~customClassName~~、~~html~~、~~multiple~~ +- **属性:** customText、noTeaser + +## 导航菜单 + +一组帮助访客浏览站点的功能区块。([源码](https://github.com/WordPress/gutenberg/tree/trunk/packages/block-library/src/navigation)) + +- **名称:** core/navigation +- **分类:** 主题 +- **允许嵌套块:** core/navigation-link、core/search、core/social-links、core/page-list、core/spacer、core/home-link、core/site-title、core/site-logo、core/navigation-submenu、core/loginout、core/buttons +- **支持功能:** 对齐(全宽、宽屏)、ariaLabel、contentRole、插入器、交互功能、布局(允许子级尺寸调整、默认设置、~~允许继承~~、~~允许切换~~、~~允许垂直对齐~~)、间距(块间距、单位)、排版(字体大小、行高)、~~HTML~~、~~重命名~~ +- **属性:** __unstableLocation、backgroundColor、customBackgroundColor、customOverlayBackgroundColor、customOverlayTextColor、customTextColor、hasIcon、icon、maxNestingLevel、openSubmenusOnClick、overlayBackgroundColor、overlayMenu、overlayTextColor、ref、rgbBackgroundColor、rgbTextColor、showSubmenuIcon、templateLock、textColor + +## 自定义链接 + +向导航菜单添加页面、链接或其他项目。([源码](https://github.com/WordPress/gutenberg/tree/trunk/packages/block-library/src/navigation-link)) + +- **名称:** core/navigation-link +- **分类:** 设计 +- **父级区块:** core/navigation +- **允许嵌套块:** core/navigation-link、core/navigation-submenu、core/page-list +- **支持功能:** 交互功能(客户端导航)、排版(字体大小、行高)、~~HTML~~、~~重命名~~、~~可复用~~ +- **属性:** description、id、isTopLevelLink、kind、label、opensInNewTab、rel、title、type、url + +## 子菜单 + +为导航菜单添加子菜单。([源码](https://github.com/WordPress/gutenberg/tree/trunk/packages/block-library/src/navigation-submenu)) + +- **名称:** core/navigation-submenu +- **分类:** 设计 +- **父级区块:** core/navigation +- **支持功能:** 交互功能(客户端导航)、排版(字体大小、行高)、~~HTML~~、~~可复用~~ +- **属性:** description、id、isTopLevelItem、kind、label、opensInNewTab、rel、title、type、url + +## 分页符 + +将内容分割成多页浏览体验。([源码](https://github.com/WordPress/gutenberg/tree/trunk/packages/block-library/src/nextpage)) + +- **名称:** core/nextpage +- **分类:** 设计 +- **父级区块:** core/post-content +- **支持功能:** 交互功能(客户端导航)、~~className~~、~~customClassName~~、~~html~~ + +## 页面列表 + +显示所有页面的列表。([源码](https://github.com/WordPress/gutenberg/tree/trunk/packages/block-library/src/page-list)) + +- **名称:** core/page-list +- **分类:** 小工具 +- **允许嵌套区块:** core/page-list-item +- **支持功能:** 颜色(背景、渐变、链接、文字)、内容角色、交互性(客户端导航)、间距(边距、内边距)、排版(字体大小、行高)、~~HTML~~、~~可复用性~~ +- **属性:** 是否嵌套、父页面ID + +## 页面列表项 + +在页面列表中显示单个页面。([源码](https://github.com/WordPress/gutenberg/tree/trunk/packages/block-library/src/page-list-item)) + +- **名称:** core/page-list-item +- **分类:** 小工具 +- **父区块:** core/page-list +- **支持功能:** 交互性(客户端导航)、~~HTML~~、~~插入器~~、~~锁定~~、~~可复用性~~ +- **属性:** 是否包含子项、页面ID、标签、链接、标题 + +## 段落 + +所有叙事内容的基础构建单元。([源码](https://github.com/WordPress/gutenberg/tree/trunk/packages/block-library/src/paragraph)) + +- **名称:** core/paragraph +- **分类:** 文本 +- **支持功能:** __unstablePasteTextInline、锚点、颜色(背景、渐变、链接、文字)、交互性(客户端导航)、间距(边距、内边距)、拆分、排版(自适应文字、字体大小、行高)、~~类名~~ +- **属性:** 对齐方式、内容、文本方向、首字下沉、占位符 + +## 模式占位符 + +显示区块模式。([源码](https://github.com/WordPress/gutenberg/tree/trunk/packages/block-library/src/pattern)) + +- **名称:** core/pattern +- **分类:** 主题 +- **支持功能:** 交互性(客户端导航)、~~区块可见性~~、~~HTML~~、~~插入器~~、~~重命名~~ +- **属性:** 模式别名 + +## 文章作者 + +显示文章作者详细信息,包括姓名、头像和个人简介。([源码](https://github.com/WordPress/gutenberg/tree/trunk/packages/block-library/src/post-author)) + +- **名称:** core/post-author +- **分类:** 主题 +- **支持功能:** 颜色(背景、渐变、链接、文字)、滤镜(双色调)、交互性(客户端导航)、间距(边距、内边距)、排版(字体大小、行高)、~~HTML~~ +- **属性:** 头像尺寸、署名行、是否设为链接、链接打开方式、显示头像、显示简介、文本对齐 + +## 作者简介 + +显示作者的个人简介。([源码](https://github.com/WordPress/gutenberg/tree/trunk/packages/block-library/src/post-author-biography)) + +- **名称:** core/post-author-biography +- **分类:** 主题 +- **支持功能:** 颜色(背景、渐变、链接、文字)、交互性(客户端导航)、间距(边距、内边距)、排版(字体大小、行高) +- **属性:** 文本对齐 + +## 作者姓名 + +显示作者姓名。([源码](https://github.com/WordPress/gutenberg/tree/trunk/packages/block-library/src/post-author-name)) + +- **名称:** core/post-author-name +- **分类:** 主题 +- **支持功能:** 颜色(背景、渐变、链接、文字)、交互性(客户端导航)、间距(边距、内边距)、排版(字体大小、行高)、~~HTML~~ +- **属性:** 是否设为链接、链接打开方式、文本对齐 + +## 评论(已弃用) + +此区块已被弃用,请改用评论区区块。([源码](https://github.com/WordPress/gutenberg/tree/trunk/packages/block-library/src/post-comment)) + +- **名称:** core/post-comment +- **实验性功能:** 全站编辑 +- **分类:** 主题 +- **允许嵌套区块:** core/avatar、core/comment-author-name、core/comment-content、core/comment-date、core/comment-edit-link、core/comment-reply-link +- **支持功能:** 交互性(客户端导航)、~~HTML~~、~~插入器~~ +- **属性:** 评论ID + +## 评论数量 + +显示文章的评论数量。([源码](https://github.com/WordPress/gutenberg/tree/trunk/packages/block-library/src/post-comments-count)) + +- **名称:** core/post-comments-count +- **分类:** 主题 +- **支持功能:** 颜色(背景、渐变、文字)、交互性(客户端导航)、间距(边距、内边距)、排版(字体大小、行高)、~~HTML~~ +- **属性:** 文本对齐 + +## 脚注 + +显示页面中添加的脚注。([来源](https://github.com/WordPress/gutenberg/tree/trunk/packages/block-library/src/footnotes)) + +- **名称:** core/footnotes +- **分类:** 文本 +- **支持功能:** 颜色(背景、链接、文本)、交互性(客户端导航)、间距(边距、内边距)、排版(字体大小、行高)、~~HTML~~、~~插入器~~、~~多选~~、~~可复用~~ + +## 表单 + +表单。([来源](https://github.com/WordPress/gutenberg/tree/trunk/packages/block-library/src/form)) + +- **名称:** core/form +- **实验性:** 是 +- **分类:** 通用 +- **允许包含的块:** core/paragraph、core/heading、core/form-input、core/form-submit-button、core/form-submission-notification、core/group、core/columns +- **支持功能:** 锚点、颜色(背景、渐变、链接、文本)、间距(边距、内边距)、排版(字体大小、行高) +- **属性:** action、email、method、submissionMethod + +## 输入字段 + +表单的基本构建块。([来源](https://github.com/WordPress/gutenberg/tree/trunk/packages/block-library/src/form-input)) + +- **名称:** core/form-input +- **实验性:** 是 +- **分类:** 通用 +- **父级块:** core/form +- **支持功能:** 锚点、间距(边距)、~~可复用~~ +- **属性:** inlineLabel、label、name、placeholder、required、type、value、visibilityPermissions + +## 表单提交通知 + +在表单提交后提供通知消息。([来源](https://github.com/WordPress/gutenberg/tree/trunk/packages/block-library/src/form-submission-notification)) + +- **名称:** core/form-submission-notification +- **实验性:** 是 +- **分类:** 通用 +- **父级块:** core/form +- **属性:** type + +## 表单提交按钮 + +表单的提交按钮。([来源](https://github.com/WordPress/gutenberg/tree/trunk/packages/block-library/src/form-submit-button)) + +- **名称:** core/form-submit-button +- **实验性:** 是 +- **分类:** 通用 +- **父级块:** core/form +- **允许包含的块:** core/buttons、core/button + +## 经典编辑器 + +使用经典的 WordPress 编辑器。([来源](https://github.com/WordPress/gutenberg/tree/trunk/packages/block-library/src/freeform)) + +- **名称:** core/freeform +- **分类:** 文本 +- **支持功能:** ~~className~~、~~customClassName~~、~~可复用~~ +- **属性:** content + +## 图库 + +在丰富的图库中展示多张图片。([来源](https://github.com/WordPress/gutenberg/tree/trunk/packages/block-library/src/gallery)) + +- **名称:** core/gallery +- **分类:** 媒体 +- **允许包含的块:** core/image +- **支持功能:** 对齐、锚点、颜色(背景、渐变、~~文本~~)、交互性(客户端导航)、布局(默认、~~允许编辑~~、~~允许继承~~、~~允许切换~~)、间距(块间距、边距、内边距)、单位(em、px、rem、vh、vw)、~~HTML~~ +- **属性:** allowResize、aspectRatio、caption、columns、fixedHeight、ids、imageCrop、images、linkTarget、linkTo、randomOrder、shortCodeTransforms、sizeSlug + +## 组 + +将块聚集在一个布局容器中。([来源](https://github.com/WordPress/gutenberg/tree/trunk/packages/block-library/src/group)) + +- **名称:** core/group +- **分类:** 设计 +- **支持功能:** 对齐(全宽、宽屏)、允许块、锚点、ariaLabel、背景(背景图片、背景尺寸)、颜色(背景、按钮、渐变、标题、链接、文本)、尺寸(最小高度)、交互性(客户端导航)、布局(允许子元素调整大小)、位置(粘性)、阴影、间距(块间距、边距、内边距)、排版(字体大小、行高)、~~HTML~~ +- **属性:** tagName、templateLock + +## 标题 + +引入新章节并组织内容,以帮助访问者(和搜索引擎)理解内容结构。([来源](https://github.com/WordPress/gutenberg/tree/trunk/packages/block-library/src/heading)) + +- **名称:** core/heading +- **分类:** 文本 +- **支持功能:** __unstablePasteTextInline、对齐(全宽、宽屏)、锚点、className、颜色(背景、渐变、链接、文本)、交互性(客户端导航)、间距(边距、内边距)、拆分、排版(自适应文本、字体大小、行高) +- **属性:** content、level、levelOptions、placeholder、textAlign + +## 面包屑导航 + +为层级化文章类型或基于分类法条目显示面包屑导航路径。([源代码](https://github.com/WordPress/gutenberg/tree/trunk/packages/block-library/src/breadcrumbs)) + +- **名称:** core/breadcrumbs +- **实验性功能:** 是 +- **分类:** 主题 +- **支持功能:** 颜色(背景、渐变、链接、文本)、交互性(客户端导航)、间距(边距、内边距)、排版(字体大小、行高)、~~HTML~~ +- **属性:** 分隔符、显示首页链接、类型 + +## 按钮 + +通过按钮式链接引导访客执行操作。([源代码](https://github.com/WordPress/gutenberg/tree/trunk/packages/block-library/src/button)) + +- **名称:** core/button +- **分类:** 设计 +- **父级块:** core/buttons +- **支持功能:** 锚点、颜色(背景、渐变、文本)、交互性(客户端导航)、阴影、间距(内边距)、拆分效果、排版(字体大小、行高)、~~宽对齐~~、~~对齐~~、~~可复用~~ +- **属性:** 背景色、渐变、链接目标、占位符、关联关系、标签名称、文本、文本对齐、文字颜色、标题、类型、链接地址、宽度 + +## 按钮组 + +通过一组按钮式链接引导访客执行操作。([源代码](https://github.com/WordPress/gutenberg/tree/trunk/packages/block-library/src/buttons)) + +- **名称:** core/buttons +- **分类:** 设计 +- **允许嵌套块:** core/button +- **支持功能:** 对齐(全宽、宽幅)、锚点、颜色(背景、渐变、~~文本~~)、内容角色、交互性(客户端导航)、布局(默认、~~允许继承~~、~~允许切换~~)、间距(块间距、边距、内边距)、排版(字体大小、行高)、~~HTML~~ + +## 日历 + +显示您站点的文章日历。([源代码](https://github.com/WordPress/gutenberg/tree/trunk/packages/block-library/src/calendar)) + +- **名称:** core/calendar +- **分类:** 小工具 +- **支持功能:** 对齐、颜色(背景、链接、文本)、交互性(客户端导航)、排版(字体大小、行高)、~~HTML~~ +- **属性:** 月份、年份 + +## 分类列表 + +显示指定分类法下的所有条目列表。([源代码](https://github.com/WordPress/gutenberg/tree/trunk/packages/block-library/src/categories)) + +- **名称:** core/categories +- **分类:** 小工具 +- **支持功能:** 对齐、颜色(背景、渐变、链接、文本)、交互性(客户端导航)、间距(边距、内边距)、排版(字体大小、行高)、~~HTML~~ +- **属性:** 下拉式显示、标签、显示空项、显示层级、显示标签、仅显示顶级分类、显示文章计数、分类法 + +## 代码块 + +展示保留原始空格和制表符的代码片段。([源代码](https://github.com/WordPress/gutenberg/tree/trunk/packages/block-library/src/code)) + +- **名称:** core/code +- **分类:** 文本 +- **支持功能:** 对齐(宽幅)、锚点、颜色(背景、渐变、文本)、交互性(客户端导航)、间距(边距、内边距)、排版(字体大小、行高) +- **属性:** 内容 + +## 单列 + +列块中的独立列单元。([源代码](https://github.com/WordPress/gutenberg/tree/trunk/packages/block-library/src/column)) + +- **名称:** core/column +- **分类:** 设计 +- **父级块:** core/columns +- **支持功能:** 允许嵌套块、锚点、颜色(背景、按钮、渐变、标题、链接、文本)、交互性(客户端导航)、布局、阴影、间距(块间距、内边距)、排版(字体大小、行高)、~~HTML~~、~~可复用~~ +- **属性:** 模板锁定、垂直对齐、宽度 + +## 多列布局 + +以多列形式展示内容,每列可添加独立内容块。([源代码](https://github.com/WordPress/gutenberg/tree/trunk/packages/block-library/src/columns)) + +- **名称:** core/columns +- **分类:** 设计 +- **允许嵌套块:** core/column +- **支持功能:** 对齐(全宽、宽幅)、锚点、颜色(背景、按钮、渐变、标题、链接、文本)、交互性(客户端导航)、布局(默认、~~允许编辑~~、~~允许继承~~、~~允许切换~~)、阴影、间距(块间距、边距、内边距)、排版(字体大小、行高)、~~HTML~~ +- **属性:** 移动端堆叠显示、模板锁定、垂直对齐 + +## 评论表单 + +显示文章的评论表单。([源码](https://github.com/WordPress/gutenberg/tree/trunk/packages/block-library/src/post-comments-form)) + +- **名称:** core/post-comments-form +- **分类:** 主题 +- **支持:** 颜色(背景、渐变、标题、链接、文本),间距(外边距、内边距),版式(字体大小、行高),~~HTML~~ +- **属性:** 文本对齐 + +## 评论链接 + +显示当前文章评论的链接。([源码](https://github.com/WordPress/gutenberg/tree/trunk/packages/block-library/src/post-comments-link)) + +- **名称:** core/post-comments-link +- **分类:** 主题 +- **支持:** 颜色(背景、链接、~~文本~~),交互性(客户端导航),间距(外边距、内边距),版式(字体大小、行高),~~HTML~~ +- **属性:** 文本对齐 + +## 内容 + +显示文章或页面的内容。([源码](https://github.com/WordPress/gutenberg/tree/trunk/packages/block-library/src/post-content)) + +- **名称:** core/post-content +- **分类:** 主题 +- **支持:** 对齐(全宽、宽),背景(背景图片、背景尺寸),颜色(背景、渐变、标题、链接、文本),尺寸(最小高度),交互性(客户端导航),布局,间距(块间距、外边距、内边距),版式(字体大小、行高),~~HTML~~ +- **属性:** 标签名称 + +## 日期 + +显示文章或页面等内容的发布日期。([源码](https://github.com/WordPress/gutenberg/tree/trunk/packages/block-library/src/post-date)) + +- **名称:** core/post-date +- **分类:** 主题 +- **支持:** 颜色(背景、渐变、链接、文本),交互性(客户端导航),间距(外边距、内边距),版式(字体大小、行高),~~HTML~~ +- **属性:** 日期时间、格式、是否为链接、文本对齐 + +## 摘要 + +显示文章的摘要。([源码](https://github.com/WordPress/gutenberg/tree/trunk/packages/block-library/src/post-excerpt)) + +- **名称:** core/post-excerpt +- **分类:** 主题 +- **支持:** 颜色(背景、渐变、链接、文本),交互性(客户端导航),间距(外边距、内边距),版式(字体大小、行高),~~HTML~~ +- **属性:** 摘要长度、更多文本、在新行显示更多、文本对齐 + +## 特色图片 + +显示文章的特色图片。([源码](https://github.com/WordPress/gutenberg/tree/trunk/packages/block-library/src/post-featured-image)) + +- **名称:** core/post-featured-image +- **分类:** 主题 +- **支持:** 对齐(居中、全宽、左、右、宽),颜色(~~背景~~、~~文本~~),滤镜(双色调),交互性(客户端导航),阴影(),间距(外边距、内边距),~~HTML~~ +- **属性:** 宽高比、自定义渐变、自定义覆盖颜色、暗化比例、渐变、高度、是否为链接、链接目标、覆盖颜色、关联属性、缩放、尺寸标识、使用文章首张图片、宽度 + +## 文章导航链接 + +显示与当前文章相邻的上一篇或下一篇文章链接。([源码](https://github.com/WordPress/gutenberg/tree/trunk/packages/block-library/src/post-navigation-link)) + +- **名称:** core/post-navigation-link +- **分类:** 主题 +- **支持:** 颜色(背景、链接、文本),交互性(客户端导航),版式(字体大小、行高),~~HTML~~,~~可复用~~ +- **属性:** 箭头、标签、链接标签、显示标题、分类法、文本对齐、类型 + +## 文章模板 + +包含用于渲染文章的块元素,如标题、日期、特色图片、内容或摘要等。([源码](https://github.com/WordPress/gutenberg/tree/trunk/packages/block-library/src/post-template)) + +- **名称:** core/post-template +- **分类:** 主题 +- **祖先:** core/query +- **支持:** 对齐(全宽、宽),颜色(背景、渐变、链接、文本),交互性(客户端导航),布局,间距(块间距、外边距、内边距),版式(字体大小、行高),~~HTML~~,~~可复用~~ + +## 文章分类项 + +显示文章的分类项。([源码](https://github.com/WordPress/gutenberg/tree/trunk/packages/block-library/src/post-terms)) + +- **名称:** core/post-terms +- **分类:** 主题 +- **支持:** 颜色(背景、渐变、链接、文本),交互性(客户端导航),间距(外边距、内边距),版式(字体大小、行高),~~HTML~~ +- **属性:** 前缀、分隔符、后缀、分类项、文本对齐 + +## 评论作者头像 (已弃用) + +此区块已被弃用,请改用头像区块。([源码](https://github.com/WordPress/gutenberg/tree/trunk/packages/block-library/src/comment-author-avatar)) + +- **名称:** core/comment-author-avatar +- **实验性:** fse +- **分类:** 主题 +- **祖先区块:** core/comment-template +- **支持功能:** 颜色(背景、~~文字~~)、交互性(客户端导航)、间距(边距、内边距)、~~HTML~~、~~插入器~~ +- **属性:** 高度、宽度 + +## 评论作者名称 + +显示评论作者的名称。([源码](https://github.com/WordPress/gutenberg/tree/trunk/packages/block-library/src/comment-author-name)) + +- **名称:** core/comment-author-name +- **分类:** 主题 +- **祖先区块:** core/comment-template +- **支持功能:** 颜色(背景、渐变、链接、文字)、交互性(客户端导航)、间距(边距、内边距)、排版(字体大小、行高)、~~HTML~~ +- **属性:** 是否为链接、链接目标、文本对齐 + +## 评论内容 + +显示评论的内容。([源码](https://github.com/WordPress/gutenberg/tree/trunk/packages/block-library/src/comment-content)) + +- **名称:** core/comment-content +- **分类:** 主题 +- **祖先区块:** core/comment-template +- **支持功能:** 颜色(背景、渐变、链接、文字)、间距(内边距)、排版(字体大小、行高)、~~HTML~~ +- **属性:** 文本对齐 + +## 评论日期 + +显示评论发布的日期。([源码](https://github.com/WordPress/gutenberg/tree/trunk/packages/block-library/src/comment-date)) + +- **名称:** core/comment-date +- **分类:** 主题 +- **祖先区块:** core/comment-template +- **支持功能:** 颜色(背景、渐变、链接、文字)、交互性(客户端导航)、间距(边距、内边距)、排版(字体大小、行高)、~~HTML~~ +- **属性:** 日期格式、是否为链接 + +## 评论编辑链接 + +显示指向 WordPress 仪表盘中编辑评论的链接。此链接仅对具有编辑评论权限的用户可见。([源码](https://github.com/WordPress/gutenberg/tree/trunk/packages/block-library/src/comment-edit-link)) + +- **名称:** core/comment-edit-link +- **分类:** 主题 +- **祖先区块:** core/comment-template +- **支持功能:** 颜色(背景、渐变、链接、~~文字~~)、交互性(客户端导航)、间距(边距、内边距)、排版(字体大小、行高)、~~HTML~~ +- **属性:** 链接目标、文本对齐 + +## 评论回复链接 + +显示用于回复评论的链接。([源码](https://github.com/WordPress/gutenberg/tree/trunk/packages/block-library/src/comment-reply-link)) + +- **名称:** core/comment-reply-link +- **分类:** 主题 +- **祖先区块:** core/comment-template +- **支持功能:** 颜色(背景、渐变、链接、~~文字~~)、间距(边距、内边距)、排版(字体大小、行高)、~~HTML~~ +- **属性:** 文本对齐 + +## 评论模板 + +包含用于显示评论的区块元素,如标题、日期、作者、头像等。([源码](https://github.com/WordPress/gutenberg/tree/trunk/packages/block-library/src/comment-template)) + +- **名称:** core/comment-template +- **分类:** 设计 +- **父区块:** core/comments +- **支持功能:** 对齐、交互性(客户端导航)、间距(边距、内边距)、排版(字体大小、行高)、~~HTML~~、~~可复用~~ + +## 评论 + +高级区块,允许使用不同的视觉配置显示文章评论。([源码](https://github.com/WordPress/gutenberg/tree/trunk/packages/block-library/src/comments)) + +- **名称:** core/comments +- **分类:** 主题 +- **支持功能:** 对齐(全宽、宽)、颜色(背景、渐变、标题、链接、文字)、间距(边距、内边距)、排版(字体大小、行高)、~~HTML~~ +- **属性:** 传统模式、标签名称 + +## 评论分页 + +在适用时显示分页导航,用于跳转至上一组/下一组评论。([源码](https://github.com/WordPress/gutenberg/tree/trunk/packages/block-library/src/comments-pagination)) + +- **名称:** core/comments-pagination +- **分类:** 主题 +- **父区块:** core/comments +- **允许的子区块:** core/comments-pagination-previous、core/comments-pagination-numbers、core/comments-pagination-next +- **支持功能:** 对齐、颜色(背景、渐变、链接、文字)、交互性(客户端导航)、布局(默认、~~允许继承~~、~~允许切换~~)、排版(字体大小、行高)、~~HTML~~、~~可复用~~ +- **属性:** 分页箭头 + +## 短代码 + +通过WordPress短代码插入额外的自定义元素。([源码](https://github.com/WordPress/gutenberg/tree/trunk/packages/block-library/src/shortcode)) + +- **名称:** core/shortcode +- **分类:** 小工具 +- **支持:** ~~className~~、~~customClassName~~、~~html~~ +- **属性:** text + +## 站点标志 + +显示代表此站点的图像。更新此区块,更改将应用到所有位置。([源码](https://github.com/WordPress/gutenberg/tree/trunk/packages/block-library/src/site-logo)) + +- **名称:** core/site-logo +- **分类:** 主题 +- **支持:** 对齐、颜色(~~背景~~、~~文本~~)、滤镜(双色调)、交互性(客户端导航)、间距(边距、内边距)、~~alignWide~~、~~html~~ +- **属性:** isLink、linkTarget、shouldSyncIcon、width + +## 站点标语 + +用简短的文字描述此站点的内容。这对搜索结果、社交媒体分享非常重要,并能向访问者清晰传达信息。([源码](https://github.com/WordPress/gutenberg/tree/trunk/packages/block-library/src/site-tagline)) + +- **名称:** core/site-tagline +- **分类:** 主题 +- **支持:** 对齐(全宽、宽)、颜色(背景、渐变、文本)、内容角色、交互性(客户端导航)、间距(边距、内边距)、排版(字体大小、行高)、~~html~~ +- **属性:** level、levelOptions、textAlign + +## 站点标题 + +显示此站点的名称。更新该区块,更改将应用到所有使用位置。这也将显示在浏览器标题栏和搜索结果中。([源码](https://github.com/WordPress/gutenberg/tree/trunk/packages/block-library/src/site-title)) + +- **名称:** core/site-title +- **分类:** 主题 +- **支持:** 对齐(全宽、宽)、颜色(背景、渐变、链接、文本)、交互性(客户端导航)、间距(边距、内边距)、排版(字体大小、行高)、~~html~~ +- **属性:** isLink、level、levelOptions、linkTarget、textAlign + +## 社交图标 + +显示链接到社交资料或站点的图标。([源码](https://github.com/WordPress/gutenberg/tree/trunk/packages/block-library/src/social-link)) + +- **名称:** core/social-link +- **分类:** 小工具 +- **父级:** core/social-links +- **支持:** 交互性(客户端导航)、~~html~~、~~reusable~~ +- **属性:** label、rel、service、url + +## 社交图标组 + +显示链接到您的社交资料或站点的图标。([源码](https://github.com/WordPress/gutenberg/tree/trunk/packages/block-library/src/social-links)) + +- **名称:** core/social-links +- **分类:** 小工具 +- **允许的区块:** core/social-link +- **支持:** 对齐(居中、左、右)、锚点、颜色(背景、渐变、~~enableContrastChecker~~、~~文本~~)、内容角色、交互性(客户端导航)、布局(默认、~~allowInheriting~~、~~allowSwitching~~、~~allowVerticalAlignment~~)、间距(区块间距、边距、内边距、单位)、~~html~~ +- **属性:** customIconBackgroundColor、customIconColor、iconBackgroundColor、iconBackgroundColorValue、iconColor、iconColorValue、openInNewTab、showLabels、size + +## 间距块 + +在区块之间添加空白并自定义其高度。([源码](https://github.com/WordPress/gutenberg/tree/trunk/packages/block-library/src/spacer)) + +- **名称:** core/spacer +- **分类:** 设计 +- **支持:** 锚点、交互性(客户端导航)、间距(边距) +- **属性:** height、width + +## 表格 + +以行和列的形式创建结构化内容以显示信息。([源码](https://github.com/WordPress/gutenberg/tree/trunk/packages/block-library/src/table)) + +- **名称:** core/table +- **分类:** 文本 +- **支持:** 对齐、锚点、颜色(背景、渐变、文本)、交互性(客户端导航)、间距(边距、内边距)、排版(字体大小、行高) +- **属性:** body、caption、foot、hasFixedLayout、head + +## 目录 + +用标题列表总结您的文章。为标题区块添加HTML锚点以在此处链接它们。([源码](https://github.com/WordPress/gutenberg/tree/trunk/packages/block-library/src/table-of-contents)) + +- **名称:** core/table-of-contents +- **实验性:** 是 +- **分类:** 设计 +- **支持:** ariaLabel、颜色(背景、渐变、链接、文本)、交互性(客户端导航)、间距(边距、内边距)、排版(字体大小、行高)、~~html~~ +- **属性:** headings、maxLevel、onlyIncludeCurrentPage、ordered + +## 页码 + +显示用于分页的页码列表。([来源](https://github.com/WordPress/gutenberg/tree/trunk/packages/block-library/src/query-pagination-numbers)) + +- **名称:** core/query-pagination-numbers +- **分类:** theme(主题) +- **父级:** core/query-pagination +- **支持:** color(background背景,gradients渐变,~~text文字~~),interactivity(clientNavigation客户端导航),typography(fontSize字体大小,lineHeight行高),~~html~~,~~reusable可复用~~ +- **属性:** midSize + +## 上一页 + +显示上一篇文章页面链接。([来源](https://github.com/WordPress/gutenberg/tree/trunk/packages/block-library/src/query-pagination-previous)) + +- **名称:** core/query-pagination-previous +- **分类:** theme(主题) +- **父级:** core/query-pagination +- **支持:** color(background背景,gradients渐变,~~text文字~~),interactivity(clientNavigation客户端导航),typography(fontSize字体大小,lineHeight行高),~~html~~,~~reusable可复用~~ +- **属性:** label + +## 查询标题 + +显示查询标题。([来源](https://github.com/WordPress/gutenberg/tree/trunk/packages/block-library/src/query-title)) + +- **名称:** core/query-title +- **分类:** theme(主题) +- **支持:** align(full全宽,wide宽),color(background背景,gradients渐变,text文字),interactivity(clientNavigation客户端导航),spacing(margin边距,padding内边距),typography(fontSize字体大小,lineHeight行高),~~html~~ +- **属性:** level,levelOptions,showPrefix,showSearchTerm,textAlign,type + +## 查询总数 + +显示查询结果总数。([来源](https://github.com/WordPress/gutenberg/tree/trunk/packages/block-library/src/query-total)) + +- **名称:** core/query-total +- **分类:** theme(主题) +- **祖先:** core/query +- **支持:** align(full全宽,wide宽),color(background背景,gradients渐变,text文字),interactivity(clientNavigation客户端导航),spacing(margin边距,padding内边距),typography(fontSize字体大小,lineHeight行高),~~html~~ +- **属性:** displayType + +## 引用 + +为引用的文本提供视觉强调。"在引用他人时,我们其实是在引用自己。" —— 胡里奥·科塔萨尔([来源](https://github.com/WordPress/gutenberg/tree/trunk/packages/block-library/src/quote)) + +- **名称:** core/quote +- **分类:** text(文本) +- **支持:** align(full全宽,left左,right右,wide宽),allowedBlocks,anchor,background(backgroundImage背景图片,backgroundSize背景尺寸),color(background背景,gradients渐变,heading标题,link链接,text文字),dimensions(minHeight最小高度),interactivity(clientNavigation客户端导航),layout(~~allowEditing允许编辑~~),spacing(blockGap块间距,margin边距,padding内边距),typography(fontSize字体大小,lineHeight行高),~~html~~ +- **属性:** citation,textAlign,value + +## 阅读更多 + +显示文章、页面或任何其他内容类型的链接。([来源](https://github.com/WordPress/gutenberg/tree/trunk/packages/block-library/src/read-more)) + +- **名称:** core/read-more +- **分类:** theme(主题) +- **支持:** color(background背景,gradients渐变,text文字),interactivity(clientNavigation客户端导航),spacing(margin边距,padding内边距),typography(fontSize字体大小,lineHeight行高),~~html~~ +- **属性:** content,linkTarget + +## RSS + +显示来自任何RSS或Atom订阅源的条目。([来源](https://github.com/WordPress/gutenberg/tree/trunk/packages/block-library/src/rss)) + +- **名称:** core/rss +- **分类:** widgets(小工具) +- **支持:** align,color(background背景,gradients渐变,link链接,text文字),interactivity(clientNavigation客户端导航),spacing(margin边距,padding内边距),~~html~~ +- **属性:** blockLayout,columns,displayAuthor,displayDate,displayExcerpt,excerptLength,feedURL,itemsToShow,openInNewTab,rel + +## 搜索 + +帮助访问者找到您的内容。([来源](https://github.com/WordPress/gutenberg/tree/trunk/packages/block-library/src/search)) + +- **名称:** core/search +- **分类:** widgets(小工具) +- **支持:** align(center居中,left左,right右),color(background背景,gradients渐变,text文字),interactivity,spacing(margin边距),typography(fontSize字体大小,lineHeight行高),~~html~~ +- **属性:** buttonPosition,buttonText,buttonUseIcon,isSearchFieldHidden,label,placeholder,query,showLabel,width,widthUnit + +## 分隔符 + +用水平分隔符在想法或部分之间创建间隔。([来源](https://github.com/WordPress/gutenberg/tree/trunk/packages/block-library/src/separator)) + +- **名称:** core/separator +- **分类:** design(设计) +- **支持:** align(center居中,full全宽,wide宽),anchor,color(background背景,gradients渐变,~~enableContrastChecker启用对比度检查器~~,~~text文字~~),interactivity(clientNavigation客户端导航),spacing(margin边距) +- **属性:** opacity,tagName \ No newline at end of file diff --git a/reference-guides/data/README.md b/reference-guides/data/README.md new file mode 100644 index 0000000..62e73e3 --- /dev/null +++ b/reference-guides/data/README.md @@ -0,0 +1,20 @@ +# 数据模块参考 + +- [**core**:WordPress 核心数据](/docs/reference-guides/data/data-core.md) +- [**core/annotations**:注释](/docs/reference-guides/data/data-core-annotations.md) +- [**core/block-directory**:区块目录](/docs/reference-guides/data/data-core-block-directory.md) +- [**core/block-editor**:区块编辑器的数据](/docs/reference-guides/data/data-core-block-editor.md) +- [**core/blocks**:区块类型数据](/docs/reference-guides/data/data-core-blocks.md) +- [**core/commands**:命令面板](/docs/reference-guides/data/data-core-commands.md) +- [**core/customize-widgets**:自定义小工具](/docs/reference-guides/data/data-core-customize-widgets.md) +- [**core/edit-post**:编辑器界面数据](/docs/reference-guides/data/data-core-edit-post.md) +- [**core/edit-site**:编辑站点](/docs/reference-guides/data/data-core-edit-site.md) +- [**core/edit-widgets**:编辑小工具](/docs/reference-guides/data/data-core-edit-widgets.md) +- [**core/editor**:文章编辑器的数据](/docs/reference-guides/data/data-core-editor.md) +- [**core/keyboard-shortcuts**:键盘快捷键数据](/docs/reference-guides/data/data-core-keyboard-shortcuts.md) +- [**core/notices**:通知数据](/docs/reference-guides/data/data-core-notices.md) +- [**core/nux**:NUX(新用户体验)数据](/docs/reference-guides/data/data-core-nux.md) +- [**core/preferences**:偏好设置](/docs/reference-guides/data/data-core-preferences.md) +- [**core/reusable-blocks**:可复用区块](/docs/reference-guides/data/data-core-reusable-blocks.md) +- [**core/rich-text**:富文本](/docs/reference-guides/data/data-core-rich-text.md) +- [**core/viewport**:视口数据](/docs/reference-guides/data/data-core-viewport.md) \ No newline at end of file diff --git a/reference-guides/data/data-core-annotations.md b/reference-guides/data/data-core-annotations.md new file mode 100644 index 0000000..3d507be --- /dev/null +++ b/reference-guides/data/data-core-annotations.md @@ -0,0 +1,23 @@ +# 注释功能 + +命名空间:`core/annotations`。 + +
    +本功能包仍处于实验阶段。“实验性”意味着这是早期实现版本,可能发生重大且不兼容的变更。 +
    + +## 选择器 + + + +暂无内容需要记录。 + + + +## 操作 + + + +暂无内容需要记录。 + + \ No newline at end of file diff --git a/reference-guides/data/data-core-block-directory.md b/reference-guides/data/data-core-block-directory.md new file mode 100644 index 0000000..084bfad --- /dev/null +++ b/reference-guides/data/data-core-block-directory.md @@ -0,0 +1,223 @@ +# 区块目录 + +命名空间:`core/block-directory` + +## 选择器 + + + +### getDownloadableBlocks + +返回可用的未安装区块。 + +_参数_ + +- _state_ `Object`: 全局应用状态 +- _filterValue_ `string`: 搜索关键词 + +_返回值_ + +- `Array`: 可下载区块列表 + +### getErrorNoticeForBlock + +返回指定区块的错误通知。 + +_参数_ + +- _state_ `Object`: 全局应用状态 +- _blockId_ `string`: 区块插件ID,例如:my-block + +_返回值_ + +- `string|boolean`: 错误文本,若无错误则返回 false + +### getErrorNotices + +返回所有区块错误通知。 + +_参数_ + +- _state_ `Object`: 全局应用状态 + +_返回值_ + +- `Object`: 包含错误通知的对象 + +### getInstalledBlockTypes + +返回当前会话中已在服务器上安装的区块类型。 + +_参数_ + +- _state_ `Object`: 全局应用状态 + +_返回值_ + +- `Array`: 区块类型项 + +### getNewBlockTypes + +返回已在服务器安装并在当前文章中使用的区块类型。 + +_参数_ + +- _state_ `Object`: 全局应用状态 + +_返回值_ + +- `Array`: 区块类型项 + +### getUnusedBlockTypes + +返回已在服务器安装但未在当前文章中使用的区块类型。 + +_参数_ + +- _state_ `Object`: 全局应用状态 + +_返回值_ + +- `Array`: 区块类型项 + +### isInstalling + +判断区块插件是否正在安装中。 + +_参数_ + +- _state_ `Object`: 全局应用状态 +- _blockId_ `string`: 区块ID + +_返回值_ + +- `boolean`: 该区块是否正在安装 + +### isRequestingDownloadableBlocks + +判断应用是否正在请求可下载区块。 + +_参数_ + +- _state_ `Object`: 全局应用状态 +- _filterValue_ `string`: 搜索关键词 + +_返回值_ + +- `boolean`: 是否正在请求区块列表 + + + +## 操作 + + + +### addInstalledBlockType + +返回用于将区块类型添加到"新安装"跟踪列表的操作对象。 + +_参数_ + +- _item_ `Object`: 包含区块ID和名称的区块项 + +_返回值_ + +- `Object`: 操作对象 + +### clearErrorNotice + +清空指定区块的错误通知。 + +_参数_ + +- _blockId_ `string`: 区块插件ID,例如:my-block + +_返回值_ + +- `Object`: 操作对象 + +### fetchDownloadableBlocks + +返回用于表示正在请求和加载可下载区块的操作对象。 + +_参数_ + +- _filterValue_ `string`: 搜索关键词 + +_返回值_ + +- `Object`: 操作对象 + +### installBlockType + +触发安装区块插件的操作。 + +_参数_ + +- _block_ `Object`: 搜索返回的区块项 + +_返回值_ + +- `boolean`: 区块是否成功安装和加载 + +### receiveDownloadableBlocks + +返回用于表示可下载区块已更新的操作对象。 + +_参数_ + +- _downloadableBlocks_ `Array`: 可下载区块 +- _filterValue_ `string`: 搜索关键词 + +_返回值_ + +- `Object`: 操作对象 + +### removeInstalledBlockType + +返回用于从"新安装"跟踪列表中移除区块类型的操作对象。 + +_参数_ + +- _item_ `string`: 包含区块ID和名称的区块项 + +_返回值_ + +- `Object`: 操作对象 + +### setErrorNotice + +设置要为用户显示的指定区块错误通知。 + +_参数_ + +- _blockId_ `string`: 区块插件ID,例如:my-block +- _message_ `string`: 通知中显示的消息 +- _isFatal_ `boolean`: 用户是否可以从错误中恢复 + +_返回值_ + +- `Object`: 操作对象 + +### setIsInstalling + +返回用于指示安装进度的操作对象。 + +_参数_ + +- _blockId_ `string`: +- _isInstalling_ `boolean`: + +_返回值_ + +- `Object`: 操作对象 + +### uninstallBlockType + +触发卸载区块插件的操作。 + +_参数_ + +- _block_ `Object`: 区块类型对象 + + \ No newline at end of file diff --git a/reference-guides/data/data-core-block-editor.md b/reference-guides/data/data-core-block-editor.md new file mode 100644 index 0000000..cac4aec --- /dev/null +++ b/reference-guides/data/data-core-block-editor.md @@ -0,0 +1,1878 @@ +### updateBlockListSettings + +用于更改指定区块嵌套设置的操作。 + +**参数** + +- _clientId_ `string | SettingsByClientId`: 要获取嵌套设置的区块客户端ID,或按客户端ID分组的设置对象 +- _settings_ `Object`: 包含嵌套区块新设置的对象 + +**返回值** + +- `Object`: 操作对象 + +### updateSettings + +用于更新区块编辑器设置的操作。 + +**参数** + +- _settings_ `Object`: 更新后的设置 + +**返回值** + +- `Object`: 操作对象 + +### validateBlocksToTemplate + +区块有效性是区块状态(在重置时)与模板设置的函数。作为对其在不同状态部分中位置的折中方案,此处将其实现为区块重置操作的副作用。 + +**参数** + +- _blocks_ `Array`: 区块数组 + + + +### removeBlock(移除区块) + +返回一个操作对象,用于发出信号指示将移除具有指定客户端ID的区块。 + +**参数** + +- _clientId_ `string`:要移除的区块的客户端ID。 +- _selectPrevious_ `boolean`:如果为 true,则在移除区块时选择前一个区块。 + +**返回值** + +- `Object`:操作对象。 + +### removeBlocks(移除多个区块) + +生成操作对象,用于发出信号指示将移除与指定客户端ID集合对应的区块。 + +**参数** + +- _clientIds_ `string|string[]`:要移除的区块的客户端ID。 +- _selectPrevious_ `boolean`:如果为 true,则在移除区块时选择前一个区块或直接父级(如果不存在前一个区块)。 + +### replaceBlock(替换区块) + +将单个区块替换为一个或多个替换区块的操作。 + +**参数** + +- _clientId_ `(string|string[])`:要替换的区块的客户端ID。 +- _block_ `(Object|Object[])`:替换的区块。 + +**返回值** + +- `Object`:操作对象。 + +### replaceBlocks(替换多个区块) + +将指定区块替换为一个或多个替换区块的操作。 + +**参数** + +- _clientIds_ `(string|string[])`:要替换的区块的客户端ID。 +- _blocks_ `(Object|Object[])`:替换的区块。 +- _indexToSelect_ `number`:要选择的替换区块的索引。 +- _initialPosition_ `0|-1|null`:操作后选中区块中光标的初始位置。 +- _meta_ `?Object`:可选,传递给操作对象的元数据值。 + +**返回值** + +- `Object`:操作对象。 + +### replaceInnerBlocks(替换内部区块) + +返回一个操作对象,用于发出信号指示应替换具有指定客户端ID的内部区块。 + +**参数** + +- _rootClientId_ `string`:其内部区块将被替换的区块的客户端ID。 +- _blocks_ `Object[]`:作为新内部区块插入的区块对象。 +- _updateSelection_ `?boolean`:如果为 true,将更新区块选择;如果为 false,区块选择不会改变。默认为 false。 +- _initialPosition_ `0|-1|null`:初始区块位置。 + +**返回值** + +- `Object`:操作对象。 + +### resetBlocks(重置区块) + +将区块状态重置为指定的区块数组的操作,优先于状态中反映的任何其他编辑内容。 + +**参数** + +- _blocks_ `Array`:区块数组。 + +### resetSelection(重置选择) + +返回一个操作对象,用于发出信号指示应将选择状态重置为指定的选择。 + +**参数** + +- _selectionStart_ `WPBlockSelection`:选择的起始位置。 +- _selectionEnd_ `WPBlockSelection`:选择的结束位置。 +- _initialPosition_ `0|-1|null`:初始区块位置。 + +**返回值** + +- `Object`:操作对象。 + +### selectBlock(选择区块) + +返回一个操作对象,用于发出信号指示已选择具有指定客户端ID的区块,并可选择接受反映其选择方向的位置值。初始位置为 -1 表示反向选择。 + +**参数** + +- _clientId_ `string`:区块的客户端ID。 +- _initialPosition_ `0|-1|null`:可选的初始位置。传入 -1 表示反向选择,或传入 `null` 以防止聚焦该区块。 + +**返回值** + +- `Object`:操作对象。 + +### selectionChange(选择变更) + +更改用户光标位置的操作。 + +**参数** + +- _clientId_ `string|WPSelection`:选中的区块客户端ID。 +- _attributeKey_ `string`:选中的区块属性键。 +- _startOffset_ `number`:起始偏移量。 +- _endOffset_ `number`:结束偏移量。 + +**返回值** + +- `Object`:操作对象。 + +### selectNextBlock(选择下一个区块) + +生成操作对象,用于发出信号指示应选择给定客户端ID之后的下一个区块。 + +**参数** + +- _clientId_ `string`:区块的客户端ID。 + +### selectPreviousBlock(选择上一个区块) + +生成操作对象,用于发出信号指示应选择给定客户端ID之前的上一个区块(或可选择从下到上的第一个父级)。 + +**参数** + +- _clientId_ `string`:区块的客户端ID。 +- _fallbackToParent_ `boolean`:如果为 true,在没有上一个区块时选择第一个父级。 + +### getLowestCommonAncestorWithSelectedBlock + +给定一个区块客户端ID,返回与选定客户端ID的最低公共祖先。 + +**参数** + +- _state_ `Object`:编辑器状态。 +- _clientId_ `string`:用于查找公共祖先客户端ID的起始区块。 + +**返回值** + +- `string`:公共祖先客户端ID或未定义值 + +### getMultiSelectedBlockClientIds + +返回当前多选区块的客户端ID集合,若无多选则返回空数组。 + +**参数** + +- _state_ `Object`:编辑器状态。 + +**返回值** + +- `Array`:多选区块的客户端ID集合。 + +### getMultiSelectedBlocks + +返回当前多选区块的对象集合,若无多选则返回空数组。 + +**参数** + +- _state_ `Object`:编辑器状态。 + +**返回值** + +- `Array`:多选区块对象集合。 + +### getMultiSelectedBlocksEndClientId + +返回多选集合的结束区块客户端ID,若无多选则返回null。 + +注意:该ID不一定是选区中的最后一个客户端ID。 + +**关联函数** + +- getLastMultiSelectedBlockClientId + +**参数** + +- _state_ `Object`:编辑器状态。 + +**返回值** + +- `?string`:结束多选的区块客户端ID。 + +### getMultiSelectedBlocksStartClientId + +返回多选集合的起始区块客户端ID,若无多选则返回null。 + +注意:该ID不一定是选区中的第一个客户端ID。 + +**关联函数** + +- getFirstMultiSelectedBlockClientId + +**参数** + +- _state_ `Object`:编辑器状态。 + +**返回值** + +- `?string`:开始多选的区块客户端ID。 + +### getNextBlockClientId + +从给定的参考起始ID返回下一个相邻区块的客户端ID。默认起始点为当前选定区块。若无下一区块则返回null。 + +**参数** + +- _state_ `Object`:编辑器状态。 +- _startClientId_ `?string`:可选参数,指定搜索起始区块的客户端ID。 + +**返回值** + +- `?string`:相邻区块的客户端ID,若无则返回null。 + +### getPatternsByBlockTypes + +根据模式声明的`blockTypes`属性和区块名称返回匹配的模式列表。模式可通过`blockTypes`集成到工作流中,例如在占位符状态(插入期间)推荐适用模式或进行区块转换。 + +**参数** + +- _state_ `Object`:编辑器状态。 +- _blockNames_ `string|string[]`:用于匹配模式的单个或多个区块名称。 +- _rootClientId_ `?string`:可选参数,指定目标根级客户端ID。 + +**返回值** + +- `Array`:基于声明`blockTypes`和区块名称匹配的区块模式列表。 + +### getPreviousBlockClientId + +从给定的参考起始ID返回上一个相邻区块的客户端ID。默认起始点为当前选定区块。若无上一区块则返回null。 + +**参数** + +- _state_ `Object`:编辑器状态。 +- _startClientId_ `?string`:可选参数,指定搜索起始区块的客户端ID。 + +**返回值** + +- `?string`:相邻区块的客户端ID,若无则返回null。 + +### getSelectedBlock + +返回当前选定的区块对象,若无选定区块则返回null。 + +**使用示例** + +```js +import { select } from '@wordpress/data'; +import { store as blockEditorStore } from '@wordpress/block-editor'; + +// 初始化活动区块客户端ID +let activeBlockClientId = null; + +const getActiveBlockData = () => { + const activeBlock = select( blockEditorStore ).getSelectedBlock(); + + if ( activeBlock && activeBlock.clientId !== activeBlockClientId ) { + activeBlockClientId = activeBlock.clientId; + + // 获取活动区块名称和属性 + const activeBlockName = activeBlock.name; + const activeBlockAttributes = activeBlock.attributes; + + // 输出活动区块名称和属性 + console.log( activeBlockName, activeBlockAttributes ); + } +}; + +// 订阅编辑器变更 +// wp.data.subscribe(() => { +// getActiveBlockData() +// }) + +// 点击时更新活动区块数据 +// onclick="getActiveBlockData()" +``` + +**参数** + +- _state_ `Object`:全局应用状态。 + +**返回值** + +- `?Object`:选定的区块对象。 + +### 是否处于多选状态 + +指示是否正处于多选过程中。该标志仅在通过鼠标移动进行多选时为真,多选完成后将变为假。 + +**相关项** + +- hasMultiSelection + +**参数** + +- _state_ `对象`: 全局应用状态。 + +**返回值** + +- `布尔值`: 若处于多选状态返回真,否则返回假。 + +### 是否启用多选 + +选择器,返回多选功能是否启用。 + +**参数** + +- _state_ `对象`: 全局应用状态。 + +**返回值** + +- `布尔值`: 若允许多选块则返回真,若禁用多选则返回假。 + +### 是否正在输入 + +若用户正在输入则返回真,否则返回假。 + +**参数** + +- _state_ `对象`: 全局应用状态。 + +**返回值** + +- `布尔值`: 用户是否正在输入。 + +### 是否可解组 + +指示块是否可解组。若块为包含内部块的单一分组块,则该块可解组。若块具有“解组”转换,则即使不是默认分组块也可解组。此外,仅当块包含内部块且可被移除时才能解组。 + +**参数** + +- _state_ `对象`: 全局应用状态。 +- _clientId_ `字符串`: 块的客户端ID。若未传递,则使用所选块的客户端ID。 + +**返回值** + +- `布尔值`: 若块可解组则返回真。 + +### 是否为有效模板 + +返回块是否与模板匹配。 + +**参数** + +- _state_ `布尔值`: + +**返回值** + +- `?布尔值`: 模板是否有效。 + +### 块是否刚被插入 + +判断指定clientId的块是否刚被插入。 + +**参数** + +- _state_ `对象`: 全局应用状态。 +- _clientId_ `对象`: 块的客户端ID。 +- _source_ `?字符串`: 块的可选插入来源。 + +**返回值** + +- `布尔值`: 若该块与指定来源最后插入的块匹配则返回真。 + + + +## 操作 + + + +### 清除选中块 + +清除块选择的操作。 + +**返回值** + +- `对象`: 操作对象。 + +### 复制块 + +复制块列表的操作。 + +**参数** + +- _clientIds_ `字符串[]`: +- _updateSelection_ `布尔值`: + +### 进入格式化文本 + +> **已弃用** + +返回用于指示光标已进入格式化文本的操作对象。 + +**返回值** + +- `对象`: 操作对象。 + +### 退出格式化文本 + +> **已弃用** + +返回用于指示用户光标已退出格式化文本的操作对象。 + +**返回值** + +- `对象`: 操作对象。 + +### 闪烁块 + +通过律动高亮方式“闪烁”指定`clientId`块的操作。 + +**参数** + +- _clientId_ `字符串`: 目标块客户端ID。 +- _timeout_ `数字`: 高亮持续时长(毫秒)。默认为150毫秒。 + +### 隐藏插入点 + +隐藏插入点的操作。 + +### 悬停块 + +> **已弃用** + +返回用于指示指定客户端ID的块已被悬停的操作对象。 + +### 在块后插入 + +在指定块后插入默认块的操作。 + +**参数** + +- _clientId_ `字符串`: + +### 在块前插入 + +在指定块前插入默认块的操作。 + +**参数** + +- _clientId_ `字符串`: + +### 插入块 + +插入单个块的操作,可选择在根块列表的特定索引位置插入。 + +仅允许插入已授权的块。若块未获授权或块列表上存在模板锁定,该操作可能会静默失败。 + +**参数** + +- _block_ `对象`: 要插入的块对象。 +- _index_ `?数字`: 块应插入的索引位置。 +- _rootClientId_ `?字符串`: 要插入的块列表的可选根客户端ID。 +- _updateSelection_ `?布尔值`: 若为真则更新块选择状态。若为假则保持原选择状态。默认为真。 +- _meta_ `?对象`: 要传递给操作对象的可选元数据值。 + +**返回值** + +- `对象`: 操作对象。 + +### setBlockEditingMode + +设置指定区块的编辑模式。 + +_相关_ + +- useBlockEditingMode + +_参数_ + +- _clientId_ `string`: 区块客户端ID,根容器使用 `''` +- _mode_ `BlockEditingMode`: 区块编辑模式。可选值:`'disabled'`、`'contentOnly'` 或 `'default'` + +_返回值_ + +- `Object`: 操作对象 + +### setBlockMovingClientId + +> **已弃用** + +设置被移动区块的客户端ID。 + +_返回值_ + +- `Object`: 操作对象 + +### setBlockVisibility + +设置指定区块在画布上的可见性。 + +_参数_ + +- _updates_ `Record`: 包含各区块客户端ID及其新可见性设置的键值对 + +### setHasControlledInnerBlocks + +设置区块是否具有受控内部区块。 + +_参数_ + +- _clientId_ `string`: 区块客户端ID +- _hasControlledInnerBlocks_ `boolean`: 若区块内部区块受控则为true + +### setTemplateValidity + +重置模板有效性状态。 + +_参数_ + +- _isValid_ `boolean`: 模板有效性标志 + +_返回值_ + +- `Object`: 操作对象 + +### showInsertionPoint + +显示插入点指示器。 + +_参数_ + +- _rootClientId_ `?string`: 可选,要插入区块列表的根客户端ID +- _index_ `?number`: 区块插入位置的索引 +- _\_\_unstableOptions_ `?Object`: 附加选项 + +_返回值_ + +- `Object`: 操作对象 + +_属性_ + +- _\_\_unstableWithInserter_ `boolean`: 是否显示插入器按钮 +- _operation_ `WPDropOperation`: 应用时执行的操作,目前支持 'insert' 或 'replace' + +### startDraggingBlocks + +返回表示用户开始拖拽区块的操作对象。 + +_参数_ + +- _clientIds_ `string[]`: 被拖拽区块的客户端ID数组 + +_返回值_ + +- `Object`: 操作对象 + +### startMultiSelect + +启动区块多选模式。 + +_返回值_ + +- `Object`: 操作对象 + +### startTyping + +返回表示用户开始输入的操作对象。 + +_返回值_ + +- `Object`: 操作对象 + +### stopDraggingBlocks + +返回表示用户停止拖拽区块的操作对象。 + +_返回值_ + +- `Object`: 操作对象 + +### stopMultiSelect + +停止区块多选模式。 + +_返回值_ + +- `Object`: 操作对象 + +### stopTyping + +返回表示用户停止输入的操作对象。 + +_返回值_ + +- `Object`: 操作对象 + +### synchronizeTemplate + +将模板与区块列表进行同步。 + +_返回值_ + +- `Object`: 操作对象 + +### toggleBlockHighlight + +切换区块高亮状态。 + +_参数_ + +- _clientId_ `string`: 区块客户端ID +- _isHighlighted_ `boolean`: 高亮状态 + +### toggleBlockMode + +在可视化模式与HTML模式之间切换区块编辑模式。 + +_参数_ + +- _clientId_ `string`: 区块客户端ID + +_返回值_ + +- `Object`: 操作对象 + +### toggleSelection + +启用或禁用区块选择功能。 + +_参数_ + +- _isSelectionEnabled_ `[boolean]`: 是否启用区块选择 + +_返回值_ + +- `Object`: 操作对象 + +### unsetBlockEditingMode + +清除指定区块的编辑模式设置。 + +_相关_ + +- useBlockEditingMode + +_参数_ + +- _clientId_ `string`: 区块客户端ID,根容器使用 `''` + +_返回值_ + +- `Object`: 操作对象 + +### updateBlock + +更新指定客户端ID的区块。 + +_参数_ + +- _clientId_ `string`: 区块客户端ID +- _updates_ `Object`: 待合并的区块属性 + +_返回值_ + +- `Object`: 操作对象 + +### updateBlockAttributes + +更新多个指定区块的属性。 + +_参数_ + +- _clientIds_ `string|string[]`: 区块客户端ID(单个或数组) +- _attributes_ `Object`: 待合并的区块属性。若 `options.uniqueByBlock` 为true,应按clientIds建立键值映射 +- _options_ `Object`: 更新选项 +- _options.uniqueByBlock_ `[boolean]`: clientIds数组中每个区块是否具有独立属性集 + +_返回值_ + +- `Object`: 操作对象 + +```markdown +### getSelectedBlockClientId + +返回当前选中区块的客户端ID,若无选中区块则返回null。 + +_参数_ + +- _state_ `Object`: 编辑器状态。 + +_返回值_ + +- `?string`: 选中区块的客户端ID。 + +### getSelectedBlockClientIds + +返回当前选中的区块客户端ID集合(多选或单选)。 + +_参数_ + +- _state_ `Object`: 编辑器状态。 + +_返回值_ + +- `Array`: 多选区块的客户端ID数组。 + +### getSelectedBlockCount + +返回当前文章中选中的区块数量。 + +_参数_ + +- _state_ `Object`: 全局应用状态。 + +_返回值_ + +- `number`: 文章中选中的区块数量。 + +### getSelectedBlocksInitialCaretPosition + +返回选中区块的初始光标位置。该位置用于在选中区块变更时正确定位光标。若当前区块非富文本区块,初始位置设为0表示"聚焦区块"。 + +_参数_ + +- _state_ `Object`: 全局应用状态。 + +_返回值_ + +- `0|-1|null`: 初始位置。 + +### getSelectionEnd + +返回当前选区结束位置的区块客户端ID、属性键和文本偏移量。 + +_参数_ + +- _state_ `Object`: 区块编辑器状态。 + +_返回值_ + +- `WPBlockSelection`: 选区结束信息。 + +### getSelectionStart + +返回当前选区开始位置的区块客户端ID、属性键和文本偏移量。 + +_参数_ + +- _state_ `Object`: 区块编辑器状态。 + +_返回值_ + +- `WPBlockSelection`: 选区开始信息。 + +### getSettings + +返回编辑器设置。 + +_参数_ + +- _state_ `Object`: 编辑器状态。 + +_返回值_ + +- `Object`: 编辑器设置对象。 + +### getTemplate + +返回已定义的区块模板。 + +_参数_ + +- _state_ `boolean`: 状态标识。 + +_返回值_ + +- `?Array`: 区块模板。 + +### getTemplateLock + +返回已定义的区块模板锁定状态。可选择传入根区块客户端ID作为上下文,默认为全局上下文。 + +_参数_ + +- _state_ `Object`: 编辑器状态。 +- _rootClientId_ `?string`: 可选的根区块客户端ID。 + +_返回值_ + +- `string|false`: 区块模板锁定状态。 + +### hasBlockMovingClientId + +> **已弃用** + +返回是否启用区块移动模式。 + +### hasDraggedInnerBlock + +当区块的内部子块被拖拽时返回true。 + +_参数_ + +- _state_ `Object`: 编辑器状态。 +- _clientId_ `string`: 区块客户端ID。 +- _deep_ `boolean`: 是否执行深度检查。 + +_返回值_ + +- `boolean`: 该区块是否有内部子块被拖拽。 + +### hasInserterItems + +判断插入器中是否有可显示项。 + +_参数_ + +- _state_ `Object`: 编辑器状态。 +- _rootClientId_ `?string`: 可选的区块列表根客户端ID。 + +_返回值_ + +- `boolean`: 插入器中是否显示项目。 + +### hasMultiSelection + +如果已进行多选操作则返回true,否则返回false。 + +_参数_ + +- _state_ `Object`: 编辑器状态。 + +_返回值_ + +- `boolean`: 是否已进行多选。 + +### hasSelectedBlock + +如果存在单个选中区块则返回true,否则返回false。 + +_参数_ + +- _state_ `Object`: 编辑器状态。 + +_返回值_ + +- `boolean`: 是否选中单个区块。 + +### hasSelectedInnerBlock + +当区块的内部子块被选中时返回true。 + +_参数_ + +- _state_ `Object`: 编辑器状态。 +- _clientId_ `string`: 区块客户端ID。 +- _deep_ `boolean`: 是否执行深度检查。 + +_返回值_ + +- `boolean`: 该区块是否有内部子块被选中。 + +### isAncestorBeingDragged + +返回该区块的父级/祖先级是否正在被拖拽。 + +_参数_ + +- _state_ `Object`: 全局应用状态。 +- _clientId_ `string`: 待检查区块的客户端ID。 + +_返回值_ + +- `boolean`: 该区块的祖先级是否正在被拖拽。 + +### isAncestorMultiSelected + +如果该区块的祖先级被多选则返回true,否则返回false。 + +_参数_ + +- _state_ `Object`: 编辑器状态。 +- _clientId_ `string`: 区块客户端ID。 + +_返回值_ + +- `boolean`: 该区块的祖先级是否处于多选集合中。 +``` + +### getBlockParentsByBlockName(根据区块名称获取父级区块列表) + +给定一个区块客户端ID和区块名称,返回其所有父级区块从顶至底的列表,并按指定名称进行筛选。例如,若传入 'core/group' 作为区块名称,则仅返回属于群组区块的父级。若传入 `[ 'core/group', 'core/cover']` 作为区块名称,则同时返回群组区块和封面区块的父级。 + +**参数** +- _state_ `Object`:编辑器状态。 +- _clientId_ `string`:需要查找根级客户端ID的区块。 +- _blockName_ `string|string[]`:用于筛选的区块名称(可多选)。 +- _ascending_ `boolean`:排序方式。设为 true 时按从底到顶排列,false 时按从顶到底排列。 + +**返回值** +- `Array`:父级区块的客户端ID集合。 + +### getBlockRootClientId(获取区块根级客户端ID) + +根据区块客户端ID,返回该区块嵌套结构的根级区块。顶层区块返回空字符串,不存在的区块返回 null。 + +**参数** +- _state_ `Object`:编辑器状态。 +- _clientId_ `string`:需要查找根级客户端ID的区块。 + +**返回值** +- `?string`:根级客户端ID(若存在)。 + +### getBlocks(获取全部区块) + +以数组形式返回当前编辑文章中的所有区块对象,排列顺序与文章中的显示顺序一致。注意:此操作将排除嵌套内部区块控制器中的子区块。 + +**参数** +- _state_ `Object`:编辑器状态。 +- _rootClientId_ `?string`:可选参数,指定区块列表的根级客户端ID。 + +**返回值** +- `Object[]`:文章区块集合。 + +### getBlocksByClientId(根据客户端ID获取区块) + +根据区块客户端ID数组,返回对应的区块对象数组。 + +**参数** +- _state_ `Object`:编辑器状态。 +- _clientIds_ `string[]`:需要获取的区块客户端ID集合。 + +**返回值** +- `WPBlock[]`:区块对象数组。 + +### getBlocksByName(根据名称获取区块) + +返回所有匹配指定区块名称的区块,搜索结果包含嵌套区块。 + +**参数** +- _state_ `Object`:全局应用状态。 +- _blockName_ `string[]`:需要获取的区块名称集合。 + +**返回值** +- `Array`:符合名称条件的区块客户端ID数组。 + +### getBlockSelectionEnd(获取区块选区终点) + +返回当前区块选区的终点。该值可能为 null,可能表示单选区块终点或多选区块终点。当选区起点与终点一致时为单选状态。 + +**参数** +- _state_ `Object`:全局应用状态。 + +**返回值** +- `?string`:区块选区终点的客户端ID。 + +### getBlockSelectionStart(获取区块选区起点) + +返回当前区块选区的起点。该值可能为 null,可能表示单选区块起点或多选区块起点。当选区起点与终点一致时为单选状态。 + +**参数** +- _state_ `Object`:全局应用状态。 + +**返回值** +- `?string`:区块选区起点的客户端ID。 + +### getBlockTransformItems(获取区块转换项列表) + +确定可用区块转换列表中显示的条目。 + +每个条目对象包含在转换列表中显示菜单项和处理选择所需的内容。 + +“frecency”属性是结合了区块使用频率和近期使用情况的启发式指标()。 + +返回的条目按“frecency”值降序排列。 + +**参数** +- _state_ `Object`:编辑器状态。 +- _blocks_ `Object|Object[]`:单个区块对象或区块对象数组。 +- _rootClientId_ `?string`:可选参数,指定区块列表的根级客户端ID。 + +**返回值** +- `WPEditorTransformItem[]`:插入器中显示的转换项集合。 + +**类型定义** +- _WPEditorTransformItem_ `Object` + +**属性说明** +- _id_ `string`:条目的唯一标识符。 +- _name_ `string`:需要创建的区块类型。 +- _title_ `string`:插入器中显示的条目标题。 +- _icon_ `string`:插入器中显示的Dashicon图标。 +- _isDisabled_ `boolean`:是否禁止用户插入此条目。 +- _frecency_ `number`:结合频率与近因度的启发式指标。 + +### getClientIdsOfDescendants(获取后代区块的客户端ID) + +返回一个数组,包含指定区块的所有后代区块的客户端ID。返回的ID首先按照给定ID的顺序排序,其次按照它们在编辑器中出现的顺序排序。 + +**参数** + +- _state_ `Object`:全局应用程序状态。 +- _rootIds_ `string|string[]`:需要返回后代区块的客户端ID(单个或多个)。 + +**返回值** + +- `Array`:后代区块的客户端ID。 + +### getClientIdsWithDescendants(获取包含后代的客户端ID) + +返回一个数组,包含顶级区块及其任意深度的后代区块(适用于嵌套区块)的客户端ID。返回的ID顺序与它们在编辑器中出现的顺序一致。 + +**参数** + +- _state_ `Object`:全局应用程序状态。 + +**返回值** + +- `Array`:顶级区块及后代区块的ID。 + +### getDirectInsertBlock(获取直接插入的区块) + +返回由区块添加器直接插入的区块。 + +**参数** + +- _state_ `Object`:编辑器状态。 +- _rootClientId_ `?string`:区块列表的可选根客户端ID。 + +**返回值** + +- `WPDirectInsertBlock|undefined`:要直接插入的区块类型。 + +**类型定义** + +- _WPDirectInsertBlock_ `Object` + +**属性** + +- _name_ `string`:区块的类型。 +- _attributes_ `?Object`:传递给新创建区块的属性。 +- _attributesToCopy_ `?Array`:插入时从相邻区块复制的属性。 + +### getDraggedBlockClientIds(获取被拖动区块的客户端ID) + +返回当前被直接拖动的区块的客户端ID。 + +此结果不包括被拖动父区块的子区块。 + +**参数** + +- _state_ `Object`:全局应用程序状态。 + +**返回值** + +- `string[]`:被拖动区块的客户端ID数组。 + +### getFirstMultiSelectedBlockClientId(获取多选区块集合中的首个客户端ID) + +返回多选区块集合中第一个区块的客户端ID,如果没有多选则返回null。 + +**参数** + +- _state_ `Object`:编辑器状态。 + +**返回值** + +- `?string`:多选区块集合中第一个区块的客户端ID。 + +### getGlobalBlockCount(获取全局区块数量) + +返回文章中的区块总数,或特定名称的区块总数。返回的数量包括嵌套区块。 + +**参数** + +- _state_ `Object`:全局应用程序状态。 +- _blockName_ `?string`:可选的区块名称,如果指定,则仅统计该类型的区块。 + +**返回值** + +- `number`:文章中的区块数量,或与指定区块名称相同的区块数量。 + +### getHoveredBlockClientId(获取悬停区块的客户端ID) + +> **已弃用** + +返回当前悬停的区块。 + +### getInserterItems(获取插入器项目) + +确定插入器中显示的项目。包括静态项目(例如常规区块类型)和动态项目(例如可重用区块)。 + +每个项目对象包含在插入器中显示按钮并处理其选择所需的内容。 + +“frecency”属性是一种结合了区块使用频率和最近使用时间的启发式算法()。 + +项目按其“效用”和“frecency”降序排列返回。 + +**参数** + +- _state_ `Object`:编辑器状态。 +- _rootClientId_ `?string`:区块列表的可选根客户端ID。 + +**返回值** + +- `WPEditorInserterItem[]`:插入器中显示的项目。 + +**类型定义** + +- _WPEditorInserterItem_ `Object` + +**属性** + +- _id_ `string`:项目的唯一标识符。 +- _name_ `string`:要创建的区块类型。 +- _initialAttributes_ `Object`:传递给新创建区块的属性。 +- _title_ `string`:项目在插入器中显示的标题。 +- _icon_ `string`:项目在插入器中显示的Dashicon图标。 +- _category_ `string`:项目关联的区块分类。 +- _keywords_ `string[]`:可搜索到此项目的关键词。 +- _isDisabled_ `boolean`:是否阻止用户插入此项目。 +- _frecency_ `number`:结合频率和最近使用时间的启发式值。 + +### getLastMultiSelectedBlockClientId(获取多选区块集合中的最后一个客户端ID) + +返回多选区块集合中最后一个区块的客户端ID,如果没有多选则返回null。 + +**参数** + +- _state_ `Object`:编辑器状态。 + +**返回值** + +- `?string`:多选区块集合中最后一个区块的客户端ID。 + +### isBlockBeingDragged(是否正在拖拽区块) + +返回该区块是否正在被拖拽。 + +仅当区块被直接拖拽时返回 true,若区块作为被拖拽父级的子级则不会返回 true。关于子区块请参阅 `isAncestorBeingDragged`。 + +**参数** + +- _state_ `Object`:全局应用状态。 +- _clientId_ `string`:需检查区块的客户端 ID。 + +**返回值** + +- `boolean`:区块是否正在被拖拽。 + +### isBlockHighlighted(区块是否高亮) + +若当前高亮区块与指定区块客户端 ID 匹配则返回 true。 + +**参数** + +- _state_ `Object`:全局应用状态。 +- _clientId_ `string`:需检查的区块。 + +**返回值** + +- `boolean`:区块当前是否处于高亮状态。 + +### isBlockInsertionPointVisible(区块插入点是否可见) + +返回区块插入点是否可见。 + +**参数** + +- _state_ `Object`:全局应用状态。 + +**返回值** + +- `?boolean`:插入点是否可见。 + +### isBlockMultiSelected(区块是否被多选) + +若指定客户端 ID 的区块处于多选范围内则返回 true,否则返回 false。 + +**参数** + +- _state_ `Object`:编辑器状态。 +- _clientId_ `string`:区块客户端 ID。 + +**返回值** + +- `boolean`:区块是否处于多选集合中。 + +### isBlockSelected(区块是否被选中) + +若指定客户端 ID 的区块当前被选中且未开启多选则返回 true,否则返回 false。 + +**参数** + +- _state_ `Object`:编辑器状态。 +- _clientId_ `string`:区块客户端 ID。 + +**返回值** + +- `boolean`:区块是否被选中且存在多选状态。 + +### isBlockValid(区块是否有效) + +返回区块是否有效。 + +**参数** + +- _state_ `Object`:编辑器状态。 +- _clientId_ `string`:区块客户端 ID。 + +**返回值** + +- `boolean`:是否有效。 + +### isBlockVisible(区块是否可见) + +判断区块在画布上是否可见。 + +**参数** + +- _state_ `Object`:全局应用状态。 +- _clientId_ `Object`:区块的客户端 ID。 + +**返回值** + +- `boolean`:区块可见时返回 true。 + +### isBlockWithinSelection(区块是否位于选中范围内) + +若指定客户端 ID 的区块当前被选中但不是已选区块中的最后一个则返回 true。此处的“最后”指文档中的区块顺序,*而非*多选顺序(因此不使用 `state.selectionEnd`)。 + +**参数** + +- _state_ `Object`:编辑器状态。 +- _clientId_ `string`:区块客户端 ID。 + +**返回值** + +- `boolean`:区块是否被选中且非选中序列末位。 + +### isCaretWithinFormattedText(光标是否位于格式化文本内) + +> **已弃用** + +若光标位于格式化文本内则返回 true,否则返回 false。 + +**返回值** + +- `boolean`:光标是否位于格式化文本内。 + +### isDraggingBlocks(是否正在拖拽区块) + +若用户正在拖拽区块则返回 true,否则返回 false。 + +**参数** + +- _state_ `Object`:全局应用状态。 + +**返回值** + +- `boolean`:用户是否正在拖拽区块。 + +### isFirstMultiSelectedBlock(是否为首个多选区块) + +若存在多选且指定客户端 ID 的区块为多选集合中的首个区块则返回 true,否则返回 false。 + +**参数** + +- _state_ `Object`:编辑器状态。 +- _clientId_ `string`:区块客户端 ID。 + +**返回值** + +- `boolean`:该区块是否为多选首项。 + +### isGroupable(是否可成组) + +判断指定客户端 ID 的区块是否可成组。需满足至少存在一个区块、已设置分组区块名称且可移除这些区块的条件。 + +**参数** + +- _state_ `Object`:全局应用状态。 +- _clientIds_ `string[]`:区块客户端 ID 数组。若未传递则使用当前选中区块的客户端 ID。 + +**返回值** + +- `boolean`:区块可成组时返回 true。 + +### isLastBlockChangePersistent(末次区块变更是否持久化) + +若最近一次区块变更被认定为持久化变更则返回 true,否则返回 false。持久化变更指通过 BlockEditorProvider 的 `onChange` 回调(及 `onInput`)提交的变更。 + +**参数** + +- _state_ `Object`:区块编辑器状态。 + +**返回值** + +- `boolean`:最近一次区块变更是否具有持久性。 + +### getBlockAttributes + +根据区块的客户端ID返回其属性,如果不存在对应客户端ID的区块则返回 null。 + +**参数** + +- _state_ `Object`: 编辑器状态。 +- _clientId_ `string`: 区块客户端ID。 + +**返回值** + +- `?Object`: 区块属性。 + +### getBlockCount + +返回当前文章中的区块总数。 + +**参数** + +- _state_ `Object`: 编辑器状态。 +- _rootClientId_ `?string`: 可选的区块列表根客户端ID。 + +**返回值** + +- `number`: 文章中的区块数量。 + +### getBlockEditingMode + +返回指定区块的编辑模式。 + +编辑模式包含三种选项: + +- `'disabled'`: 完全禁止编辑区块,即不可被选中。 +- `'contentOnly'`: 隐藏所有非内容界面元素,例如工具栏辅助控件、区块移动器、区块设置。 +- `'default'`: 正常编辑区块。 + +区块可通过 `useBlockEditingMode` 钩子设置模式。 + +该模式会被所有内部区块继承,除非这些内部区块已设置自有模式。 + +模板锁定也可设置模式。若模板锁定为 `'contentOnly'`,且区块具有内容角色属性,则其模式会被覆盖为 `'contentOnly'`,否则设为 `'disabled'`。 + +**关联项** + +- useBlockEditingMode + +**参数** + +- _state_ `Object`: 全局应用状态。 +- _clientId_ `string`: 区块客户端ID,根容器使用 `''`。 + +**返回值** + +- `BlockEditingMode`: 区块编辑模式。值为 `'disabled'`、`'contentOnly'` 或 `'default'`。 + +### getBlockHierarchyRootClientId + +根据区块客户端ID,返回该区块嵌套层级的根节点,根层级区块返回自身ID。 + +**参数** + +- _state_ `Object`: 编辑器状态。 +- _clientId_ `string`: 要查找根客户端ID的区块。 + +**返回值** + +- `string`: 根客户端ID。 + +### getBlockIndex + +返回指定客户端ID的区块在区块顺序中的索引,若区块不存在则返回 `-1`。 + +**参数** + +- _state_ `Object`: 编辑器状态。 +- _clientId_ `string`: 区块客户端ID。 + +**返回值** + +- `number`: 区块在顺序中的索引位置。 + +### getBlockInsertionPoint + +返回插入提示位置,默认为最末索引。 + +**参数** + +- _state_ `Object`: 编辑器状态。 + +**返回值** + +- `Object`: 包含 `rootClientId` 和 `index` 的插入点对象。 + +### getBlockListSettings + +返回区块的区块列表设置(如果存在)。 + +**参数** + +- _state_ `Object`: 编辑器状态。 +- _clientId_ `?string`: 区块客户端ID。 + +**返回值** + +- `?Object`: 已设置的区块配置。 + +### getBlockMode + +返回区块的编辑模式,未显式设置时默认为"visual"模式。 + +**参数** + +- _state_ `Object`: 编辑器状态。 +- _clientId_ `string`: 区块客户端ID。 + +**返回值** + +- `Object`: 区块编辑模式。 + +### getBlockName + +根据区块客户端ID返回其名称,如果不存在对应客户端ID的区块则返回 null。 + +**参数** + +- _state_ `Object`: 编辑器状态。 +- _clientId_ `string`: 区块客户端ID。 + +**返回值** + +- `string`: 区块名称。 + +### getBlockNamesByClientId + +根据区块客户端ID数组返回对应的区块名称数组。 + +**参数** + +- _state_ `Object`: 编辑器状态。 +- _clientIds_ `string[]`: 需要获取名称的客户端ID数组。 + +**返回值** + +- `string[]`: 区块名称数组。 + +### getBlockOrder + +返回编辑器中按出现顺序排列的所有区块客户端ID数组。可接受区块列表的根客户端ID参数,默认为顶层区块顺序。 + +**参数** + +- _state_ `Object`: 编辑器状态。 +- _rootClientId_ `?string`: 可选的区块列表根客户端ID。 + +**返回值** + +- `Array`: 编辑器区块的有序客户端ID数组。 + +### getBlockParents + +根据区块客户端ID返回其所有父级区块的列表(从上至下)。 + +**参数** + +- _state_ `Object`: 编辑器状态。 +- _clientId_ `string`: 要查找父级层级的区块。 +- _ascending_ `boolean`: 按从底到顶(true)或从上到下(false)排序结果。 + +**返回值** + +- `Array`: 父级区块的客户端ID数组。 + +# 区块编辑器的数据 + +命名空间:`core/block-editor` + +## 选择器 + + + +### areInnerBlocksControlled + +检查指定区块是否具有受控内部区块。 + +_参数_ + +- _state_ `Object`: 全局应用状态 +- _clientId_ `string`: 要检查的区块 + +_返回值_ + +- `boolean`: 如果区块具有受控内部区块则返回 true + +### canEditBlock + +判断指定区块是否允许被编辑。 + +_参数_ + +- _state_ `Object`: 编辑器状态 +- _clientId_ `string`: 区块客户端 ID + +_返回值_ + +- `boolean`: 指定区块是否允许被编辑 + +### canInsertBlocks + +判断指定区块是否允许插入到区块列表中。 + +_参数_ + +- _state_ `Object`: 编辑器状态 +- _clientIds_ `string[]`: 要插入的区块客户端 ID 数组 +- _rootClientId_ `?string`: 区块列表的可选根客户端 ID + +_返回值_ + +- `boolean`: 指定区块是否允许被插入 + +### canInsertBlockType + +判断指定区块类型是否允许插入到区块列表中。 + +_参数_ + +- _state_ `Object`: 编辑器状态 +- _blockName_ `string`: 区块类型名称,例如 'core/paragraph' +- _rootClientId_ `?string`: 区块列表的可选根客户端 ID + +_返回值_ + +- `boolean`: 指定区块类型是否允许被插入 + +### canLockBlockType + +判断指定区块类型是否可由用户锁定/解锁。 + +_参数_ + +- _state_ `Object`: 编辑器状态 +- _nameOrType_ `(string|Object)`: 区块名称或类型对象 + +_返回值_ + +- `boolean`: 指定区块类型是否可被锁定/解锁 + +### canMoveBlock + +判断指定区块是否允许被移动。 + +_参数_ + +- _state_ `Object`: 编辑器状态 +- _clientId_ `string`: 区块客户端 ID + +_返回值_ + +- `boolean`: 指定区块是否允许被移动 + +### canMoveBlocks + +判断指定区块是否允许被移动。 + +_参数_ + +- _state_ `Object`: 编辑器状态 +- _clientIds_ `string`: 要移动的区块客户端 ID + +_返回值_ + +- `boolean`: 指定区块是否允许被移动 + +### canRemoveBlock + +判断指定区块是否允许被删除。 + +_参数_ + +- _state_ `Object`: 编辑器状态 +- _clientId_ `string`: 区块客户端 ID + +_返回值_ + +- `boolean`: 指定区块是否允许被移除 + +### canRemoveBlocks + +判断指定区块是否允许被移除。 + +_参数_ + +- _state_ `Object`: 编辑器状态 +- _clientIds_ `string`: 要移除的区块客户端 ID + +_返回值_ + +- `boolean`: 指定区块是否允许被移除 + +### didAutomaticChange + +如果最后一次更改是自动更改则返回 true,否则返回 false。 + +_参数_ + +- _state_ `Object`: 全局应用状态 + +_返回值_ + +- `boolean`: 最后一次更改是否为自动更改 + +### getAdjacentBlockClientId + +返回在给定参考 startClientId 和方向修饰符下的相邻区块客户端 ID。默认 startClientId 为所选区块,方向为下一个区块。如果没有相邻区块则返回 null。 + +_参数_ + +- _state_ `Object`: 编辑器状态 +- _startClientId_ `?string`: 搜索起始区块的可选客户端 ID +- _modifier_ `?number`: 方向性乘数(1 下一个,-1 上一个) + +_返回值_ + +- `?string`: 返回区块的客户端 ID,如果不存在则返回 null + +### getAllowedBlocks + +返回内部区块子级允许的插入器区块列表。 + +_参数_ + +- _state_ `Object`: 编辑器状态 +- _rootClientId_ `?string`: 区块列表的可选根客户端 ID + +_返回值_ + +- `Array?`: 允许的区块类型列表 + +### getBlock + +根据客户端 ID 返回区块。这是区块的解析副本,包含其 `blockName`、`clientId` 和当前 `attributes` 状态。这不是区块的注册设置,必须从区块模块注册存储中检索。 + +getBlock 会递归遍历其内部区块,直到检索到所有子区块。请注意,getBlock 不会返回内部区块控制器的子内部区块。这是因为内部区块控制器会与其实体自行同步,因此不应包含在不同实体的区块中。例如,假设您调用 `getBlocks(TP)` 来获取模板部件的区块。如果另一个模板部件是 TP 的子级,则嵌套模板部件的子区块将不会被返回。这样,模板区块本身被视为父级的一部分,但其子级不是。 + +_参数_ + +- _state_ `Object`: 编辑器状态 +- _clientId_ `string`: 区块客户端 ID + +_返回值_ + +- `Object`: 解析后的区块对象 + +```markdown +### insertBlocks + +在指定根区块列表中插入区块数组的操作,可选择在特定索引位置插入。 + +仅允许插入合规的区块。对于未授权的区块或当区块列表处于模板锁定状态时,该操作可能会静默失败。 + +_参数_ + +- _blocks_ `Object[]`: 待插入的区块对象数组 +- _index_ `?number`: 区块插入的索引位置 +- _rootClientId_ `?string`: 可选的目标区块列表根客户端ID +- _updateSelection_ `?boolean`: 为true时更新区块选择状态,为false时保持原选择状态。默认为true +- _initialPosition_ `0|-1|null`: 初始聚焦位置。设为null可阻止聚焦已插入区块 +- _meta_ `?Object`: 可选元数据,将传递至操作对象 + +_返回值_ + +- `Object`: 操作对象 + +### insertDefaultBlock + +向区块列表添加默认类型新区块的操作 + +_参数_ + +- _attributes_ `?Object`: 可选的要分配的区块属性 +- _rootClientId_ `?string`: 可选的目标区块列表根客户端ID +- _index_ `?number`: 可选的要插入默认区块的索引位置 + +### mergeBlocks + +合并两个区块的操作 + +_参数_ + +- _firstBlockClientId_ `string`: 要合并的第一个区块客户端ID +- _secondBlockClientId_ `string`: 要合并的第二个区块客户端ID + +### moveBlocksDown + +未记录的声明 + +### moveBlocksToPosition + +将指定区块移动到新位置的操作 + +_参数_ + +- _clientIds_ `?string`: 区块的客户端ID +- _fromRootClientId_ `?string`: 源根客户端ID +- _toRootClientId_ `?string`: 目标根客户端ID +- _index_ `number`: 要移动到的目标索引 + +### moveBlocksUp + +未记录的声明 + +### moveBlockToPosition + +将指定区块移动到新位置的操作 + +_参数_ + +- _clientId_ `?string`: 区块的客户端ID +- _fromRootClientId_ `?string`: 源根客户端ID +- _toRootClientId_ `?string`: 目标根客户端ID +- _index_ `number`: 要移动到的目标索引 + +### multiSelect + +更改区块多选状态的操作 + +_参数_ + +- _start_ `string`: 多选的起始区块 +- _end_ `string`: 多选的结束区块 +- _\_\_experimentalInitialPosition_ `number|null`: 可选的初始位置。传入null可跳过编辑器画布内的聚焦 + +### receiveBlocks + +> **已弃用** + +返回用于通知已接收区块的操作对象。与resetBlocks不同,这些区块应追加到现有已知集合,而非替换。 + +_参数_ + +- _blocks_ `Object[]`: 区块对象数组 + +_返回值_ + +- `Object`: 操作对象 + +### registerInserterMediaCategory + +注册新的插入器媒体分类。注册后,该媒体分类将在插入器的媒体标签页中可用。 + +使用的接口如下: + +_类型定义_ + +- _InserterMediaRequest_ `Object`: 插入器媒体请求接口 + +_属性_ + +- _per_page_ `number`: 每页获取的项目数量 +- _search_ `string`: 用于筛选结果的搜索词 + +_类型定义_ + +- _InserterMediaItem_ `Object`: 插入器媒体响应接口。任何媒体资源都应将其响应映射到此接口,以创建核心WordPress媒体区块(图像、视频、音频) + +_属性_ + +- _title_ `string`: 媒体项标题 +- _url_ \`string: 媒体项源URL +- _previewUrl_ `[string]`: 在媒体列表中显示的预览图URL +- _id_ `[number]`: 媒体项的WordPress ID +- _sourceId_ `[number|string]`: 外部来源的媒体项ID +- _alt_ `[string]`: 媒体项替代文本 +- _caption_ `[string]`: 媒体项说明文字 + +_使用示例_ + +```js +wp.data.dispatch( 'core/block-editor' ).registerInserterMediaCategory( { + name: 'openverse', + labels: { + name: 'Openverse', + search_items: '搜索Openverse', + }, + mediaType: 'image', + async fetch( query = {} ) { + const defaultArgs = { + mature: false, + excluded_source: 'flickr,inaturalist,wikimedia', + license: 'pdm,cc0', + }; + const finalQuery = { ...query, ...defaultArgs }; + // 有时需要根据`InserterMediaRequest`接口映射支持的请求参数 + // 此示例中`search`查询参数命名为`q` + const mapFromInserterMediaRequest = { + per_page: 'page_size', + search: 'q', + }; + const url = new URL( 'https://api.openverse.org/v1/images/' ); + Object.entries( finalQuery ).forEach( ( [ key, value ] ) => { + const queryKey = mapFromInserterMediaRequest[ key ] || key; + url.searchParams.set( queryKey, value ); + } ); + const response = await window.fetch( url, { + headers: { + 'User-Agent': 'WordPress/inserter-media-fetch', + }, + } ); + const jsonResponse = await response.json(); + const results = jsonResponse.results; + return results.map( ( result ) => ( { + ...result, + // 如果响应结果包含需要后续访问的`id`属性,应将其映射到`InserterMediaItem`的`sourceId`属性 + // 这在提供报告URL获取器时特别有用 + // 同时应始终清除响应结果的`id`值,因为该值用于标识WordPress媒体项 + sourceId: result.id, + id: undefined, + caption: result.caption, + previewUrl: result.thumbnail, + } ) ); + }, + getReportUrl: ( { sourceId } ) => + `https://wordpress.org/openverse/image/${ sourceId }/report/`, + isExternalResource: true, +} ); +``` + +_参数_ + +- _category_ `InserterMediaCategory`: 要注册的插入器媒体分类 + +_类型定义_ + +- _InserterMediaCategory_ `Object`: 插入器媒体分类接口 + +_属性_ + +- _name_ `string`: 媒体分类名称,在所有媒体分类中应保持唯一 +- _labels_ `Object`: 媒体分类标签 +- _labels.name_ `string`: 媒体分类通用名称,用于插入器媒体项目列表 +- _labels.search_items_ `[string]`: 搜索项目标签,默认为‘搜索文章’/‘搜索页面’ +- _mediaType_ `('image'|'audio'|'video')`: 媒体分类的媒体类型 +- _fetch_ `(InserterMediaRequest) => Promise`: 获取该分类媒体项目的函数 +- _getReportUrl_ `[(InserterMediaItem) => string]`: 如果媒体分类支持举报媒体项,此函数应返回媒体项的举报URL,接收`InserterMediaItem`作为参数 +- _isExternalResource_ `[boolean]`: 如果媒体分类属于外部资源,应设为true。这用于避免在用户操作时向外部资源发起请求 +``` \ No newline at end of file diff --git a/reference-guides/data/data-core-blocks.md b/reference-guides/data/data-core-blocks.md new file mode 100644 index 0000000..e2aa507 --- /dev/null +++ b/reference-guides/data/data-core-blocks.md @@ -0,0 +1,721 @@ +### hasChildBlocksWithInserterSupport + +返回一个布尔值,用于指示某个区块是否至少拥有一个支持插入器的子区块。 + +_使用示例_ + +```js +import { __, sprintf } from '@wordpress/i18n'; +import { store as blocksStore } from '@wordpress/blocks'; +import { useSelect } from '@wordpress/data'; + +const ExampleComponent = () => { + const navigationBlockHasChildBlocksWithInserterSupport = useSelect( + ( select ) => + select( blocksStore ).hasChildBlocksWithInserterSupport( + 'core/navigation' + ), + [] + ); + + return ( +

    + { sprintf( + __( + 'core/navigation 区块拥有支持插入器的子区块:%s' + ), + navigationBlockHasChildBlocksWithInserterSupport + ) } +

    + ); +}; +``` + +_参数说明_ + +- _state_ `Object`: 数据状态。 +- _blockName_ `string`: 区块类型名称。 + +_返回值_ + +- `boolean`: 如果区块包含至少一个支持插入器的子区块则返回 true,否则返回 false。 + +### isMatchingSearchTerm + +如果给定名称或对象值的区块类型与搜索词匹配则返回 true,否则返回 false。 + +_使用示例_ + +```js +import { __, sprintf } from '@wordpress/i18n'; +import { store as blocksStore } from '@wordpress/blocks'; +import { useSelect } from '@wordpress/data'; + +const ExampleComponent = () => { + const termFound = useSelect( + ( select ) => + select( blocksStore ).isMatchingSearchTerm( + 'core/navigation', + 'theme' + ), + [] + ); + + return ( +

    + { sprintf( + __( + '在 block.json 的标题、关键词、分类或描述中找到搜索词:%s' + ), + termFound + ) } +

    + ); +}; +``` + +_参数说明_ + +- _state_ `Object`: 区块状态。 +- _nameOrType_ `(string|Object)`: 区块名称或类型对象。 +- _searchTerm_ `string`: 用于筛选的搜索词。 + +_返回值_ + +- `Object[]`: 区块类型是否与搜索词匹配。 + + + +## 操作 + +此包中的操作不应直接使用。请改用[公共 API](/packages/blocks/README.md) 中列出的函数。 + + + +### reapplyBlockTypeFilters + +发出信号表示应重新计算所有区块类型。该操作使用存储的未处理区块类型和最近注册的所有筛选器列表。 + +它解决了第三方区块筛选器在第三方区块之后注册的问题。示例顺序:1. 筛选器 A → 2. 区块 B → 3. 区块 C → 4. 筛选器 D → 5. 筛选器 E → 6. 区块 F → 7. 筛选器 G。在此场景中,部分筛选器因注册过晚而无法应用于所有区块。 + + + +### getFreeformFallbackBlockName + +返回用于处理非区块内容的区块名称。 + +_用法_ + +```js +import { __, sprintf } from '@wordpress/i18n'; +import { store as blocksStore } from '@wordpress/blocks'; +import { useSelect } from '@wordpress/data'; + +const ExampleComponent = () => { + const freeformFallbackBlockName = useSelect( + ( select ) => select( blocksStore ).getFreeformFallbackBlockName(), + [] + ); + + return ( + freeformFallbackBlockName && ( +

    + { sprintf( + __( '自由形态降级区块名称:%s' ), + freeformFallbackBlockName + ) } +

    + ) + ); +}; +``` + +_参数_ + +- _state_ `Object`: 数据状态。 + +_返回值_ + +- `?string`: 用于处理非区块内容的区块名称。 + +### getGroupingBlockName + +返回用于处理区块分组的区块名称。 + +_用法_ + +```js +import { __, sprintf } from '@wordpress/i18n'; +import { store as blocksStore } from '@wordpress/blocks'; +import { useSelect } from '@wordpress/data'; + +const ExampleComponent = () => { + const groupingBlockName = useSelect( + ( select ) => select( blocksStore ).getGroupingBlockName(), + [] + ); + + return ( + groupingBlockName && ( +

    + { sprintf( + __( '默认分组区块名称:%s' ), + groupingBlockName + ) } +

    + ) + ); +}; +``` + +_参数_ + +- _state_ `Object`: 数据状态。 + +_返回值_ + +- `?string`: 用于处理区块分组的区块名称。 + +### getUnregisteredFallbackBlockName + +返回用于处理未注册区块的区块名称。 + +_用法_ + +```js +import { __, sprintf } from '@wordpress/i18n'; +import { store as blocksStore } from '@wordpress/blocks'; +import { useSelect } from '@wordpress/data'; + +const ExampleComponent = () => { + const unregisteredFallbackBlockName = useSelect( + ( select ) => select( blocksStore ).getUnregisteredFallbackBlockName(), + [] + ); + + return ( + unregisteredFallbackBlockName && ( +

    + { sprintf( + __( '未注册降级区块名称:%s' ), + unregisteredFallbackBlockName + ) } +

    + ) + ); +}; +``` + +_参数_ + +- _state_ `Object`: 数据状态。 + +_返回值_ + +- `?string`: 用于处理未注册区块的区块名称。 + +### hasBlockSupport + +如果区块定义了特定功能的支持则返回 true,否则返回 false。 + +_用法_ + +```js +import { __, sprintf } from '@wordpress/i18n'; +import { store as blocksStore } from '@wordpress/blocks'; +import { useSelect } from '@wordpress/data'; + +const ExampleComponent = () => { + const paragraphBlockSupportClassName = useSelect( ( select ) => + select( blocksStore ).hasBlockSupport( 'core/paragraph', 'className' ), + [] + ); + + return ( +

    + { sprintf( + __( 'core/paragraph 是否支持自定义类名:%s' ), + paragraphBlockSupportClassName + ) } +

    + ); +}; +``` + +_参数_ + +- _state_ `Object`: 数据状态。 +- _nameOrType_ `(string|Object)`: 区块名称或类型对象。 +- _feature_ `string`: 要测试的功能特性。 +- _defaultSupports_ `boolean`: 未显式定义时是否默认支持该功能。 + +_返回值_ + +- `boolean`: 区块是否支持该功能。 + +### hasChildBlocks + +返回一个布尔值,指示区块是否包含子区块。 + +_用法_ + +```js +import { __, sprintf } from '@wordpress/i18n'; +import { store as blocksStore } from '@wordpress/blocks'; +import { useSelect } from '@wordpress/data'; + +const ExampleComponent = () => { + const navigationBlockHasChildBlocks = useSelect( + ( select ) => select( blocksStore ).hasChildBlocks( 'core/navigation' ), + [] + ); + + return ( +

    + { sprintf( + __( 'core/navigation 是否包含子区块:%s' ), + navigationBlockHasChildBlocks + ) } +

    + ); +}; +``` + +_参数_ + +- _state_ `Object`: 数据状态。 +- _blockName_ `string`: 区块类型名称。 + +_返回值_ + +- `boolean`: 如果区块包含子区块则为 true,否则为 false。 + +# 区块类型数据 + +命名空间:`core/blocks` + +## 选择器 + + + +### getActiveBlockVariation + +根据区块属性返回指定区块的激活变体。变体通过其 `isActive` 属性确定,该属性可以是区块属性键的数组或函数。 + +当为区块属性键数组时,将使用严格相等性检查将 `attributes` 与变体的属性进行比较。 + +当为函数类型时,该函数应接受区块属性和变体属性,并确定变体是否处于激活状态。该函数接受区块属性和变体属性,并确定变体是否处于激活状态。 + +_用法_ + +```js +import { __ } from '@wordpress/i18n'; +import { store as blocksStore } from '@wordpress/blocks'; +import { store as blockEditorStore } from '@wordpress/block-editor'; +import { useSelect } from '@wordpress/data'; + +const ExampleComponent = () => { + // 此示例假设 core/embed 区块是区块编辑器中的第一个区块 + const activeBlockVariation = useSelect( ( select ) => { + // 获取区块列表 + const [ firstBlock ] = select( blockEditorStore ).getBlocks(); + + // 返回第一个区块的激活区块变体 + return select( blocksStore ).getActiveBlockVariation( + firstBlock.name, + firstBlock.attributes + ); + }, [] ); + + return activeBlockVariation && activeBlockVariation.name === 'spotify' ? ( +

    { __( 'Spotify 变体' ) }

    + ) : ( +

    { __( '其他变体' ) }

    + ); +}; +``` + +_参数_ + +- _state_ `Object`: 数据状态 +- _blockName_ `string`: 区块名称(例如:"core/columns") +- _attributes_ `Object`: 用于确定激活变体的区块属性 +- _scope_ `[WPBlockVariationScope]`: 区块变体作用域名称 + +_返回值_ + +- `(WPBlockVariation|undefined)`: 激活的区块变体 + +### getBlockStyles + +通过区块名称返回区块样式。 + +_用法_ + +```js +import { store as blocksStore } from '@wordpress/blocks'; +import { useSelect } from '@wordpress/data'; + +const ExampleComponent = () => { + const buttonBlockStyles = useSelect( + ( select ) => select( blocksStore ).getBlockStyles( 'core/button' ), + [] + ); + + return ( +
      + { buttonBlockStyles && + buttonBlockStyles.map( ( style ) => ( +
    • { style.label }
    • + ) ) } +
    + ); +}; +``` + +_参数_ + +- _state_ `Object`: 数据状态 +- _name_ `string`: 区块类型名称 + +_返回值_ + +- `Array?`: 区块样式 + +### getBlockSupport + +返回指定功能的区块支持值(如果已定义)。 + +_用法_ + +```js +import { __, sprintf } from '@wordpress/i18n'; +import { store as blocksStore } from '@wordpress/blocks'; +import { useSelect } from '@wordpress/data'; + +const ExampleComponent = () => { + const paragraphBlockSupportValue = useSelect( + ( select ) => + select( blocksStore ).getBlockSupport( 'core/paragraph', 'anchor' ), + [] + ); + + return ( +

    + { sprintf( + __( 'core/paragraph 支持的 anchor 值:%s' ), + paragraphBlockSupportValue + ) } +

    + ); +}; +``` + +_参数_ + +- _state_ `Object`: 数据状态 +- _nameOrType_ `(string|Object)`: 区块名称或类型对象 +- _feature_ `Array|string`: 要检索的功能 +- _defaultSupports_ `*`: 未明确定义时返回的默认值 + +_返回值_ + +- `?*`: 区块支持值 + +### getBlockType + +通过名称返回区块类型。 + +_用法_ + +```js +import { store as blocksStore } from '@wordpress/blocks'; +import { useSelect } from '@wordpress/data'; + +const ExampleComponent = () => { + const paragraphBlock = useSelect( + ( select ) => ( select ) => + select( blocksStore ).getBlockType( 'core/paragraph' ), + [] + ); + + return ( +
      + { paragraphBlock && + Object.entries( paragraphBlock.supports ).map( + ( blockSupportsEntry ) => { + const [ propertyName, value ] = blockSupportsEntry; + return ( +
    • { `${ propertyName } : ${ value }` }
    • + ); + } + ) } +
    + ); +}; +``` + +_参数_ + +- _state_ `Object`: 数据状态 +- _name_ `string`: 区块类型名称 + +_返回值_ + +- `?Object`: 区块类型 + +### getBlockTypes + +返回所有可用的区块类型。 + +_用法_ + +```js +import { store as blocksStore } from '@wordpress/blocks'; +import { useSelect } from '@wordpress/data'; + +const ExampleComponent = () => { + const blockTypes = useSelect( + ( select ) => select( blocksStore ).getBlockTypes(), + [] + ); + + return ( +
      + { blockTypes.map( ( block ) => ( +
    • { block.title }
    • + ) ) } +
    + ); +}; +``` + +_参数_ + +- _state_ `Object`: 数据状态。 + +_返回值_ + +- `Array`: 区块类型数组。 + +### getBlockVariations + +根据区块名称返回区块变体。 + +_用法_ + +```js +import { store as blocksStore } from '@wordpress/blocks'; +import { useSelect } from '@wordpress/data'; + +const ExampleComponent = () => { + const socialLinkVariations = useSelect( + ( select ) => + select( blocksStore ).getBlockVariations( 'core/social-link' ), + [] + ); + + return ( +
      + { socialLinkVariations && + socialLinkVariations.map( ( variation ) => ( +
    • { variation.title }
    • + ) ) } +
    + ); +}; +``` + +_参数_ + +- _state_ `Object`: 数据状态。 +- _blockName_ `string`: 区块类型名称。 +- _scope_ `[WPBlockVariationScope]`: 区块变体作用域名称。 + +_返回值_ + +- `(WPBlockVariation[]|void)`: 区块变体数组。 + +### getCategories + +返回所有可用的区块分类。 + +_用法_ + +```js +import { store as blocksStore } from '@wordpress/blocks'; +import { useSelect } from '@wordpress/data'; + +const ExampleComponent = () => { + const blockCategories = useSelect( + ( select ) => select( blocksStore ).getCategories(), + [] + ); + + return ( +
      + { blockCategories.map( ( category ) => ( +
    • { category.title }
    • + ) ) } +
    + ); +}; +``` + +_参数_ + +- _state_ `Object`: 数据状态。 + +_返回值_ + +- `WPBlockCategory[]`: 分类列表。 + +### getChildBlockNames + +返回指定区块的子区块数组。 + +_用法_ + +```js +import { store as blocksStore } from '@wordpress/blocks'; +import { useSelect } from '@wordpress/data'; + +const ExampleComponent = () => { + const childBlockNames = useSelect( + ( select ) => + select( blocksStore ).getChildBlockNames( 'core/navigation' ), + [] + ); + + return ( +
      + { childBlockNames && + childBlockNames.map( ( child ) => ( +
    • { child }
    • + ) ) } +
    + ); +}; +``` + +_参数_ + +- _state_ `Object`: 数据状态。 +- _blockName_ `string`: 区块类型名称。 + +_返回值_ + +- `Array`: 子区块名称数组。 + +### getCollections + +返回所有可用的集合。 + +_用法_ + +```js +import { store as blocksStore } from '@wordpress/blocks'; +import { useSelect } from '@wordpress/data'; + +const ExampleComponent = () => { + const blockCollections = useSelect( + ( select ) => select( blocksStore ).getCollections(), + [] + ); + + return ( +
      + { Object.values( blockCollections ).length > 0 && + Object.values( blockCollections ).map( ( collection ) => ( +
    • { collection.title }
    • + ) ) } +
    + ); +}; +``` + +_参数_ + +- _state_ `Object`: 数据状态。 + +_返回值_ + +- `Object`: 集合列表。 + +### getDefaultBlockName + +返回默认区块名称。 + +_用法_ + +```js +import { __, sprintf } from '@wordpress/i18n'; +import { store as blocksStore } from '@wordpress/blocks'; +import { useSelect } from '@wordpress/data'; + +const ExampleComponent = () => { + const defaultBlockName = useSelect( + ( select ) => select( blocksStore ).getDefaultBlockName(), + [] + ); + + return ( + defaultBlockName && ( +

    + { sprintf( __( '默认区块名称: %s' ), defaultBlockName ) } +

    + ) + ); +}; +``` + +_参数_ + +- _state_ `Object`: 数据状态。 + +_返回值_ + +- `?string`: 默认区块名称。 + +### getDefaultBlockVariation + +返回指定区块类型的默认区块变体。当存在多个标记为默认的变体时,将选择最后添加的项。这简化了注册覆盖操作。当未设置默认变体时,返回第一个项。 + +_用法_ + +```js +import { __, sprintf } from '@wordpress/i18n'; +import { store as blocksStore } from '@wordpress/blocks'; +import { useSelect } from '@wordpress/data'; + +const ExampleComponent = () => { + const defaultEmbedBlockVariation = useSelect( + ( select ) => + select( blocksStore ).getDefaultBlockVariation( 'core/embed' ), + [] + ); + + return ( + defaultEmbedBlockVariation && ( +

    + { sprintf( + __( 'core/embed 默认变体: %s' ), + defaultEmbedBlockVariation.title + ) } +

    + ) + ); +}; +``` + +_参数_ + +- _state_ `Object`: 数据状态。 +- _blockName_ `string`: 区块类型名称。 +- _scope_ `[WPBlockVariationScope]`: 区块变体作用域名称。 + +_返回值_ + +- `?WPBlockVariation`: 默认区块变体。 \ No newline at end of file diff --git a/reference-guides/data/data-core-commands.md b/reference-guides/data/data-core-commands.md new file mode 100644 index 0000000..446b351 --- /dev/null +++ b/reference-guides/data/data-core-commands.md @@ -0,0 +1,129 @@ +# 命令数据 + +命名空间:`core/commands` + +## 选择器 + + + +### getCommandLoaders + +返回已注册的命令加载器。 + +_参数_ + +- _state_ `Object`: 状态树 +- _contextual_ `boolean`: 是否仅返回上下文相关的命令加载器 + +_返回值_ + +- `import('./actions').WPCommandLoaderConfig[]`: 已注册的命令加载器列表 + +### getCommands + +返回已注册的静态命令。 + +_参数_ + +- _state_ `Object`: 状态树 +- _contextual_ `boolean`: 是否仅返回上下文相关的命令 + +_返回值_ + +- `import('./actions').WPCommandConfig[]`: 已注册的命令列表 + +### getContext + +返回当前活动上下文。 + +_参数_ + +- _state_ `Object`: 状态树 + +_返回值_ + +- `string`: 上下文 + +### isOpen + +返回命令面板是否处于打开状态。 + +_参数_ + +- _state_ `Object`: 状态树 + +_返回值_ + +- `boolean`: 返回命令面板是否打开 + + + +## 操作 + + + +### close + +关闭命令面板。 + +_返回值_ + +- `Object`: 操作对象 + +### open + +打开命令面板。 + +_返回值_ + +- `Object`: 操作对象 + +### registerCommand + +返回用于注册新命令的操作对象。 + +_参数_ + +- _config_ `WPCommandConfig`: 命令配置 + +_返回值_ + +- `Object`: 操作对象 + +### registerCommandLoader + +注册命令加载器。 + +_参数_ + +- _config_ `WPCommandLoaderConfig`: 命令加载器配置 + +_返回值_ + +- `Object`: 操作对象 + +### unregisterCommand + +返回用于取消注册命令的操作对象。 + +_参数_ + +- _name_ `string`: 命令名称 + +_返回值_ + +- `Object`: 操作对象 + +### unregisterCommandLoader + +取消注册命令加载器钩子。 + +_参数_ + +- _name_ `string`: 命令加载器名称 + +_返回值_ + +- `Object`: 操作对象 + + \ No newline at end of file diff --git a/reference-guides/data/data-core-customize-widgets.md b/reference-guides/data/data-core-customize-widgets.md new file mode 100644 index 0000000..6a2a6df --- /dev/null +++ b/reference-guides/data/data-core-customize-widgets.md @@ -0,0 +1,86 @@ +# 自定义小工具 + +命名空间:`core/customize-widgets` + +## 选择器 + + + +### isInserterOpened + +判断插入器是否开启时返回 true + +_使用示例_ + +```js +import { store as customizeWidgetsStore } from '@wordpress/customize-widgets'; +import { __ } from '@wordpress/i18n'; +import { useSelect } from '@wordpress/data'; + +const ExampleComponent = () => { + const { isInserterOpened } = useSelect( + ( select ) => select( customizeWidgetsStore ), + [] + ); + + return isInserterOpened() + ? __( '插入器已开启' ) + : __( '插入器已关闭' ); +}; +``` + +_参数_ + +- _state_ `Object`: 全局应用状态 + +_返回值_ + +- `boolean`: 插入器是否开启 + + + +## 操作 + + + +### setIsInserterOpened + +返回用于开启/关闭插入器的操作对象 + +_使用示例_ + +```js +import { useState } from 'react'; +import { store as customizeWidgetsStore } from '@wordpress/customize-widgets'; +import { __ } from '@wordpress/i18n'; +import { useDispatch } from '@wordpress/data'; +import { Button } from '@wordpress/components'; + +const ExampleComponent = () => { + const { setIsInserterOpened } = useDispatch( customizeWidgetsStore ); + const [ isOpen, setIsOpen ] = useState( false ); + + return ( + + ); +}; +``` + +_参数_ + +- _value_ `boolean|Object`: 插入器应开启(true)或关闭(false)。要指定插入位置,请使用对象格式 +- _value.rootClientId_ `string`: 要插入的根客户端ID +- _value.insertionIndex_ `number`: 要插入的索引位置 + +_返回值_ + +- `Object`: 操作对象 + + \ No newline at end of file diff --git a/reference-guides/data/data-core-edit-post.md b/reference-guides/data/data-core-edit-post.md new file mode 100644 index 0000000..c2ed6da --- /dev/null +++ b/reference-guides/data/data-core-edit-post.md @@ -0,0 +1,559 @@ +### openPublishSidebar + +> **已弃用** + +返回用于通知用户已打开发布侧边栏的动作对象。 + +_返回值_ + +- `Object`:动作对象 + +### removeEditorPanel + +> **已弃用** + +返回用于从编辑器中移除面板的动作对象。 + +_参数_ + +- _panelName_ `string`:标识要移除的面板的字符串。 + +_返回值_ + +- `Object`:动作对象。 + +### requestMetaBoxUpdates + +更新元数据框。 + +### setAvailableMetaBoxesPerLocation + +存储关于哪些元数据框在哪些位置可用的信息。 + +_参数_ + +- _metaBoxesPerLocation_ `Object`:每个位置的元数据框。 + +### setIsEditingTemplate + +> **已弃用** + +返回用于切换到模板编辑的动作对象。 + +### setIsInserterOpened + +> **已弃用** + +返回用于打开/关闭插入器的动作对象。 + +_参数_ + +- _value_ `boolean|Object`:插入器应打开(true)还是关闭(false)。 + +### setIsListViewOpened + +> **已弃用** + +返回用于打开/关闭列表视图的动作对象。 + +_参数_ + +- _isOpen_ `boolean`:表示列表视图应打开还是关闭的布尔值。 + +### showBlockTypes + +将提供的区块类型更新为可见。 + +_参数_ + +- _blockNames_ `string[]`:要显示的区块类型的名称。 + +### switchEditorMode + +> **已弃用** + +触发用于切换编辑器模式的动作。 + +_参数_ + +- _mode_ `string`:编辑器模式。 + +### toggleDistractionFree + +> **已弃用** + +切换无干扰模式的动作。无干扰模式下预期没有侧边栏,因为设置的 z-index 值会导致无法关闭侧边栏。 + +### toggleEditorPanelEnabled + +> **已弃用** + +返回用于启用或禁用编辑器中面板的动作对象。 + +_参数_ + +- _panelName_ `string`:标识要启用或禁用的面板的字符串。 + +_返回值_ + +- `Object`:动作对象。 + +### toggleEditorPanelOpened + +> **已弃用** + +打开已关闭的面板并关闭已打开的面板。 + +_参数_ + +- _panelName_ `string`:标识要打开或关闭的面板的字符串。 + +### toggleFeature + +触发用于切换功能标志的动作。 + +_参数_ + +- _feature_ `string`:功能名称。 + +### toggleFullscreenMode + +切换全屏模式视图选项的动作。 + +### togglePinnedPluginItem + +触发用于切换插件名称标志的动作对象。 + +_参数_ + +- _pluginName_ `string`:插件名称。 + +### togglePublishSidebar + +> **已弃用** + +返回用于通知用户切换发布侧边栏的动作对象。 + +_返回值_ + +- `Object`:动作对象 + +### updatePreferredStyleVariations + +> **已弃用** + +返回用于通知在创建区块时应自动应用样式的动作对象。 + + + +# 编辑器界面数据 + +命名空间:`core/edit-post` + +## 选择器 + + + +### areMetaBoxesInitialized + +判断元框是否已完成初始化。 + +_参数_ + +- _state_ `Object`: 全局应用状态 + +_返回值_ + +- `boolean`: 元框是否已初始化 + +### getActiveGeneralSidebarName + +返回当前活动的通用侧边栏名称,若无活动侧边栏则返回null。活动通用侧边栏是用于识别编辑器或插件侧边栏的唯一名称。 + +示例: + +- `edit-post/document` +- `my-plugin/insert-image-sidebar` + +_参数_ + +- _state_ `Object`: 全局应用状态 + +_返回值_ + +- `?string`: 活动通用侧边栏名称 + +### getActiveMetaBoxLocations + +返回活动元框位置的数组。 + +_参数_ + +- _state_ `Object`: 文章编辑器状态 + +_返回值_ + +- `string[]`: 活动元框位置 + +### getAllMetaBoxes + +返回所有可用元框的列表。 + +_参数_ + +- _state_ `Object`: 全局应用状态 + +_返回值_ + +- `Array`: 元框列表 + +### getEditedPostTemplate + +获取当前编辑文章的模板。 + +_返回值_ + +- `?Object`: 文章模板 + +### getEditorMode + +返回当前编辑模式。 + +_参数_ + +- _state_ `Object`: 全局应用状态 + +_返回值_ + +- `string`: 编辑模式 + +### getHiddenBlockTypes + +返回被隐藏的区块数组。 + +_返回值_ + +- `Array`: 隐藏区块类型列表 + +### getMetaBoxesPerLocation + +返回指定位置的所有可用元框列表。 + +_参数_ + +- _state_ `Object`: 全局应用状态 +- _location_ `string`: 要检测的元框位置 + +_返回值_ + +- `?Array`: 元框列表 + +### getPreference + +_参数_ + +- _state_ `Object`: 全局应用状态 +- _preferenceKey_ `string`: 偏好设置键名 +- _defaultValue_ `*`: 默认值 + +_返回值_ + +- `*`: 偏好设置值 + +### getPreferences + +返回偏好设置(这些设置会在本地持久化保存)。 + +_参数_ + +- _state_ `Object`: 全局应用状态 + +_返回值_ + +- `Object`: 偏好设置对象 + +### hasMetaBoxes + +判断文章是否正在使用元框。 + +_参数_ + +- _state_ `Object`: 全局应用状态 + +_返回值_ + +- `boolean`: 是否存在元框 + +### isEditingTemplate + +> **已弃用** + +判断是否启用了模板编辑模式。 + +### isEditorPanelEnabled + +> **已弃用** + +判断指定面板是否启用,面板默认处于启用状态。 + +_参数_ + +- _state_ `Object`: 全局应用状态 +- _panelName_ `string`: 面板标识字符串 + +_返回值_ + +- `boolean`: 面板是否启用 + +### isEditorPanelOpened + +> **已弃用** + +判断指定面板是否打开,面板默认处于关闭状态。 + +_参数_ + +- _state_ `Object`: 全局应用状态 +- _panelName_ `string`: 面板标识字符串 + +_返回值_ + +- `boolean`: 面板是否打开 + +### isEditorPanelRemoved + +> **已弃用** + +判断指定面板是否已被程序化移除,所有面板默认不会被移除。 + +_参数_ + +- _state_ `Object`: 全局应用状态 +- _panelName_ `string`: 面板标识字符串 + +_返回值_ + +- `boolean`: 面板是否已被移除 + +### isEditorSidebarOpened + +判断编辑器侧边栏是否打开。 + +_参数_ + +- _state_ `Object`: 全局应用状态 + +_返回值_ + +- `boolean`: 编辑器侧边栏是否打开 + +### isFeatureActive + +判断指定功能是否启用。 + +_参数_ + +- _state_ `Object`: 全局应用状态 +- _feature_ `string`: 功能标识符 + +_返回值_ + +- `boolean`: 是否处于活动状态 + +### isInserterOpened + +> **已弃用** + +返回插入器是否处于打开状态。 + +_参数_ + +- _state_ `Object`: 全局应用状态。 + +_返回值_ + +- `boolean`: 插入器是否已打开。 + +### isListViewOpened + +返回列表视图是否处于打开状态。 + +_参数_ + +- _state_ `Object`: 全局应用状态。 + +_返回值_ + +- `boolean`: 列表视图是否已打开。 + +### isMetaBoxLocationActive + +如果给定位置存在活动的元框则返回 true,否则返回 false。 + +_参数_ + +- _state_ `Object`: 文章编辑器状态。 +- _location_ `string`: 要测试的元框位置。 + +_返回值_ + +- `boolean`: 元框位置是否处于活动状态。 + +### isMetaBoxLocationVisible + +如果元框位置处于活动状态且可见则返回 true。 + +_参数_ + +- _state_ `Object`: 文章编辑器状态。 +- _location_ `string`: 要测试的元框位置。 + +_返回值_ + +- `boolean`: 元框位置是否处于活动且可见状态。 + +### isModalActive + +> **已弃用**(自 WP 6.3 起,请改用 `core/interface` 存储中间名选择器) + +如果模态框处于活动状态则返回 true,否则返回 false。 + +_参数_ + +- _state_ `Object`: 全局应用状态。 +- _modalName_ `string`: 唯一标识模态框的字符串。 + +_返回值_ + +- `boolean`: 模态框是否处于活动状态。 + +### isPluginItemPinned + +如果插件项已固定到标题区域则返回 true。未设置值时默认返回 true。 + +_参数_ + +- _state_ `Object`: 全局应用状态。 +- _pluginName_ `string`: 插件项名称。 + +_返回值_ + +- `boolean`: 插件项是否已固定。 + +### isPluginSidebarOpened + +返回插件侧边栏是否处于打开状态。 + +_参数_ + +- _state_ `Object`: 全局应用状态。 + +_返回值_ + +- `boolean`: 插件侧边栏是否已打开。 + +### isPublishSidebarOpened + +> **已弃用** + +返回发布侧边栏是否处于打开状态。 + +_参数_ + +- _state_ `Object`: 全局应用状态。 + +_返回值_ + +- `boolean`: 发布侧边栏是否已打开。 + +### isSavingMetaBoxes + +如果元框正在保存则返回 true。 + +_参数_ + +- _state_ `Object`: 全局应用状态。 + +_返回值_ + +- `boolean`: 元框是否正在保存。 + + + +## 操作 + + + +### closeGeneralSidebar + +返回表示用户关闭侧边栏的操作对象。 + +### closeModal + +> **已弃用**(自 WP 6.3 起,请改用 `core/interface` 存储中间名操作) + +返回表示用户关闭模态框的操作对象。 + +_返回值_ + +- `Object`: 操作对象。 + +### closePublishSidebar + +> **已弃用** + +返回表示用户关闭发布侧边栏的操作对象。 + +_返回值_ + +- `Object`: 操作对象。 + +### hideBlockTypes + +将指定区块类型设置为隐藏状态。 + +_参数_ + +- _blockNames_ `string[]`: 需要隐藏的区块类型名称。 + +### initializeMetaBoxes + +初始化 WordPress `postboxes` 脚本及元框保存逻辑。 + +### metaBoxUpdatesFailure + +返回表示元框更新失败的操作对象。 + +_返回值_ + +- `Object`: 操作对象。 + +### metaBoxUpdatesSuccess + +返回表示元框更新成功的操作对象。 + +_返回值_ + +- `Object`: 操作对象。 + +### openGeneralSidebar + +返回表示用户打开编辑器侧边栏的操作对象。 + +_参数_ + +- _name_ `?string`: 要打开的侧边栏名称。 + +### openModal + +> **已弃用**(自 WP 6.3 起,请改用 `core/interface` 存储中间名操作) + +返回表示用户打开模态框的操作对象。 + +_参数_ + +- _name_ `string`: 唯一标识模态框的字符串。 + +_返回值_ + +- `Object`: 操作对象。 \ No newline at end of file diff --git a/reference-guides/data/data-core-edit-site.md b/reference-guides/data/data-core-edit-site.md new file mode 100644 index 0000000..a0dbec8 --- /dev/null +++ b/reference-guides/data/data-core-edit-site.md @@ -0,0 +1,448 @@ +# 编辑站点 + +命名空间:`core/edit-site` + +## 选择器 + + + +### getCanUserCreateMedia + +返回当前用户是否可创建媒体。 + +_参数_ + +- _state_ `Object`: 全局应用状态。 + +_返回值_ + +- `Object`: 当前用户是否可创建媒体。 + +### getCurrentTemplateNavigationPanelSubMenu + +> **已弃用** + +### getCurrentTemplateTemplateParts + +> **已弃用** + +返回当前编辑模板的模板部件及其区块。 + +_参数_ + +- _state_ `Object`: 全局应用状态。 + +_返回值_ + +- `Array`: 包含模板部件及其区块的数组。 + +### getEditedPostContext + +> **已弃用** + +返回已编辑文章的背景对象。 + +_参数_ + +- _state_ `Object`: 全局应用状态。 + +_返回值_ + +- `Object`: 页面对象。 + +### getEditedPostId + +> **已弃用** + +返回当前编辑的模板或模板部件的ID。 + +_参数_ + +- _state_ `Object`: 全局应用状态。 + +_返回值_ + +- `?string`: 文章ID。 + +### getEditedPostType + +> **已弃用** + +返回当前编辑的文章类型(wp_template 或 wp_template_part)。 + +_参数_ + +- _state_ `Object`: 全局应用状态。 + +_返回值_ + +- `?TemplateType`: 模板类型。 + +### getEditorMode + +返回当前编辑模式。 + +_参数_ + +- _state_ `Object`: 全局应用状态。 + +_返回值_ + +- `string`: 编辑模式。 + +### getHomeTemplateId + +> **已弃用** + +### getNavigationPanelActiveMenu + +> **已弃用** + +### getPage + +> **已弃用** + +返回当前页面对象。 + +_参数_ + +- _state_ `Object`: 全局应用状态。 + +_返回值_ + +- `Object`: 页面对象。 + +### getReusableBlocks + +返回所有可用的可重用区块。 + +_参数_ + +- _state_ `Object`: 全局应用状态。 + +_返回值_ + +- `Array`: 可用的可重用区块。 + +### getSettings + +返回站点编辑器设置。 + +_参数_ + +- _state_ `Object`: 全局应用状态。 + +_返回值_ + +- `Object`: 设置对象。 + +### hasPageContentFocus + +> **已弃用** + +判断编辑器是否仅允许编辑页面内容。 + +_返回值_ + +- `boolean`: 是否聚焦于编辑页面内容。 + +### isFeatureActive + +> **已弃用** + +返回指定功能是否启用。 + +_参数_ + +- _state_ `Object`: 全局应用状态。 +- _featureName_ `string`: 功能标识符。 + +_返回值_ + +- `boolean`: 是否启用。 + +### isInserterOpened + +> **已弃用** + +如果插入器已打开则返回true。 + +_参数_ + +- _state_ `Object`: 全局应用状态。 + +_返回值_ + +- `boolean`: 插入器是否打开。 + +### isListViewOpened + +如果列表视图已打开则返回true。 + +_参数_ + +- _state_ `Object`: 全局应用状态。 + +_返回值_ + +- `boolean`: 列表视图是否打开。 + +### isNavigationOpened + +> **已弃用** + +### isPage + +> **已弃用** + +判断编辑器是否已加载页面。 + +_相关_ + +- setPage + +_参数_ + +- _state_ `Object`: 全局应用状态。 + +_返回值_ + +- `boolean`: 编辑器是否已加载页面。 + +### isSaveViewOpened + +返回保存面板当前的打开/关闭状态。 + +_参数_ + +- _state_ `Object`: 全局应用状态。 + +_返回值_ + +- `boolean`: 如果保存面板应打开则为true,关闭则为false。 + + + +## 操作 + + + +### addTemplate + +> **已弃用** + +添加新模板并设置为当前模板的操作。 + +_参数_ + +- _template_ `Object`: 模板对象。 + +_返回值_ + +- `Object`: 用于设置当前模板的操作对象。 + +### closeGeneralSidebar + +关闭侧边栏的操作。 + +### openGeneralSidebar + +打开编辑器侧边栏的操作。 + +_参数_ + +- _name_ `?string`: 要打开的侧边栏名称。 + +### openNavigationPanelToMenu + +> **已弃用** + +同时打开导航面板并设置其活动菜单。 + +### removeTemplate + +移除模板的操作。 + +_参数_ + +- _template_ `Object`: 模板对象。 + +### revertTemplate + +将模板恢复至原始主题提供的文件。 + +_参数_ + +- _template_ `Object`: 要恢复的模板。 +- _options_ `[Object]`: +- _options.allowUndo_ `[boolean]`: 是否允许用户撤销恢复模板操作。默认为true。 + +### setEditedEntity + +> **已弃用** + +设置已编辑实体的操作。 + +_参数_ + +- _postType_ `string`: 实体的文章类型。 +- _postId_ `string`: 实体的ID。 +- _context_ `Object`: 实体的上下文。 + +_返回值_ + +- `Object`: 操作对象。 + +### setEditedPostContext + +> **已弃用** + +设置当前区块编辑器上下文。 + +_参数_ + +- _context_ `Object`: 上下文对象。 + +_返回值_ + +- `Object`: 操作对象。 + +### setHasPageContentFocus + +设置编辑器是否仅允许编辑页面内容。 + +_参数_ + +- _hasPageContentFocus_ `boolean`: 为true时仅允许编辑页面内容,为false时允许编辑模板。 + +### setHomeTemplateId + +> **已弃用** + +### setIsInserterOpened + +> **已弃用** + +返回用于打开/关闭插入器的操作对象。 + +_参数_ + +- _value_ `boolean|Object`: 插入器应打开(true)或关闭(false)。 + +### setIsListViewOpened + +> **已弃用** + +返回用于打开/关闭列表视图的操作对象。 + +_参数_ + +- _isOpen_ `boolean`: 表示列表视图应打开或关闭的布尔值。 + +### setIsNavigationPanelOpened + +> **已弃用** + +设置导航面板是否应打开。 + +### setIsSaveViewOpened + +设置保存视图面板是否应打开。 + +_参数_ + +- _isOpen_ `boolean`: 为true时打开保存视图,为false时关闭。此操作不切换状态,而是直接设置状态。 + +### setNavigationMenu + +> **已弃用** + +设置导航菜单的操作。 + +_参数_ + +- _navigationMenuId_ `string`: 导航菜单文章ID。 + +_返回值_ + +- `Object`: 操作对象。 + +### setNavigationPanelActiveMenu + +> **已弃用** + +设置活动导航面板菜单的操作。 + +_返回值_ + +- `Object`: 操作对象。 + +### setPage + +> **已弃用** + +解析页面模板并同时显示两者。如果未提供路径,则尝试使用postId生成类似`?p=${ postId }`的路径。 + +_返回值_ + +- `Object`: 操作对象。 + +### setTemplate + +设置模板的操作,可选择从REST API获取。 + +_返回值_ + +- `Object`: 操作对象。 + +### setTemplatePart + +> **已弃用** + +设置模板部件的操作。 + +_参数_ + +- _templatePartId_ `string`: 模板部件ID。 + +_返回值_ + +- `Object`: 操作对象。 + +### switchEditorMode + +> **已弃用** + +触发用于切换编辑器模式的操作。 + +_参数_ + +- _mode_ `string`: 编辑器模式。 + +### toggleDistractionFree + +> **已弃用** + +切换无干扰模式的操作。无干扰模式下预期无侧边栏,因为设置的z-index值会导致无法关闭侧边栏。 + +### toggleFeature + +触发切换功能标志的操作。 + +_参数_ + +- _featureName_ `string`: 功能名称。 + +### updateSettings + +返回用于更新设置的操作对象。 + +_参数_ + +- _settings_ `Object`: 新设置。 + +_返回值_ + +- `Object`: 操作对象。 + + \ No newline at end of file diff --git a/reference-guides/data/data-core-edit-widgets.md b/reference-guides/data/data-core-edit-widgets.md new file mode 100644 index 0000000..864dcfd --- /dev/null +++ b/reference-guides/data/data-core-edit-widgets.md @@ -0,0 +1,362 @@ +# 编辑小工具 + +命名空间:`core/edit-widgets` + +## 选择器 + + + +### canInsertBlockInWidgetArea + +判断是否可以将区块插入到小工具区域。 + +_参数_ + +- _state_ `Array`: 小工具区域的打开状态。 +- _blockName_ `string`: 要插入的区块名称。 + +_返回值_ + +- `boolean`: 如果区块可以插入到小工具区域则返回 true。 + +### getEditedWidgetAreas + +返回所有已编辑的小工具区域实体记录。 + +_返回值_ + +- `Object[]`: 已编辑的小工具区域实体记录列表。 + +### getIsWidgetAreaOpen + +获取小工具区域是否处于打开状态。 + +_参数_ + +- _state_ `Array`: 小工具区域的打开状态。 +- _clientId_ `string`: 小工具区域的客户端 ID。 + +_返回值_ + +- `boolean`: 如果小工具区域处于打开状态则返回 true。 + +### getParentWidgetAreaBlock + +根据子级客户端 ID 返回父级小工具区域区块。 + +_参数_ + +- _clientId_ `string`: 小工具区域中某个区块的客户端 ID。 + +_返回值_ + +- `WPBlock`: 小工具区域区块。 + +### getReferenceWidgetBlocks + +返回所有表示引用小工具的区块。 + +_参数_ + +- _referenceWidgetName_ `string`: 可选参数。如果指定,则只返回具有此名称的引用小工具。 + +_返回值_ + +- `Array`: 表示引用小工具的所有区块列表。 + +### getWidget + +返回特定小工具 ID 的 API 小工具数据。 + +_参数_ + +- _id_ `number`: 小工具 ID。 + +_返回值_ + +- `Object`: 特定小工具 ID 的 API 小工具数据。 + +### getWidgetAreaForWidgetId + +返回包含指定 widgetId 所标识区块的小工具区域。 + +_参数_ + +- _widgetId_ `string`: 小工具的 ID。 + +_返回值_ + +- `Object`: 包含小工具区域的对象。 + +### getWidgetAreas + +返回所有 API 小工具区域。 + +_返回值_ + +- `Object[]`: API 小工具区域列表。 + +### getWidgets + +返回所有 API 小工具。 + +_返回值_ + +- `Object[]`: API 小工具列表。 + +### isInserterOpened + +判断插入器是否处于打开状态。 + +_参数_ + +- _state_ `Object`: 全局应用状态。 + +_返回值_ + +- `boolean`: 插入器是否处于打开状态。 + +### isListViewOpened + +判断列表视图是否处于打开状态。 + +_参数_ + +- _state_ `Object`: 全局应用状态。 + +_返回值_ + +- `boolean`: 列表视图是否处于打开状态。 + +### isSavingWidgetAreas + +判断是否有小工具区域正在被保存。 + +_返回值_ + +- `boolean`: 如果有小工具区域正在被保存则返回 true,否则返回 false。 + +### isWidgetSavingLocked + +返回小工具保存是否被锁定。 + +_用法_ + +```jsx +import { __ } from '@wordpress/i18n'; +import { store as widgetStore } from '@wordpress/edit-widgets'; +import { useSelect } from '@wordpress/data'; + +const ExampleComponent = () => { + const isSavingLocked = useSelect( + ( select ) => select( widgetStore ).isWidgetSavingLocked(), + [] + ); + + return isSavingLocked ? ( +

    { __( '小工具保存已被锁定' ) }

    + ) : ( +

    { __( '小工具保存未被锁定' ) }

    + ); +}; +``` + +_参数_ + +- _state_ `Object`: 全局应用状态。 + +_返回值_ + +- `boolean`: 是否被锁定。 + + + +## 操作 + + + +### closeGeneralSidebar + +返回表示用户关闭侧边栏的操作对象。 + +_返回值_ + +- `Object`: 操作创建器。 + +### lockWidgetSaving + +返回用于表示小工具保存被锁定的操作对象。 + +_用法_ + +```js +import { store as widgetStore } from '@wordpress/edit-widgets'; +import { useDispatch } from '@wordpress/data'; + +const ExampleComponent = () => { + const { lockWidgetSaving } = useDispatch( widgetStore ); + return ( + + ); +}; +``` + +_参数_ + +- _lockName_ `string`: 锁名称。 + +_返回值_ + +- `Object`: 操作对象。 + +### moveBlockToWidgetArea + +处理在小工具区域之间移动区块的操作。 + +_参数_ + +- _clientId_ `string`: 要移动区块的客户端 ID。 +- _widgetAreaId_ `string`: 要将区块移动到的目标小工具区域 ID。 + +### persistStubPost + +将指定 ID 的存根文章持久化到核心数据存储中。该文章仅存在于内存中,不应通过 API 保存。 + +_参数_ + +- _id_ `string`: 文章 ID。 +- _blocks_ `Array`: 文章应包含的区块。 + +_返回值_ + +- `Object`: 文章对象。 + +### saveEditedWidgetAreas + +将已编辑小工具区域中的所有区块转换为小工具,并提交批量请求一次性保存所有内容。 + +在成功或出错时创建 snackbar 通知。 + +_返回值_ + +- `Function`: 操作创建器。 + +### saveWidgetArea + +将指定 ID 的小工具区域中的所有区块转换为小工具,并提交批量请求一次性保存所有内容。 + +_参数_ + +- _widgetAreaId_ `string`: 要处理的小工具区域 ID。 + +_返回值_ + +- `Function`: 操作创建器。 + +### saveWidgetAreas + +将指定小工具区域中的所有区块转换为小工具,并提交批量请求一次性保存所有内容。 + +_参数_ + +- _widgetAreas_ `Object[]`: 要保存的小工具区域。 + +_返回值_ + +- `Function`: 操作创建器。 + +### setIsInserterOpened + +返回用于打开/关闭插入器的操作对象。 + +_参数_ + +- _value_ `boolean|Object`: 插入器应打开(true)还是关闭(false)。要指定插入点,请使用对象。 +- _value.rootClientId_ `string`: 要插入的根客户端 ID。 +- _value.insertionIndex_ `number`: 要插入的索引位置。 + +_返回值_ + +- `Object`: 操作对象。 + +### setIsListViewOpened + +返回用于打开/关闭列表视图的操作对象。 + +_参数_ + +- _isOpen_ `boolean`: 表示列表视图应打开还是关闭的布尔值。 + +_返回值_ + +- `Object`: 操作对象。 + +### setIsWidgetAreaOpen + +设置小工具区域的打开状态。 + +_参数_ + +- _clientId_ `string`: 小工具区域的客户端 ID。 +- _isOpen_ `boolean`: 小工具区域是否应打开。 + +_返回值_ + +- `Object`: 操作。 + +### setWidgetAreasOpenState + +设置所有小工具区域的打开状态。 + +_参数_ + +- _widgetAreasOpenState_ `Object`: 所有小工具区域的打开状态。 + +_返回值_ + +- `Object`: 操作。 + +### setWidgetIdForClientId + +设置特定 widgetId 对应的客户端 ID。 + +_参数_ + +- _clientId_ `number`: 客户端 ID。 +- _widgetId_ `number`: 小工具 ID。 + +_返回值_ + +- `Object`: 操作。 + +### unlockWidgetSaving + +返回用于表示小工具保存已解锁的操作对象。 + +_用法_ + +```js +import { store as widgetStore } from '@wordpress/edit-widgets'; +import { useDispatch } from '@wordpress/data'; + +const ExampleComponent = () => { + const { unlockWidgetSaving } = useDispatch( widgetStore ); + return ( + + ); +}; +``` + +_参数_ + +- _lockName_ `string`: 锁名称。 + +_返回值_ + +- `Object`: 操作对象。 + + \ No newline at end of file diff --git a/reference-guides/data/data-core-editor.md b/reference-guides/data/data-core-editor.md new file mode 100644 index 0000000..2fe9054 --- /dev/null +++ b/reference-guides/data/data-core-editor.md @@ -0,0 +1,1721 @@ +### unlockPostAutosaving + +用于解除文章自动保存锁定的操作。 + +_使用方法_ + + // 使用锁定键 `mylock` 解除文章自动保存锁定: + wp.data.dispatch( 'core/editor' ).unlockPostAutosaving( 'mylock' ); + +_参数_ + +- _lockName_ `字符串`: 锁定名称。 + +_返回值_ + +- `对象`: 操作对象。 + +### unlockPostSaving + +用于解除文章保存锁定的操作。 + +_使用方法_ + + // 使用锁定键 `mylock` 解除文章保存锁定: + wp.data.dispatch( 'core/editor' ).unlockPostSaving( 'mylock' ); + +_参数_ + +- _lockName_ `字符串`: 锁定名称。 + +_返回值_ + +- `对象`: 操作对象。 + +### updateBlock + +_相关_ + +- core/block-editor 存储中的 updateBlock。 + +### updateBlockAttributes + +_相关_ + +- core/block-editor 存储中的 updateBlockAttributes。 + +### updateBlockListSettings + +_相关_ + +- core/block-editor 存储中的 updateBlockListSettings。 + +### updateEditorSettings + +未记录的声明。 + +### updatePost + +> 自 Gutenberg 9.7.0 起**已弃用**。 + +返回一个操作对象,用于表示已接收到针对最新版本文章的更新补丁。 + +_返回值_ + +- `对象`: 操作对象。 + +### updatePostLock + +用于锁定编辑器的操作。 + +_参数_ + +- _lock_ `对象`: 关于文章锁定状态、用户和随机数的详细信息。 + +_返回值_ + +- `对象`: 操作对象。 + + + +# 文章编辑器的数据 + +命名空间:`core/editor` + +## 选择器 + + + +### canInsertBlockType + +_相关项_ + +- 核心区块编辑器存储中的 canInsertBlockType + +### canUserUseUnfilteredHTML + +返回用户是否具有 unfiltered_html 权限。 + +_参数_ + +- _state_ `Object`: 编辑器状态 + +_返回值_ + +- `boolean`: 用户是否能够发布未过滤的 HTML + +### didPostSaveRequestFail + +如果之前的文章保存尝试失败则返回 true,否则返回 false。 + +_参数_ + +- _state_ `Object`: 全局应用状态 + +_返回值_ + +- `boolean`: 文章是否保存失败 + +### didPostSaveRequestSucceed + +如果之前的文章保存尝试成功则返回 true,否则返回 false。 + +_参数_ + +- _state_ `Object`: 全局应用状态 + +_返回值_ + +- `boolean`: 文章是否成功保存 + +### getActivePostLock + +返回当前的文章锁定状态。 + +_参数_ + +- _state_ `Object`: 全局应用状态 + +_返回值_ + +- `Object`: 锁定对象 + +### getAdjacentBlockClientId + +_相关项_ + +- 核心区块编辑器存储中的 getAdjacentBlockClientId + +### getAutosaveAttribute + +> 自 5.6 版本起**弃用**。调用者应使用 '@wordpress/core-data' 包中的 `getAutosave( postType, postId, userId )` 选择器,并通过 getPostRawValue 访问返回的自动保存对象属性。 + +返回文章当前自动保存版本的属性值,如果文章没有自动保存则返回 null。 + +_参数_ + +- _state_ `Object`: 全局应用状态 +- _attributeName_ `string`: 自动保存属性名称 + +_返回值_ + +- `*`: 自动保存属性值 + +### getBlock + +_相关项_ + +- 核心区块编辑器存储中的 getBlock + +### getBlockAttributes + +_相关项_ + +- 核心区块编辑器存储中的 getBlockAttributes + +### getBlockCount + +_相关项_ + +- 核心区块编辑器存储中的 getBlockCount + +### getBlockHierarchyRootClientId + +_相关项_ + +- 核心区块编辑器存储中的 getBlockHierarchyRootClientId + +### getBlockIndex + +_相关项_ + +- 核心区块编辑器存储中的 getBlockIndex + +### getBlockInsertionPoint + +_相关项_ + +- 核心区块编辑器存储中的 getBlockInsertionPoint + +### getBlockListSettings + +_相关项_ + +- 核心区块编辑器存储中的 getBlockListSettings + +### getBlockMode + +_相关项_ + +- 核心区块编辑器存储中的 getBlockMode + +### getBlockName + +_相关项_ + +- 核心区块编辑器存储中的 getBlockName + +### getBlockOrder + +_相关项_ + +- 核心区块编辑器存储中的 getBlockOrder + +### getBlockRootClientId + +_相关项_ + +- 核心区块编辑器存储中的 getBlockRootClientId + +### getBlocks + +_相关项_ + +- 核心区块编辑器存储中的 getBlocks + +### getBlocksByClientId + +_相关项_ + +- 核心区块编辑器存储中的 getBlocksByClientId + +### getBlockSelectionEnd + +_相关项_ + +- 核心区块编辑器存储中的 getBlockSelectionEnd + +### getBlockSelectionStart + +_相关项_ + +- 核心区块编辑器存储中的 getBlockSelectionStart + +### getClientIdsOfDescendants + +_相关项_ + +- 核心区块编辑器存储中的 getClientIdsOfDescendants + +### getClientIdsWithDescendants + +_相关项_ + +- 核心区块编辑器存储中的 getClientIdsWithDescendants + +### getCurrentPost + +返回当前正在编辑的文章的最后已知保存状态,不包括未保存的编辑。如果文章尚未保存,则返回包含相关默认文章值的对象。 + +_参数_ + +- _state_ `Object`: 全局应用状态 + +_返回值_ + +- `Object`: 文章对象 + +### getCurrentPostAttribute + +返回已保存文章的属性值。 + +_参数_ + +- _state_ `Object`: 全局应用状态 +- _attributeName_ `string`: 文章属性名称 + +_返回值_ + +- `*`: 文章属性值 + +### getCurrentPostId + +返回当前正在编辑的文章 ID,如果文章尚未保存则返回 null。 + +_参数_ + +- _state_ `Object`: 全局应用状态 + +_返回值_ + +- `?(number|string)`: 当前文章 ID(数字)或模板别名(字符串) + +### isEditedPostDirty + +判断当前编辑会话是否存在未保存的数值。若编辑状态与已保存或新建文章状态一致则返回 false。 + +**参数** + +- _state_ `Object`: 全局应用状态。 + +**返回值** + +- `boolean`: 是否存在未保存的数值。 + +### isEditedPostEmpty + +判断被编辑文章是否包含内容。当文章至少包含一个可保存区块,或具有非空内容属性时视为有内容。 + +**参数** + +- _state_ `Object`: 全局应用状态。 + +**返回值** + +- `boolean`: 文章是否包含内容。 + +### isEditedPostNew + +判断当前编辑文章是否尚未保存。若文章已保存则返回 false。 + +**参数** + +- _state_ `Object`: 全局应用状态。 + +**返回值** + +- `boolean`: 文章是否为新建状态。 + +### isEditedPostPublishable + +判断正在编辑的文章是否可发布。 + +**参数** + +- _state_ `Object`: 全局应用状态。 + +**返回值** + +- `boolean`: 文章是否可发布。 + +### isEditedPostSaveable + +判断文章是否可保存。文章必须包含标题、摘要或非空内容才符合保存条件。 + +**参数** + +- _state_ `Object`: 全局应用状态。 + +**返回值** + +- `boolean`: 文章是否可保存。 + +### isEditorPanelEnabled + +判断指定面板是否启用。面板默认处于启用状态。 + +**参数** + +- _state_ `Object`: 全局应用状态。 +- _panelName_ `string`: 面板标识字符串。 + +**返回值** + +- `boolean`: 面板是否启用。 + +### isEditorPanelOpened + +判断指定面板是否处于展开状态。面板默认处于关闭状态。 + +**参数** + +- _state_ `Object`: 全局应用状态。 +- _panelName_ `string`: 面板标识字符串。 + +**返回值** + +- `boolean`: 面板是否展开。 + +### isEditorPanelRemoved + +判断指定面板是否被程序化移除。所有面板默认未被移除。 + +**参数** + +- _state_ `Object`: 全局应用状态。 +- _panelName_ `string`: 面板标识字符串。 + +**返回值** + +- `boolean`: 面板是否被移除。 + +### isFirstMultiSelectedBlock + +**关联项** + +- core/block-editor 存储库中的 isFirstMultiSelectedBlock。 + +### isInserterOpened + +判断插入器是否处于开启状态。 + +**参数** + +- _state_ `Object`: 全局应用状态。 + +**返回值** + +- `boolean`: 插入器是否开启。 + +### isListViewOpened + +判断列表视图是否处于开启状态。 + +**参数** + +- _state_ `Object`: 全局应用状态。 + +**返回值** + +- `boolean`: 列表视图是否开启。 + +### isMultiSelecting + +**关联项** + +- core/block-editor 存储库中的 isMultiSelecting。 + +### isPermalinkEditable + +判断固定链接是否可编辑。 + +**参数** + +- _state_ `Object`: 编辑器状态。 + +**返回值** + +- `boolean`: 固定链接是否可编辑。 + +### isPostAutosavingLocked + +判断文章自动保存功能是否被锁定。 + +**使用示例** + +```jsx +import { __ } from '@wordpress/i18n'; +import { store as editorStore } from '@wordpress/editor'; +import { useSelect } from '@wordpress/data'; + +const ExampleComponent = () => { + const isAutoSavingLocked = useSelect( + ( select ) => select( editorStore ).isPostAutosavingLocked(), + [] + ); + + return isAutoSavingLocked ? ( +

    { __( '文章自动保存功能已被锁定' ) }

    + ) : ( +

    { __( '文章自动保存功能未锁定' ) }

    + ); +}; +``` + +**参数** + +- _state_ `Object`: 全局应用状态。 + +**返回值** + +- `boolean`: 是否被锁定。 + +### isPostLocked + +判断文章是否被锁定。 + +**参数** + +- _state_ `Object`: 全局应用状态。 + +**返回值** + +- `boolean`: 是否被锁定。 + +### isPostLockTakeover + +判断文章编辑权限是否被接管。 + +**参数** + +- _state_ `Object`: 全局应用状态。 + +**返回值** + +- `boolean`: 编辑权限是否被接管。 + +### setIsInserterOpened + +返回用于打开/关闭插入器的操作对象。 + +**参数** + +- _value_ `boolean|Object`: 控制插入器应开启(true)还是关闭(false)。如需指定插入位置,需传入对象参数。 +- _value.rootClientId_ `string`: 插入位置的根客户端ID。 +- _value.insertionIndex_ `number`: 插入位置的索引编号。 +- _value.filterValue_ `string`: 用于筛选插入器结果的查询条件。 +- _value.onSelect_ `Function`: 选中条目时的回调函数。 +- _value.tab_ `string`: 插入器默认激活的标签页。 +- _value.category_ `string`: 插入器初始化的分类目录。 + +**返回值** + +- `Object`: 操作对象。 + +### setIsListViewOpened + +返回用于打开/关闭列表视图的操作对象。 + +**参数** + +- _isOpen_ `boolean`: 控制列表视图开启或关闭的布尔值。 + +**返回值** + +- `Object`: 操作对象。 + +### setRenderingMode + +返回用于设置文章编辑器渲染模式的操作。我们支持以下渲染模式: + +- `post-only`:此模式从模板中提取文章区块并仅渲染这些区块,使用户能够在无模板包裹的情况下独立编辑文章/页面。 +- `template-locked`:此模式同时渲染模板区块和文章区块,其中模板区块被锁定不可编辑,而文章区块保持可编辑状态。 + +**参数** + +- _mode_ `string`: 渲染模式(可选 'post-only' 或 'template-locked')。 + +### setTemplateValidity + +**关联项** + +- core/block-editor 存储库中的 setTemplateValidity 方法。 + +### setupEditor + +返回用于标识编辑器已通过指定文章对象和编辑器设置完成初始化的操作生成器。 + +**参数** + +- _post_ `Object`: 文章对象。 +- _edits_ `Object`: 初始编辑属性对象。 +- _template_ `[Array]`: 区块模板。 + +### setupEditorState + +> **已弃用** + +初始化编辑器状态。 + +**参数** + +- _post_ `Object`: 文章对象。 + +### showInsertionPoint + +**关联项** + +- core/block-editor 存储库中的 showInsertionPoint 方法。 + +### startMultiSelect + +**关联项** + +- core/block-editor 存储库中的 startMultiSelect 方法。 + +### startTyping + +**关联项** + +- core/block-editor 存储库中的 startTyping 方法。 + +### stopMultiSelect + +**关联项** + +- core/block-editor 存储库中的 stopMultiSelect 方法。 + +### stopTyping + +**关联项** + +- core/block-editor 存储库中的 stopTyping 方法。 + +### switchEditorMode + +触发用于切换编辑器模式的操作。 + +**参数** + +- _mode_ `string`: 编辑器模式。 + +### synchronizeTemplate + +**关联项** + +- core/block-editor 存储库中的 synchronizeTemplate 方法。 + +### toggleBlockMode + +**关联项** + +- core/block-editor 存储库中的 toggleBlockMode 方法。 + +### toggleDistractionFree + +切换无干扰模式的操作。该模式下由于z-index值设置,侧边栏将无法关闭,因此要求不存在任何侧边栏。 + +**参数** + +- _options_ `[Object]`: 可选配置对象 +- _options.createNotice_ `[boolean]`: 是否创建通知 + +### toggleEditorPanelEnabled + +返回用于启用或禁用编辑器面板的操作对象。 + +**参数** + +- _panelName_ `string`: 标识目标面板的唯一字符串。 + +**返回值** + +- `Object`: 操作对象。 + +### toggleEditorPanelOpened + +开启已关闭面板,关闭已开启面板。 + +**参数** + +- _panelName_ `string`: 标识目标面板的唯一字符串。 + +### togglePublishSidebar + +返回用于标识用户切换发布侧边栏状态的操作对象。 + +**返回值** + +- `Object`: 操作对象 + +### toggleSelection + +**关联项** + +- core/block-editor 存储库中的 toggleSelection 方法。 + +### toggleSpotlightMode + +切换聚光灯模式视图选项的操作。 + +### toggleTopToolbar + +切换顶部工具栏视图选项的操作。 + +### trashPost + +将编辑器中当前文章移至垃圾箱的操作。 + +### undo + +从撤销历史记录中弹出最近记录并执行撤销操作。 + +### getCurrentPostLastRevisionId + +返回当前编辑文章的最近修订版本ID,若文章无修订版本则返回 null。 + +**参数** + +- _state_ `Object`: 全局应用状态。 + +**返回值** + +- `?number`: 最近修订版本ID。 + +### getCurrentPostRevisionsCount + +返回当前编辑文章的修订版本数量。 + +**参数** + +- _state_ `Object`: 全局应用状态。 + +**返回值** + +- `number`: 修订版本数量。 + +### getCurrentPostType + +返回当前编辑文章的文章类型。 + +**使用示例** + +```js +const currentPostType = wp.data.select( 'core/editor' ).getCurrentPostType(); +``` + +**参数** + +- _state_ `Object`: 全局应用状态。 + +**返回值** + +- `string`: 文章类型。 + +### getCurrentTemplateId + +返回当前正在渲染/编辑的模板ID。 + +**参数** + +- _state_ `Object`: 全局应用状态。 + +**返回值** + +- `?string`: 模板ID。 + +### getDeviceType + +返回当前编辑画布的设备类型。 + +**参数** + +- _state_ `Object`: 全局应用状态。 + +**返回值** + +- `string`: 设备类型。 + +### getEditedPostAttribute + +返回正在编辑文章的单个属性,优先返回未保存的编辑值(若存在),否则回退到最后已知的已保存状态属性值。 + +**使用示例** + +```js +// 基于特色媒体ID获取特定媒体尺寸 +// 注意:可将 sizes?.large 替换为任何已注册尺寸 +const getFeaturedMediaUrl = useSelect( ( select ) => { + const getFeaturedMediaId = + select( 'core/editor' ).getEditedPostAttribute( 'featured_media' ); + const media = select( 'core' ).getEntityRecord( + 'postType', + 'attachment', + getFeaturedMediaId + ); + + return ( + media?.media_details?.sizes?.large?.source_url || + media?.source_url || + '' + ); +}, [] ); +``` + +**参数** + +- _state_ `Object`: 全局应用状态。 +- _attributeName_ `string`: 文章属性名称。 + +**返回值** + +- `*`: 文章属性值。 + +### getEditedPostContent + +返回正在编辑文章的内容。 + +**参数** + +- _state_ `Object`: 全局应用状态。 + +**返回值** + +- `string`: 文章内容。 + +### getEditedPostPreviewLink + +返回文章预览链接。 + +**参数** + +- _state_ `Object`: 全局应用状态。 + +**返回值** + +- `string | undefined`: 预览链接。 + +### getEditedPostSlug + +返回正在编辑文章的别名 slug,优先返回手动编辑的值(若存在),其次是当前文章标题的格式化版本,最后是文章ID。 + +**参数** + +- _state_ `Object`: 编辑器状态。 + +**返回值** + +- `string`: 编辑器中显示的当前别名。 + +### getEditedPostVisibility + +返回正在编辑文章的当前可见性,若未保存值与已保存文章不同则优先返回未保存值。返回值为 "private"、"password" 或 "public" 之一。 + +**参数** + +- _state_ `Object`: 全局应用状态。 + +**返回值** + +- `string`: 文章可见性。 + +### getEditorBlocks + +返回当前区块列表。 + +**参数** + +- _state_ `Object`: + +**返回值** + +- `Array`: 区块列表。 + +### getEditorMode + +返回当前编辑模式。 + +**参数** + +- _state_ `Object`: 全局应用状态。 + +**返回值** + +- `string`: 编辑模式。 + +### getEditorSelection + +返回当前选区。 + +**参数** + +- _state_ `Object`: + +**返回值** + +- `WPBlockSelection`: 选区结束位置。 + +### getEditorSelectionEnd + +> 自 Gutenberg 10.0.0 版本起**弃用**。 + +返回当前选区结束位置。 + +**参数** + +- _state_ `Object`: + +**返回值** + +- `WPBlockSelection`: 选区结束位置。 + +### getEditorSelectionStart + +> 自 Gutenberg 10.0.0 版本起**弃用**。 + +返回当前选区起始位置。 + +**参数** + +- _state_ `Object`: + +**返回值** + +- `WPBlockSelection`: 选区起始位置。 + +### getEditorSettings + +返回文章编辑器设置。 + +**参数** + +- _state_ `Object`: 编辑器状态。 + +**返回值** + +- `Object`: 编辑器设置对象。 + +### getFirstMultiSelectedBlockClientId + +_相关链接_ + +- core/block-editor 存储中的 getFirstMultiSelectedBlockClientId。 + +### getGlobalBlockCount + +_相关链接_ + +- core/block-editor 存储中的 getGlobalBlockCount。 + +### getInserterItems + +_相关链接_ + +- core/block-editor 存储中的 getInserterItems。 + +### getLastMultiSelectedBlockClientId + +_相关链接_ + +- core/block-editor 存储中的 getLastMultiSelectedBlockClientId。 + +### getMultiSelectedBlockClientIds + +_相关链接_ + +- core/block-editor 存储中的 getMultiSelectedBlockClientIds。 + +### getMultiSelectedBlocks + +_相关链接_ + +- core/block-editor 存储中的 getMultiSelectedBlocks。 + +### getMultiSelectedBlocksEndClientId + +_相关链接_ + +- core/block-editor 存储中的 getMultiSelectedBlocksEndClientId。 + +### getMultiSelectedBlocksStartClientId + +_相关链接_ + +- core/block-editor 存储中的 getMultiSelectedBlocksStartClientId。 + +### getNextBlockClientId + +_相关链接_ + +- core/block-editor 存储中的 getNextBlockClientId。 + +### getPermalink + +返回文章的固定链接。 + +_参数_ + +- _state_ `Object`:编辑器状态。 + +_返回值_ + +- `?string`:固定链接,如果文章不可查看则返回 null。 + +### getPermalinkParts + +返回文章的固定链接,并将其拆分为三个部分:前缀、文章名称和后缀。 + +_参数_ + +- _state_ `Object`:编辑器状态。 + +_返回值_ + +- `Object`:包含固定链接的前缀、文章名称和后缀的对象,如果文章不可查看则返回 null。 + +### getPostEdits + +返回在编辑器中已更改但尚未保存的文章值。 + +_参数_ + +- _state_ `Object`:全局应用状态。 + +_返回值_ + +- `Object`:包含未保存编辑的键值对对象。 + +### getPostLockUser + +返回关于文章锁定用户的详细信息。 + +_参数_ + +- _state_ `Object`:全局应用状态。 + +_返回值_ + +- `Object`:用户对象。 + +### getPostTypeLabel + +根据当前文章返回文章类型标签。 + +_参数_ + +- _state_ `Object`:全局应用状态。 + +_返回值_ + +- `string|undefined`:文章类型标签(如果可用),否则返回 undefined。 + +### getPreviousBlockClientId + +_相关链接_ + +- core/block-editor 存储中的 getPreviousBlockClientId。 + +### getRenderingMode + +返回文章编辑器的渲染模式。 + +_参数_ + +- _state_ `Object`:编辑器状态。 + +_返回值_ + +- `string`:渲染模式。 + +### getSelectedBlock + +_相关链接_ + +- core/block-editor 存储中的 getSelectedBlock。 + +### getSelectedBlockClientId + +_相关链接_ + +- core/block-editor 存储中的 getSelectedBlockClientId。 + +### getSelectedBlockCount + +_相关链接_ + +- core/block-editor 存储中的 getSelectedBlockCount。 + +### getSelectedBlocksInitialCaretPosition + +_相关链接_ + +- core/block-editor 存储中的 getSelectedBlocksInitialCaretPosition。 + +### getStateBeforeOptimisticTransaction + +> 自 Gutenberg 9.7.0 起**已弃用**。 + +返回指定乐观事务 ID 之前的状态对象,如果找不到与给定 ID 对应的事务,则返回 `null`。 + +### getSuggestedPostFormat + +返回当前文章的推荐文章格式,仅当文章内存在单个块且该块类型已知与默认文章格式匹配时才推断。如果无法确定格式,则返回 null。 + +_返回值_ + +- `?string`:推荐的文章格式。 + +### getTemplate + +_相关链接_ + +- core/block-editor 存储中的 getTemplate。 + +### getTemplateLock + +_相关链接_ + +- core/block-editor 存储中的 getTemplateLock。 + +### hasChangedContent + +如果内容包含未保存的更改,则返回 true,否则返回 false。 + +_参数_ + +- _state_ `Object`:编辑器状态。 + +_返回值_ + +- `boolean`:内容是否包含未保存的更改。 + +### hasEditorRedo + +如果存在任何未来的编辑器历史快照,则返回 true,否则返回 false。 + +_参数_ + +- _state_ `Object`:全局应用状态。 + +_返回值_ + +- `boolean`:是否存在重做历史记录。 + +### hasEditorUndo + +如果存在任何过去的编辑器历史快照,则返回 true,否则返回 false。 + +_参数_ + +- _state_ `Object`:全局应用状态。 + +_返回值_ + +- `boolean`:是否存在撤销历史记录。 + +### hasInserterItems(是否存在插入项) + +**相关链接** +- 核心区块编辑器存储中的 hasInserterItems + +### hasMultiSelection(是否存在多选状态) + +**相关链接** +- 核心区块编辑器存储中的 hasMultiSelection + +### hasNonPostEntityChanges(是否存在非文章类实体变更) + +当存在除当前编辑文章外的其他实体未保存编辑时返回 true,否则返回 false。 + +**参数** +- _state_ `Object`: 全局应用状态 + +**返回值** +- `boolean`: 是否存在未保存编辑 + +### hasSelectedBlock(是否存在选中区块) + +**相关链接** +- 核心区块编辑器存储中的 hasSelectedBlock + +### hasSelectedInnerBlock(是否存在选中内部区块) + +**相关链接** +- 核心区块编辑器存储中的 hasSelectedInnerBlock + +### inSomeHistory(历史记录检查) + +> 自 Gutenberg 9.7.0 起**弃用** + +当存在待提交的乐观事务,且其前置状态满足给定判定函数时返回 true。 + +### isAncestorMultiSelected(是否多选父级区块) + +**相关链接** +- 核心区块编辑器存储中的 isAncestorMultiSelected + +### isAutosavingPost(是否正在自动保存文章) + +如果文章正在自动保存则返回 true,否则返回 false。 + +**参数** +- _state_ `Object`: 全局应用状态 + +**返回值** +- `boolean`: 文章是否正在自动保存 + +### isBlockInsertionPointVisible(是否显示区块插入点) + +**相关链接** +- 核心区块编辑器存储中的 isBlockInsertionPointVisible + +### isBlockMultiSelected(是否多选区块) + +**相关链接** +- 核心区块编辑器存储中的 isBlockMultiSelected + +### isBlockSelected(是否选中区块) + +**相关链接** +- 核心区块编辑器存储中的 isBlockSelected + +### isBlockValid(区块是否有效) + +**相关链接** +- 核心区块编辑器存储中的 isBlockValid + +### isBlockWithinSelection(区块是否处于选中范围内) + +**相关链接** +- 核心区块编辑器存储中的 isBlockWithinSelection + +### isCaretWithinFormattedText(光标是否在格式化文本内) + +**相关链接** +- 核心区块编辑器存储中的 isCaretWithinFormattedText + +### isCleanNewPost(是否为全新未编辑文章) + +当当前编辑会话不存在未保存值,且当前编辑文章为全新文章(从未保存过)时返回 true。 + +**参数** +- _state_ `Object`: 全局应用状态 + +**返回值** +- `boolean`: 是否为全新文章且存在未保存值 + +### isCurrentPostPending(当前文章是否待审核) + +如果文章正在等待审核则返回 true。 + +**参数** +- _state_ `Object`: 全局应用状态 + +**返回值** +- `boolean`: 当前文章是否待审核 + +### isCurrentPostPublished(当前文章是否已发布) + +如果当前文章已发布则返回 true。 + +**参数** +- _state_ `Object`: 全局应用状态 +- _currentPost_ `[Object]`: 用于绕过注册选择器的显式当前文章对象 + +**返回值** +- `boolean`: 文章是否已发布 + +### isCurrentPostScheduled(当前文章是否已排程) + +如果文章已设置发布排程则返回 true。 + +**参数** +- _state_ `Object`: 全局应用状态 + +**返回值** +- `boolean`: 当前文章是否已设置发布排程 + +### isDeletingPost(是否正在删除文章) + +如果文章当前正在被删除则返回 true,否则返回 false。 + +**参数** +- _state_ `Object`: 编辑器状态 + +**返回值** +- `boolean`: 文章是否正在被删除 + +### isEditedPostAutosaveable(编辑中的文章是否可自动保存) + +如果文章可自动保存则返回 true,否则返回 false。 + +**参数** +- _state_ `Object`: 全局应用状态 +- _autosave_ `Object`: 来自 REST API 的原始自动保存对象 + +**返回值** +- `boolean`: 文章是否可自动保存 + +### isEditedPostBeingScheduled(编辑中的文章是否正在排程) + +如果正在编辑的文章正在设置排程则返回 true,优先使用未保存的状态值。 + +**参数** +- _state_ `Object`: 全局应用状态 + +**返回值** +- `boolean`: 文章是否已发布 + +### isEditedPostDateFloating(编辑中的文章是否为浮动日期) + +返回当前文章是否应被视为具有"浮动"日期(即将会"立即"发布而非设定具体发布时间)。 + +与 PHP 后端不同,当数据库中存在 0000-00-00T00:00:00 占位符时,REST API 会返回完整的日期字符串。为推断文章是否设置为"立即"发布,我们会检查日期与修改日期是否相同。 + +**参数** +- _state_ `Object`: 编辑器状态 + +**返回值** +- `boolean`: 编辑中的文章是否具有浮动日期值 + +### isPostSavingLocked(文章保存是否锁定) + +返回文章保存功能是否被锁定。 + +_使用方法_ + +```jsx +import { __ } from '@wordpress/i18n'; +import { store as editorStore } from '@wordpress/editor'; +import { useSelect } from '@wordpress/data'; + +const ExampleComponent = () => { + const isSavingLocked = useSelect( + ( select ) => select( editorStore ).isPostSavingLocked(), + [] + ); + + return isSavingLocked ? ( +

    { __( '文章保存功能已锁定' ) }

    + ) : ( +

    { __( '文章保存功能未锁定' ) }

    + ); +}; +``` + +_参数_ + +- _state_ `Object`: 全局应用状态。 + +_返回值_ + +- `boolean`: 是否锁定。 + +### isPreviewingPost(是否正在预览文章) + +如果文章正在预览中则返回 true,否则返回 false。 + +_参数_ + +- _state_ `Object`: 全局应用状态。 + +_返回值_ + +- `boolean`: 是否正在预览文章。 + +### isPublishingPost(是否正在发布文章) + +如果文章正在发布中则返回 true,否则返回 false。 + +_参数_ + +- _state_ `Object`: 全局应用状态。 + +_返回值_ + +- `boolean`: 是否正在发布文章。 + +### isPublishSidebarEnabled(是否启用发布侧边栏) + +返回当用户点击“发布”按钮时,是否应显示或跳过预发布面板。 + +_返回值_ + +- `boolean`: 是否应显示预发布面板。 + +### isPublishSidebarOpened(发布侧边栏是否打开) + +如果发布侧边栏已打开则返回 true。 + +_参数_ + +- _state_ `Object`: 全局应用状态。 + +_返回值_ + +- `boolean`: 发布侧边栏是否打开。 + +### isSavingNonPostEntityChanges(是否正在保存非文章实体更改) + +如果当前正在保存非文章实体则返回 true,否则返回 false。 + +_参数_ + +- _state_ `Object`: 全局应用状态。 + +_返回值_ + +- `boolean`: 是否正在保存非文章实体。 + +### isSavingPost(是否正在保存文章) + +如果当前正在保存文章则返回 true,否则返回 false。 + +_参数_ + +- _state_ `Object`: 全局应用状态。 + +_返回值_ + +- `boolean`: 是否正在保存文章。 + +### isSelectionEnabled(是否启用选择功能) + +_相关_ + +- core/block-editor 存储中的 isSelectionEnabled。 + +### isTyping(是否正在输入) + +_相关_ + +- core/block-editor 存储中的 isTyping。 + +### isValidTemplate(是否为有效模板) + +_相关_ + +- core/block-editor 存储中的 isValidTemplate。 + + + +## 操作 + + + +### autosave(自动保存) + +自动保存当前文章的操作。包括服务器端自动保存(默认)和客户端(即本地)自动保存(例如在 Web 上,文章可能会提交到会话存储)。 + +_参数_ + +- _options_ `[Object]`: 用于标识自动保存的额外标志。 +- _options.local_ `[boolean]`: 是否执行本地自动保存。 + +### clearSelectedBlock(清除选中区块) + +_相关_ + +- core/block-editor 存储中的 clearSelectedBlock。 + +### closePublishSidebar(关闭发布侧边栏) + +返回用于通知用户已关闭发布侧边栏的操作对象。 + +_返回值_ + +- `Object`: 操作对象。 + +### createUndoLevel(创建撤销层级) + +> **已弃用** 自 WordPress 6.0 起 + +创建撤销历史记录的操作。 + +### disablePublishSidebar(禁用发布侧边栏) + +禁用发布侧边栏。 + +### editPost(编辑文章) + +返回用于通知文章属性已被编辑的操作对象。 + +_使用方法_ + +```js +// 更新文章标题 +wp.data.dispatch( 'core/editor' ).editPost( { title: `${ newTitle }` } ); +``` + +_参数_ + +- _edits_ `Object`: 要编辑的文章属性。 +- _options_ `[Object]`: 编辑选项。 + +_返回值_ + +- `Object`: 操作对象。 + +### enablePublishSidebar(启用发布侧边栏) + +启用发布侧边栏。 + +### enterFormattedText(进入格式化文本) + +_相关_ + +- core/block-editor 存储中的 enterFormattedText。 + +### exitFormattedText(退出格式化文本) + +_相关_ + +- core/block-editor 存储中的 exitFormattedText。 + +### hideInsertionPoint(隐藏插入点) + +_相关_ + +- core/block-editor 存储中的 hideInsertionPoint。 + +### insertBlock(插入区块) + +_相关_ + +- core/block-editor 存储中的 insertBlock。 + +### insertBlocks(插入多个区块) + +_相关_ + +- core/block-editor 存储中的 insertBlocks。 + +### insertDefaultBlock(插入默认区块) + +**相关项** + +- core/block-editor 存储中的 insertDefaultBlock。 + +### lockPostAutosaving(锁定文章自动保存) + +用于锁定文章自动保存的操作。 + +**用法** + + // 使用锁定键 `mylock` 锁定文章自动保存: + wp.data.dispatch( 'core/editor' ).lockPostAutosaving( 'mylock' ); + +**参数** + +- _lockName_ `string`:锁定名称。 + +**返回值** + +- `Object`:操作对象。 + +### lockPostSaving(锁定文章保存) + +用于锁定文章保存的操作。 + +**用法** + + const { subscribe } = wp.data; + + const initialPostStatus = wp.data.select( 'core/editor' ).getEditedPostAttribute( 'status' ); + + // 仅允许发布设置为未来日期的文章。 + if ( 'publish' !== initialPostStatus ) { + + // 跟踪锁定状态。 + let locked = false; + + // 监听发布事件。 + let unssubscribe = subscribe( () => { + const currentPostStatus = wp.data.select( 'core/editor' ).getEditedPostAttribute( 'status' ); + if ( 'publish' !== currentPostStatus ) { + + // 比较文章日期与当前日期,如果日期不在未来,则锁定文章。 + const postDate = new Date( wp.data.select( 'core/editor' ).getEditedPostAttribute( 'date' ) ); + const currentDate = new Date(); + if ( postDate.getTime() <= currentDate.getTime() ) { + if ( ! locked ) { + locked = true; + wp.data.dispatch( 'core/editor' ).lockPostSaving( 'futurelock' ); + } + } else { + if ( locked ) { + locked = false; + wp.data.dispatch( 'core/editor' ).unlockPostSaving( 'futurelock' ); + } + } + } + } ); + } + +**参数** + +- _lockName_ `string`:锁定名称。 + +**返回值** + +- `Object`:操作对象。 + +### mergeBlocks(合并区块) + +**相关项** + +- core/block-editor 存储中的 mergeBlocks。 + +### moveBlocksDown(下移区块) + +**相关项** + +- core/block-editor 存储中的 moveBlocksDown。 + +### moveBlocksUp(上移区块) + +**相关项** + +- core/block-editor 存储中的 moveBlocksUp。 + +### moveBlockToPosition(移动区块至指定位置) + +**相关项** + +- core/block-editor 存储中的 moveBlockToPosition。 + +### multiSelect(多选) + +**相关项** + +- core/block-editor 存储中的 multiSelect。 + +### openPublishSidebar(打开发布侧边栏) + +返回一个操作对象,用于表示用户打开了发布侧边栏。 + +**返回值** + +- `Object`:操作对象。 + +### receiveBlocks(接收区块) + +**相关项** + +- core/block-editor 存储中的 receiveBlocks。 + +### redo(重做) + +用于恢复撤销历史中最后弹出的状态的操作。 + +### refreshPost(刷新文章) + +> **已弃用** 自 WordPress 6.0 起。 + +用于刷新当前文章的操作。 + +### removeBlock(移除区块) + +**相关项** + +- core/block-editor 存储中的 removeBlock。 + +### removeBlocks(移除多个区块) + +**相关项** + +- core/block-editor 存储中的 removeBlocks。 + +### removeEditorPanel(移除编辑器面板) + +返回一个操作对象,用于从编辑器中移除一个面板。 + +**参数** + +- _panelName_ `string`:标识要移除的面板的字符串。 + +**返回值** + +- `Object`:操作对象。 + +### replaceBlock(替换区块) + +**相关项** + +- core/block-editor 存储中的 replaceBlock。 + +### replaceBlocks(替换多个区块) + +**相关项** + +- core/block-editor 存储中的 replaceBlocks。 + +### resetBlocks(重置区块) + +**相关项** + +- core/block-editor 存储中的 resetBlocks。 + +### resetEditorBlocks(重置编辑器区块) + +返回一个操作对象,用于表示区块已更新。 + +**参数** + +- _blocks_ `Array`:区块数组。 +- _options_ `[Object]`:可选选项。 + +### resetPost(重置文章) + +> **已弃用** 自 WordPress 6.0 起。 + +返回一个操作对象,用于表示已接收到文章的最新版本(通过初始化或保存)。 + +### savePost(保存文章) + +用于保存编辑器中当前文章的操作。 + +**参数** + +- _options_ `[Object]`:选项。 + +### selectBlock(选择区块) + +**相关项** + +- core/block-editor 存储中的 selectBlock。 + +### setDeviceType(设置设备类型) + +用于更改编辑画布宽度的操作。 + +**参数** + +- _deviceType_ `string`:设备类型。 + +**返回值** + +- `Object`:操作对象。 + +### setEditedPost(设置编辑中的文章) + +返回一个操作,用于设置当前文章类型和文章 ID。 + +**参数** + +- _postType_ `string`:文章类型。 +- _postId_ `string`:文章 ID。 + +**返回值** + +- `Object`:操作对象。 \ No newline at end of file diff --git a/reference-guides/data/data-core-keyboard-shortcuts.md b/reference-guides/data/data-core-keyboard-shortcuts.md new file mode 100644 index 0000000..b68a414 --- /dev/null +++ b/reference-guides/data/data-core-keyboard-shortcuts.md @@ -0,0 +1,440 @@ +### 注销快捷键 + +返回用于注销键盘快捷键的操作对象。 + +_用法_ + +```js +import { useEffect } from 'react'; +import { store as keyboardShortcutsStore } from '@wordpress/keyboard-shortcuts'; +import { useSelect, useDispatch } from '@wordpress/data'; +import { __ } from '@wordpress/i18n'; + +const 示例组件 = () => { + const { 注销快捷键 } = useDispatch( keyboardShortcutsStore ); + + useEffect( () => { + 注销快捷键( 'core/editor/next-region' ); + }, [] ); + + const 快捷键 = useSelect( + ( select ) => + select( keyboardShortcutsStore ).获取快捷键组合( + 'core/editor/next-region' + ), + [] + ); + + return 快捷键 ? ( +

    { __( '快捷键未注销。' ) }

    + ) : ( +

    { __( '快捷键已注销。' ) }

    + ); +}; +``` + +_参数_ + +- _名称_ `字符串`: 快捷键名称。 + +_返回值_ + +- `对象`: 操作对象。 + + + +# 键盘快捷键数据 + +命名空间:`core/keyboard-shortcuts` + +## 选择器 + + + +### getAllShortcutKeyCombinations + +返回包含指定快捷键名称别名的所有快捷键组合。 + +_使用方法_ + +```js +import { store as keyboardShortcutsStore } from '@wordpress/keyboard-shortcuts'; +import { useSelect } from '@wordpress/data'; +import { createInterpolateElement } from '@wordpress/element'; +import { sprintf } from '@wordpress/i18n'; + +const ExampleComponent = () => { + const allShortcutKeyCombinations = useSelect( + ( select ) => + select( keyboardShortcutsStore ).getAllShortcutKeyCombinations( + 'core/editor/next-region' + ), + [] + ); + + return ( + allShortcutKeyCombinations.length > 0 && ( +
      + { allShortcutKeyCombinations.map( + ( { character, modifier }, index ) => ( +
    • + { createInterpolateElement( + sprintf( + '字符:%s / 修饰键:%s', + character, + modifier + ), + { + code: , + } + ) } +
    • + ) + ) } +
    + ) + ); +}; +``` + +_参数_ + +- _state_ `Object`: 全局状态 +- _name_ `string`: 快捷键名称 + +_返回值_ + +- `WPShortcutKeyCombination[]`: 按键组合数组 + +### getAllShortcutRawKeyCombinations + +返回指定快捷键名称的所有键盘组合的原始表示形式。 + +_使用方法_ + +```js +import { store as keyboardShortcutsStore } from '@wordpress/keyboard-shortcuts'; +import { useSelect } from '@wordpress/data'; +import { createInterpolateElement } from '@wordpress/element'; +import { sprintf } from '@wordpress/i18n'; + +const ExampleComponent = () => { + const allShortcutRawKeyCombinations = useSelect( + ( select ) => + select( keyboardShortcutsStore ).getAllShortcutRawKeyCombinations( + 'core/editor/next-region' + ), + [] + ); + + return ( + allShortcutRawKeyCombinations.length > 0 && ( +
      + { allShortcutRawKeyCombinations.map( + ( shortcutRawKeyCombination, index ) => ( +
    • + { createInterpolateElement( + sprintf( + ' %s', + shortcutRawKeyCombination + ), + { + code: , + } + ) } +
    • + ) + ) } +
    + ) + ); +}; +``` + +_参数_ + +- _state_ `Object`: 全局状态 +- _name_ `string`: 快捷键名称 + +_返回值_ + +- `string[]`: 快捷键数组 + +### getCategoryShortcuts + +返回指定分类名称的快捷键名称列表。 + +_使用方法_ + +```js +import { store as keyboardShortcutsStore } from '@wordpress/keyboard-shortcuts'; +import { useSelect } from '@wordpress/data'; + +const ExampleComponent = () => { + const categoryShortcuts = useSelect( + ( select ) => + select( keyboardShortcutsStore ).getCategoryShortcuts( 'block' ), + [] + ); + + return ( + categoryShortcuts.length > 0 && ( +
      + { categoryShortcuts.map( ( categoryShortcut ) => ( +
    • { categoryShortcut }
    • + ) ) } +
    + ) + ); +}; +``` + +_参数_ + +- _state_ `Object`: 全局状态 +- _name_ `string`: 分类名称 + +_返回值_ + +- `string[]`: 快捷键名称数组 + +### getShortcutAliases + +返回指定快捷键名称的别名。 + +_使用方法_ + +```js +import { store as keyboardShortcutsStore } from '@wordpress/keyboard-shortcuts'; +import { useSelect } from '@wordpress/data'; +import { createInterpolateElement } from '@wordpress/element'; +import { sprintf } from '@wordpress/i18n'; +const ExampleComponent = () => { + const shortcutAliases = useSelect( + ( select ) => + select( keyboardShortcutsStore ).getShortcutAliases( + 'core/editor/next-region' + ), + [] + ); + + return ( + shortcutAliases.length > 0 && ( +
      + { shortcutAliases.map( ( { character, modifier }, index ) => ( +
    • + { createInterpolateElement( + sprintf( + '字符:%s / 修饰键:%s', + character, + modifier + ), + { + code: , + } + ) } +
    • + ) ) } +
    + ) + ); +}; +``` + +_参数_ + +- _state_ `Object`: 全局状态 +- _name_ `string`: 快捷键名称 + +_返回值_ + +- `WPShortcutKeyCombination[]`: 按键组合数组 + +### getShortcutDescription + +返回指定名称的快捷键描述。 + +_使用方法_ + +```js +import { store as keyboardShortcutsStore } from '@wordpress/keyboard-shortcuts'; +import { useSelect } from '@wordpress/data'; +import { __ } from '@wordpress/i18n'; +const ExampleComponent = () => { + const shortcutDescription = useSelect( + ( select ) => + select( keyboardShortcutsStore ).getShortcutDescription( + 'core/editor/next-region' + ), + [] + ); + + return shortcutDescription ? ( +
    { shortcutDescription }
    + ) : ( +
    { __( '无描述信息。' ) }
    + ); +}; +``` + +_参数_ + +- _state_ `Object`: 全局状态。 +- _name_ `string`: 快捷键名称。 + +_返回值_ + +- `?string`: 快捷键描述。 + +### getShortcutKeyCombination + +返回指定快捷键名称的主键组合。 + +_使用方法_ + +```js +import { store as keyboardShortcutsStore } from '@wordpress/keyboard-shortcuts'; +import { useSelect } from '@wordpress/data'; +import { createInterpolateElement } from '@wordpress/element'; +import { sprintf } from '@wordpress/i18n'; +const ExampleComponent = () => { + const { character, modifier } = useSelect( + ( select ) => + select( keyboardShortcutsStore ).getShortcutKeyCombination( + 'core/editor/next-region' + ), + [] + ); + + return ( +
    + { createInterpolateElement( + sprintf( + '字符:%s / 修饰键:%s', + character, + modifier + ), + { + code: , + } + ) } +
    + ); +}; +``` + +_参数_ + +- _state_ `Object`: 全局状态。 +- _name_ `string`: 快捷键名称。 + +_返回值_ + +- `WPShortcutKeyCombination?`: 键位组合。 + +### getShortcutRepresentation + +返回表示指定快捷键名称主键组合的字符串。 + +_使用方法_ + +```js +import { store as keyboardShortcutsStore } from '@wordpress/keyboard-shortcuts'; +import { useSelect } from '@wordpress/data'; +import { sprintf } from '@wordpress/i18n'; + +const ExampleComponent = () => { + const { display, raw, ariaLabel } = useSelect( ( select ) => { + return { + display: select( keyboardShortcutsStore ).getShortcutRepresentation( + 'core/editor/next-region' + ), + raw: select( keyboardShortcutsStore ).getShortcutRepresentation( + 'core/editor/next-region', + 'raw' + ), + ariaLabel: select( + keyboardShortcutsStore + ).getShortcutRepresentation( + 'core/editor/next-region', + 'ariaLabel' + ), + }; + }, [] ); + + return ( +
      +
    • { sprintf( '显示字符串:%s', display ) }
    • +
    • { sprintf( '原始字符串:%s', raw ) }
    • +
    • { sprintf( '无障碍标签字符串:%s', ariaLabel ) }
    • +
    + ); +}; +``` + +_参数_ + +- _state_ `Object`: 全局状态。 +- _name_ `string`: 快捷键名称。 +- _representation_ `keyof FORMATTING_METHODS`: 表示类型(display, raw, ariaLabel)。 + +_返回值_ + +- `?string`: 快捷键表示形式。 + + + +## 操作 + + + +### registerShortcut + +返回用于注册新键盘快捷键的操作对象。 + +_使用方法_ + +```js +import { useEffect } from 'react'; +import { store as keyboardShortcutsStore } from '@wordpress/keyboard-shortcuts'; +import { useSelect, useDispatch } from '@wordpress/data'; +import { __ } from '@wordpress/i18n'; + +const ExampleComponent = () => { + const { registerShortcut } = useDispatch( keyboardShortcutsStore ); + + useEffect( () => { + registerShortcut( { + name: 'custom/my-custom-shortcut', + category: 'my-category', + description: __( '我的自定义快捷键' ), + keyCombination: { + modifier: 'primary', + character: 'j', + }, + } ); + }, [] ); + + const shortcut = useSelect( + ( select ) => + select( keyboardShortcutsStore ).getShortcutKeyCombination( + 'custom/my-custom-shortcut' + ), + [] + ); + + return shortcut ? ( +

    { __( '快捷键已注册。' ) }

    + ) : ( +

    { __( '快捷键未注册。' ) }

    + ); +}; +``` + +_参数_ + +- _config_ `WPShortcutConfig`: 快捷键配置。 + +_返回值_ + +- `Object`: 操作对象。 \ No newline at end of file diff --git a/reference-guides/data/data-core-notices.md b/reference-guides/data/data-core-notices.md new file mode 100644 index 0000000..c93869a --- /dev/null +++ b/reference-guides/data/data-core-notices.md @@ -0,0 +1,406 @@ +### removeNotices + +返回一个用于指示需要移除多个通知的动作对象。 + +**用法** + +```js +import { __ } from '@wordpress/i18n'; +import { useDispatch, useSelect } from '@wordpress/data'; +import { store as noticesStore } from '@wordpress/notices'; +import { Button } from '@wordpress/components'; + +const ExampleComponent = () => { + const notices = useSelect( ( select ) => + select( noticesStore ).getNotices() + ); + const { removeNotices } = useDispatch( noticesStore ); + return ( + <> +
      + { notices.map( ( notice ) => ( +
    • { notice.content }
    • + ) ) } +
    + + + ); +}; +``` + +**参数** + +- _ids_ `string[]`: 唯一通知标识符列表。 +- _context_ `[string]`: 可选参数,指定通知显示的情境(分组)。默认为默认情境。 + +**返回值** + +- `Object`: 动作对象。 + + + +### createSuccessNotice + +返回用于指示创建成功通知的操作对象。有关选项说明,请参阅 `createNotice`。 + +**相关** + +- createNotice + +**用法** + +```js +import { __ } from '@wordpress/i18n'; +import { useDispatch } from '@wordpress/data'; +import { store as noticesStore } from '@wordpress/notices'; +import { Button } from '@wordpress/components'; + +const ExampleComponent = () => { + const { createSuccessNotice } = useDispatch( noticesStore ); + return ( + + ); +}; +``` + +**参数** + +- _content_ `string`: 通知消息。 +- _options_ `[Object]`: 可选的通知选项。 + +**返回值** + +- `Object`: 操作对象。 + +### createWarningNotice + +返回用于指示创建警告通知的操作对象。有关选项说明,请参阅 `createNotice`。 + +**相关** + +- createNotice + +**用法** + +```js +import { __ } from '@wordpress/i18n'; +import { useDispatch } from '@wordpress/data'; +import { store as noticesStore } from '@wordpress/notices'; +import { Button } from '@wordpress/components'; + +const ExampleComponent = () => { + const { createWarningNotice, createInfoNotice } = + useDispatch( noticesStore ); + return ( + + ); +}; +``` + +**参数** + +- _content_ `string`: 通知消息。 +- _options_ `[Object]`: 可选的通知选项。 + +**返回值** + +- `Object`: 操作对象。 + +### removeAllNotices + +从给定上下文中移除所有通知。默认为默认上下文。 + +**用法** + +```js +import { __ } from '@wordpress/i18n'; +import { useDispatch, useSelect } from '@wordpress/data'; +import { store as noticesStore } from '@wordpress/notices'; +import { Button } from '@wordpress/components'; + +export const ExampleComponent = () => { + const notices = useSelect( ( select ) => + select( noticesStore ).getNotices() + ); + const { removeAllNotices } = useDispatch( noticesStore ); + return ( + <> +
      + { notices.map( ( notice ) => ( +
    • { notice.content }
    • + ) ) } +
    + + + + ); +}; +``` + +**参数** + +- _noticeType_ `string`: 要移除所有通知的上下文。 +- _context_ `string`: 要移除所有通知的上下文。 + +**返回值** + +- `Object`: 操作对象。 + +### removeNotice + +返回用于指示移除通知的操作对象。 + +**用法** + +```js +import { __ } from '@wordpress/i18n'; +import { useDispatch } from '@wordpress/data'; +import { store as noticesStore } from '@wordpress/notices'; +import { Button } from '@wordpress/components'; + +const ExampleComponent = () => { + const notices = useSelect( ( select ) => + select( noticesStore ).getNotices() + ); + const { createWarningNotice, removeNotice } = useDispatch( noticesStore ); + + return ( + <> + + { notices.length > 0 && ( + + ) } + + ); +}; +``` + +**参数** + +- _id_ `string`: 通知的唯一标识符。 +- _context_ `[string]`: 通知出现的可选上下文(分组)。默认为默认上下文。 + +**返回值** + +- `Object`: 操作对象。 + +# 通知数据 + +命名空间:`core/notices` + +## 选择器 + + + +### getNotices + +以数组形式返回所有通知,可选择指定上下文。默认为全局上下文。 + +_用法_ + +```js +import { useSelect } from '@wordpress/data'; +import { store as noticesStore } from '@wordpress/notices'; + +const ExampleComponent = () => { + const notices = useSelect( ( select ) => + select( noticesStore ).getNotices() + ); + return ( +
      + { notices.map( ( notice ) => ( +
    • { notice.content }
    • + ) ) } +
    + ); +}; +``` + +_参数_ + +- _state_ `Object`: 通知状态 +- _context_ `?string`: 可选的分组上下文 + +_返回值_ + +- `WPNotice[]`: 通知数组 + + + +## 操作 + + + +### createErrorNotice + +返回用于指示创建错误通知的操作对象。选项文档请参考 `createNotice`。 + +_相关_ + +- createNotice + +_用法_ + +```js +import { __ } from '@wordpress/i18n'; +import { useDispatch } from '@wordpress/data'; +import { store as noticesStore } from '@wordpress/notices'; +import { Button } from '@wordpress/components'; + +const ExampleComponent = () => { + const { createErrorNotice } = useDispatch( noticesStore ); + return ( + + ); +}; +``` + +_参数_ + +- _content_ `string`: 通知消息 +- _options_ `[Object]`: 可选的通知选项 + +_返回值_ + +- `Object`: 操作对象 + +### createInfoNotice + +返回用于指示创建信息通知的操作对象。选项文档请参考 `createNotice`。 + +_相关_ + +- createNotice + +_用法_ + +```js +import { __ } from '@wordpress/i18n'; +import { useDispatch } from '@wordpress/data'; +import { store as noticesStore } from '@wordpress/notices'; +import { Button } from '@wordpress/components'; + +const ExampleComponent = () => { + const { createInfoNotice } = useDispatch( noticesStore ); + return ( + + ); +}; +``` + +_参数_ + +- _content_ `string`: 通知消息 +- _options_ `[Object]`: 可选的通知选项 + +_返回值_ + +- `Object`: 操作对象 + +### createNotice + +返回用于指示创建通知的操作对象。 + +_用法_ + +```js +import { __ } from '@wordpress/i18n'; +import { useDispatch } from '@wordpress/data'; +import { store as noticesStore } from '@wordpress/notices'; +import { Button } from '@wordpress/components'; + +const ExampleComponent = () => { + const { createNotice } = useDispatch( noticesStore ); + return ( + + ); +}; +``` + +_参数_ + +- _status_ `string|undefined`: 通知状态(如果传入undefined则为"info") +- _content_ `string`: 通知消息 +- _options_ `[Object]`: 通知选项 +- _options.context_ `[string]`: 通知分组上下文 +- _options.id_ `[string]`: 通知标识符。未指定时自动分配 +- _options.isDismissible_ `[boolean]`: 用户是否可关闭通知 +- _options.type_ `[string]`: 通知类型,可选 `default` 或 `snackbar` +- _options.speak_ `[boolean]`: 是否向屏幕阅读器播报通知内容 +- _options.actions_ `[Array]`: 与通知一起显示的用户操作 +- _options.icon_ `[string]`: 通知显示的图标。仅当类型设置为 `snackbar` 时使用 +- _options.explicitDismiss_ `[boolean]`: 通知是否包含显式关闭按钮且不能通过点击通知主体关闭。仅当类型设置为 `snackbar` 时适用 +- _options.onDismiss_ `[Function]`: 通知关闭时调用的函数 + +_返回值_ + +- `Object`: 操作对象 + + \ No newline at end of file diff --git a/reference-guides/data/data-core-nux.md b/reference-guides/data/data-core-nux.md new file mode 100644 index 0000000..529340b --- /dev/null +++ b/reference-guides/data/data-core-nux.md @@ -0,0 +1,93 @@ +# NUX(新用户体验)数据 + +命名空间:`core/nux` + +## 选择器 + + + +### areTipsEnabled + +返回提示功能是否全局启用。 + +_参数_ + +- _state_ `Object`: 全局应用状态。 + +_返回值_ + +- `boolean`: 提示功能是否全局启用。 + +### getAssociatedGuide + +返回描述给定提示所属引导(如果存在)的对象。 + +_参数_ + +- _state_ `Object`: 全局应用状态。 +- _tipId_ `string`: 要查询的提示。 + +_返回值_ + +- `?NUXGuideInfo`: 关联引导的信息。 + +### isTipVisible + +判断指定提示是否正在显示。如果提示被禁用、已被关闭或不是所在引导中的当前提示,则不会显示。 + +_参数_ + +- _state_ `Object`: 全局应用状态。 +- _tipId_ `string`: 要查询的提示。 + +_返回值_ + +- `boolean`: 指定提示是否正在显示。 + + + +## 操作 + + + +### disableTips + +返回一个操作对象,当被分发时,将阻止所有提示再次显示。 + +_返回值_ + +- `Object`: 操作对象。 + +### dismissTip + +返回一个操作对象,当被分发时,将关闭指定提示。被关闭的提示将不再显示。 + +_参数_ + +- _id_ `string`: 要关闭的提示。 + +_返回值_ + +- `Object`: 操作对象。 + +### enableTips + +返回一个操作对象,当被分发时,将重新启用所有提示显示。 + +_返回值_ + +- `Object`: 操作对象。 + +### triggerGuide + +返回一个操作对象,当被分发时,将呈现一个引导,逐步引导用户完成一系列提示。 + +_参数_ + +- _tipIds_ `string[]`: 引导中要显示的提示列表。 + +_返回值_ + +- `Object`: 操作对象。 + + \ No newline at end of file diff --git a/reference-guides/data/data-core-preferences.md b/reference-guides/data/data-core-preferences.md new file mode 100644 index 0000000..2a37f1d --- /dev/null +++ b/reference-guides/data/data-core-preferences.md @@ -0,0 +1,84 @@ +# 偏好设置 + +命名空间:`core/preferences` + +## 选择器 + + + +### get + +返回一个布尔值,指示特定作用域中的偏好设置是否处于启用状态。 + +_参数_ + +- _state_ `Object`: 存储状态 +- _scope_ `string`: 功能的作用域(例如:core/edit-post) +- _name_ `string`: 功能名称 + +_返回值_ + +- `*`: 该功能是否启用? + + + +## 操作 + + + +### set + +返回用于指示应将偏好设置设定为特定值的操作对象。 + +_参数_ + +- _scope_ `string`: 偏好设置作用域(例如:core/edit-post) +- _name_ `string`: 偏好设置名称 +- _value_ `*`: 要设定的值 + +_返回值_ + +- `Object`: 操作对象 + +### setDefaults + +返回用于指示应设定偏好设置默认值的操作对象。 + +_参数_ + +- _scope_ `string`: 偏好设置作用域(例如:core/edit-post) +- _defaults_ `Object`: 偏好设置名称与值的键值映射表 + +_返回值_ + +- `Object`: 操作对象 + +### setPersistenceLayer + +设置持久化层。 + +当设置持久化层后,偏好设置存储将: + +- 立即调用 `get` 方法,并将存储状态更新为返回的值 +- 当偏好设置值发生变更时,调用 `set` 方法更新所有偏好设置 + +理想情况下,应在应用程序生命周期开始时、在向偏好设置存储分发任何其他操作之前分发 `setPersistenceLayer`。 + +_参数_ + +- _persistenceLayer_ `WPPreferencesPersistenceLayer`: 持久化层 + +_返回值_ + +- `Object`: 操作对象 + +### toggle + +返回用于指示应切换偏好设置状态的操作对象。 + +_参数_ + +- _scope_ `string`: 偏好设置作用域(例如:core/edit-post) +- _name_ `string`: 偏好设置名称 + + \ No newline at end of file diff --git a/reference-guides/data/data-core-reusable-blocks.md b/reference-guides/data/data-core-reusable-blocks.md new file mode 100644 index 0000000..0e1e82b --- /dev/null +++ b/reference-guides/data/data-core-reusable-blocks.md @@ -0,0 +1,23 @@ +# 可复用区块 + +命名空间:`core/reusable-blocks`。 + +
    +本软件包仍处于实验阶段。"实验性"意味着这是早期实现版本,可能发生剧烈且不兼容的变更。 +
    + +## 选择器 + + + +暂无内容需要记录。 + + + +## 操作 + + + +暂无内容需要记录。 + + \ No newline at end of file diff --git a/reference-guides/data/data-core-rich-text.md b/reference-guides/data/data-core-rich-text.md new file mode 100644 index 0000000..bfc23f9 --- /dev/null +++ b/reference-guides/data/data-core-rich-text.md @@ -0,0 +1,162 @@ +# 富文本 + +命名空间:`core/rich-text` + +## 选择器 + + + +### getFormatType + +通过名称返回格式类型。 + +_使用方法_ + +```js +import { __, sprintf } from '@wordpress/i18n'; +import { store as richTextStore } from '@wordpress/rich-text'; +import { useSelect } from '@wordpress/data'; + +const ExampleComponent = () => { + const { getFormatType } = useSelect( + ( select ) => select( richTextStore ), + [] + ); + + const boldFormat = getFormatType( 'core/bold' ); + + return boldFormat ? ( +
      + { Object.entries( boldFormat )?.map( ( [ key, value ] ) => ( +
    • + { key } : { value } +
    • + ) ) } +
    + ) : ( + __( '未找到' ) + ; +}; +``` + +_参数_ + +- _state_ `Object`: 数据状态。 +- _name_ `string`: 格式类型名称。 + +_返回值_ + +- `?Object`: 格式类型。 + +### getFormatTypeForBareElement + +获取可以处理裸元素(不带 data-format-type 属性)的格式类型(如果有),给定该元素的标签名称。 + +_使用方法_ + +```js +import { __, sprintf } from '@wordpress/i18n'; +import { store as richTextStore } from '@wordpress/rich-text'; +import { useSelect } from '@wordpress/data'; + +const ExampleComponent = () => { + const { getFormatTypeForBareElement } = useSelect( + ( select ) => select( richTextStore ), + [] + ); + + const format = getFormatTypeForBareElement( 'strong' ); + + return format &&

    { sprintf( __( '格式名称: %s' ), format.name ) }

    ; +}; +``` + +_参数_ + +- _state_ `Object`: 数据状态。 +- _bareElementTagName_ `string`: 要查找格式类型的元素的标签名称。 + +_返回值_ + +- `?Object`: 格式类型。 + +### getFormatTypeForClassName + +获取可以处理元素的格式类型(如果有),给定其类名。 + +_使用方法_ + +```js +import { __, sprintf } from '@wordpress/i18n'; +import { store as richTextStore } from '@wordpress/rich-text'; +import { useSelect } from '@wordpress/data'; + +const ExampleComponent = () => { + const { getFormatTypeForClassName } = useSelect( + ( select ) => select( richTextStore ), + [] + ); + + const format = getFormatTypeForClassName( 'has-inline-color' ); + + return format &&

    { sprintf( __( '格式名称: %s' ), format.name ) }

    ; +}; +``` + +_参数_ + +- _state_ `Object`: 数据状态。 +- _elementClassName_ `string`: 要查找格式类型的元素的类名。 + +_返回值_ + +- `?Object`: 格式类型。 + +### getFormatTypes + +返回所有可用的格式类型。 + +_使用方法_ + +```js +import { __, sprintf } from '@wordpress/i18n'; +import { store as richTextStore } from '@wordpress/rich-text'; +import { useSelect } from '@wordpress/data'; + +const ExampleComponent = () => { + const { getFormatTypes } = useSelect( + ( select ) => select( richTextStore ), + [] + ); + + const availableFormats = getFormatTypes(); + + return availableFormats ? ( +
      + { availableFormats?.map( ( format ) => ( +
    • { format.name }
    • + ) ) } +
    + ) : ( + __( '无可用格式' ) + ); +}; +``` + +_参数_ + +- _state_ `Object`: 数据状态。 + +_返回值_ + +- `Array`: 格式类型。 + + + +## 操作 + + + +暂无内容需要记录。 + + \ No newline at end of file diff --git a/reference-guides/data/data-core-viewport.md b/reference-guides/data/data-core-viewport.md new file mode 100644 index 0000000..7d6e2d4 --- /dev/null +++ b/reference-guides/data/data-core-viewport.md @@ -0,0 +1,52 @@ +# 视口数据 + +命名空间:`core/viewport` + +## 选择器 + + + +### isViewportMatch + +若视口匹配指定查询条件则返回 true,否则返回 false。 + +_使用示例_ + +```js +import { store as viewportStore } from '@wordpress/viewport'; +import { useSelect } from '@wordpress/data'; +import { __ } from '@wordpress/i18n'; +const ExampleComponent = () => { + const isMobile = useSelect( + ( select ) => select( viewportStore ).isViewportMatch( '< small' ), + [] + ); + + return isMobile ? ( +
    { __( '移动端' ) }
    + ) : ( +
    { __( '非移动端' ) }
    + ); +}; +``` + +_参数说明_ + +- _state_ `Object`: 视口状态对象 +- _query_ `string`: 查询字符串。包含运算符和断点名称,以空格分隔。运算符默认为 >= + +_返回值_ + +- `boolean`: 视口是否匹配查询条件 + + + +## 操作 + +此包中的操作不应直接使用。 + + + +暂无需要说明的内容 + + \ No newline at end of file diff --git a/reference-guides/data/data-core.md b/reference-guides/data/data-core.md new file mode 100644 index 0000000..187986e --- /dev/null +++ b/reference-guides/data/data-core.md @@ -0,0 +1,938 @@ +### 撤销 + +触发此操作可撤销对实体记录的最后一次编辑(如果存在)。 + + + +### hasEntityRecord + +若给定参数对应的实体记录已接收则返回 true,否则返回 false。 + +注意:若本地状态中不存在该实体记录,此操作不会触发从 API 请求实体记录。 + +_参数_ + +- _state_ `State`: 状态树 +- _kind_ `string`: 实体类型 +- _name_ `string`: 实体名称 +- _key_ `EntityRecordKey`: 记录主键 +- _query_ `GetRecordsHttpQuery`: 可选查询条件 + +_返回值_ + +- `boolean`: 是否已接收到实体记录 + +### hasEntityRecords + +若给定参数对应的实体记录集已接收则返回 true,否则返回 false。 + +_参数_ + +- _state_ `State`: 状态树 +- _kind_ `string`: 实体类型 +- _name_ `string`: 实体名称 +- _query_ `GetRecordsHttpQuery`: 可选筛选查询。有效查询参数请参阅 REST API 手册中的[参考文档](https://developer.wordpress.org/rest-api/reference/),选择对应实体类型后查看“列出[实体类型]”可用参数。 + +_返回值_ + +- `boolean`: 是否已接收到实体记录集 + +### hasFetchedAutosaves + +若自动保存记录的 REST 请求已完成则返回 true。 + +_参数_ + +- _state_ `State`: 状态树 +- _postType_ `string`: 父级文章类型 +- _postId_ `EntityRecordKey`: 父级文章ID + +_返回值_ + +- `boolean`: REST 请求是否已完成 + +### hasRedo + +若实体记录编辑历史中存在当前撤销偏移量之后的下一个编辑操作则返回 true,否则返回 false。 + +_参数_ + +- _state_ `State`: 状态树 + +_返回值_ + +- `boolean`: 是否存在后续编辑操作 + +### hasUndo + +若实体记录编辑历史中存在当前撤销偏移量之前的上一编辑操作则返回 true,否则返回 false。 + +_参数_ + +- _state_ `State`: 状态树 + +_返回值_ + +- `boolean`: 是否存在先前编辑操作 + +### isAutosavingEntityRecord + +若指定实体记录正在自动保存则返回 true,否则返回 false。 + +_参数_ + +- _state_ `State`: 状态树 +- _kind_ `string`: 实体类型 +- _name_ `string`: 实体名称 +- _recordId_ `EntityRecordKey`: 记录ID + +_返回值_ + +- `boolean`: 实体记录是否正在自动保存 + +### isDeletingEntityRecord + +若指定实体记录正在删除则返回 true,否则返回 false。 + +_参数_ + +- _state_ `State`: 状态树 +- _kind_ `string`: 实体类型 +- _name_ `string`: 实体名称 +- _recordId_ `EntityRecordKey`: 记录ID + +_返回值_ + +- `boolean`: 实体记录是否正在删除 + +### isPreviewEmbedFallback + +判断返回的预览是否为 oEmbed 链接回退方案。 + +当 WordPress 检测到某个 URL 无法嵌入时,可配置为返回该 URL 的简单链接。我们需要根据 oEmbed 预览 API 的返回结果来判断 URL 是否可嵌入。 + +_参数_ + +- _state_ `State`: 数据状态 +- _url_ `string`: 嵌入的 URL + +_返回值_ + +- `boolean`: 该 URL 的预览是否为 oEmbed 链接回退 + +### isRequestingEmbedPreview + +若正在请求嵌入预览数据则返回 true,否则返回 false。 + +_参数_ + +- _state_ `State`: 数据状态 +- _url_ `string`: 预览对应的 URL + +_返回值_ + +- `boolean`: 是否正在请求嵌入预览 + +### isSavingEntityRecord + +若指定实体记录正在保存则返回 true,否则返回 false。 + +_参数_ + +- _state_ `State`: 状态树 +- _kind_ `string`: 实体类型 +- _name_ `string`: 实体名称 +- _recordId_ `EntityRecordKey`: 记录ID + +_返回值_ + +- `boolean`: 实体记录是否正在保存 + + + +## 操作 + + + +### getEntity + +> **已弃用** 自 WordPress 6.0 起。请改用 getEntityConfig + +根据实体的种类和名称返回其配置。 + +_参数_ + +- _state_ `State`:数据状态。 +- _kind_ `string`:实体种类。 +- _name_ `string`:实体名称。 + +_返回值_ + +- `any`:实体配置 + +### getEntityConfig + +根据实体的种类和名称返回其配置。 + +_参数_ + +- _state_ `State`:数据状态。 +- _kind_ `string`:实体种类。 +- _name_ `string`:实体名称。 + +_返回值_ + +- `any`:实体配置 + +### getEntityRecord + +根据键返回实体的记录对象。如果值尚未接收,则返回 `null`;如果已知该实体不存在,则返回 `undefined`;如果实体存在且已接收,则返回实体对象。 + +_参数_ + +- _state_ `State`:状态树。 +- _kind_ `string`:实体种类。 +- _name_ `string`:实体名称。 +- _key_ `EntityRecordKey`:可选的记录键。如果请求全局记录(例如站点设置),可以省略键。如果请求特定项目,则必须始终包含键。 +- _query_ `GetRecordsHttpQuery`:可选的查询。如果请求特定字段,字段必须始终包含 ID。有关有效的查询参数,请参阅 REST API 手册中的[参考](https://developer.wordpress.org/rest-api/reference/),并选择实体种类。然后查看“检索 [实体种类]”可用的参数。 + +_返回值_ + +- `EntityRecord | undefined`:记录。 + +### getEntityRecordEdits + +返回指定实体记录的编辑内容。 + +_参数_ + +- _state_ `State`:状态树。 +- _kind_ `string`:实体种类。 +- _name_ `string`:实体名称。 +- _recordId_ `EntityRecordKey`:记录 ID。 + +_返回值_ + +- `Optional< any >`:实体记录的编辑内容。 + +### getEntityRecordNonTransientEdits + +返回指定实体记录的非临时编辑内容。 + +临时编辑不会创建撤销级别,并且不会用于变更检测。它们在实体的配置中定义。 + +_参数_ + +- _state_ `State`:状态树。 +- _kind_ `string`:实体种类。 +- _name_ `string`:实体名称。 +- _recordId_ `EntityRecordKey`:记录 ID。 + +_返回值_ + +- `Optional< any >`:实体记录的非临时编辑内容。 + +### getEntityRecords + +返回实体的记录。 + +_参数_ + +- _state_ `State`:状态树。 +- _kind_ `string`:实体种类。 +- _name_ `string`:实体名称。 +- _query_ `GetRecordsHttpQuery`:可选的查询条件。如果请求特定字段,字段必须始终包含 ID。有关有效的查询参数,请参阅 REST API 手册中的[参考](https://developer.wordpress.org/rest-api/reference/),并选择实体种类。然后查看“列出 [实体种类]”可用的参数。 + +_返回值_ + +- `EntityRecord[] | null`:记录。 + +### getEntityRecordsTotalItems + +返回给定查询下实体的总可用记录数(忽略分页)。 + +_参数_ + +- _state_ `State`:状态树。 +- _kind_ `string`:实体种类。 +- _name_ `string`:实体名称。 +- _query_ `GetRecordsHttpQuery`:可选的查询条件。如果请求特定字段,字段必须始终包含 ID。有关有效的查询参数,请参阅 REST API 手册中的[参考](https://developer.wordpress.org/rest-api/reference/),并选择实体种类。然后查看“列出 [实体种类]”可用的参数。 + +_返回值_ + +- `number | null`:数字或 null。 + +### getEntityRecordsTotalPages + +返回给定查询下的可用页数。 + +_参数_ + +- _state_ `State`:状态树。 +- _kind_ `string`:实体种类。 +- _name_ `string`:实体名称。 +- _query_ `GetRecordsHttpQuery`:可选的查询条件。如果请求特定字段,字段必须始终包含 ID。有关有效的查询参数,请参阅 REST API 手册中的[参考](https://developer.wordpress.org/rest-api/reference/),并选择实体种类。然后查看“列出 [实体种类]”可用的参数。 + +_返回值_ + +- `number | null`:数字或 null。 + +# WordPress 核心数据 + +命名空间:`core` + +## 动态生成的选择器 + +这里提供了一系列用户友好的选择器,它们是对通用选择器 `getEntityRecord` 和 `getEntityRecords` 的封装,可用于获取各类实体的信息。 + +### getPostType + +返回指定文章类型的信息。 + +*使用示例* + +```javascript +import { useSelect } from '@wordpress/data'; +import { store as coreDataStore } from '@wordpress/core-data'; + +const postType = useSelect( + ( select ) => select( coreDataStore ).getPostType( 'post' ) + + // 等价于:select( coreDataStore ).getEntityRecord( 'root', 'postType', 'post' ) +); +``` + +*参数* + +- postType `string` + +*返回值* + +- `EntityRecord | undefined`: 记录对象 + +### getPostTypes + +返回文章类型的信息。 + +*使用示例* + +```javascript +import { useSelect } from '@wordpress/data'; +import { store as coreDataStore } from '@wordpress/core-data'; + +const postTypes = useSelect( ( select ) => { + return select( coreDataStore ).getPostTypes( { per_page: 4 } ); + + // 等价于: + // select( coreDataStore ).getEntityRecords( 'root', 'postType', { per_page: 4 } ); +} ); +``` + +*参数* + +- _query_ `GetRecordsHttpQuery`: 可选的查询条件。如需请求特定字段,字段必须始终包含ID。有效查询参数请参阅REST API手册中的[参考文档](https://developer.wordpress.org/rest-api/reference/),选择实体类型后查看"列出[实体类型]"可用的参数。 + +*返回值* + +- `EntityRecord[] | null`: 记录数组 + +### getTaxonomy + +返回指定分类法的信息。 + +*使用示例* + +```javascript +import { useSelect } from '@wordpress/data'; +import { store as coreDataStore } from '@wordpress/core-data'; + +const taxonomy = useSelect( ( select ) => { + return select( coreDataStore ).getTaxonomy( 'category' ); + + // 等价于: + // select( coreDataStore ).getEntityRecord( 'root', 'taxonomy', 'category' ); +} ); +``` + +*参数* + +- taxonomy `string` + +*返回值* + +- `EntityRecord | undefined`: 记录对象 + +### getTaxonomies + +返回分类法的信息。 + +*使用示例* + +```javascript +import { useSelect } from '@wordpress/data'; +import { store as coreDataStore } from '@wordpress/core-data'; + +const taxonomies = useSelect( ( select ) => { + return select( coreDataStore ).getTaxonomies( { type: 'post' } ); + + // 等价于: + // select( coreDataStore ).getEntityRecords( 'root', 'taxonomy', { type: 'post' } ); +} ); +``` + +*参数* + +- _query_ `GetRecordsHttpQuery`: 可选的查询条件。如需请求特定字段,字段必须始终包含ID。有效查询参数请参阅REST API手册中的[参考文档](https://developer.wordpress.org/rest-api/reference/),选择实体类型后查看"列出[实体类型]"可用的参数。 + +*返回值* + +- `EntityRecord[] | null`: 记录数组 + +## 其他选择器 + + + +### canUser + +返回当前用户是否可以对指定REST资源执行给定操作。 + +调用此方法可能通过 `canUser()` 解析器触发对REST API的OPTIONS请求。 + + + +*参数* + +- _state_ `State`: 数据状态 +- _action_ `string`: 要检查的操作。可选值:'create'、'read'、'update'、'delete' +- _resource_ `string | EntityResource`: 要检查的实体资源。接受实体对象 `{ kind: 'postType', name: 'attachment', id: 1 }` 或字符串形式的REST基础路径 - `media` +- _id_ `EntityRecordKey`: 要检查的REST资源ID(可选) + +*返回值* + +- `boolean | undefined`: 用户是否能执行该操作,如果OPTIONS请求仍在进行中则返回 `undefined` + +### canUserEditEntityRecord + +返回当前用户是否可以编辑指定实体。 + +调用此方法可能通过 `canUser()` 解析器触发对REST API的OPTIONS请求。 + + + +*参数* + +- _state_ `State`: 数据状态 +- _kind_ `string`: 实体类型 +- _name_ `string`: 实体名称 +- _recordId_ `EntityRecordKey`: 记录ID + +*返回值* + +- `boolean | undefined`: 用户是否可以编辑,如果OPTIONS请求仍在进行中则返回 `undefined` + +### getLastEntityDeleteError + +返回指定实体记录的最后删除错误。 + +**参数** + +- _state_ `State`:状态树。 +- _kind_ `string`:实体类型。 +- _name_ `string`:实体名称。 +- _recordId_ `EntityRecordKey`:记录ID。 + +**返回值** + +- `any`:实体记录的保存错误。 + +### getLastEntitySaveError + +返回指定实体记录的最后保存错误。 + +**参数** + +- _state_ `State`:状态树。 +- _kind_ `string`:实体类型。 +- _name_ `string`:实体名称。 +- _recordId_ `EntityRecordKey`:记录ID。 + +**返回值** + +- `any`:实体记录的保存错误。 + +### getRawEntityRecord + +通过键返回实体的记录对象,其属性映射为原始值。 + +**参数** + +- _state_ `State`:状态树。 +- _kind_ `string`:实体类型。 +- _name_ `string`:实体名称。 +- _key_ `EntityRecordKey`:记录的键。 + +**返回值** + +- `EntityRecord | undefined`:包含实体原始属性的对象。 + +### getRedoEdit + +> 自 6.3 版本起**已弃用** + +从当前撤销偏移量中返回实体记录编辑历史中的下一个编辑(如果有)。 + +**参数** + +- _state_ `State`:状态树。 + +**返回值** + +- `Optional< any >`:编辑内容。 + +### getReferenceByDistinctEdits + +当编辑值发生变化时返回一个新的引用。这在通过严格相等比较返回值来推断状态之间是否发生编辑时非常有用。 + +**用法** + + const hasEditOccurred = ( + getReferenceByDistinctEdits( beforeState ) !== + getReferenceByDistinctEdits( afterState ) + ); + +**参数** + +- _state_ 编辑器状态。 + +**返回值** + +- 一个仅在编辑发生时引用才会改变的值。 + +### getRevision + +返回父实体的单个特定修订版本。 + +**参数** + +- _state_ `State`:状态树。 +- _kind_ `string`:实体类型。 +- _name_ `string`:实体名称。 +- _recordKey_ `EntityRecordKey`:要获取修订版本的实体记录的键。 +- _revisionKey_ `EntityRecordKey`:修订版本的键。 +- _query_ `GetRecordsHttpQuery`:可选查询。如果请求特定字段,字段必须始终包含ID。有关有效查询参数,请参阅 [REST API 手册](https://developer.wordpress.org/rest-api/reference/) 中的修订模式,然后查看“检索 [实体类型]”可用的参数。 + +**返回值** + +- `RevisionRecord | Record< PropertyKey, never > | undefined`:记录。 + +### getRevisions + +返回实体的修订版本。 + +**参数** + +- _state_ `State`:状态树。 +- _kind_ `string`:实体类型。 +- _name_ `string`:实体名称。 +- _recordKey_ `EntityRecordKey`:要获取修订版本的实体记录的键。 +- _query_ `GetRecordsHttpQuery`:可选查询。如果请求特定字段,字段必须始终包含ID。有关有效查询参数,请参阅 [REST API 手册](https://developer.wordpress.org/rest-api/reference/) 中的修订模式,然后查看“检索 [实体类型]”可用的参数。 + +**返回值** + +- `RevisionRecord[] | null`:记录。 + +### getThemeSupports + +返回索引中的主题支持数据。 + +**参数** + +- _state_ `State`:数据状态。 + +**返回值** + +- `any`:索引数据。 + +### getUndoEdit + +> 自 6.3 版本起**已弃用** + +从当前撤销偏移量中返回实体记录编辑历史中的上一个编辑(如果有)。 + +**参数** + +- _state_ `State`:状态树。 + +**返回值** + +- `Optional< any >`:编辑内容。 + +### getUserPatternCategories + +检索已注册的用户模式分类。 + +**参数** + +- _state_ `State`:数据状态。 + +**返回值** + +- `Array< UserPatternCategory >`:用户模式分类数组。 + +### getUserQueryResults + +返回由查询ID返回的所有用户。 + +**参数** + +- _state_ `State`:数据状态。 +- _queryID_ `string`:查询ID。 + +**返回值** + +- `ET.User< 'edit' >[]`:用户列表。 + +### hasEditsForEntityRecord + +如果指定的实体记录有编辑,则返回 true,否则返回 false。 + +**参数** + +- _state_ `State`:状态树。 +- _kind_ `string`:实体类型。 +- _name_ `string`:实体名称。 +- _recordId_ `EntityRecordKey`:记录ID。 + +**返回值** + +- `boolean`:实体记录是否有编辑。 + +### getAuthors(获取作者列表) + +> **已弃用**(自 11.3 版本起)。调用方应改用 `select( 'core' ).getUsers({ who: 'authors' })`。 + +返回所有可用的作者。 + +**参数** + +- _state_ `State`:数据状态。 +- _query_ `GetRecordsHttpQuery`:可选的查询参数对象,随请求一同发送。有关有效的查询参数,请参阅 REST API 手册中的[用户页面](https://developer.wordpress.org/rest-api/reference/users/),并查看[列出用户](https://developer.wordpress.org/rest-api/reference/users/#list-users)和[检索用户](https://developer.wordpress.org/rest-api/reference/users/#retrieve-a-user)的参数说明。 + +**返回值** + +- `ET.User[]`:作者列表。 + +### getAutosave(获取自动保存内容) + +返回文章和作者的自动保存内容。 + +**参数** + +- _state_ `State`:状态树。 +- _postType_ `string`:父文章的类型。 +- _postId_ `EntityRecordKey`:父文章的 ID。 +- _authorId_ `EntityRecordKey`:作者的 ID。 + +**返回值** + +- `EntityRecord | undefined`:文章和作者的自动保存内容。 + +### getAutosaves(获取自动保存列表) + +返回文章的最新自动保存内容。 + +由于后端会为每篇文章的每位作者存储一个自动保存内容,因此可能返回多个自动保存记录。 + +**参数** + +- _state_ `State`:状态树。 +- _postType_ `string`:父文章的类型。 +- _postId_ `EntityRecordKey`:父文章的 ID。 + +**返回值** + +- `Array< any > | undefined`:文章的自动保存内容数组,如果没有则返回 `undefined`。 + +### getBlockPatternCategories(获取区块模式分类) + +检索已注册的区块模式分类列表。 + +**参数** + +- _state_ `State`:数据状态。 + +**返回值** + +- `Array< any >`:区块模式分类列表。 + +### getBlockPatterns(获取区块模式) + +检索已注册的区块模式列表。 + +**参数** + +- _state_ `State`:数据状态。 + +**返回值** + +- `Array< any >`:区块模式列表。 + +### getCurrentTheme(获取当前主题) + +返回当前主题。 + +**参数** + +- _state_ `State`:数据状态。 + +**返回值** + +- `any`:当前主题。 + +### getCurrentThemeGlobalStylesRevisions(获取当前主题全局样式修订版本) + +> **已弃用**(自 WordPress 6.5.0 起)。调用方应改用 `select( 'core' ).getRevisions( 'root', 'globalStyles', ${ recordKey } )`,其中 `recordKey` 是全局样式父文章的 ID。 + +返回当前全局样式主题的修订版本。 + +**参数** + +- _state_ `State`:数据状态。 + +**返回值** + +- `Array< object > | null`:当前全局样式。 + +### getCurrentUser(获取当前用户) + +返回当前用户。 + +**参数** + +- _state_ `State`:数据状态。 + +**返回值** + +- `ET.User< 'view' >`:当前用户对象。 + +### getDefaultTemplateId(获取默认模板 ID) + +返回用于渲染指定查询的默认模板。 + +**参数** + +- _state_ `State`:数据状态。 +- _query_ `TemplateQuery`:查询参数。 + +**返回值** + +- `string`:指定查询的默认模板 ID。 + +### getEditedEntityRecord(获取编辑后的实体记录) + +返回指定的实体记录,并合并其编辑内容。 + +**参数** + +- _state_ `State`:状态树。 +- _kind_ `string`:实体类型。 +- _name_ `string`:实体名称。 +- _recordId_ `EntityRecordKey`:记录 ID。 + +**返回值** + +- `ET.Updatable< EntityRecord > | false`:合并编辑内容后的实体记录。 + +### getEmbedPreview(获取嵌入预览) + +返回指定 URL 的嵌入预览内容。 + +**参数** + +- _state_ `State`:数据状态。 +- _url_ `string`:嵌入的 URL。 + +**返回值** + +- `any`:如果预览尚未获取,则返回 `undefined`;否则返回从嵌入预览 API 获取的预览内容。 + +### getEntitiesByKind(按类型获取实体) + +> **已弃用**(自 WordPress 6.0 起)。请改用 `getEntitiesConfig`。 + +返回指定类型的已加载实体。 + +**参数** + +- _state_ `State`:数据状态。 +- _kind_ `string`:实体类型。 + +**返回值** + +- `Array< any >`:与类型匹配的实体配置数组。 + +### getEntitiesConfig(获取实体配置) + +返回指定类型的已加载实体。 + +**参数** + +- _state_ `State`:数据状态。 +- _kind_ `string`:实体类型。 + +**返回值** + +- `Array< any >`:与类型匹配的实体配置数组。 + +### addEntities(添加实体) + +返回用于添加新实体的操作对象。 + +**参数** + +- _entities_ `Array`:接收到的实体。 + +**返回值** + +- `Object`:操作对象。 + +### deleteEntityRecord(删除实体记录) + +触发删除实体记录的操作。 + +**参数** + +- _kind_ `string`:被删除实体的类型。 +- _name_ `string`:被删除实体的名称。 +- _recordId_ `number|string`:被删除实体的记录 ID。 +- _query_ `?Object`:DELETE API 调用的特殊查询参数。 +- _options_ `[Object]`:删除选项。 +- _options.\_\_unstableFetch_ `[Function]`:仅限内部使用。用于替代 `apiFetch()` 调用的函数,必须返回一个 Promise。 +- _options.throwOnError_ `[boolean]`:如果为 false,此操作将抑制所有异常,默认为 false。 + +### editEntityRecord(编辑实体记录) + +返回触发对实体记录进行编辑的操作对象。 + +**参数** + +- _kind_ `string`:被编辑实体记录的类型。 +- _name_ `string`:被编辑实体记录的名称。 +- _recordId_ `number|string`:被编辑实体记录的记录 ID。 +- _edits_ `Object`:编辑内容。 +- _options_ `Object`:编辑选项。 +- _options.undoIgnore_ `[boolean]`:是否在撤销历史中忽略此编辑。 + +**返回值** + +- `Object`:操作对象。 + +### receiveDefaultTemplateId(接收默认模板 ID) + +返回用于为给定查询设置模板的操作对象。 + +**参数** + +- _query_ `Object`:查询对象。 +- _templateId_ `string`:解析后的模板 ID。 + +**返回值** + +- `Object`:操作对象。 + +### receiveEntityRecords(接收实体记录) + +返回用于表示已接收到实体记录的操作对象。 + +**参数** + +- _kind_ `string`:接收到的实体记录的类型。 +- _name_ `string`:接收到的实体记录的名称。 +- _records_ `Array|Object`:接收到的记录。 +- _query_ `?Object`:查询对象。 +- _invalidateCache_ `?boolean`:是否应使查询缓存失效。 +- _edits_ `?Object`:要重置的编辑内容。 +- _meta_ `?Object`:分页相关的元信息。 + +**返回值** + +- `Object`:操作对象。 + +### receiveNavigationFallbackId(接收导航回退 ID) + +返回表示已接收到回退导航菜单 ID 的操作对象。 + +**参数** + +- _fallbackId_ `integer`:回退导航菜单的 ID。 + +**返回值** + +- `Object`:操作对象。 + +### receiveRevisions(接收修订版本) + +触发接收修订条目的操作。 + +**参数** + +- _kind_ `string`:接收到的实体记录修订的类型。 +- _name_ `string`:接收到的实体记录修订的名称。 +- _recordKey_ `number|string`:要获取修订的实体记录的键。 +- _records_ `Array|Object`:接收到的修订内容。 +- _query_ `?Object`:查询对象。 +- _invalidateCache_ `?boolean`:是否应使查询缓存失效。 +- _meta_ `?Object`:分页相关的元信息。 + +### receiveThemeSupports(接收主题支持功能) + +> 自 WP 5.9 起**已弃用**,不再有用,请直接使用选择器。 + +返回用于表示已接收到索引的操作对象。 + +**返回值** + +- `Object`:操作对象。 + +### receiveUploadPermissions(接收上传权限) + +> 自 WP 5.9 起**已弃用**,请使用 receiveUserPermission 替代。 + +返回用于表示已接收到上传权限的操作对象。 + +**参数** + +- _hasUploadPermissions_ `boolean`:用户是否有上传文件的权限? + +**返回值** + +- `Object`:操作对象。 + +### redo(重做) + +触发重做上一次对实体记录的撤销编辑(如果有的话)。 + +### saveEditedEntityRecord(保存已编辑的实体记录) + +触发保存实体记录的编辑内容。 + +**参数** + +- _kind_ `string`:实体的类型。 +- _name_ `string`:实体的名称。 +- _recordId_ `Object`:记录的 ID。 +- _options_ `Object=`:保存选项。 + +### saveEntityRecord(保存实体记录) + +触发保存实体记录的操作。 + +**参数** + +- _kind_ `string`:接收到的实体的类型。 +- _name_ `string`:接收到的实体的名称。 +- _record_ `Object`:要保存的记录。 +- _options_ `Object`:保存选项。 +- _options.isAutosave_ `[boolean]`:是否为自动保存。 +- _options.\_\_unstableFetch_ `[Function]`:仅限内部使用。用于替代 `apiFetch()` 调用的函数,必须返回一个 Promise。 +- _options.throwOnError_ `[boolean]`:如果为 false,此操作将抑制所有异常,默认为 false。 \ No newline at end of file diff --git a/reference-guides/filters/README.md b/reference-guides/filters/README.md new file mode 100644 index 0000000..9b7f47e --- /dev/null +++ b/reference-guides/filters/README.md @@ -0,0 +1,7 @@ +# 钩子参考 + +[钩子](https://developer.wordpress.org/plugins/hooks/)是一种让代码片段相互交互/修改的机制。它们为插件和主题提供了一种与编辑器交互的方式,同时WordPress核心程序本身也广泛使用这种机制。 + +钩子主要分为两种类型:[动作钩子](https://developer.wordpress.org/plugins/hooks/actions/)和[过滤器钩子](https://developer.wordpress.org/plugins/hooks/filters/)。除了PHP的动作钩子和过滤器钩子外,WordPress还提供了在JavaScript中注册和执行钩子的机制。该功能也以[@wordpress/hooks](https://www.npmjs.com/package/@wordpress/hooks)软件包的形式发布于npm平台,可供通用场景使用。 + +您还可以了解更多关于这两种API的信息:[PHP](https://developer.wordpress.org/reference/)和[JavaScript](/packages/hooks/README.md)。 \ No newline at end of file diff --git a/reference-guides/filters/autocomplete-filters.md b/reference-guides/filters/autocomplete-filters.md new file mode 100644 index 0000000..41aeea3 --- /dev/null +++ b/reference-guides/filters/autocomplete-filters.md @@ -0,0 +1,44 @@ +# 自动补全 + +`editor.Autocomplete.completers` 过滤器用于扩展和覆盖区块所使用的自动补全程序列表。 + +`@wordpress/block-editor` 中的 `Autocomplete` 组件会应用此过滤器。`@wordpress/components` 包提供了基础的 `Autocomplete` 组件,但该组件不会应用此类过滤器。不过,区块通常应使用 `@wordpress/block-editor` 提供的组件。 + +### 示例 + +以下示例演示如何使用 `editor.Autocomplete.completers` 过滤器添加首字母缩写补全程序。你可以在 `@wordpress/components` 包中找到关于自动补全程序接口的完整文档。 + +```jsx +// 我们的补全程序 +const acronymCompleter = { + name: 'acronyms', + triggerPrefix: '::', + options: [ + { letters: 'FYI', expansion: '供您参考' }, + { letters: 'AFAIK', expansion: '据我所知' }, + { letters: 'IIRC', expansion: '如果我没记错' }, + ], + getOptionKeywords( { letters, expansion } ) { + const expansionWords = expansion.split( /\s+/ ); + return [ letters, ...expansionWords ]; + }, + getOptionLabel: acronym => acronym.letters, + getOptionCompletion: ( { letters, expansion } ) => ( + { letters } + ), +}; + +// 过滤器函数 +function appendAcronymCompleter( completers, blockName ) { + return blockName === 'my-plugin/foo' ? + [ ...completers, acronymCompleter ] : + completers; +} + +// 添加过滤器 +wp.hooks.addFilter( + 'editor.Autocomplete.completers', + 'my-plugin/autocompleters/acronym', + appendAcronymCompleter +); +``` \ No newline at end of file diff --git a/reference-guides/filters/block-filters.md b/reference-guides/filters/block-filters.md new file mode 100644 index 0000000..07ed83b --- /dev/null +++ b/reference-guides/filters/block-filters.md @@ -0,0 +1,583 @@ +## 管理区块分类 + +### `block_categories_all` + +
    + 在 WordPress 5.8 之前,这个钩子名为 block_categories(现已弃用)。如需支持旧版 WordPress,可能需要检测应使用的过滤器。您可以通过检查 5.8 版本引入的 WP_Block_Editor_Context 类是否存在,来判断 block_categories 是否可安全使用。 +
    + +可通过 `block_categories_all` 过滤器对默认区块分类列表进行筛选。您可以在服务器端通过返回分类列表的函数实现此功能,该列表将用于区块注册和在插入器中分组区块。您还可以使用第二个参数 `$editor_context` 根据其内容进行筛选。 + +```php +// my-plugin.php +function example_filter_block_categories_when_post_provided( $block_categories, $editor_context ) { + if ( ! empty( $editor_context->post ) ) { + array_push( + $block_categories, + array( + 'slug' => 'custom-category', + 'title' => __( '自定义分类', 'custom-plugin' ), + 'icon' => null, + ) + ); + } + return $block_categories; +} +add_filter( 'block_categories_all', 'example_filter_block_categories_when_post_provided', 10, 2 ); +``` + +### `wp.blocks.updateCategory` + +您还可以通过设置 `icon` 属性为区块分类显示图标。该值可以是 [WordPress Dashicon](https://developer.wordpress.org/resource/dashicons/) 的标识符。 + +您也可以设置 SVG 格式的自定义图标。为此,图标需要在前端渲染并设置,以便利用 WordPress 的 SVG 功能,确保移动端兼容性并提升图标的可访问性。 + +要为前例中的分类设置 SVG 图标,请在编辑器中添加以下 JavaScript 代码示例(调用 `wp.blocks.updateCategory`): + +```js +( function () { + var el = React.createElement; + var SVG = wp.primitives.SVG; + var circle = el( 'circle', { + cx: 10, + cy: 10, + r: 10, + fill: 'red', + stroke: 'blue', + strokeWidth: '10', + } ); + var svgIcon = el( + SVG, + { width: 20, height: 20, viewBox: '0 0 20 20' }, + circle + ); + wp.blocks.updateCategory( 'my-category', { icon: svgIcon } ); +} )(); +``` + +# 区块过滤器 + +WordPress 提供了多个 API,允许您修改现有区块的行为。 + +## 注册机制 + +WordPress 中的区块通常通过服务端和客户端使用 `block.json` 元数据完成注册。您可以使用以下过滤器在 PHP 服务端注册和 JavaScript 客户端注册期间修改或扩展区块设置。了解更多信息,请参阅[区块注册指南](https://developer.wordpress.org/block-editor/getting-started/fundamentals/registration-of-a-block/)。 + +### `block_type_metadata` + +在通过 PHP 服务端注册区块类型时,过滤从 `block.json` 文件加载的原始元数据。该过滤器允许在元数据处理前进行修改。 + +此过滤器的回调函数接收一个参数: + +- `$metadata` (`array`): 从 `block.json` 加载的用于注册区块类型的元数据。 + +以下示例将所有区块的 `apiVersion` 设置为 `2`: + +```php +function example_filter_metadata_registration( $metadata ) { + $metadata['apiVersion'] = 2; + return $metadata; +}; +add_filter( 'block_type_metadata', 'example_filter_metadata_registration' ); +``` + +下面是一个更复杂的示例,用于禁用标题区块的背景色和渐变支持。`block_type_metadata` 过滤器非常适合用于[优化编辑器体验](https://developer.wordpress.org/block-editor/how-to-guides/curating-the-editor-experience/): + +```php +function example_disable_heading_background_color_and_gradients( $metadata ) { + + // 仅对标题区块应用此过滤器 + if ( ! isset( $metadata['name'] ) || 'core/heading' !== $metadata['name'] ) { + return $metadata; + } + + // 检查是否存在 supports 键 + if ( isset( $metadata['supports'] ) && isset( $metadata['supports']['color'] ) ) { + + // 移除背景色和渐变支持 + $metadata['supports']['color']['background'] = false; + $metadata['supports']['color']['gradients'] = false; + } + + return $metadata; +} +add_filter( 'block_type_metadata', 'example_disable_heading_background_color_and_gradients' ); +``` + +### `block_type_metadata_settings` + +过滤根据已处理的区块类型元数据确定的设置。通过该过滤器可以应用默认处理流程未覆盖的区块元数据自定义修改。 + +此过滤器的回调函数接收两个参数: + +- `$settings` (`array`): 注册区块类型时确定的设置数组 +- `$metadata` (`array`): 从 `block.json` 文件加载的元数据 + +以下示例将所有区块的 `apiVersion` 数值增加 `1`: + +```php +function example_filter_metadata_registration( $settings, $metadata ) { + $settings['api_version'] = $metadata['apiVersion'] + 1; + return $settings; +}; +add_filter( 'block_type_metadata_settings', 'example_filter_metadata_registration', 10, 2 ); +``` + +### `register_block_type_args` + +在区块类型正式于服务端注册之前,过滤区块的参数数组 (`$args`)。 + +此过滤器的回调函数接收两个参数: + +- `$args` (`array`): 注册区块类型的参数数组 +- `$block_type` (`string`): 包含命名空间的区块类型名称 + +`register_block_type_args` 是可用粒度最细的 PHP 过滤器,对服务端注册的所有区块均有效。服务端定义的所有设置都会以高于客户端设置的优先级传播到客户端。 + +以下代码将禁用段落、标题、列表和列表项区块的颜色控制功能: + +```php +function example_disable_color_for_specific_blocks( $args, $block_type ) { + + // 需要修改的区块类型列表 + $block_types_to_modify = [ + 'core/paragraph', + 'core/heading', + 'core/list', + 'core/list-item' + ]; + + // 检查当前区块类型是否在列表中 + if ( in_array( $block_type, $block_types_to_modify, true ) ) { + // 禁用颜色控制 + $args['supports']['color'] = array( + 'text' => false, + 'background' => false, + 'link' => false, + ); + } + + return $args; +} +add_filter( 'register_block_type_args', 'example_disable_color_for_specific_blocks', 10, 2 ); +``` + +### `blocks.registerBlockType` + +用于在使用 JavaScript 在客户端注册区块时过滤区块设置。该过滤器接收区块设置、已注册区块的名称,以及 null 或已弃用的区块设置(当应用于已注册的弃用情况时)作为参数。此过滤器也会应用于区块的每个弃用设置。 + +以下示例确保列表区块保存时使用规范的生成类名 (`wp-block-list`): + +```js +function addListBlockClassName( settings, name ) { + if ( name !== 'core/list' ) { + return settings; + } + + return { + ...settings, + supports: { + ...settings.supports, + className: true, + }, + }; +} + +wp.hooks.addFilter( + 'blocks.registerBlockType', + 'my-plugin/class-names/list-block', + addListBlockClassName +); +``` + +## 前端 + +以下 PHP 过滤器可用于更改区块在前端的输出。 + +### `render_block` + +过滤任何区块的前端内容。此过滤器不会影响区块在编辑器中的行为。 + +该过滤器的回调函数接收三个参数: + +- `$block_content` (`string`):区块内容。 +- `$block` (`array`):完整的区块,包括名称和属性。 +- `$instance` (`WP_Block`):区块实例。 + +以下示例在前端为所有段落区块添加 `example-class` 类。此处使用 [HTML API](https://make.wordpress.org/core/2023/03/07/introducing-the-html-api-in-wordpress-6-2/) 来轻松添加类,而不依赖正则表达式。 + +```php +function example_add_custom_class_to_paragraph_block( $block_content, $block ) { + + // 检查区块是否为段落区块。 + if ( 'core/paragraph' === $block['blockName'] ) { + + // 使用 HTML API 向区块内容添加自定义类。 + $processor = new WP_HTML_Tag_Processor( $block_content ); + + if ( $processor->next_tag( 'p' ) ) { + $processor->add_class( 'example-class' ); + } + + return $processor->get_updated_html(); + } + + return $block_content; +} +add_filter( 'render_block', 'example_add_custom_class_to_paragraph_block', 10, 2 ); +``` + +### `render_block_{namespace/block}` + +过滤指定区块的前端内容。这是 `render_block` 的简化形式,适用于仅需修改特定区块类型的情况。 + +该过滤器的回调函数接收三个参数: + +- `$block_content` (`string`):区块内容。 +- `$block` (`array`):完整的区块,包括名称和属性。 +- `$instance` (`WP_Block`):区块实例。 + +以下示例在前端为所有段落区块添加 `example-class` 类。与上述 `render_block` 示例相比,此处无需在修改内容前检查区块类型。同样使用 [HTML API](https://make.wordpress.org/core/2023/03/07/introducing-the-html-api-in-wordpress-6-2/) 替代正则表达式。 + +```php +function example_add_custom_class_to_paragraph_block( $block_content, $block ) { + + // 使用 HTML API 向区块内容添加自定义类。 + $processor = new WP_HTML_Tag_Processor( $block_content ); + + if ( $processor->next_tag( 'p' ) ) { + $processor->add_class( 'example-class' ); + } + + return $processor->get_updated_html(); +} +add_filter( 'render_block_core/paragraph', 'example_add_custom_class_to_paragraph_block', 10, 2 ); +``` + +## 编辑器 + +以下 JavaScript 过滤器可用于在编辑器中更改区块的行为。 + +### `blocks.getSaveElement` + +一个应用于区块 `save` 函数结果的过滤器。此过滤器用于替换或扩展元素,例如使用 `React.cloneElement` 修改元素的属性、替换其子元素或返回一个全新的元素。 + +该过滤器的回调函数接收三个参数: + +- `element` (`Object`):待修改并返回的元素。 +- `blockType` (`Object`):区块类型定义对象。 +- `attributes` (`Object`):区块的属性。 + +以下示例将封面区块包装在一个外部容器 `div` 中。 + +```js +function wrapCoverBlockInContainer( element, blockType, attributes ) { + + // 如果元素未定义,跳过处理。 + if ( ! element ) { + return; + } + + // 仅应用于封面区块。 + if ( blockType.name !== 'core/cover' ) { + return element; + } + + // 返回包装在 div 中的元素。 + return
    { element }
    ; +} + +wp.hooks.addFilter( + 'blocks.getSaveElement', + 'my-plugin/wrap-cover-block-in-container', + wrapCoverBlockInContainer +); +``` + +### `blocks.getSaveContent.extraProps` + +这是一个应用于所有在 `save` 函数中返回 WP 元素的区块过滤器。该过滤器用于向 `save` 函数的根元素添加额外属性。例如,您可以添加 className、id 或该元素任何有效的属性。 + +此过滤器的回调函数接收三个参数: + +- `props`(`Object`):当前 `save` 元素的属性,将被修改并返回。 +- `blockType`(`Object`):区块类型定义对象。 +- `attributes`(`Object`):区块的属性。 + +以下示例为所有区块默认添加红色背景: + +```js +function addBackgroundColorStyle( props ) { + return { + ...props, + style: { backgroundColor: 'red' }, + }; +} + +wp.hooks.addFilter( + 'blocks.getSaveContent.extraProps', + 'my-plugin/add-background-color-style', + addBackgroundColorStyle +); +``` + +**注意:** 如果此过滤器在下次编辑文章时修改了现有内容,将会出现[区块验证](/docs/reference-guides/block-api/block-edit-save.md#validation)错误。编辑器会验证存储在文章中的内容是否与 `save()` 函数输出的内容匹配。 + +为避免此验证错误,请使用服务器端的 `render_block` 来修改现有文章内容,而不是使用此过滤器。请参阅 [render_block 文档](https://developer.wordpress.org/reference/hooks/render_block/)。 + +### `blocks.getBlockDefaultClassName` + +为区块生成的 HTML 类遵循 `wp-block-{name}` 命名规则。此过滤器允许提供替代的类名。 + +```js +// 我们的过滤器函数。 +function setBlockCustomClassName( className, blockName ) { + return blockName === 'core/code' ? 'my-plugin-code' : className; +} + +// 添加过滤器。 +wp.hooks.addFilter( + 'blocks.getBlockDefaultClassName', + 'my-plugin/set-block-custom-class-name', + setBlockCustomClassName +); +``` + +### `blocks.switchToBlockType.transformedBlock` + +用于过滤区块转换中的单个转换结果。由于转换是多对多而非一对一的,因此所有原始区块都会被传递。 + +### `blocks.getBlockAttributes` + +在区块属性默认解析之后、验证之前调用,允许插件及时操作属性值,以便进行验证和/或在编辑器中渲染区块的初始值。 + +此过滤器的回调函数接受 4 个参数: +- `blockAttributes`(`Object`):所有区块属性。 +- `blockType`(`Object`):区块类型。 +- `innerHTML`(`string`):原始区块内容。 +- `attributes`(`object`):已知的区块属性(来自分隔符)。 + +在以下示例中,我们使用 `blocks.getBlockAttributes` 过滤器锁定页面中所有段落区块的位置。 + +```js +// 我们的过滤器函数 +function lockParagraphs( blockAttributes, blockType, innerHTML, attributes ) { + if('core/paragraph' === blockType.name) { + blockAttributes['lock'] = {move: true} + } + return blockAttributes; +} + +// 添加过滤器 +wp.hooks.addFilter( + 'blocks.getBlockAttributes', + 'my-plugin/lock-paragraphs', + lockParagraphs +); +``` + +### `editor.BlockEdit` + +用于修改区块的 `edit` 组件。它接收原始的区块 `BlockEdit` 组件并返回一个新的包装组件。 + +以下示例为所有区块添加一个新的检查器面板。 + +```js +const { createHigherOrderComponent } = wp.compose; +const { InspectorControls } = wp.blockEditor; +const { PanelBody } = wp.components; + +const withMyPluginControls = createHigherOrderComponent( ( BlockEdit ) => { + return ( props ) => { + return ( + <> + + + 我的自定义控件 + + + ); + }; +}, 'withMyPluginControls' ); + +wp.hooks.addFilter( + 'editor.BlockEdit', + 'my-plugin/with-inspector-controls', + withMyPluginControls +); +``` + +请注意,由于此钩子对**所有区块**运行,使用它可能会导致性能下降,尤其是在区块选择指标方面。 + +为了缓解此问题,请考虑是否可以将您执行的任何工作调整为仅在特定条件下运行。 + +例如,假设您添加的组件仅在区块**被选中时**需要渲染。在这种情况下,您可以使用区块的“选中”状态(`props.isSelected`)来条件化您的渲染。 + +以下示例为所有区块添加一个新的检查器面板,但仅在区块被选中时显示。 + +```js +const withMyPluginControls = createHigherOrderComponent( ( BlockEdit ) => { + return ( props ) => { + return ( + <> + + { props.isSelected && ( + + 我的自定义控件 + + ) } + + ); + }; +}, 'withMyPluginControls' ); +``` + +### `editor.BlockListBlock` + +用于修改包含块编辑组件及所有工具栏的块包装组件。该钩子接收原始的 `BlockListBlock` 组件并返回新的包装组件。 + +以下示例为所有块添加唯一类名: + +```js +const { createHigherOrderComponent } = wp.compose; + +const withClientIdClassName = createHigherOrderComponent( + ( BlockListBlock ) => { + return ( props ) => { + return ( + + ); + }; + }, + 'withClientIdClassName' +); + +wp.hooks.addFilter( + 'editor.BlockListBlock', + 'my-plugin/with-client-id-class-name', + withClientIdClassName +); +``` + +可通过返回组件的 `wrapperProps` 属性为块包装组件添加新属性,如下例所示: + +```js +const { createHigherOrderComponent } = wp.compose; +const withMyWrapperProp = createHigherOrderComponent( ( BlockListBlock ) => { + return ( props ) => { + const wrapperProps = { + ...props.wrapperProps, + 'data-my-property': 'the-value', + }; + return ; + }; +}, 'withMyWrapperProp' ); + +wp.hooks.addFilter( + 'editor.BlockListBlock', + 'my-plugin/with-my-wrapper-prop', + withMyWrapperProp +); +``` + +### `editor.postContentBlockTypes` + +用于修改即使在锁定模板内使用也应启用的块列表。所有将数据保存到文章的块都应添加至此。文章特色图片块即为一例:该块虽常用于模板,但在模板锁定时仍应允许选择图片。 + +以下示例启用虚构块 `namespace/example`: + +```js +const addExampleBlockToPostContentBlockTypes = ( blockTypes ) => { + return [ ...blockTypes, 'namespace/example' ]; +}; + +wp.hooks.addFilter( + 'editor.postContentBlockTypes', + 'my-plugin/post-content-block-types', + addExampleBlockToPostContentBlockTypes +); +``` + +## 移除区块 + +### 使用禁用列表 + +添加区块十分简单,移除区块同样便捷。插件或主题开发者可通过 JavaScript 禁用列表来“取消注册”区块。 + +将以下代码置于 `my-plugin.js` 文件中: + +```js +// my-plugin.js +import { unregisterBlockType } from '@wordpress/blocks'; +import domReady from '@wordpress/dom-ready'; + +domReady( function () { + unregisterBlockType( 'core/verse' ); +} ); +``` + +随后通过以下函数在编辑器中加载此脚本: + +```php + + 取消注册区块时,可能存在竞态条件:即注册区块与取消注册区块的代码执行顺序冲突。为确保取消注册代码最后执行,需将注册区块的组件指定为依赖项(本例中为 wp-edit-post)。此外,使用 wp.domReady() 可确保取消注册代码在 DOM 加载完成后执行。 +

    + +### 使用允许列表 + +若需仅保留允许列表中的区块,可调整上述脚本如下: + +```js +// my-plugin.js + +var allowedBlocks = [ + 'core/paragraph', + 'core/image', + 'core/html', + 'core/freeform', +]; + +wp.blocks.getBlockTypes().forEach( function ( blockType ) { + if ( allowedBlocks.indexOf( blockType.name ) === -1 ) { + wp.blocks.unregisterBlockType( blockType.name ); + } +} ); +``` + +## 从插入器中隐藏区块 + +### `allowed_block_types_all` + +
    + 在 WordPress 5.8 之前,此钩子名为 allowed_block_types(现已弃用)。如需支持旧版 WordPress,可能需要通过检测 WP_Block_Editor_Context 类(5.8 版本引入)是否存在来判断应使用的过滤器。 +
    + +在服务器端,可通过 `allowed_block_types_all` 过滤器筛选插入器中显示的区块列表。可返回 true(支持所有区块类型)、false(不支持任何区块类型)或允许的区块类型名称数组。还可使用第二个参数 `$editor_context` 根据内容筛选区块类型。 + +```php +post ) ) { + return array( 'core/paragraph', 'core/heading' ); + } + return $allowed_block_types; +} +add_filter( 'allowed_block_types_all', 'example_filter_allowed_block_types_when_post_provided', 10, 2 ); +``` \ No newline at end of file diff --git a/reference-guides/filters/editor-filters.md b/reference-guides/filters/editor-filters.md new file mode 100644 index 0000000..7d51fa0 --- /dev/null +++ b/reference-guides/filters/editor-filters.md @@ -0,0 +1,246 @@ +### `media.crossOrigin` + +此过滤器用于为外部来源的媒体元素(即 `