自动格式化

PreviousNext

由 input rules 驱动的 Markdown 快捷输入与文本替换。

Loading…

功能特性

  • Markdown 快捷输入由对应功能插件自己拥有。
  • 智能引号、箭头、分数等文本替换通过本地 createTextSubstitutionInputRule(...) 定义。
  • 通过 inputRules 显式启用。
  • 没有隐藏的默认快捷行为。

套件使用

安装

最简单的方式是直接使用 AutoformatKit

'use client';
 
import type { SlateEditor } from 'platejs';
 
import {
  createSlatePlugin,
  createTextSubstitutionInputRule,
  KEYS,
} from 'platejs';
 
const isTextSubstitutionBlocked = (editor: SlateEditor) =>
  editor.api.some({
    match: {
      type: [editor.getType(KEYS.codeBlock)],
    },
  });
 
const createAutoformatTextSubstitutionRule = ({
  patterns,
}: {
  patterns: Parameters<typeof createTextSubstitutionInputRule>[0]['patterns'];
}) =>
  createTextSubstitutionInputRule({
    enabled: ({ editor }) => !isTextSubstitutionBlocked(editor),
    patterns,
  });
 
const arrowsRule = createAutoformatTextSubstitutionRule({
  patterns: [
    { format: '→', match: '->' },
    { format: '←', match: '<-' },
    { format: '⇒', match: '=>' },
    { format: '⇐', match: ['<=', '≤='] },
  ],
});
 
const comparisonsRule = createAutoformatTextSubstitutionRule({
  patterns: [
    { format: '≯', match: '!>' },
    { format: '≮', match: '!<' },
    { format: '≥', match: '>=' },
    { format: '≤', match: '<=' },
    { format: '≱', match: '!>=' },
    { format: '≰', match: '!<=' },
  ],
});
 
const equalityRule = createAutoformatTextSubstitutionRule({
  patterns: [
    { format: '≠', match: '!=' },
    { format: '≡', match: '==' },
    { format: '≢', match: ['!==', '≠='] },
    { format: '≈', match: '~=' },
    { format: '≉', match: '!~=' },
  ],
});
 
const fractionsRule = createAutoformatTextSubstitutionRule({
  patterns: [
    { format: '½', match: '1/2' },
    { format: '⅓', match: '1/3' },
    { format: '¼', match: '1/4' },
    { format: '⅕', match: '1/5' },
    { format: '⅙', match: '1/6' },
    { format: '⅐', match: '1/7' },
    { format: '⅛', match: '1/8' },
    { format: '⅑', match: '1/9' },
    { format: '⅒', match: '1/10' },
    { format: '⅔', match: '2/3' },
    { format: '⅖', match: '2/5' },
    { format: '¾', match: '3/4' },
    { format: '⅗', match: '3/5' },
    { format: '⅜', match: '3/8' },
    { format: '⅘', match: '4/5' },
    { format: '⅚', match: '5/6' },
    { format: '⅝', match: '5/8' },
    { format: '⅞', match: '7/8' },
  ],
});
 
const legalRule = createAutoformatTextSubstitutionRule({
  patterns: [
    { format: '™', match: ['(tm)', '(TM)'] },
    { format: '®', match: ['(r)', '(R)'] },
    { format: '©', match: ['(c)', '(C)'] },
  ],
});
 
const legalHtmlRule = createAutoformatTextSubstitutionRule({
  patterns: [
    { format: '™', match: '&trade;' },
    { format: '®', match: '&reg;' },
    { format: '©', match: '&copy;' },
    { format: '§', match: '&sect;' },
  ],
});
 
const operatorsRule = createAutoformatTextSubstitutionRule({
  patterns: [
    { format: '±', match: '+-' },
    { format: '‰', match: '%%' },
    { format: '‱', match: ['%%%', '‰%'] },
  ],
});
 
const punctuationRule = createAutoformatTextSubstitutionRule({
  patterns: [
    { format: '»', match: '>>' },
    { format: '«', match: '<<' },
  ],
});
 
const smartQuotesRule = createAutoformatTextSubstitutionRule({
  patterns: [
    { format: ['“', '”'], match: '"' },
    { format: ['‘', '’'], match: "'" },
  ],
});
 
