├── .npmrc ├── .gitignore ├── .npmignore ├── style └── gapcursor.css ├── src ├── README.md ├── index.ts └── gapcursor.ts ├── LICENSE ├── package.json ├── README.md ├── CHANGELOG.md └── CONTRIBUTING.md /.npmrc: -------------------------------------------------------------------------------- 1 | package-lock=false 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /node_modules 2 | .tern-port 3 | /dist 4 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | /node_modules 2 | .tern-port 3 | /test 4 | -------------------------------------------------------------------------------- /style/gapcursor.css: -------------------------------------------------------------------------------- 1 | .ProseMirror-gapcursor { 2 | display: none; 3 | pointer-events: none; 4 | position: absolute; 5 | } 6 | 7 | .ProseMirror-gapcursor:after { 8 | content: ""; 9 | display: block; 10 | position: absolute; 11 | top: -2px; 12 | width: 20px; 13 | border-top: 1px solid black; 14 | animation: ProseMirror-cursor-blink 1.1s steps(2, start) infinite; 15 | } 16 | 17 | @keyframes ProseMirror-cursor-blink { 18 | to { 19 | visibility: hidden; 20 | } 21 | } 22 | 23 | .ProseMirror-focused .ProseMirror-gapcursor { 24 | display: block; 25 | } 26 | -------------------------------------------------------------------------------- /src/README.md: -------------------------------------------------------------------------------- 1 | This is a plugin that adds a type of selection for focusing places 2 | that don't allow regular selection (such as positions that have a leaf 3 | block node, table, or the end of the document both before and after 4 | them). By default, leaf blocks and isolating nodes will allow gap 5 | cursors to appear next to them. You can add a `creatGapCursor: true` 6 | property to a block node's spec to make them appear next to other 7 | nodes as well. 8 | 9 | You'll probably want to load `style/gapcursor.css`, which contains 10 | basic styling for the simulated cursor (as a short, blinking 11 | horizontal stripe). 12 | 13 | By default, gap cursor are only allowed in places where the default 14 | content node (in the schema content constraints) is a textblock node. 15 | You can customize this by adding an `allowGapCursor` property to your 16 | node specs—if it's true, gap cursor are allowed everywhere in that 17 | node, if it's `false` they are never allowed. 18 | 19 | @gapCursor 20 | 21 | @GapCursor 22 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (C) 2015-2017 by Marijn Haverbeke and others 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in 11 | all copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | THE SOFTWARE. 20 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "prosemirror-gapcursor", 3 | "version": "1.4.0", 4 | "description": "ProseMirror plugin for cursors at normally impossible-to-reach positions", 5 | "type": "module", 6 | "main": "dist/index.cjs", 7 | "module": "dist/index.js", 8 | "types": "dist/index.d.ts", 9 | "exports": { 10 | ".": { 11 | "import": "./dist/index.js", 12 | "require": "./dist/index.cjs" 13 | }, 14 | "./style/gapcursor.css": "./style/gapcursor.css" 15 | }, 16 | "sideEffects": ["./style/gapcursor.css"], 17 | "style": "style/gapcursor.css", 18 | "license": "MIT", 19 | "maintainers": [ 20 | { 21 | "name": "Marijn Haverbeke", 22 | "email": "marijn@haverbeke.berlin", 23 | "web": "http://marijnhaverbeke.nl" 24 | } 25 | ], 26 | "repository": { 27 | "type": "git", 28 | "url": "git://github.com/prosemirror/prosemirror-gapcursor.git" 29 | }, 30 | "dependencies": { 31 | "prosemirror-keymap": "^1.0.0", 32 | "prosemirror-model": "^1.0.0", 33 | "prosemirror-state": "^1.0.0", 34 | "prosemirror-view": "^1.0.0" 35 | }, 36 | "devDependencies": { 37 | "@prosemirror/buildhelper": "^0.1.5" 38 | }, 39 | "scripts": { 40 | "prepare": "pm-buildhelper src/index.ts" 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # prosemirror-gapcursor 2 | 3 | [ [**WEBSITE**](https://prosemirror.net) | [**ISSUES**](https://github.com/prosemirror/prosemirror/issues) | [**FORUM**](https://discuss.prosemirror.net) | [**CHANGELOG**](https://github.com/ProseMirror/prosemirror-gapcursor/blob/master/CHANGELOG.md) ] 4 | 5 | This is a [core module](https://prosemirror.net/docs/ref/#gapcursor) of [ProseMirror](https://prosemirror.net). 6 | ProseMirror is a well-behaved rich semantic content editor based on 7 | contentEditable, with support for collaborative editing and custom 8 | document schemas. 9 | 10 | This [plugin](https://prosemirror.net/docs/ref/#gapcursor) implements a 11 | block-level cursor that can be used to focus places that don't allow 12 | regular selection (such as positions that have a leaf block node, 13 | table, or the end of the document both before and after them). Make 14 | sure you load `style/gapcursor.css` (or define your own styling for 15 | the cursor). 16 | 17 | The [project page](https://prosemirror.net) has more information, a 18 | number of [examples](https://prosemirror.net/examples/) and the 19 | [documentation](https://prosemirror.net/docs/). 20 | 21 | This code is released under an 22 | [MIT license](https://github.com/prosemirror/prosemirror/tree/master/LICENSE). 23 | There's a [forum](http://discuss.prosemirror.net) for general 24 | discussion and support requests, and the 25 | [Github bug tracker](https://github.com/prosemirror/prosemirror/issues) 26 | is the place to report issues. 27 | 28 | We aim to be an inclusive, welcoming community. To make that explicit, 29 | we have a [code of 30 | conduct](http://contributor-covenant.org/version/1/1/0/) that applies 31 | to communication around the project. 32 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## 1.4.0 (2025-10-15) 2 | 3 | ### New features 4 | 5 | Nodes can now set a `createGapCursor: true` property on their spec to allow gap cursors to appear next to them. 6 | 7 | ## 1.3.2 (2023-05-17) 8 | 9 | ### Bug fixes 10 | 11 | Include CommonJS type declarations in the package to please new TypeScript resolution settings. 12 | 13 | ## 1.3.1 (2022-06-07) 14 | 15 | ### Bug fixes 16 | 17 | Export CSS file from package.json. 18 | 19 | ## 1.3.0 (2022-05-30) 20 | 21 | ### New features 22 | 23 | Include TypeScript type declarations. 24 | 25 | ## 1.2.2 (2022-02-25) 26 | 27 | ### Bug fixes 28 | 29 | Make sure compositions started from a gap cursor have their inline context created in advance, so that they don't get aborted right away. 30 | 31 | ## 1.2.1 (2021-12-20) 32 | 33 | ### Bug fixes 34 | 35 | Fix an issue where a gap cursor would fail to show up at the start or end of some isolating nodes. 36 | 37 | ## 1.2.0 (2021-09-20) 38 | 39 | ### New features 40 | 41 | The `GapCursor` constructor is now public. 42 | 43 | ## 1.1.5 (2020-04-05) 44 | 45 | ### Bug fixes 46 | 47 | Fix an issue where the gap cursor plugin would sometimes cause perfectly selectable content to be skipped when moving the selection with the arrow keys. 48 | 49 | ## 1.1.4 (2020-03-20) 50 | 51 | ### Bug fixes 52 | 53 | Improve behavior around unselectable block nodes. 54 | 55 | ## 1.1.3 (2020-01-22) 56 | 57 | ### Bug fixes 58 | 59 | Fix a crash in documents that have a textblock as top node. 60 | 61 | ## 1.1.2 (2019-11-20) 62 | 63 | ### Bug fixes 64 | 65 | Rename ES module files to use a .js extension, since Webpack gets confused by .mjs 66 | 67 | ## 1.1.1 (2019-11-19) 68 | 69 | ### Bug fixes 70 | 71 | The file referred to in the package's `module` field now is compiled down to ES5. 72 | 73 | ## 1.1.0 (2019-11-08) 74 | 75 | ### New features 76 | 77 | Add a `module` field to package json file. 78 | 79 | ## 1.0.4 (2019-06-24) 80 | 81 | ### Bug fixes 82 | 83 | Do not show a gap cursor when the view isn't editable. 84 | 85 | ## 1.0.3 (2018-10-01) 86 | 87 | ### Bug fixes 88 | 89 | Don't blanket-forbid gap cursors next to textblocks 90 | 91 | ## 1.0.2 (2018-03-15) 92 | 93 | ### Bug fixes 94 | 95 | Throw errors, rather than constructing invalid objects, when deserializing from invalid JSON data. 96 | 97 | ## 1.0.1 (2018-02-16) 98 | 99 | ### Bug fixes 100 | 101 | Prevent issue where clicking on a selectable node near a valid gap cursor position would create a gap cursor rather than select the node. 102 | 103 | ## 1.0.0 (2017-10-13) 104 | 105 | ### New features 106 | 107 | Valid gap cursor positions are not determined in a way that allows them inside nested nodes. By default, any position where a textblock can be inserted is valid gap cursor position. 108 | 109 | Nodes can override whether they allow gap cursors with the `allowGapCursor` property in their spec. 110 | 111 | ## 0.23.1 (2017-09-19) 112 | 113 | ### Bug fixes 114 | 115 | Moving out of a table with the arrow keys now creates a gap cursor when appropriate. 116 | 117 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | import {keydownHandler} from "prosemirror-keymap" 2 | import {TextSelection, NodeSelection, Plugin, Command, EditorState} from "prosemirror-state" 3 | import {Fragment, Slice} from "prosemirror-model" 4 | import {Decoration, DecorationSet, EditorView} from "prosemirror-view" 5 | 6 | import {GapCursor} from "./gapcursor" 7 | 8 | /// Create a gap cursor plugin. When enabled, this will capture clicks 9 | /// near and arrow-key-motion past places that don't have a normally 10 | /// selectable position nearby, and create a gap cursor selection for 11 | /// them. The cursor is drawn as an element with class 12 | /// `ProseMirror-gapcursor`. You can either include 13 | /// `style/gapcursor.css` from the package's directory or add your own 14 | /// styles to make it visible. 15 | export function gapCursor(): Plugin { 16 | return new Plugin({ 17 | props: { 18 | decorations: drawGapCursor, 19 | 20 | createSelectionBetween(_view, $anchor, $head) { 21 | return $anchor.pos == $head.pos && GapCursor.valid($head) ? new GapCursor($head) : null 22 | }, 23 | 24 | handleClick, 25 | handleKeyDown, 26 | handleDOMEvents: {beforeinput: beforeinput as any} 27 | } 28 | }) 29 | } 30 | 31 | export {GapCursor} 32 | 33 | const handleKeyDown = keydownHandler({ 34 | "ArrowLeft": arrow("horiz", -1), 35 | "ArrowRight": arrow("horiz", 1), 36 | "ArrowUp": arrow("vert", -1), 37 | "ArrowDown": arrow("vert", 1) 38 | }) 39 | 40 | function arrow(axis: "vert" | "horiz", dir: number): Command { 41 | const dirStr = axis == "vert" ? (dir > 0 ? "down" : "up") : (dir > 0 ? "right" : "left") 42 | return function(state, dispatch, view) { 43 | let sel = state.selection 44 | let $start = dir > 0 ? sel.$to : sel.$from, mustMove = sel.empty 45 | if (sel instanceof TextSelection) { 46 | if (!view!.endOfTextblock(dirStr) || $start.depth == 0) return false 47 | mustMove = false 48 | $start = state.doc.resolve(dir > 0 ? $start.after() : $start.before()) 49 | } 50 | let $found = GapCursor.findGapCursorFrom($start, dir, mustMove) 51 | if (!$found) return false 52 | if (dispatch) dispatch(state.tr.setSelection(new GapCursor($found))) 53 | return true 54 | } 55 | } 56 | 57 | function handleClick(view: EditorView, pos: number, event: MouseEvent) { 58 | if (!view || !view.editable) return false 59 | let $pos = view.state.doc.resolve(pos) 60 | if (!GapCursor.valid($pos)) return false 61 | let clickPos = view.posAtCoords({left: event.clientX, top: event.clientY}) 62 | if (clickPos && clickPos.inside > -1 && NodeSelection.isSelectable(view.state.doc.nodeAt(clickPos.inside)!)) return false 63 | view.dispatch(view.state.tr.setSelection(new GapCursor($pos))) 64 | return true 65 | } 66 | 67 | // This is a hack that, when a composition starts while a gap cursor 68 | // is active, quickly creates an inline context for the composition to 69 | // happen in, to avoid it being aborted by the DOM selection being 70 | // moved into a valid position. 71 | function beforeinput(view: EditorView, event: InputEvent) { 72 | if (event.inputType != "insertCompositionText" || !(view.state.selection instanceof GapCursor)) return false 73 | 74 | let {$from} = view.state.selection 75 | let insert = $from.parent.contentMatchAt($from.index()).findWrapping(view.state.schema.nodes.text) 76 | if (!insert) return false 77 | 78 | let frag = Fragment.empty 79 | for (let i = insert.length - 1; i >= 0; i--) frag = Fragment.from(insert[i].createAndFill(null, frag)) 80 | let tr = view.state.tr.replace($from.pos, $from.pos, new Slice(frag, 0, 0)) 81 | tr.setSelection(TextSelection.near(tr.doc.resolve($from.pos + 1))) 82 | view.dispatch(tr) 83 | return false 84 | } 85 | 86 | function drawGapCursor(state: EditorState) { 87 | if (!(state.selection instanceof GapCursor)) return null 88 | let node = document.createElement("div") 89 | node.className = "ProseMirror-gapcursor" 90 | return DecorationSet.create(state.doc, [Decoration.widget(state.selection.head, node, {key: "gapcursor"})]) 91 | } 92 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # How to contribute 2 | 3 | - [Getting help](#getting-help) 4 | - [Submitting bug reports](#submitting-bug-reports) 5 | - [Contributing code](#contributing-code) 6 | 7 | ## Getting help 8 | 9 | Community discussion, questions, and informal bug reporting is done on the 10 | [discuss.ProseMirror forum](http://discuss.prosemirror.net). 11 | 12 | ## Submitting bug reports 13 | 14 | Report bugs on the 15 | [GitHub issue tracker](http://github.com/prosemirror/prosemirror/issues). 16 | Before reporting a bug, please read these pointers. 17 | 18 | - The issue tracker is for *bugs*, not requests for help. Questions 19 | should be asked on the [forum](http://discuss.prosemirror.net). 20 | 21 | - Include information about the version of the code that exhibits the 22 | problem. For browser-related issues, include the browser and browser 23 | version on which the problem occurred. 24 | 25 | - Mention very precisely what went wrong. "X is broken" is not a good 26 | bug report. What did you expect to happen? What happened instead? 27 | Describe the exact steps a maintainer has to take to make the 28 | problem occur. A screencast can be useful, but is no substitute for 29 | a textual description. 30 | 31 | - A great way to make it easy to reproduce your problem, if it can not 32 | be trivially reproduced on the website demos, is to submit a script 33 | that triggers the issue. 34 | 35 | ## Contributing code 36 | 37 | If you want to make a change that involves a significant overhaul of 38 | the code or introduces a user-visible new feature, create an 39 | [RFC](https://github.com/ProseMirror/rfcs/) first with your proposal. 40 | 41 | - Make sure you have a [GitHub Account](https://github.com/signup/free) 42 | 43 | - Fork the relevant repository 44 | ([how to fork a repo](https://help.github.com/articles/fork-a-repo)) 45 | 46 | - Create a local checkout of the code. You can use the 47 | [main repository](https://github.com/prosemirror/prosemirror) to 48 | easily check out all core modules. 49 | 50 | - Make your changes, and commit them 51 | 52 | - Follow the code style of the rest of the project (see below). Run 53 | `npm run lint` (in the main repository checkout) to make sure that 54 | the linter is happy. 55 | 56 | - If your changes are easy to test or likely to regress, add tests in 57 | the relevant `test/` directory. Either put them in an existing 58 | `test-*.js` file, if they fit there, or add a new file. 59 | 60 | - Make sure all tests pass. Run `npm run test` to verify tests pass 61 | (you will need Node.js v6+). 62 | 63 | - Submit a pull request ([how to create a pull request](https://help.github.com/articles/fork-a-repo)). 64 | Don't put more than one feature/fix in a single pull request. 65 | 66 | By contributing code to ProseMirror you 67 | 68 | - Agree to license the contributed code under the project's [MIT 69 | license](https://github.com/ProseMirror/prosemirror/blob/master/LICENSE). 70 | 71 | - Confirm that you have the right to contribute and license the code 72 | in question. (Either you hold all rights on the code, or the rights 73 | holder has explicitly granted the right to use it like this, 74 | through a compatible open source license or through a direct 75 | agreement with you.) 76 | 77 | ### Coding standards 78 | 79 | - ES6 syntax, targeting an ES5 runtime (i.e. don't use library 80 | elements added by ES6, don't use ES7/ES.next syntax). 81 | 82 | - 2 spaces per indentation level, no tabs. 83 | 84 | - No semicolons except when necessary. 85 | 86 | - Follow the surrounding code when it comes to spacing, brace 87 | placement, etc. 88 | 89 | - Brace-less single-statement bodies are encouraged (whenever they 90 | don't impact readability). 91 | 92 | - [getdocs](https://github.com/marijnh/getdocs)-style doc comments 93 | above items that are part of the public API. 94 | 95 | - When documenting non-public items, you can put the type after a 96 | single colon, so that getdocs doesn't pick it up and add it to the 97 | API reference. 98 | 99 | - The linter (`npm run lint`) complains about unused variables and 100 | functions. Prefix their names with an underscore to muffle it. 101 | 102 | - ProseMirror does *not* follow JSHint or JSLint prescribed style. 103 | Patches that try to 'fix' code to pass one of these linters will not 104 | be accepted. 105 | -------------------------------------------------------------------------------- /src/gapcursor.ts: -------------------------------------------------------------------------------- 1 | import {Selection, NodeSelection} from "prosemirror-state" 2 | import {Slice, ResolvedPos, Node, NodeType} from "prosemirror-model" 3 | import {Mappable} from "prosemirror-transform" 4 | 5 | /// Gap cursor selections are represented using this class. Its 6 | /// `$anchor` and `$head` properties both point at the cursor position. 7 | export class GapCursor extends Selection { 8 | /// Create a gap cursor. 9 | constructor($pos: ResolvedPos) { 10 | super($pos, $pos) 11 | } 12 | 13 | map(doc: Node, mapping: Mappable): Selection { 14 | let $pos = doc.resolve(mapping.map(this.head)) 15 | return GapCursor.valid($pos) ? new GapCursor($pos) : Selection.near($pos) 16 | } 17 | 18 | content() { return Slice.empty } 19 | 20 | eq(other: Selection): boolean { 21 | return other instanceof GapCursor && other.head == this.head 22 | } 23 | 24 | toJSON(): any { 25 | return {type: "gapcursor", pos: this.head} 26 | } 27 | 28 | /// @internal 29 | static fromJSON(doc: Node, json: any): GapCursor { 30 | if (typeof json.pos != "number") throw new RangeError("Invalid input for GapCursor.fromJSON") 31 | return new GapCursor(doc.resolve(json.pos)) 32 | } 33 | 34 | /// @internal 35 | getBookmark() { return new GapBookmark(this.anchor) } 36 | 37 | /// @internal 38 | static valid($pos: ResolvedPos) { 39 | let parent = $pos.parent 40 | if (parent.isTextblock || !closedBefore($pos) || !closedAfter($pos)) return false 41 | let override = parent.type.spec.allowGapCursor 42 | if (override != null) return override 43 | let deflt = parent.contentMatchAt($pos.index()).defaultType 44 | return deflt && deflt.isTextblock 45 | } 46 | 47 | /// @internal 48 | static findGapCursorFrom($pos: ResolvedPos, dir: number, mustMove = false) { 49 | search: for (;;) { 50 | if (!mustMove && GapCursor.valid($pos)) return $pos 51 | let pos = $pos.pos, next = null 52 | // Scan up from this position 53 | for (let d = $pos.depth;; d--) { 54 | let parent = $pos.node(d) 55 | if (dir > 0 ? $pos.indexAfter(d) < parent.childCount : $pos.index(d) > 0) { 56 | next = parent.child(dir > 0 ? $pos.indexAfter(d) : $pos.index(d) - 1) 57 | break 58 | } else if (d == 0) { 59 | return null 60 | } 61 | pos += dir 62 | let $cur = $pos.doc.resolve(pos) 63 | if (GapCursor.valid($cur)) return $cur 64 | } 65 | 66 | // And then down into the next node 67 | for (;;) { 68 | let inside: Node | null = dir > 0 ? next.firstChild : next.lastChild 69 | if (!inside) { 70 | if (next.isAtom && !next.isText && !NodeSelection.isSelectable(next)) { 71 | $pos = $pos.doc.resolve(pos + next.nodeSize * dir) 72 | mustMove = false 73 | continue search 74 | } 75 | break 76 | } 77 | next = inside 78 | pos += dir 79 | let $cur = $pos.doc.resolve(pos) 80 | if (GapCursor.valid($cur)) return $cur 81 | } 82 | 83 | return null 84 | } 85 | } 86 | } 87 | 88 | GapCursor.prototype.visible = false 89 | 90 | ;(GapCursor as any).findFrom = GapCursor.findGapCursorFrom 91 | 92 | Selection.jsonID("gapcursor", GapCursor) 93 | 94 | class GapBookmark { 95 | constructor(readonly pos: number) {} 96 | 97 | map(mapping: Mappable) { 98 | return new GapBookmark(mapping.map(this.pos)) 99 | } 100 | resolve(doc: Node) { 101 | let $pos = doc.resolve(this.pos) 102 | return GapCursor.valid($pos) ? new GapCursor($pos) : Selection.near($pos) 103 | } 104 | } 105 | 106 | function needsGap(type: NodeType) { 107 | return type.isAtom || type.spec.isolating || type.spec.createGapCursor 108 | } 109 | 110 | function closedBefore($pos: ResolvedPos) { 111 | for (let d = $pos.depth; d >= 0; d--) { 112 | let index = $pos.index(d), parent = $pos.node(d) 113 | // At the start of this parent, look at next one 114 | if (index == 0) { 115 | if (parent.type.spec.isolating) return true 116 | continue 117 | } 118 | // See if the node before (or its first ancestor) is closed 119 | for (let before = parent.child(index - 1);; before = before.lastChild!) { 120 | if ((before.childCount == 0 && !before.inlineContent) || needsGap(before.type)) return true 121 | if (before.inlineContent) return false 122 | } 123 | } 124 | // Hit start of document 125 | return true 126 | } 127 | 128 | function closedAfter($pos: ResolvedPos) { 129 | for (let d = $pos.depth; d >= 0; d--) { 130 | let index = $pos.indexAfter(d), parent = $pos.node(d) 131 | if (index == parent.childCount) { 132 | if (parent.type.spec.isolating) return true 133 | continue 134 | } 135 | for (let after = parent.child(index);; after = after.firstChild!) { 136 | if ((after.childCount == 0 && !after.inlineContent) || needsGap(after.type)) return true 137 | if (after.inlineContent) return false 138 | } 139 | } 140 | return true 141 | } 142 | --------------------------------------------------------------------------------