# 配置插件

先判断装配位置，再区分创建配置、文档属性、命令和运行状态。

假设产品只允许一、二级标题。需要修改的是 Heading 的 `levels` 配置：它决定这个编辑器接受哪些标题级别。把当前段落改成二级标题，则是运行时的 `toggleHeading` 命令。这两个操作发生在不同阶段。

## 先找对装配位置

先查看参考页顶部的 Classic、Block 状态及所需预设，详细规则见[选择编辑器与接入方式](/plugins/compatibility)。适用范围与许可标签分别判断。

| 你接入的是什么 | 放在哪里 | 例子 |
| --- | --- | --- |
| 文档节点、文字格式、编辑行为、View 注册 | Core 的 `extensions` | Heading、Link、Search、canvasViewExtension |
| 一组 Core 扩展 | Core 的 `extensions` | StarterKit、TextStyleKit |
| 产品的菜单、侧栏、文件服务 | Classic 的 `plugins` | createClassicWordDocxPlugin、createClassicEditorPdfPlugin |
| 完整产品装配策略 | Classic 的 `preset` | createClassicWordPreset、createClassicWordProPreset |
| Block 默认内容与图形工作区选项 | `createBlockEditor` 的 `preset` 配置对象 | starterKit、flowchart、mindMap |
| Block 附加能力数组 | 展开到 `createBlockEditor` 的 `extensions` | createBlockTableExtensions、createBlockNumberingExtensions |

名称中带 Classic 的扩展也可能仍是 Core Extension，例如 ClassicWordTable。以每页的接入说明为准，不能只按名字猜。

## 配置一个预设里的插件

StarterKit 已包含 Heading，直接改它的子项，不要再追加一个 Heading。

```ts
import { StarterKit } from '@quirekit/starter-kit'

const extensions = [
  StarterKit.configure({ heading: { levels: [1, 2] } }),
]
```

将这个数组交给 Editor 的 `extensions`。创建后的标题解析、命令和快捷键都只接受这两个级别，宿主工具栏也应只提供相应入口。

如果需要自己提供同名实现，先把预设的对应项设为 `false`，再装替代扩展。只有字段类型允许 `false` 时才能这样关闭，不要给任意配置都传 `false`。

```ts
import { StarterKit } from '@quirekit/starter-kit'
import { Heading } from '@quirekit/extension-heading'

const extensions = [
  StarterKit.configure({ heading: false }),
  Heading.configure({ levels: [1, 2] }),
]
```

这两种方式选一种即可。上面只展示要修改的装配片段，完整容器与挂载见[快速开始](/guide/quick-start)。

## 配置和正文内容分开

| 内容 | 生命周期 | 示例 |
| --- | --- | --- |
| 插件 options | 创建或装配扩展时决定 | Heading 的 levels |
| 节点、标记属性 | 保存到规范文档，通过命令修改 | 当前标题的 level、链接的 href |
| storage | 当前编辑器的运行状态或读取接口 | characterCount.characters() |
| 宿主服务 | 由应用创建和释放 | 校对 provider、图形工作区、网络连接 |

`.configure()` 返回扩展的配置副本，不会把一个已经挂载的实例就地改造。已有文档中的二级标题也不会因为你改了配置对象自动变成一级；需要修改正文时调用命令。

```ts
editor.chain().focus().toggleHeading({ level: 2 }).run()
```

命令返回 `false` 时不应继续显示成功。例如当前 Heading 只允许一、二级，传 `level: 3` 会被拒绝。接界面时使用 `editor.can()`、`editor.isActive()` 与事务订阅，见[把命令接到产品界面](/build/commands)。

## 试一下配置后的行为

下面允许一、二级标题。先点二级标题，再尝试三级：二级操作会更新正文，三级操作返回失败并保留原文。源码只包含当前例程需要的创建、按钮和销毁逻辑。

### plugins/configuration.vue