const subscriptNumbersRule = createAutoformatTextSubstitutionRule({
  patterns: [
    { format: '₀', match: '~0' },
    { format: '₁', match: '~1' },
    { format: '₂', match: '~2' },
    { format: '₃', match: '~3' },
    { format: '₄', match: '~4' },
    { format: '₅', match: '~5' },
    { format: '₆', match: '~6' },
    { format: '₇', match: '~7' },
    { format: '₈', match: '~8' },
    { format: '₉', match: '~9' },
  ],
});
 
const subscriptSymbolsRule = createAutoformatTextSubstitutionRule({
  patterns: [
    { format: '₊', match: '~+' },
    { format: '₋', match: '~-' },
  ],
});
 
const superscriptNumbersRule = createAutoformatTextSubstitutionRule({
  patterns: [
    { format: '⁰', match: '^0' },
    { format: '¹', match: '^1' },
    { format: '²', match: '^2' },
    { format: '³', match: '^3' },
    { format: '⁴', match: '^4' },
    { format: '⁵', match: '^5' },
    { format: '⁶', match: '^6' },
    { format: '⁷', match: '^7' },
    { format: '⁸', match: '^8' },
    { format: '⁹', match: '^9' },
  ],
});
 
const superscriptSymbolsRule = createAutoformatTextSubstitutionRule({
  patterns: [
    { format: '°', match: '^o' },
    { format: '⁺', match: '^+' },
    { format: '⁻', match: '^-' },
  ],
});
 
const AutoformatShortcutsPlugin = createSlatePlugin({
  key: 'autoformatShortcuts',
  inputRules: [
    legalRule,
    legalHtmlRule,
    arrowsRule,
    comparisonsRule,
    equalityRule,
    fractionsRule,
    operatorsRule,
    punctuationRule,
    smartQuotesRule,
    subscriptNumbersRule,
    subscriptSymbolsRule,
    superscriptNumbersRule,
    superscriptSymbolsRule,
  ],
});
 
export const AutoformatKit = [AutoformatShortcutsPlugin];
'use client';
 
import type { SlateEditor } from 'platejs';
 
import {
  createSlatePlugin,
  createTextSubstitutionInputRule,
  KEYS,
} from 'platejs';
 
const isTextSubstitutionBlocked = (editor: SlateEditor) =>
  editor.api.some({
    match: {
      type: [editor.getType(KEYS.codeBlock)],
    },
  });
 
const createAutoformatTextSubstitutionRule = ({
  patterns,
}: {
  patterns: Parameters<typeof createTextSubstitutionInputRule>[0]['patterns'];
}) =>
  createTextSubstitutionInputRule({
    enabled: ({ editor }) => !isTextSubstitutionBlocked(editor),
    patterns,
  });
 
const arrowsRule = createAutoformatTextSubstitutionRule({
  patterns: [
    { format: '→', match: '->' },
    { format: '←', match: '<-' },
    { format: '⇒', match: '=>' },
    { format: '⇐', match: ['<=', '≤='] },
  ],
});
 
const comparisonsRule = createAutoformatTextSubstitutionRule({
  patterns: [
    { format: '≯', match: '!>' },
    { format: '≮', match: '!<' },
    { format: '≥', match: '>=' },
    { format: '≤', match: '<=' },
    { format: '≱', match: '!>=' },
    { format: '≰', match: '!<=' },
  ],
});
 
const equalityRule = createAutoformatTextSubstitutionRule({
  patterns: [
    { format: '≠', match: '!=' },
    { format: '≡', match: '==' },
    { format: '≢', match: ['!==', '≠='] },
    { format: '≈', match: '~=' },
    { format: '≉', match: '!~=' },
  ],
});
 
const fractionsRule = createAutoformatTextSubstitutionRule({
  patterns: [
    { format: '½', match: '1/2' },
    { format: '⅓', match: '1/3' },
    { format: '¼', match: '1/4' },
    { format: '⅕', match: '1/5' },
    { format: '⅙', match: '1/6' },
    { format: '⅐', match: '1/7' },
    { format: '⅛', match: '1/8' },
    { format: '⅑', match: '1/9' },
    { format: '⅒', match: '1/10' },
    { format: '⅔', match: '2/3' },
    { format: '⅖', match: '2/5' },
    { format: '¾', match: '3/4' },
    { format: '⅗', match: '3/5' },
    { format: '⅜', match: '3/8' },
    { format: '⅘', match: '4/5' },
    { format: '⅚', match: '5/6' },
    { format: '⅝', match: '5/8' },
    { format: '⅞', match: '7/8' },
  ],
});
 
