91 lines
2.9 KiB
TypeScript
91 lines
2.9 KiB
TypeScript
|
import {tempEditor, requireFocus} from "./temp-editor"
|
||
|
import {EditorSelection} from "@codemirror/next/state"
|
||
|
import ist from "ist"
|
||
|
|
||
|
function setDOMSel(node: Node, offset: number) {
|
||
|
let range = document.createRange()
|
||
|
range.setEnd(node, offset)
|
||
|
range.setStart(node, offset)
|
||
|
let sel = window.getSelection()!
|
||
|
sel.removeAllRanges()
|
||
|
sel.addRange(range)
|
||
|
}
|
||
|
|
||
|
describe("EditorView selection", () => {
|
||
|
it("can read the DOM selection", () => {
|
||
|
let cm = requireFocus(tempEditor("one\n\nthree"))
|
||
|
|
||
|
function test(node: Node, offset: number, expected: number) {
|
||
|
setDOMSel(node, offset)
|
||
|
cm.contentDOM.focus()
|
||
|
cm.observer.flush()
|
||
|
ist(cm.state.selection.primary.head, expected)
|
||
|
}
|
||
|
let one = cm.contentDOM.firstChild!.firstChild!
|
||
|
let three = cm.contentDOM.lastChild!.firstChild!
|
||
|
test(one, 0, 0)
|
||
|
test(one, 1, 1)
|
||
|
test(one, 3, 3)
|
||
|
test(one.parentNode!, 0, 0)
|
||
|
test(one.parentNode!, 1, 3)
|
||
|
test(cm.contentDOM.childNodes[1], 0, 4)
|
||
|
test(three, 0, 5)
|
||
|
test(three, 2, 7)
|
||
|
test(three.parentNode!, 0, 5)
|
||
|
test(three.parentNode!, 1, 10)
|
||
|
})
|
||
|
|
||
|
it("syncs the DOM selection with the editor selection", () => {
|
||
|
let cm = requireFocus(tempEditor("abc\n\ndef"))
|
||
|
function test(pos: number, node: Node, offset: number) {
|
||
|
cm.dispatch(cm.state.t().setSelection(EditorSelection.single(pos)))
|
||
|
let sel = window.getSelection()!
|
||
|
ist(isEquivalentPosition(node, offset, sel.focusNode, sel.focusOffset))
|
||
|
}
|
||
|
let abc = cm.contentDOM.firstChild!.firstChild!
|
||
|
let def = cm.contentDOM.lastChild!.firstChild!
|
||
|
test(0, abc.parentNode!, 0)
|
||
|
test(1, abc, 1)
|
||
|
test(2, abc, 2)
|
||
|
test(3, abc.parentNode!, 1)
|
||
|
test(4, cm.contentDOM.childNodes[1], 0)
|
||
|
test(5, def.parentNode!, 0)
|
||
|
test(6, def, 1)
|
||
|
test(8, def.parentNode!, 1)
|
||
|
})
|
||
|
})
|
||
|
|
||
|
function isEquivalentPosition(node: Node, off: number, targetNode: Node | null, targetOff: number): boolean {
|
||
|
function scanFor(node: Node, off: number, targetNode: Node, targetOff: number, dir: -1 | 1): boolean {
|
||
|
for (;;) {
|
||
|
if (node == targetNode && off == targetOff) return true
|
||
|
if (off == (dir < 0 ? 0 : maxOffset(node))) {
|
||
|
if (node.nodeName == "DIV") return false
|
||
|
let parent = node.parentNode
|
||
|
if (!parent || parent.nodeType != 1) return false
|
||
|
off = domIndex(node) + (dir < 0 ? 0 : 1)
|
||
|
node = parent
|
||
|
} else if (node.nodeType == 1) {
|
||
|
node = node.childNodes[off + (dir < 0 ? -1 : 0)]
|
||
|
off = dir < 0 ? maxOffset(node) : 0
|
||
|
} else {
|
||
|
return false
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
function domIndex(node: Node): number {
|
||
|
for (var index = 0;; index++) {
|
||
|
node = node.previousSibling!
|
||
|
if (!node) return index
|
||
|
}
|
||
|
}
|
||
|
|
||
|
function maxOffset(node: Node): number {
|
||
|
return node.nodeType == 3 ? node.nodeValue!.length : node.childNodes.length
|
||
|
}
|
||
|
|
||
|
return targetNode ? (scanFor(node, off, targetNode, targetOff, -1) ||
|
||
|
scanFor(node, off, targetNode, targetOff, 1)) : false
|
||
|
}
|