---
title: "策略语言"
description: "策略类型、选择器、权限和规则优先级。"
canonical_url: "https://tiptap.zhcndoc.com/composable-docs/content-protection/api-reference/policy"
---

# 策略语言

策略类型、选择器、权限和规则优先级。

策略是定义读取和编辑权限的 JSON 对象。除非匹配的规则拒绝访问，否则允许所有内容。

## `ContentProtectionPolicy`

### 属性

- `version` (`1`)：策略格式版本。仅支持 `1`。
- `rules` (`ProtectionRule[]`)：权限规则。空数组允许所有读取和编辑。

```ts
const policy: ContentProtectionPolicy = {
  version: 1,
  rules: [
    {
      selector: { target: 'node', node: { types: ['heading'] } },
      permissions: { edit: false },
    },
  ],
}
```

## `ProtectionRule`

一个选择器及其授予或拒绝的权限。

### 属性

- `id?` (`string`)：可选的诊断标识符。在策略中必须非空且唯一。匿名规则通过其数组索引标识。
- `selector` (`ProtectionSelector`)：规则适用的对象。
- `permissions` (`{ read?: boolean; edit?: boolean }`)：至少指定一项权限。`true` 表示允许，`false` 表示拒绝。省略权限的规则没有效果，会被忽略。
- `priority?` (`number`)：整数优先级。省略时为 `0`。数值越高越优先；允许使用负值。在优先级相同的情况下，无论规则顺序如何，拒绝都优先于允许。两条规则都省略 `priority` 时同样适用。
- `reason?` (`string`)：在诊断信息中返回的纯文本说明。不影响权限，且不能包含隐藏内容。

## `ProtectionSelector`

使用字符串 `target` 选择一个方面，或使用非空目标数组选择多个方面。节点目标使用 `node`，标记目标使用 `mark`。`SingleProtectionSelector` 是该类型的字符串目标形式。

### 多个目标

当多个方面共享权限时，可以组合相同匹配节点或标记的方面：

```ts
const rule = {
  selector: {
    target: ['nodeType', 'attribute'],
    node: { types: ['heading'] },
    names: ['level'],
  },
  permissions: { edit: false },
} satisfies ProtectionRule
```

这会保护标题结构和级别，同时保持文本可编辑。它的行为类似于权限和优先级相同的独立 `nodeType` 和 `attribute` 规则。

- 节点目标（`node`、`nodeType`、`nodeContent`、`children`、`attribute`）必须与 `node` 一起使用，标记目标（`mark`、`markType`、`markContent`、`markAttribute`）必须与 `mark` 一起使用。混合两类目标无效。
- 任意选定方面适用时，规则就会参与。所有方面共享其 ID、原因和优先级。匹配多个方面不会提高优先级，也不会在诊断元数据中重复该规则。重复目标不会产生额外效果。
- 只有选择 `attribute` 或 `markAttribute` 时才能使用 `names`，并且它只应用于该方面。对于节点属性，省略表示选择所有属性。标记属性必须提供 `names`。空的 `names` 数组不选择任何属性，其他目标仍然生效。
- 如果任意目标是 `children`，规则只支持 `edit`。`read` 的任意值都无效。
- 空目标数组无效。现有的字符串目标和策略版本 `1` 仍受支持。

