diff --git a/src/extensions/base/BaseInputRules/BaseInputRules.test.ts b/src/extensions/base/BaseInputRules/BaseInputRules.test.ts new file mode 100644 index 00000000..7ec9f761 --- /dev/null +++ b/src/extensions/base/BaseInputRules/BaseInputRules.test.ts @@ -0,0 +1,63 @@ +import {EditorState, TextSelection} from 'prosemirror-state'; +import {builders} from 'prosemirror-test-builder'; +import {EditorView} from 'prosemirror-view'; + +import {ExtensionsManager} from '../../../core'; +import {CodeSpecs, codeMarkName} from '../../markdown/Code/CodeSpecs'; +import {BaseNode, BaseSchemaSpecs} from '../specs'; + +import {BaseInputRules} from './index'; + +const {schema, plugins} = new ExtensionsManager({ + extensions: (builder) => builder.use(BaseSchemaSpecs, {}).use(CodeSpecs).use(BaseInputRules), +}).build(); + +const {doc, p, c} = builders<'doc' | 'p', 'c'>(schema, { + doc: {nodeType: BaseNode.Doc}, + p: {nodeType: BaseNode.Paragraph}, + c: {markType: codeMarkName}, +}); + +describe('BaseInputRules ellipsis', () => { + it('does not replace triple dots inside inline code', () => { + const startDoc = doc(p(c('text'))); + const state = EditorState.create({ + schema, + doc: startDoc, + selection: TextSelection.create(startDoc, startDoc.tag.a), + plugins, + }); + const view = new EditorView(document.createElement('div'), {state}); + + const handled = view.someProp('handleTextInput', (f: any) => + f(view, startDoc.tag.a, startDoc.tag.a, '...'), + ); + + if (!handled) { + view.dispatch(view.state.tr.insertText('...', startDoc.tag.a, startDoc.tag.a)); + } + + expect(view.state.doc).toMatchNode(doc(p(c('text...')))); + }); + + it('replaces triple dots in regular text', () => { + const startDoc = doc(p('text')); + const state = EditorState.create({ + schema, + doc: startDoc, + selection: TextSelection.create(startDoc, startDoc.tag.a), + plugins, + }); + const view = new EditorView(document.createElement('div'), {state}); + + const handled = view.someProp('handleTextInput', (f: any) => + f(view, startDoc.tag.a, startDoc.tag.a, '...'), + ); + + if (!handled) { + view.dispatch(view.state.tr.insertText('...', startDoc.tag.a, startDoc.tag.a)); + } + + expect(view.state.doc).toMatchNode(doc(p('text…'))); + }); +}); diff --git a/src/extensions/base/BaseInputRules/index.ts b/src/extensions/base/BaseInputRules/index.ts index b9ed7e02..c45f8a06 100644 --- a/src/extensions/base/BaseInputRules/index.ts +++ b/src/extensions/base/BaseInputRules/index.ts @@ -1,7 +1,13 @@ -import {ellipsis} from 'prosemirror-inputrules'; +import {InputRule} from 'prosemirror-inputrules'; import type {ExtensionAuto} from '../../../core'; +import {hasCodeMark} from '../../../utils/inputrules'; + +const ellipsisInputRule = new InputRule(/\.\.\.$/, (state, match, start, end) => { + if (hasCodeMark(state, match, start, end)) return null; + return state.tr.insertText('…', start, end); +}); export const BaseInputRules: ExtensionAuto = (builder) => { - builder.addInputRules(() => ({rules: [ellipsis]})); + builder.addInputRules(() => ({rules: [ellipsisInputRule]})); };