## `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排列方式，则无法匹配转换器。同理，若其中出现的是 `<h3>` 而非 `<h2>`，匹配也会失败。

在处理包含非短语内容（如带 `<summary>` 的 `<details>`）的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(),
                } );
            },
        },
    ],
}
```

<h4 id="内容模型与架构">内容模型与架构</h4>

在粘贴内容时，可以定义用于验证和处理粘贴内容的[内容模型](https://html.spec.whatwg.org/multipage/dom.html#content-models)。粘贴到编辑器中的 HTML 通常包含应当转移和不应转移的混合元素。例如考虑将 `<span class="time">12:04 pm</span>` 粘贴到编辑器中的情况：我们需要复制 `12:04 pm` 并忽略 `<span>` 及其 `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
<div data-post-id="13">
	<h2>文章标题</h2>
	<p>一段<em>精彩</em>内容</p>
</div>
```

我们需要通过提供以下架构来告知编辑器允许内部的 `h2` 和 `p` 元素。此示例中使用函数形式，该函数接收包含 `phrasingContentSchema` 的参数（以及指示转换操作是否以粘贴文本开始的布尔值 `isPaste`）。`phrasingContentSchema` 预定义为匹配 HTML 短语元素，如 `<strong>`、`<sup>` 和 `<kbd>`。任何期望使用 `<RichText />` 组件的地方都适合允许短语内容，否则在转换过程中将丢失所有文本格式。

```js
schema = ({ phrasingContentSchema }) => {
    div: {
        required: true,
        attributes: [ 'data-post-id' ],
        children: {
            h2: { children: phrasingContentSchema },
            p: { children: phrasingContentSchema }
        }
    }
}
```