import { Node, nodeInputRule, textblockTypeInputRule, wrappingInputRule } from '@tiptap/core'; import { attributesFromSpec, nodeSpec, passthroughNodeIO } from '../spec-utils'; declare module '@tiptap/core' { interface Commands { documentTextBasics: { setParagraph: () => ReturnType; setHeading: (level: 1 | 2 | 3 | 4) => ReturnType; toggleHeading: (level: 1 | 2 | 3 | 4) => ReturnType; toggleBlockquote: () => ReturnType; toggleCodeBlock: () => ReturnType; setHorizontalRule: () => ReturnType; setHardBreak: () => ReturnType; }; documentSection: { /** Wrap the selection in a styled section (issue #75). */ wrapInSection: (attrs: { pluginId: string; styleId: string }) => ReturnType; /** Unwrap the nearest section, leaving its content in place. */ unwrapSection: () => ReturnType; }; } } /** ProseMirror's document root; `topNode` is a schema-construction option, * not part of the NodeSpec, so it is not derived from `nodeSpec('doc')`. */ export const Doc = Node.create({ name: 'doc', topNode: true, content: nodeSpec('doc').content, }); export const Text = Node.create({ name: 'text', group: nodeSpec('text').group, }); const paragraphSpec = nodeSpec('paragraph'); export const Paragraph = Node.create({ name: 'paragraph', group: paragraphSpec.group, content: paragraphSpec.content, ...passthroughNodeIO(paragraphSpec), addCommands() { return { setParagraph: () => ({ commands }) => commands.setNode(this.name), }; }, addKeyboardShortcuts() { return { 'Mod-Alt-0': () => this.editor.commands.setParagraph() }; }, }); const headingSpec = nodeSpec('heading'); const HEADING_LEVELS = [1, 2, 3, 4] as const; export const Heading = Node.create({ name: 'heading', group: headingSpec.group, content: headingSpec.content, defining: headingSpec.defining, addAttributes() { return attributesFromSpec(headingSpec); }, ...passthroughNodeIO(headingSpec), addCommands() { return { setHeading: (level: 1 | 2 | 3 | 4) => ({ commands }) => commands.setNode(this.name, { level }), toggleHeading: (level: 1 | 2 | 3 | 4) => ({ commands }) => commands.toggleNode(this.name, 'paragraph', { level }), }; }, addKeyboardShortcuts() { return Object.fromEntries( HEADING_LEVELS.map((level) => [ `Mod-Alt-${level}`, () => this.editor.commands.toggleHeading(level), ]), ); }, addInputRules() { return HEADING_LEVELS.map((level) => textblockTypeInputRule({ find: new RegExp(`^(#{${level}})\\s$`), type: this.type, getAttributes: { level }, }), ); }, }); const blockquoteSpec = nodeSpec('blockquote'); export const Blockquote = Node.create({ name: 'blockquote', group: blockquoteSpec.group, content: blockquoteSpec.content, ...passthroughNodeIO(blockquoteSpec), addCommands() { return { toggleBlockquote: () => ({ commands }) => commands.toggleWrap(this.name), }; }, addKeyboardShortcuts() { return { 'Mod-Shift-b': () => this.editor.commands.toggleBlockquote() }; }, addInputRules() { return [wrappingInputRule({ find: /^\s*>\s$/, type: this.type })]; }, }); const sectionSpec = nodeSpec('section'); export const Section = Node.create({ name: 'section', group: sectionSpec.group, content: sectionSpec.content, defining: sectionSpec.defining, addAttributes() { return attributesFromSpec(sectionSpec); }, ...passthroughNodeIO(sectionSpec), addCommands() { return { wrapInSection: (attrs) => ({ commands }) => commands.wrapIn(this.name, attrs), unwrapSection: () => ({ commands }) => commands.lift(this.name), }; }, }); const codeBlockSpec = nodeSpec('code_block'); export const CodeBlock = Node.create({ name: 'code_block', group: codeBlockSpec.group, content: codeBlockSpec.content, marks: codeBlockSpec.marks, code: codeBlockSpec.code, defining: codeBlockSpec.defining, whitespace: codeBlockSpec.whitespace, ...passthroughNodeIO(codeBlockSpec), addCommands() { return { toggleCodeBlock: () => ({ commands }) => commands.toggleNode(this.name, 'paragraph'), }; }, addKeyboardShortcuts() { return { 'Mod-Alt-c': () => this.editor.commands.toggleCodeBlock() }; }, addInputRules() { return [textblockTypeInputRule({ find: /^```$/, type: this.type })]; }, }); const horizontalRuleSpec = nodeSpec('horizontal_rule'); export const HorizontalRule = Node.create({ name: 'horizontal_rule', group: horizontalRuleSpec.group, ...passthroughNodeIO(horizontalRuleSpec), addCommands() { return { setHorizontalRule: () => ({ chain }) => chain().insertContent({ type: this.name }).run(), }; }, addInputRules() { return [nodeInputRule({ find: /^---$/, type: this.type })]; }, }); const hardBreakSpec = nodeSpec('hard_break'); export const HardBreak = Node.create({ name: 'hard_break', group: hardBreakSpec.group, inline: hardBreakSpec.inline, selectable: hardBreakSpec.selectable, ...passthroughNodeIO(hardBreakSpec), addCommands() { return { setHardBreak: () => ({ chain }) => chain().insertContent({ type: this.name }).run(), }; }, addKeyboardShortcuts() { return { 'Shift-Enter': () => this.editor.commands.setHardBreak(), 'Mod-Enter': () => this.editor.commands.setHardBreak(), }; }, });