|
| 1 | +import { Plugin, TextSelection } from '@tiptap/pm/state'; |
| 2 | +import { Extension } from '@tiptap/core'; |
| 3 | + |
| 4 | +const bracketPairs = { |
| 5 | + '<': '>', |
| 6 | + '«': '»', |
| 7 | + '(': ')', |
| 8 | + '[': ']', |
| 9 | + '{': '}', |
| 10 | + '"': '"', |
| 11 | + "'": "'", |
| 12 | +}; |
| 13 | + |
| 14 | +export const SmartBracket = Extension.create({ |
| 15 | + name: 'smartBracket', |
| 16 | + |
| 17 | + addProseMirrorPlugins() { |
| 18 | + return [ |
| 19 | + new Plugin({ |
| 20 | + props: { |
| 21 | + handleTextInput: (view, from, to, text) => { |
| 22 | + if (!(text in bracketPairs)) { |
| 23 | + return false; |
| 24 | + } |
| 25 | + |
| 26 | + const { state, dispatch } = view; |
| 27 | + const { tr, selection } = state; |
| 28 | + const closing = bracketPairs[text]; |
| 29 | + |
| 30 | + if (selection.empty) { |
| 31 | + tr.insertText(text + closing, from, to); |
| 32 | + tr.setSelection( |
| 33 | + state.selection.constructor.near(tr.doc.resolve(from + 1)) |
| 34 | + ); |
| 35 | + } else { |
| 36 | + const { from: selFrom, to: selTo } = selection; |
| 37 | + tr.insertText(text, selFrom, selFrom); |
| 38 | + tr.insertText(closing, selTo + 1, selTo + 1); |
| 39 | + tr.setSelection(state.selection.constructor.create(tr.doc, selFrom + 1, selTo + 1)); |
| 40 | + } |
| 41 | + |
| 42 | + dispatch(tr); |
| 43 | + return true; |
| 44 | + }, |
| 45 | + handleKeyDown(view, event) { |
| 46 | + const { state, dispatch } = view; |
| 47 | + const { selection } = state; |
| 48 | + const { $from } = selection; |
| 49 | + |
| 50 | + // Skip over closing chars |
| 51 | + if (selection.empty && Object.values(bracketPairs).includes(event.key)) { |
| 52 | + const nextChar = $from.nodeAfter?.text?.[0]; |
| 53 | + |
| 54 | + if (nextChar === event.key) { |
| 55 | + dispatch( |
| 56 | + state.tr.setSelection( |
| 57 | + TextSelection.create(state.doc, $from.pos + 1) |
| 58 | + ) |
| 59 | + ); |
| 60 | + |
| 61 | + return true; |
| 62 | + } |
| 63 | + } |
| 64 | + |
| 65 | + // Delete bracket pair when cursor between opening and closing chars |
| 66 | + if (event.key === 'Backspace' && selection.empty) { |
| 67 | + const prevChar = $from.nodeBefore?.text?.slice(-1); |
| 68 | + const nextChar = $from.nodeAfter?.text?.[0]; |
| 69 | + |
| 70 | + if (prevChar && bracketPairs[prevChar] === nextChar) { |
| 71 | + dispatch( |
| 72 | + state.tr.delete($from.pos - 1, $from.pos + 1) |
| 73 | + ); |
| 74 | + |
| 75 | + return true; |
| 76 | + } |
| 77 | + } |
| 78 | + |
| 79 | + return false; |
| 80 | + } |
| 81 | + }, |
| 82 | + }), |
| 83 | + ]; |
| 84 | + }, |
| 85 | +}); |
0 commit comments