const legalRule = createAutoformatTextSubstitutionRule({
  patterns: [
    { format: '™', match: ['(tm)', '(TM)'] },
    { format: '®', match: ['(r)', '(R)'] },
    { format: '©', match: ['(c)', '(C)'] },
  ],
});
 
const legalHtmlRule = createAutoformatTextSubstitutionRule({
  patterns: [
    { format: '™', match: '&trade;' },
    { format: '®', match: '&reg;' },
    { format: '©', match: '&copy;' },
    { format: '§', match: '&sect;' },
  ],
});
 
const operatorsRule = createAutoformatTextSubstitutionRule({
  patterns: [
    { format: '±', match: '+-' },
    { format: '‰', match: '%%' },
    { format: '‱', match: ['%%%', '‰%'] },
  ],
});
 
const punctuationRule = createAutoformatTextSubstitutionRule({
  patterns: [
    { format: '»', match: '>>' },
    { format: '«', match: '<<' },
  ],
});
 
const smartQuotesRule = createAutoformatTextSubstitutionRule({
  patterns: [
    { format: ['“', '”'], match: '"' },
    { format: ['‘', '’'], match: "'" },
  ],
});
 
const subscriptNumbersRule = createAutoformatTextSubstitutionRule({
  patterns: [
    { format: '₀', match: '~0' },
    { format: '₁', match: '~1' },
    { format: '₂', match: '~2' },
    { format: '₃', match: '~3' },
    { format: '₄', match: '~4' },
    { format: '₅', match: '~5' },
    { format: '₆', match: '~6' },
    { format: '₇', match: '~7' },
    { format: '₈', match: '~8' },
    { format: '₉', match: '~9' },
  ],
});
 
const subscriptSymbolsRule = createAutoformatTextSubstitutionRule({
  patterns: [
    { format: '₊', match: '~+' },
    { format: '₋', match: '~-' },
  ],
});
 
const superscriptNumbersRule = createAutoformatTextSubstitutionRule({
  patterns: [
    { format: '⁰', match: '^0' },
    { format: '¹', match: '^1' },
    { format: '²', match: '^2' },
    { format: '³', match: '^3' },
    { format: '⁴', match: '^4' },
    { format: '⁵', match: '^5' },
    { format: '⁶', match: '^6' },
    { format: '⁷', match: '^7' },
    { format: '⁸', match: '^8' },
    { format: '⁹', match: '^9' },
  ],
});
 
const superscriptSymbolsRule = createAutoformatTextSubstitutionRule({
  patterns: [
    { format: '°', match: '^o' },
    { format: '⁺', match: '^+' },
    { format: '⁻', match: '^-' },
  ],
});
 
const AutoformatShortcutsPlugin = createSlatePlugin({
  key: 'autoformatShortcuts',
  inputRules: [
    legalRule,
    legalHtmlRule,
    arrowsRule,
    comparisonsRule,
    equalityRule,
    fractionsRule,
    operatorsRule,
    punctuationRule,
    smartQuotesRule,
    subscriptNumbersRule,
    subscriptSymbolsRule,
    superscriptNumbersRule,
    superscriptSymbolsRule,
  ],
});
 
export const AutoformatKit = [AutoformatShortcutsPlugin];

添加套件

import { createPlateEditor } from 'platejs/react';
import { AutoformatKit } from '@/components/editor/plugins/autoformat-kit';
 
const editor = createPlateEditor({
  plugins: [
    // ...其他插件,
    ...AutoformatKit,
  ],
});
import { createPlateEditor } from 'platejs/react';
import { AutoformatKit } from '@/components/editor/plugins/autoformat-kit';
 
const editor = createPlateEditor({
  plugins: [
    // ...其他插件,
    ...AutoformatKit,
  ],
});

手动配置

功能插件自己的 Markdown 快捷输入

import {
  BlockquoteRules,
  HeadingRules,
  HorizontalRuleRules,
} from '@platejs/basic-nodes';
import {
  BlockquotePlugin,
  H1Plugin,
  HorizontalRulePlugin,
} from '@platejs/basic-nodes/react';
import { CodeBlockRules } from '@platejs/code-block';
import { CodeBlockPlugin } from '@platejs/code-block/react';
import { LinkRules } from '@platejs/link';
import { LinkPlugin } from '@platejs/link/react';
import {
  BulletedListRules,
  OrderedListRules,
  TaskListRules,
} from '@platejs/list';
import { ListPlugin } from '@platejs/list/react';
import { MathRules } from '@platejs/math';
import { EquationPlugin, InlineEquationPlugin } from '@platejs/math/react';
 