```vue
<script setup lang="ts">
import { computed, onBeforeUnmount, onMounted, ref } from "vue";
import { Editor } from "@quirekit/core";
import { StarterKit } from "@quirekit/starter-kit";
import { DomViewExtension } from "@quirekit/dom-view";
import { QuireButton } from "@quirekit/ui/vue";
import "@quirekit/pm-view/style/prosemirror.css";

const props = defineProps<{
  /** Interface language; changing it preserves the current document. */
  locale: "zh-CN" | "en";
}>();
const element = ref<HTMLElement>();
const result = ref<boolean | null>(null);
const copy = computed(() => props.locale === "en"
  ? { level2: "Apply heading 2", level3: "Try heading 3", undo: "Undo", initial: "This editor accepts headings 1 and 2.", idle: "Try the heading buttons.", success: "Accepted: document updated.", failure: "Rejected: level 3 is not allowed; content preserved.", label: "Configured heading example" }
  : { level2: "设为二级标题", level3: "尝试三级标题", undo: "撤销", initial: "这个编辑器只接受一、二级标题。", idle: "试试上方的标题按钮。", success: "操作成功，正文已更新。", failure: "操作被拒绝：不允许三级标题，正文保持不变。", label: "标题配置示例" });
let editor: Editor | undefined;

onMounted(() => {
  editor = new Editor({
    element: element.value,
    mode: "dom",
    extensions: [StarterKit.configure({ heading: { levels: [1, 2] } }), DomViewExtension],
    content: { type: "doc", content: [{ type: "paragraph", content: [{ type: "text", text: copy.value.initial }] }] },
  });
});
onBeforeUnmount(() => editor?.destroy());

/** @param level - Requested heading level. The configured extension rejects level three. */
function applyHeading(level: 2 | 3): void {
  if (editor) result.value = editor.chain().focus().setHeading({ level }).run();
}

/** Undoes the previous accepted edit through the same Core history. */
function undo(): void { editor?.chain().focus().undo().run(); result.value = null; }
</script>

<template>
  <div class="configured-plugin-example">
    <div class="configured-plugin-tools">
      <QuireButton variant="outline" @click="applyHeading(2)">{{ copy.level2 }}</QuireButton>
      <QuireButton variant="outline" @click="applyHeading(3)">{{ copy.level3 }}</QuireButton>
      <QuireButton variant="outline" @click="undo">{{ copy.undo }}</QuireButton>
    </div>
    <div ref="element" class="configured-plugin-document" role="group" :aria-label="copy.label" />
    <p role="status">{{ result === null ? copy.idle : result ? copy.success : copy.failure }}</p>
  </div>
</template>

<style scoped>
.configured-plugin-example { padding: 20px; }
.configured-plugin-tools { display: flex; flex-wrap: wrap; gap: 8px; }
.configured-plugin-document { border: 1px solid var(--qk-color-border-default); border-radius: 6px; padding: 20px; margin-top: 16px; }
.configured-plugin-document :deep(.ProseMirror) { min-height: 120px; line-height: 1.8; }
.configured-plugin-document :deep(h2) { font-size: 22px; margin: 0; }
.configured-plugin-example > p { font-size: 12px; color: var(--qk-color-text-secondary); margin-bottom: 0; }
</style>

```

## 读懂每页的默认值

配置页列出当前入口自己的默认值。预设可能再次覆盖它：例如直接创建 Proofing 时 provider 为 `null`，完整 Word kit 可选择默认校对服务。应同时阅读你实际使用的装配层。

`undefined` 表示没有显式传值，子扩展或宿主装配仍可能选择回退。StarterKit 的子项未设置时默认启用，配置对象覆盖该子项的选项；`false` 才关闭它。

`null` 往往表示没有接入某个服务，例如图形 `open` 或校对 `provider`。它不代表扩展会自动请求网络服务。哪些能力需要宿主端口，每页的用途和配置说明都会分别指出。

## 安装后还需要什么

只有文档模型不会自动产生全部产品界面，也不能保证每种 View 和导出格式都能呈现它。按所需效果检查三个地方：当前 View 是否支持该能力；图形、校对或协作需要的宿主服务是否已接入；保存与导出是否保留相应内容。

从[全部插件](/plugins)进入具体能力的参考页。想做完整产品时继续[构建产品](/build)；产品文件服务的例程见[开发产品插件](/build/product-plugin)。
