Skip to content

Commit

Permalink
Fix finding of composed text node after a mark placeholder
Browse files Browse the repository at this point in the history
FIX: Fix a bug where starting a composition with stored marks or after a
noninclusive mark, with an IME that keeps the cursor at the start of the
composed text (like many Chinese IMEs) disrupted composition on Chrome.

Closes ProseMirror/prosemirror#1450
Issue ProseMirror/prosemirror#1296
  • Loading branch information
marijnh committed Mar 18, 2024
1 parent 5704aec commit a2af570
Show file tree
Hide file tree
Showing 3 changed files with 41 additions and 26 deletions.
32 changes: 32 additions & 0 deletions src/dom.ts
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,38 @@ export function nodeSize(node: Node) {
return node.nodeType == 3 ? node.nodeValue!.length : node.childNodes.length
}

export function textNodeBefore(node: Node, offset: number) {
for (;;) {
if (node.nodeType == 3 && offset) return node as Text
if (node.nodeType == 1 && offset > 0) {
if ((node as HTMLElement).contentEditable != "false") return null
node = node.childNodes[offset - 1]
offset = nodeSize(node)
} else if (node.parentNode && !hasBlockDesc(node)) {
offset = domIndex(node)
node = node.parentNode
} else {
return null
}
}
}

export function textNodeAfter(node: Node, offset: number) {
for (;;) {
if (node.nodeType == 3 && offset < node.nodeValue!.length) return node as Text
if (node.nodeType == 1 && offset < node.childNodes.length) {
if ((node as HTMLElement).contentEditable != "false") return null
node = node.childNodes[offset]
offset = 0
} else if (node.parentNode && !hasBlockDesc(node)) {
offset = domIndex(node) + 1
node = node.parentNode
} else {
return null
}
}
}

export function isOnEdge(node: Node, offset: number, parent: Node) {
for (let atStart = offset == 0, atEnd = offset == nodeSize(node); atStart || atEnd;) {
if (node == parent) return true
Expand Down
7 changes: 0 additions & 7 deletions src/domchange.ts
Original file line number Diff line number Diff line change
Expand Up @@ -143,13 +143,6 @@ export function readDOMChange(view: EditorView, from: number, to: number, typeOv
return
}
}
// Chrome sometimes leaves the cursor before the inserted text when
// composing after a cursor wrapper. This moves it forward.
if (browser.chrome && view.cursorWrapper && parse.sel && parse.sel.anchor == view.cursorWrapper.deco.from &&
parse.sel.head == parse.sel.anchor) {
let size = change.endB - change.start
parse.sel = {anchor: parse.sel.anchor + size, head: parse.sel.anchor + size}
}

view.input.domChangeCount++
// Handle the case where overwriting a selection by typing matches
Expand Down
28 changes: 9 additions & 19 deletions src/viewdesc.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import {DOMSerializer, Fragment, Mark, Node, ParseRule} from "prosemirror-model"
import {TextSelection} from "prosemirror-state"

import {domIndex, isEquivalentPosition, nodeSize, DOMNode} from "./dom"
import {domIndex, isEquivalentPosition, DOMNode, textNodeBefore, textNodeAfter} from "./dom"
import * as browser from "./browser"
import {Decoration, DecorationSource, WidgetConstructor, WidgetType, NodeType} from "./decoration"
import {EditorView} from "./index"
Expand Down Expand Up @@ -760,7 +760,14 @@ export class NodeViewDesc extends ViewDesc {
let {from, to} = view.state.selection
if (!(view.state.selection instanceof TextSelection) || from < pos || to > pos + this.node.content.size) return null
let sel = view.domSelectionRange()
let textNode = nearbyTextNode(sel.focusNode!, sel.focusOffset)
let textBefore = textNodeBefore(sel.focusNode!, sel.focusOffset)
let textAfter = textNodeAfter(sel.focusNode!, sel.focusOffset)
let textNode = textBefore || textAfter
if (textBefore && textAfter && textBefore != textAfter) {
let descAfter = textAfter.pmViewDesc
if (!descAfter || descAfter instanceof TextViewDesc && descAfter.node.text != textAfter.nodeValue)
textNode = textAfter
}
if (!textNode || !this.dom.contains(textNode.parentNode)) return null

if (this.node.inlineContent) {
Expand Down Expand Up @@ -1470,23 +1477,6 @@ function iosHacks(dom: HTMLElement) {
}
}

function nearbyTextNode(node: DOMNode, offset: number): Text | null {
for (;;) {
if (node.nodeType == 3) return node as Text
if (node.nodeType == 1 && offset > 0) {
if (node.childNodes.length > offset && node.childNodes[offset].nodeType == 3)
return node.childNodes[offset] as Text
node = node.childNodes[offset - 1]
offset = nodeSize(node)
} else if (node.nodeType == 1 && offset < node.childNodes.length) {
node = node.childNodes[offset]
offset = 0
} else {
return null
}
}
}

// Find a piece of text in an inline fragment, overlapping from-to
function findTextInFragment(frag: Fragment, text: string, from: number, to: number) {
for (let i = 0, pos = 0; i < frag.childCount && pos <= to;) {
Expand Down

0 comments on commit a2af570

Please sign in to comment.