import { EditorState, Plugin, PluginKey } from 'prosemirror-state'; import { Node as PMNode, Schema } from 'prosemirror-model'; import { history } from 'prosemirror-history'; import { dropCursor } from 'prosemirror-dropcursor'; import { gapCursor } from 'prosemirror-gapcursor'; import { buildKeymapPlugins, KeymapOptions } from './keymap'; import { buildInputRules } from './inputrules'; import { placeholderPlugin } from './plugins/placeholder'; import { linkPlugin, LinkPluginOptions } from './plugins/link'; import { singleLinePlugin } from './plugins/single-line'; import { tablePlugins } from './plugins/tables'; import { imagePlugin, ImagePluginOptions } from './plugins/image'; import { suggestionPlugin, SuggestionOptions } from './plugins/suggestions'; import { findReplacePlugin } from './plugins/find-replace'; import { maxLengthPlugin } from './plugins/max-length'; import { codeHighlightPlugin } from './plugins/code-highlight'; import { embedPastePlugin } from './plugins/embeds'; import { pastePlugin } from './plugins/paste'; import { formatPainterPlugin } from './plugins/format-painter'; import { defaultSchema } from './schema'; export const mentionSuggestKey = new PluginKey('nileMentionSuggest'); export const slashSuggestKey = new PluginKey('nileSlashSuggest'); export const emojiSuggestKey = new PluginKey('nileEmojiSuggest'); export interface CreateStateOptions extends KeymapOptions, LinkPluginOptions, ImagePluginOptions { schema?: Schema; doc?: PMNode; /** Read live so the placeholder updates without rebuilding state. */ getPlaceholder?: () => string; singleLine?: boolean; /** Mention suggestions (omit onChange to disable the plugin). */ mentions?: Omit; /** Slash-command suggestions, anchored to the start of a block. */ slashCommands?: Omit; /** ":" emoji suggestions (activates after two query characters). */ emoji?: Omit; /** Character limit, read live. Return 0/undefined for unlimited. */ getMaxLength?: () => number; /** Read live: when true, all pastes drop formatting. */ getForcePlainText?: () => boolean; /** Read live: when false, plain-text pastes are never parsed as Markdown. */ getMarkdownPaste?: () => boolean; /** Read live: whether pasted/parsed HTML is sanitized. Defaults to on. */ getSanitize?: () => boolean; /** Typography autocorrect (smart quotes, dashes, (c)…). Defaults to on. */ textTransform?: boolean; /** Extra plugins appended after the built-in set (later phases, tests). */ plugins?: Plugin[]; } export function createEditorState(options: CreateStateOptions = {}): EditorState { const schema = options.schema ?? defaultSchema; const plugins: Plugin[] = []; // Suggestion plugins must come BEFORE the keymaps: handleKeyDown props run // in plugin order, and the popup needs first claim on Enter/Tab/arrows // while a mention or slash query is active. if (options.mentions) { plugins.push(suggestionPlugin(mentionSuggestKey, options.mentions)); } if (options.slashCommands) { plugins.push( suggestionPlugin(slashSuggestKey, { ...options.slashCommands, getTriggers: () => ['/'], startOfLine: true, }) ); } if (options.emoji) { plugins.push( suggestionPlugin(emojiSuggestKey, { ...options.emoji, getTriggers: () => [':'], minQueryLength: 2, }) ); } plugins.push( buildInputRules(schema, { textTransform: options.textTransform }), ...buildKeymapPlugins(schema, { onLinkShortcut: options.onLinkShortcut, onFindShortcut: options.onFindShortcut, onShortcutHelp: options.onShortcutHelp, }), history(), dropCursor(), gapCursor(), placeholderPlugin(options.getPlaceholder ?? (() => '')), linkPlugin({ onOpenLink: options.onOpenLink }), ...tablePlugins(), imagePlugin({ onImageFiles: options.onImageFiles }), findReplacePlugin(), codeHighlightPlugin(), embedPastePlugin(), formatPainterPlugin(), pastePlugin(schema, { getForcePlainText: options.getForcePlainText, getMarkdownPaste: options.getMarkdownPaste, getSanitize: options.getSanitize, }) ); if (options.getMaxLength) plugins.push(maxLengthPlugin(options.getMaxLength)); if (options.singleLine) plugins.push(singleLinePlugin()); if (options.plugins) plugins.push(...options.plugins); return EditorState.create({ schema, doc: options.doc, plugins, }); }