const editor = createPlateEditor({
  plugins: [
    H1Plugin.configure({
      inputRules: [HeadingRules.markdown()],
    }),
    BlockquotePlugin.configure({
      inputRules: [BlockquoteRules.markdown()],
    }),
    HorizontalRulePlugin.configure({
      inputRules: [HorizontalRuleRules.markdown({ variant: '-' })],
    }),
    CodeBlockPlugin.configure({
      inputRules: [CodeBlockRules.markdown({ on: 'match' })],
    }),
    ListPlugin.configure({
      inputRules: [
        BulletedListRules.markdown({ variant: '-' }),
        OrderedListRules.markdown({ variant: '.' }),
        TaskListRules.markdown({ checked: false }),
      ],
    }),
    InlineEquationPlugin.configure({
      inputRules: [MathRules.markdown({ variant: '$' })],
    }),
    EquationPlugin.configure({
      inputRules: [MathRules.markdown({ on: 'break', variant: '$$' })],
    }),
    LinkPlugin.configure({
      inputRules: [
        LinkRules.markdown(),
        LinkRules.autolink({ variant: 'paste' }),
        LinkRules.autolink({ variant: 'space' }),
        LinkRules.autolink({ variant: 'break' }),
      ],
    }),
  ],
});
import {
  BlockquoteRules,
  HeadingRules,
  HorizontalRuleRules,
} from '@platejs/basic-nodes';
import {
  BlockquotePlugin,
  H1Plugin,
  HorizontalRulePlugin,
} from '@platejs/basic-nodes/react';
import { CodeBlockRules } from '@platejs/code-block';
import { CodeBlockPlugin } from '@platejs/code-block/react';
import { LinkRules } from '@platejs/link';
import { LinkPlugin } from '@platejs/link/react';
import {
  BulletedListRules,
  OrderedListRules,
  TaskListRules,
} from '@platejs/list';
import { ListPlugin } from '@platejs/list/react';
import { MathRules } from '@platejs/math';
import { EquationPlugin, InlineEquationPlugin } from '@platejs/math/react';
 
const editor = createPlateEditor({
  plugins: [
    H1Plugin.configure({
      inputRules: [HeadingRules.markdown()],
    }),
    BlockquotePlugin.configure({
      inputRules: [BlockquoteRules.markdown()],
    }),
    HorizontalRulePlugin.configure({
      inputRules: [HorizontalRuleRules.markdown({ variant: '-' })],
    }),
    CodeBlockPlugin.configure({
      inputRules: [CodeBlockRules.markdown({ on: 'match' })],
    }),
    ListPlugin.configure({
      inputRules: [
        BulletedListRules.markdown({ variant: '-' }),
        OrderedListRules.markdown({ variant: '.' }),
        TaskListRules.markdown({ checked: false }),
      ],
    }),
    InlineEquationPlugin.configure({
      inputRules: [MathRules.markdown({ variant: '$' })],
    }),
    EquationPlugin.configure({
      inputRules: [MathRules.markdown({ on: 'break', variant: '$$' })],
    }),
    LinkPlugin.configure({
      inputRules: [
        LinkRules.markdown(),
        LinkRules.autolink({ variant: 'paste' }),
        LinkRules.autolink({ variant: 'space' }),
        LinkRules.autolink({ variant: 'break' }),
      ],
    }),
  ],
});
  • 功能包导出对应的规则族,套件通过显式 rule 实例注册它们。

本地文本替换

import {
  createSlatePlugin,
  createTextSubstitutionInputRule,
} from 'platejs';
 
const ShortcutsPlugin = createSlatePlugin({
  key: 'shortcuts',
  inputRules: [
    createTextSubstitutionInputRule({
      patterns: [{ format: '—', match: '--' }],
    }),
  ],
});
 
const editor = createPlateEditor({
  plugins: [ShortcutsPlugin],
});
import {
  createSlatePlugin,
  createTextSubstitutionInputRule,
} from 'platejs';
 
const ShortcutsPlugin = createSlatePlugin({
  key: 'shortcuts',
  inputRules: [
    createTextSubstitutionInputRule({
      patterns: [{ format: '—', match: '--' }],
    }),
  ],
});
 
const editor = createPlateEditor({
  plugins: [ShortcutsPlugin],
});