`within` 中的祖先条件适用于每个选定方面。它们只用于定位匹配的节点或标记，不会保护祖先。请参阅[嵌套祖先匹配](#nested-ancestor-matching)。

### `target: 'node'`

- `node` (`NodeMatch`)：要匹配的节点。
- `edit: false`：保护节点的存在、类型、属性和全部内容，包括后代节点和标记。
- `read: false`：隐藏节点及其子树。

使用它可以锁定整个节点。更改其类型或匹配的属性不会解锁它。更高优先级的规则可以允许其中的特定编辑。

### `target: 'nodeType'`

- `node` (`NodeMatch`)：要匹配的节点。
- `edit: false`：阻止插入、移除、重新设置类型、拆分、合并或移动节点。
- `read: false`：隐藏节点及其子树。

仅保护编辑时，内容和属性仍可编辑。已隐藏节点完全不可编辑。之前编辑造成的位置偏移不属于移动。

### `target: 'nodeContent'`

- `node` (`NodeMatch`)：受其内容控制的节点。
- `edit: false`：阻止更改文本、后代节点、后代属性和标记。
- `read: false`：隐藏节点的子内容。

仅保护编辑时，所属节点的类型、属性和存在性不受保护。更改其类型或匹配的属性可能允许后续编辑。使用 `node` 保护整个节点，或添加 `nodeType` 和适当的 `attribute` 规则。删除包含受保护内容的所属节点仍需要删除这些内容的权限。

使用 `read: false` 时，所属节点也不可编辑：即使内容为空，也不能更改其类型、属性和存在性。这可以防止通过更改所属节点来显示隐藏内容。

### 嵌套内容规则

内容规则会递归应用，包括嵌套 Slot。当独立规则的优先级高于祖先编辑规则时，它们可以授予或拒绝子树内部的**编辑权限**。使用 `within` 或属性匹配嵌套区域。选择器深度不影响优先级，覆盖性的编辑规则也适用于其后代。

这不会覆盖读取限制：已隐藏祖先内部的内容仍然隐藏且无法编辑。请参阅[读取限制](#read-restrictions)。

```ts
const rules = [
  {
    selector: { target: 'nodeContent', node: { types: ['blockquote'] } },
    permissions: { edit: false },
  },
  {
    selector: {
      target: 'nodeContent',
      node: { types: ['paragraph'], within: { types: ['blockquote'] } },
    },
    permissions: { edit: true },
    priority: 10,
  },
] satisfies ProtectionRule[]
```

这允许编辑受保护 blockquote 内的段落文本和格式。blockquote 的其他内容仍受保护。允许规则的优先级必须高于拒绝规则的默认值 `0`。

### `target: 'children'`

- `node` (`NodeMatch`)：要匹配的父节点。
- `edit: false`：父节点存在时，保留直接非文本子节点的有序序列。
- `read`：不支持；请改用 `nodeContent`。

子节点中的文本和格式仍可编辑。使用 `nodeType` 保护父节点的存在性，而不冻结其内容。该目标支持列固定、单元格可编辑且正文行可删除的表格。

### `target: 'attribute'`

- `node` (`NodeMatch`)：要匹配的节点。
- `names?` (`string[]`)：要控制的属性。省略表示所有属性，`[]` 表示没有属性。
- `edit: false`：阻止更改选定属性。
- `read: false`：隐藏所属节点及其子树。

### `target: 'mark'`

- `mark` (`MarkMatch`)：要匹配的标记。
- `edit: false`：保护标记的存在性、类型、属性和标记内容，包括其他格式。
- `read: false`：隐藏标记内容。

使用它可以锁定带标记的段落及其元数据。

### `target: 'markType'`

- `mark` (`MarkMatch`)：要匹配的标记。
- `edit: false`：阻止添加、移除或用其他类型替换标记。
- `read: false`：隐藏标记内容。

文字、标记属性和其他格式仍可编辑。删除带有标记的最后一段内容会移除标记，并需要相应权限。使用 `markAttribute` 可以在允许更改文字的同时保护元数据。

### `target: 'markContent'`

- `mark` (`MarkMatch`)：标识受保护内容的标记。
- `edit: false`：阻止更改标记内容，包括其标记和后代内容。
- `read: false`：隐藏标记内容。

移除或更改选定标记也受此规则控制。对于编辑保护，`markContent` 和 `mark` 覆盖相同的标记范围。更高优先级的 `markType` 规则可以允许移除标记；请在单独的事务中编辑新解锁的内容。

### `target: 'markAttribute'`

- `mark` (`MarkMatch`)：要匹配的标记。
- `names` (`string[]`)：要控制的属性。必需；`[]` 表示不选择任何属性。
- `edit: false`：阻止更改选定的标记属性。
- `read: false`：隐藏标记内容。

## `NodeMatch`

匹配文档中的节点，与 DOM 结构或 CSS 无关。

### 属性

- `types?` (`string[]`)：精确且区分大小写的 schema 名称。省略表示所有节点类型，`[]` 表示没有类型。各项按 OR 组合。
- `attributes?` (`AttributePredicate[]`)：按 AND 组合的属性测试。省略或为 `[]` 表示不限制属性。
- `within?` (`NodeMatch`)：要求匹配的严格祖先。嵌套的 `within` 值表示更上层的祖先。

`{}` 匹配每个节点。使用其 schema 名称（通常为 `doc`）选择根节点；根节点上的 `nodeContent` 覆盖整个文档。

### 嵌套祖先匹配

```ts
const rule = {
  selector: {
    target: ['nodeType', 'attribute'],
    node: {
      types: ['heading'],
      within: {
        types: ['blockquote'],
        within: {
          types: ['blockSlot'],
          attributes: [{ name: 'id', operator: 'eq', value: 'legal-notice' }],
        },
      },
    },
    names: ['level'],
  },
  permissions: { edit: false },
} satisfies ProtectionRule
```

当标题位于 `legal-notice` Slot 内的 blockquote 中时，这会保护标题结构和级别。文本仍可编辑。每个 `within` 都要求在任意深度存在祖先，不一定是直接父节点，并且这些条件必须组成祖先链。

blockquote 和 Slot 是匹配条件，不是额外的保护目标。`within` 只接受匹配器属性；在其中放置 `target` 或 `permissions` 无效。请使用单独的规则保护祖先本身。

### 使用 UniqueID 定位单个节点

为相关节点类型配置 [UniqueID](https://tiptap.zhcndoc.com/editor/extensions/functionality/uniqueid.md)：

```ts
import { UniqueID } from '@tiptap/extension-unique-id'

UniqueID.configure({ types: ['paragraph'] })
```

匹配节点存储的 `id` 属性。将 `'paragraph-id'` 替换为文档中的 ID：

```ts
const rule = {
  selector: {
    target: 'nodeContent',
    node: {
      types: ['paragraph'],
      attributes: [{ name: 'id', operator: 'eq', value: 'paragraph-id' }],
    },
  },
  permissions: { edit: false },
} satisfies ProtectionRule
```

这会保护该段落的内容。改用 `target: 'node'` 可同时保护其类型、存在性和属性，包括 ID。如果 UniqueID 使用自定义 `attributeName`，请在谓词中使用该名称，而不是 `id`。

## `MarkMatch`

匹配行内节点上的标记。

### 属性

- `types?` (`string[]`)：精确且区分大小写的标记名称。省略表示所有标记类型，`[]` 表示没有类型。各项按 OR 组合。
- `attributes?` (`AttributePredicate[]`)：按 AND 组合的属性测试。省略或为 `[]` 表示不限制属性。
- `within?` (`NodeMatch`)：要求存在标记行内节点的匹配祖先。

不同标记类型会独立评估，包括它们重叠的情况。

## `AttributePredicate`

这些谓词专用于内容保护，不实现外部查询标准。`operator` 从下面的测试中选择一个。属性名称指完整属性，不支持带点的路径。

属性值必须是 JSON：字符串、有限数字、布尔值、`null`，或包含这些值的数组和普通对象。TypeScript API 将此类型称为 `JsonValue`。

### `operator: 'exists'`

- `name` (`string`)：键必须存在的属性。值为 `null` 仍算作存在。

### `operator: 'eq'`

- `name` (`string`)：要比较的属性。
- `value` (`JsonValue`)：要求的值。使用不进行强制转换的结构相等；忽略对象键顺序，保留数组顺序。

### `operator: 'in'`

- `name` (`string`)：要比较的属性。
- `values` (`JsonValue[]`)：可接受的值，使用 `eq` 比较。`[]` 不匹配任何内容。

schema 默认值会计为属性值。缺少属性与属性值为 `null` 不同。

## 规则优先级

对每个受影响的操作分别解析 `read` 和 `edit`：

1. 选择声明了该权限的匹配规则。
2. 如果没有匹配规则，则允许。
3. 将省略的优先级视为 `0`，然后只保留 `priority` 最高的规则。
4. 在该优先级下，只要任一规则拒绝就拒绝，否则允许。因此，两条未设置优先级的规则发生冲突时会拒绝。

列表顺序和选择器的具体程度不影响权限。只有所有受影响的操作都被允许时，事务才会被允许。

| 匹配规则                      | 结果        |
| ------------------------- | --------- |
| 内容在 `0` 拒绝；内容在 `10` 允许    | 允许访问允许区域。 |
| 在 `10` 同时允许和拒绝            | 拒绝。       |
| 属性在 `20` 拒绝；外层内容在 `10` 允许 | 拒绝属性更改。   |
| 读取在 `50` 允许；编辑在 `0` 拒绝    | 可读但不可编辑。  |

## 读取限制

- 读取规则会在每个选定目标上按优先级解析。读取被拒绝的目标会隐藏其整个子树。
- 子节点上的独立 `read: true` 规则不会显示该子节点，即使其优先级高于祖先的读取拒绝。
- 要显示子节点，请缩小祖先的拒绝范围，或使用更高优先级、针对祖先本身的读取允许规则。
- 已隐藏节点不可编辑，包括其类型、属性、存在性和内容。无需额外的 `edit: false` 规则。重新设置类型、删除或更改匹配属性都不能显示或移除隐藏内容。
- 已隐藏的标记内容不能更改，也不能移除其选定标记。无论优先级如何，编辑允许都不能覆盖读取拒绝。
- 拒绝某个属性会隐藏其所属节点，不会只对 NodeView 做选择性清理。

有关渲染、剪贴板和导出行为，请参阅[已隐藏内容](https://tiptap.zhcndoc.com/composable-docs/content-protection/api-reference/rendering.md)。

## 验证

将策略直接传给 `ContentProtection.configure({ policy })` 或 `setContentProtectionPolicy({ policy })`。扩展会自动验证策略，并存储不可变副本。

独立工具也直接接受策略 JSON，并根据文档或事务的 schema 验证。使用 [`validateContentProtectionPolicy`](https://tiptap.zhcndoc.com/composable-docs/content-protection/api-reference/utilities.md#validatecontentprotectionpolicy) 可以在不应用策略的情况下检查它。

无效策略包括：

- 不支持的版本、未知属性、目标或运算符。
- 空目标数组、混合节点/标记目标，或没有属性目标却使用 `names`。
- 任意目标为 `markAttribute` 时缺少 `names`。
- 空权限、重复或空 ID、非整数优先级。
- 无效 JSON 值，或 schema 中不存在的显式类型/属性。
- 在 `children` 上使用 `read`，包括与其他目标组合时的情况。

## 如何检查编辑

输入、粘贴内容、运行编辑器命令（包括 `setContent`）以及使用本地撤销/重做时，都会检查本地更改。[Tiptap 协作更新](https://tiptap.zhcndoc.com/composable-docs/content-protection/getting-started/overview.md#collaboration)（包括初始同步和协作撤销/重做）会绕过本地权限检查，以便使用不同策略的协作者保持同步。

如果事务的任意部分进行了被禁止的更改，整个事务都会被拒绝。例如，同时覆盖可编辑文本和受保护文本的粘贴操作会使文档保持不变。

对于仅编辑 Slot 的场景，请使用 [`createSlotFillingPolicy`](https://tiptap.zhcndoc.com/composable-docs/slots/api-reference/utilities.md#createslotfillingpolicy)。它会生成符合此策略语言的普通规则。
