@doxi/core 0.11.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +81 -0
- package/dist/collab/port.d.ts +46 -0
- package/dist/collab/port.d.ts.map +1 -0
- package/dist/collab/port.js +11 -0
- package/dist/collab/port.js.map +1 -0
- package/dist/commands/block-commands.d.ts +62 -0
- package/dist/commands/block-commands.d.ts.map +1 -0
- package/dist/commands/block-commands.js +208 -0
- package/dist/commands/block-commands.js.map +1 -0
- package/dist/commands/command.d.ts +13 -0
- package/dist/commands/command.d.ts.map +1 -0
- package/dist/commands/command.js +13 -0
- package/dist/commands/command.js.map +1 -0
- package/dist/commands/edit-commands.d.ts +5 -0
- package/dist/commands/edit-commands.d.ts.map +1 -0
- package/dist/commands/edit-commands.js +147 -0
- package/dist/commands/edit-commands.js.map +1 -0
- package/dist/commands/image-commands.d.ts +31 -0
- package/dist/commands/image-commands.d.ts.map +1 -0
- package/dist/commands/image-commands.js +130 -0
- package/dist/commands/image-commands.js.map +1 -0
- package/dist/commands/index.d.ts +11 -0
- package/dist/commands/index.d.ts.map +1 -0
- package/dist/commands/index.js +11 -0
- package/dist/commands/index.js.map +1 -0
- package/dist/commands/keymap.d.ts +34 -0
- package/dist/commands/keymap.d.ts.map +1 -0
- package/dist/commands/keymap.js +84 -0
- package/dist/commands/keymap.js.map +1 -0
- package/dist/commands/link-commands.d.ts +54 -0
- package/dist/commands/link-commands.d.ts.map +1 -0
- package/dist/commands/link-commands.js +151 -0
- package/dist/commands/link-commands.js.map +1 -0
- package/dist/commands/list-commands.d.ts +42 -0
- package/dist/commands/list-commands.d.ts.map +1 -0
- package/dist/commands/list-commands.js +316 -0
- package/dist/commands/list-commands.js.map +1 -0
- package/dist/commands/mark-commands.d.ts +53 -0
- package/dist/commands/mark-commands.d.ts.map +1 -0
- package/dist/commands/mark-commands.js +181 -0
- package/dist/commands/mark-commands.js.map +1 -0
- package/dist/commands/selection-commands.d.ts +3 -0
- package/dist/commands/selection-commands.d.ts.map +1 -0
- package/dist/commands/selection-commands.js +11 -0
- package/dist/commands/selection-commands.js.map +1 -0
- package/dist/commands/table-commands.d.ts +109 -0
- package/dist/commands/table-commands.d.ts.map +1 -0
- package/dist/commands/table-commands.js +884 -0
- package/dist/commands/table-commands.js.map +1 -0
- package/dist/history/history.d.ts +40 -0
- package/dist/history/history.d.ts.map +1 -0
- package/dist/history/history.js +139 -0
- package/dist/history/history.js.map +1 -0
- package/dist/history/index.d.ts +2 -0
- package/dist/history/index.d.ts.map +1 -0
- package/dist/history/index.js +2 -0
- package/dist/history/index.js.map +1 -0
- package/dist/html/index.d.ts +3 -0
- package/dist/html/index.d.ts.map +1 -0
- package/dist/html/index.js +3 -0
- package/dist/html/index.js.map +1 -0
- package/dist/html/parse.d.ts +4 -0
- package/dist/html/parse.d.ts.map +1 -0
- package/dist/html/parse.js +0 -0
- package/dist/html/parse.js.map +1 -0
- package/dist/html/serialize.d.ts +4 -0
- package/dist/html/serialize.d.ts.map +1 -0
- package/dist/html/serialize.js +75 -0
- package/dist/html/serialize.js.map +1 -0
- package/dist/index.d.ts +13 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +13 -0
- package/dist/index.js.map +1 -0
- package/dist/layout/index.d.ts +6 -0
- package/dist/layout/index.d.ts.map +1 -0
- package/dist/layout/index.js +6 -0
- package/dist/layout/index.js.map +1 -0
- package/dist/layout/layout-engine.d.ts +20 -0
- package/dist/layout/layout-engine.d.ts.map +1 -0
- package/dist/layout/layout-engine.js +198 -0
- package/dist/layout/layout-engine.js.map +1 -0
- package/dist/layout/measure.d.ts +9 -0
- package/dist/layout/measure.d.ts.map +1 -0
- package/dist/layout/measure.js +37 -0
- package/dist/layout/measure.js.map +1 -0
- package/dist/layout/split-paragraph.d.ts +28 -0
- package/dist/layout/split-paragraph.d.ts.map +1 -0
- package/dist/layout/split-paragraph.js +122 -0
- package/dist/layout/split-paragraph.js.map +1 -0
- package/dist/layout/split-table.d.ts +46 -0
- package/dist/layout/split-table.d.ts.map +1 -0
- package/dist/layout/split-table.js +84 -0
- package/dist/layout/split-table.js.map +1 -0
- package/dist/layout/types.d.ts +73 -0
- package/dist/layout/types.d.ts.map +1 -0
- package/dist/layout/types.js +36 -0
- package/dist/layout/types.js.map +1 -0
- package/dist/layout/widow-orphan.d.ts +15 -0
- package/dist/layout/widow-orphan.d.ts.map +1 -0
- package/dist/layout/widow-orphan.js +14 -0
- package/dist/layout/widow-orphan.js.map +1 -0
- package/dist/model/content-expr.d.ts +32 -0
- package/dist/model/content-expr.d.ts.map +1 -0
- package/dist/model/content-expr.js +106 -0
- package/dist/model/content-expr.js.map +1 -0
- package/dist/model/fragment.d.ts +17 -0
- package/dist/model/fragment.d.ts.map +1 -0
- package/dist/model/fragment.js +44 -0
- package/dist/model/fragment.js.map +1 -0
- package/dist/model/index.d.ts +10 -0
- package/dist/model/index.d.ts.map +1 -0
- package/dist/model/index.js +10 -0
- package/dist/model/index.js.map +1 -0
- package/dist/model/mark.d.ts +35 -0
- package/dist/model/mark.d.ts.map +1 -0
- package/dist/model/mark.js +89 -0
- package/dist/model/mark.js.map +1 -0
- package/dist/model/node-type.d.ts +36 -0
- package/dist/model/node-type.d.ts.map +1 -0
- package/dist/model/node-type.js +14 -0
- package/dist/model/node-type.js.map +1 -0
- package/dist/model/node.d.ts +36 -0
- package/dist/model/node.d.ts.map +1 -0
- package/dist/model/node.js +192 -0
- package/dist/model/node.js.map +1 -0
- package/dist/model/position.d.ts +66 -0
- package/dist/model/position.d.ts.map +1 -0
- package/dist/model/position.js +158 -0
- package/dist/model/position.js.map +1 -0
- package/dist/model/schema.d.ts +28 -0
- package/dist/model/schema.d.ts.map +1 -0
- package/dist/model/schema.js +195 -0
- package/dist/model/schema.js.map +1 -0
- package/dist/model/slice.d.ts +26 -0
- package/dist/model/slice.d.ts.map +1 -0
- package/dist/model/slice.js +56 -0
- package/dist/model/slice.js.map +1 -0
- package/dist/model/table-grid.d.ts +71 -0
- package/dist/model/table-grid.d.ts.map +1 -0
- package/dist/model/table-grid.js +130 -0
- package/dist/model/table-grid.js.map +1 -0
- package/dist/plugin/index.d.ts +3 -0
- package/dist/plugin/index.d.ts.map +1 -0
- package/dist/plugin/index.js +3 -0
- package/dist/plugin/index.js.map +1 -0
- package/dist/plugin/plugin-key.d.ts +13 -0
- package/dist/plugin/plugin-key.d.ts.map +1 -0
- package/dist/plugin/plugin-key.js +13 -0
- package/dist/plugin/plugin-key.js.map +1 -0
- package/dist/plugin/plugin-state.d.ts +2 -0
- package/dist/plugin/plugin-state.d.ts.map +1 -0
- package/dist/plugin/plugin-state.js +3 -0
- package/dist/plugin/plugin-state.js.map +1 -0
- package/dist/plugin/plugin.d.ts +39 -0
- package/dist/plugin/plugin.d.ts.map +1 -0
- package/dist/plugin/plugin.js +10 -0
- package/dist/plugin/plugin.js.map +1 -0
- package/dist/schema/default.d.ts +163 -0
- package/dist/schema/default.d.ts.map +1 -0
- package/dist/schema/default.js +94 -0
- package/dist/schema/default.js.map +1 -0
- package/dist/schema/index.d.ts +2 -0
- package/dist/schema/index.d.ts.map +1 -0
- package/dist/schema/index.js +2 -0
- package/dist/schema/index.js.map +1 -0
- package/dist/serialize/index.d.ts +2 -0
- package/dist/serialize/index.d.ts.map +1 -0
- package/dist/serialize/index.js +2 -0
- package/dist/serialize/index.js.map +1 -0
- package/dist/serialize/json.d.ts +15 -0
- package/dist/serialize/json.d.ts.map +1 -0
- package/dist/serialize/json.js +23 -0
- package/dist/serialize/json.js.map +1 -0
- package/dist/state/all-selection.d.ts +11 -0
- package/dist/state/all-selection.d.ts.map +1 -0
- package/dist/state/all-selection.js +17 -0
- package/dist/state/all-selection.js.map +1 -0
- package/dist/state/cell-selection.d.ts +30 -0
- package/dist/state/cell-selection.d.ts.map +1 -0
- package/dist/state/cell-selection.js +38 -0
- package/dist/state/cell-selection.js.map +1 -0
- package/dist/state/editor-state.d.ts +46 -0
- package/dist/state/editor-state.d.ts.map +1 -0
- package/dist/state/editor-state.js +211 -0
- package/dist/state/editor-state.js.map +1 -0
- package/dist/state/index.d.ts +7 -0
- package/dist/state/index.d.ts.map +1 -0
- package/dist/state/index.js +7 -0
- package/dist/state/index.js.map +1 -0
- package/dist/state/node-selection.d.ts +16 -0
- package/dist/state/node-selection.d.ts.map +1 -0
- package/dist/state/node-selection.js +51 -0
- package/dist/state/node-selection.js.map +1 -0
- package/dist/state/selection.d.ts +29 -0
- package/dist/state/selection.d.ts.map +1 -0
- package/dist/state/selection.js +24 -0
- package/dist/state/selection.js.map +1 -0
- package/dist/state/text-selection.d.ts +10 -0
- package/dist/state/text-selection.d.ts.map +1 -0
- package/dist/state/text-selection.js +26 -0
- package/dist/state/text-selection.js.map +1 -0
- package/dist/transform/attr-step.d.ts +16 -0
- package/dist/transform/attr-step.d.ts.map +1 -0
- package/dist/transform/attr-step.js +98 -0
- package/dist/transform/attr-step.js.map +1 -0
- package/dist/transform/index.d.ts +10 -0
- package/dist/transform/index.d.ts.map +1 -0
- package/dist/transform/index.js +10 -0
- package/dist/transform/index.js.map +1 -0
- package/dist/transform/mapping.d.ts +44 -0
- package/dist/transform/mapping.d.ts.map +1 -0
- package/dist/transform/mapping.js +101 -0
- package/dist/transform/mapping.js.map +1 -0
- package/dist/transform/mark-step.d.ts +27 -0
- package/dist/transform/mark-step.d.ts.map +1 -0
- package/dist/transform/mark-step.js +146 -0
- package/dist/transform/mark-step.js.map +1 -0
- package/dist/transform/replace-around-step.d.ts +35 -0
- package/dist/transform/replace-around-step.d.ts.map +1 -0
- package/dist/transform/replace-around-step.js +144 -0
- package/dist/transform/replace-around-step.js.map +1 -0
- package/dist/transform/replace-step.d.ts +17 -0
- package/dist/transform/replace-step.d.ts.map +1 -0
- package/dist/transform/replace-step.js +72 -0
- package/dist/transform/replace-step.js.map +1 -0
- package/dist/transform/replace.d.ts +18 -0
- package/dist/transform/replace.d.ts.map +1 -0
- package/dist/transform/replace.js +132 -0
- package/dist/transform/replace.js.map +1 -0
- package/dist/transform/set-page-meta-step.d.ts +42 -0
- package/dist/transform/set-page-meta-step.d.ts.map +1 -0
- package/dist/transform/set-page-meta-step.js +75 -0
- package/dist/transform/set-page-meta-step.js.map +1 -0
- package/dist/transform/step.d.ts +34 -0
- package/dist/transform/step.d.ts.map +1 -0
- package/dist/transform/step.js +23 -0
- package/dist/transform/step.js.map +1 -0
- package/dist/transform/transaction.d.ts +20 -0
- package/dist/transform/transaction.d.ts.map +1 -0
- package/dist/transform/transaction.js +38 -0
- package/dist/transform/transaction.js.map +1 -0
- package/dist/version.d.ts +2 -0
- package/dist/version.d.ts.map +1 -0
- package/dist/version.js +5 -0
- package/dist/version.js.map +1 -0
- package/dist/view/cell-drag.d.ts +33 -0
- package/dist/view/cell-drag.d.ts.map +1 -0
- package/dist/view/cell-drag.js +177 -0
- package/dist/view/cell-drag.js.map +1 -0
- package/dist/view/clipboard.d.ts +5 -0
- package/dist/view/clipboard.d.ts.map +1 -0
- package/dist/view/clipboard.js +97 -0
- package/dist/view/clipboard.js.map +1 -0
- package/dist/view/default-renderer.d.ts +3 -0
- package/dist/view/default-renderer.d.ts.map +1 -0
- package/dist/view/default-renderer.js +142 -0
- package/dist/view/default-renderer.js.map +1 -0
- package/dist/view/dom-spec.d.ts +11 -0
- package/dist/view/dom-spec.d.ts.map +1 -0
- package/dist/view/dom-spec.js +32 -0
- package/dist/view/dom-spec.js.map +1 -0
- package/dist/view/editor-view.d.ts +55 -0
- package/dist/view/editor-view.d.ts.map +1 -0
- package/dist/view/editor-view.js +143 -0
- package/dist/view/editor-view.js.map +1 -0
- package/dist/view/image-resize.d.ts +37 -0
- package/dist/view/image-resize.d.ts.map +1 -0
- package/dist/view/image-resize.js +191 -0
- package/dist/view/image-resize.js.map +1 -0
- package/dist/view/index.d.ts +15 -0
- package/dist/view/index.d.ts.map +1 -0
- package/dist/view/index.js +15 -0
- package/dist/view/index.js.map +1 -0
- package/dist/view/input-pipeline.d.ts +24 -0
- package/dist/view/input-pipeline.d.ts.map +1 -0
- package/dist/view/input-pipeline.js +226 -0
- package/dist/view/input-pipeline.js.map +1 -0
- package/dist/view/mutation-observer.d.ts +17 -0
- package/dist/view/mutation-observer.d.ts.map +1 -0
- package/dist/view/mutation-observer.js +62 -0
- package/dist/view/mutation-observer.js.map +1 -0
- package/dist/view/page-slots.d.ts +56 -0
- package/dist/view/page-slots.d.ts.map +1 -0
- package/dist/view/page-slots.js +230 -0
- package/dist/view/page-slots.js.map +1 -0
- package/dist/view/paginator.d.ts +17 -0
- package/dist/view/paginator.d.ts.map +1 -0
- package/dist/view/paginator.js +93 -0
- package/dist/view/paginator.js.map +1 -0
- package/dist/view/print.d.ts +42 -0
- package/dist/view/print.d.ts.map +1 -0
- package/dist/view/print.js +70 -0
- package/dist/view/print.js.map +1 -0
- package/dist/view/reconcile.d.ts +16 -0
- package/dist/view/reconcile.d.ts.map +1 -0
- package/dist/view/reconcile.js +158 -0
- package/dist/view/reconcile.js.map +1 -0
- package/dist/view/renderer.d.ts +31 -0
- package/dist/view/renderer.d.ts.map +1 -0
- package/dist/view/renderer.js +89 -0
- package/dist/view/renderer.js.map +1 -0
- package/dist/view/selection-sync.d.ts +35 -0
- package/dist/view/selection-sync.d.ts.map +1 -0
- package/dist/view/selection-sync.js +324 -0
- package/dist/view/selection-sync.js.map +1 -0
- package/dist/view/table-resize.d.ts +41 -0
- package/dist/view/table-resize.d.ts.map +1 -0
- package/dist/view/table-resize.js +216 -0
- package/dist/view/table-resize.js.map +1 -0
- package/package.json +93 -0
- package/styles/base.css +269 -0
- package/styles/dark.css +36 -0
- package/styles/light.css +13 -0
- package/styles/page.css +93 -0
- package/styles/print-a4.css +87 -0
- package/styles/print.css +88 -0
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
import { renderSpec as renderSpecImport, HOLE_MARKER_ATTR as HOLE_MARKER_ATTR_IMPORT } from './dom-spec.js';
|
|
2
|
+
import { renderNode } from './renderer.js';
|
|
3
|
+
/**
|
|
4
|
+
* Reconcile the DOM children of `domRoot` so they represent `newDoc`'s content,
|
|
5
|
+
* starting from a DOM that already represents `oldDoc`'s content.
|
|
6
|
+
*
|
|
7
|
+
* Algorithm (v0.1):
|
|
8
|
+
* - If oldDoc === newDoc (reference equality, which happens whenever the
|
|
9
|
+
* immutable model wasn't touched): no-op.
|
|
10
|
+
* - Otherwise walk children in parallel; if a child differs, replace it
|
|
11
|
+
* wholesale (re-render the differing subtree). More sophisticated diffing
|
|
12
|
+
* (Levenshtein on child arrays, in-place updates for same-type nodes)
|
|
13
|
+
* lands in Phase 7 polish.
|
|
14
|
+
*/
|
|
15
|
+
export function reconcile(domRoot, oldDoc, newDoc, renderer) {
|
|
16
|
+
if (oldDoc === newDoc)
|
|
17
|
+
return;
|
|
18
|
+
reconcileChildren(domRoot, oldDoc, newDoc, renderer, 0);
|
|
19
|
+
}
|
|
20
|
+
function reconcileChildren(parentEl, oldParent, newParent, renderer, baseAbsPos) {
|
|
21
|
+
const oldChildren = oldParent.content.children;
|
|
22
|
+
const newChildren = newParent.content.children;
|
|
23
|
+
const max = Math.max(oldChildren.length, newChildren.length);
|
|
24
|
+
let modelOffset = baseAbsPos + 1; // +1 to step past the parent's [open] token
|
|
25
|
+
for (let i = 0; i < max; i++) {
|
|
26
|
+
const oldChild = oldChildren[i];
|
|
27
|
+
const newChild = newChildren[i];
|
|
28
|
+
const domChild = parentEl.childNodes[i];
|
|
29
|
+
if (!newChild) {
|
|
30
|
+
// Trailing children removed
|
|
31
|
+
if (domChild)
|
|
32
|
+
parentEl.removeChild(domChild);
|
|
33
|
+
continue;
|
|
34
|
+
}
|
|
35
|
+
if (!oldChild) {
|
|
36
|
+
// New child appended
|
|
37
|
+
const fresh = renderNode(document, renderer, newChild, modelOffset);
|
|
38
|
+
parentEl.appendChild(fresh);
|
|
39
|
+
modelOffset += newChild.nodeSize;
|
|
40
|
+
continue;
|
|
41
|
+
}
|
|
42
|
+
if (oldChild === newChild) {
|
|
43
|
+
modelOffset += newChild.nodeSize;
|
|
44
|
+
continue;
|
|
45
|
+
}
|
|
46
|
+
if (sameShape(oldChild, newChild) && !newChild.isText && !newChild.isAtom && domChild) {
|
|
47
|
+
// Same node type + attrs — descend.
|
|
48
|
+
reconcileChildren(domChild, oldChild, newChild, renderer, modelOffset);
|
|
49
|
+
}
|
|
50
|
+
else if (oldChild.isText &&
|
|
51
|
+
newChild.isText &&
|
|
52
|
+
oldChild.type === newChild.type &&
|
|
53
|
+
sameMarkArrays(oldChild.marks, newChild.marks) &&
|
|
54
|
+
domChild) {
|
|
55
|
+
// Text leaf with same marks but different text — update .data in place.
|
|
56
|
+
updateTextLeaf(domChild, newChild.text);
|
|
57
|
+
}
|
|
58
|
+
else if (oldChild.isText &&
|
|
59
|
+
newChild.isText &&
|
|
60
|
+
oldChild.type === newChild.type &&
|
|
61
|
+
oldChild.text === newChild.text &&
|
|
62
|
+
domChild) {
|
|
63
|
+
// Same text, different marks — rewrap in place, preserving the Text node.
|
|
64
|
+
const innerText = findInnerTextNode(domChild);
|
|
65
|
+
if (innerText) {
|
|
66
|
+
// Record insertion point BEFORE wrapWithMarks detaches innerText
|
|
67
|
+
// (which may BE domChild itself when the leaf had no marks).
|
|
68
|
+
const next = domChild.nextSibling;
|
|
69
|
+
if (domChild.parentNode === parentEl)
|
|
70
|
+
parentEl.removeChild(domChild);
|
|
71
|
+
const wrapped = wrapWithMarks(innerText, newChild.marks, renderer);
|
|
72
|
+
if (next)
|
|
73
|
+
parentEl.insertBefore(wrapped, next);
|
|
74
|
+
else
|
|
75
|
+
parentEl.appendChild(wrapped);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
else {
|
|
79
|
+
// Different shape, or atom — replace.
|
|
80
|
+
const fresh = renderNode(document, renderer, newChild, modelOffset);
|
|
81
|
+
if (domChild)
|
|
82
|
+
parentEl.replaceChild(fresh, domChild);
|
|
83
|
+
else
|
|
84
|
+
parentEl.appendChild(fresh);
|
|
85
|
+
}
|
|
86
|
+
modelOffset += newChild.nodeSize;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
function sameShape(a, b) {
|
|
90
|
+
if (a.type !== b.type)
|
|
91
|
+
return false;
|
|
92
|
+
const ak = Object.keys(a.attrs), bk = Object.keys(b.attrs);
|
|
93
|
+
if (ak.length !== bk.length)
|
|
94
|
+
return false;
|
|
95
|
+
for (const k of ak) {
|
|
96
|
+
if (!(k in b.attrs))
|
|
97
|
+
return false;
|
|
98
|
+
if (!Object.is(a.attrs[k], b.attrs[k]))
|
|
99
|
+
return false;
|
|
100
|
+
}
|
|
101
|
+
return true;
|
|
102
|
+
}
|
|
103
|
+
function sameMarkArrays(a, b) {
|
|
104
|
+
if (a.length !== b.length)
|
|
105
|
+
return false;
|
|
106
|
+
for (let i = 0; i < a.length; i++) {
|
|
107
|
+
if (!a[i].eq(b[i]))
|
|
108
|
+
return false;
|
|
109
|
+
}
|
|
110
|
+
return true;
|
|
111
|
+
}
|
|
112
|
+
function updateTextLeaf(domNode, newText) {
|
|
113
|
+
// The rendered text leaf is either a Text node (when no marks) or an Element
|
|
114
|
+
// wrapping a Text node (one or more marks). Walk down to the innermost text.
|
|
115
|
+
let cur = domNode;
|
|
116
|
+
while (cur.nodeType !== 3 /* TEXT_NODE */) {
|
|
117
|
+
if (cur.firstChild)
|
|
118
|
+
cur = cur.firstChild;
|
|
119
|
+
else
|
|
120
|
+
break;
|
|
121
|
+
}
|
|
122
|
+
if (cur.nodeType === 3) {
|
|
123
|
+
;
|
|
124
|
+
cur.data = newText;
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
function findInnerTextNode(domNode) {
|
|
128
|
+
let cur = domNode;
|
|
129
|
+
while (cur.nodeType !== 3 /* TEXT_NODE */) {
|
|
130
|
+
if (cur.firstChild)
|
|
131
|
+
cur = cur.firstChild;
|
|
132
|
+
else
|
|
133
|
+
return null;
|
|
134
|
+
}
|
|
135
|
+
return cur;
|
|
136
|
+
}
|
|
137
|
+
function wrapWithMarks(textNode, marks, renderer) {
|
|
138
|
+
// Detach from any current parent.
|
|
139
|
+
textNode.parentNode?.removeChild(textNode);
|
|
140
|
+
let outer = textNode;
|
|
141
|
+
for (let i = marks.length - 1; i >= 0; i--) {
|
|
142
|
+
const mark = marks[i];
|
|
143
|
+
const markFn = renderer.getMarkRenderer(mark.type.name);
|
|
144
|
+
if (!markFn)
|
|
145
|
+
continue;
|
|
146
|
+
const wrapped = renderSpecImport(textNode.ownerDocument, markFn(mark));
|
|
147
|
+
const hole = wrapped.hasAttribute(HOLE_MARKER_ATTR_IMPORT)
|
|
148
|
+
? wrapped
|
|
149
|
+
: wrapped.querySelector(`[${HOLE_MARKER_ATTR_IMPORT}]`);
|
|
150
|
+
if (hole)
|
|
151
|
+
hole.appendChild(outer);
|
|
152
|
+
else
|
|
153
|
+
wrapped.appendChild(outer);
|
|
154
|
+
outer = wrapped;
|
|
155
|
+
}
|
|
156
|
+
return outer;
|
|
157
|
+
}
|
|
158
|
+
//# sourceMappingURL=reconcile.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"reconcile.js","sourceRoot":"","sources":["../../src/view/reconcile.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import type { Mark } from '../model/mark.js';
|
|
2
|
+
import type { DxNode } from '../model/node.js';
|
|
3
|
+
import { type DOMSpec } from './dom-spec.js';
|
|
4
|
+
export type NodeRenderFn = (node: DxNode) => DOMSpec;
|
|
5
|
+
export type MarkRenderFn = (mark: Mark) => DOMSpec;
|
|
6
|
+
export interface RendererSpec {
|
|
7
|
+
readonly nodes: Readonly<Record<string, NodeRenderFn>>;
|
|
8
|
+
readonly marks: Readonly<Record<string, MarkRenderFn>>;
|
|
9
|
+
}
|
|
10
|
+
export declare class Renderer {
|
|
11
|
+
readonly spec: RendererSpec;
|
|
12
|
+
constructor(spec: RendererSpec);
|
|
13
|
+
getNodeRenderer(name: string): NodeRenderFn;
|
|
14
|
+
getMarkRenderer(name: string): MarkRenderFn | undefined;
|
|
15
|
+
}
|
|
16
|
+
export interface NodeRef {
|
|
17
|
+
readonly node: DxNode;
|
|
18
|
+
/** Absolute document position of this node's [open] boundary (or -1 for root). */
|
|
19
|
+
readonly pos: number;
|
|
20
|
+
}
|
|
21
|
+
export declare function getNodeRef(el: Node): NodeRef | undefined;
|
|
22
|
+
/**
|
|
23
|
+
* Render a model node into a DOM subtree, recursively populating the content
|
|
24
|
+
* hole at each level. Attaches __dxNode/__pos back-pointers (via getNodeRef)
|
|
25
|
+
* for selection translation.
|
|
26
|
+
*
|
|
27
|
+
* `absPos` is the absolute document position of this node (where its [open]
|
|
28
|
+
* token sits). For the root doc, callers pass -1.
|
|
29
|
+
*/
|
|
30
|
+
export declare function renderNode(doc: Document, renderer: Renderer, node: DxNode, absPos?: number): Node;
|
|
31
|
+
//# sourceMappingURL=renderer.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"renderer.d.ts","sourceRoot":"","sources":["../../src/view/renderer.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,kBAAkB,CAAA;AAC5C,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,kBAAkB,CAAA;AAC9C,OAAO,EAAgC,KAAK,OAAO,EAAE,MAAM,eAAe,CAAA;AAE1E,MAAM,MAAM,YAAY,GAAG,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,CAAA;AACpD,MAAM,MAAM,YAAY,GAAG,CAAC,IAAI,EAAE,IAAI,KAAK,OAAO,CAAA;AAElD,MAAM,WAAW,YAAY;IAC3B,QAAQ,CAAC,KAAK,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,YAAY,CAAC,CAAC,CAAA;IACtD,QAAQ,CAAC,KAAK,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,YAAY,CAAC,CAAC,CAAA;CACvD;AAED,qBAAa,QAAQ;IACP,QAAQ,CAAC,IAAI,EAAE,YAAY;gBAAlB,IAAI,EAAE,YAAY;IAEvC,eAAe,CAAC,IAAI,EAAE,MAAM,GAAG,YAAY;IAM3C,eAAe,CAAC,IAAI,EAAE,MAAM,GAAG,YAAY,GAAG,SAAS;CAGxD;AAID,MAAM,WAAW,OAAO;IACtB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;IACrB,kFAAkF;IAClF,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAA;CACrB;AAED,wBAAgB,UAAU,CAAC,EAAE,EAAE,IAAI,GAAG,OAAO,GAAG,SAAS,CAExD;AAMD;;;;;;;GAOG;AACH,wBAAgB,UAAU,CAAC,GAAG,EAAE,QAAQ,EAAE,QAAQ,EAAE,QAAQ,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,SAAK,GAAG,IAAI,CA6C7F"}
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import { renderSpec, HOLE_MARKER_ATTR } from './dom-spec.js';
|
|
2
|
+
export class Renderer {
|
|
3
|
+
spec;
|
|
4
|
+
constructor(spec) {
|
|
5
|
+
this.spec = spec;
|
|
6
|
+
}
|
|
7
|
+
getNodeRenderer(name) {
|
|
8
|
+
const fn = this.spec.nodes[name];
|
|
9
|
+
if (!fn)
|
|
10
|
+
throw new Error(`Renderer: no renderer for node type '${name}'`);
|
|
11
|
+
return fn;
|
|
12
|
+
}
|
|
13
|
+
getMarkRenderer(name) {
|
|
14
|
+
return this.spec.marks[name];
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
const NODE_REF = Symbol.for('@doxi/core.view.nodeRef');
|
|
18
|
+
export function getNodeRef(el) {
|
|
19
|
+
return el[NODE_REF];
|
|
20
|
+
}
|
|
21
|
+
function setNodeRef(el, ref) {
|
|
22
|
+
;
|
|
23
|
+
el[NODE_REF] = ref;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Render a model node into a DOM subtree, recursively populating the content
|
|
27
|
+
* hole at each level. Attaches __dxNode/__pos back-pointers (via getNodeRef)
|
|
28
|
+
* for selection translation.
|
|
29
|
+
*
|
|
30
|
+
* `absPos` is the absolute document position of this node (where its [open]
|
|
31
|
+
* token sits). For the root doc, callers pass -1.
|
|
32
|
+
*/
|
|
33
|
+
export function renderNode(doc, renderer, node, absPos = -1) {
|
|
34
|
+
if (node.isText) {
|
|
35
|
+
const text = node.text;
|
|
36
|
+
let outer = doc.createTextNode(text);
|
|
37
|
+
for (let i = node.marks.length - 1; i >= 0; i--) {
|
|
38
|
+
const mark = node.marks[i];
|
|
39
|
+
const markFn = renderer.getMarkRenderer(mark.type.name);
|
|
40
|
+
if (!markFn)
|
|
41
|
+
continue;
|
|
42
|
+
const wrapped = renderSpec(doc, markFn(mark));
|
|
43
|
+
const hole = findHole(wrapped);
|
|
44
|
+
if (hole)
|
|
45
|
+
hole.appendChild(outer);
|
|
46
|
+
else
|
|
47
|
+
wrapped.appendChild(outer);
|
|
48
|
+
outer = wrapped;
|
|
49
|
+
}
|
|
50
|
+
setNodeRef(outer, { node, pos: absPos });
|
|
51
|
+
return outer;
|
|
52
|
+
}
|
|
53
|
+
const nodeFn = renderer.getNodeRenderer(node.type.name);
|
|
54
|
+
const root = renderSpec(doc, nodeFn(node));
|
|
55
|
+
setNodeRef(root, { node, pos: absPos });
|
|
56
|
+
if (!node.isAtom) {
|
|
57
|
+
const hole = findHole(root) ?? root;
|
|
58
|
+
let childPos = absPos + 1; // +1 to step past this node's [open] token
|
|
59
|
+
for (let i = 0; i < node.content.childCount; i++) {
|
|
60
|
+
const child = node.content.child(i);
|
|
61
|
+
hole.appendChild(renderNode(doc, renderer, child, childPos));
|
|
62
|
+
childPos += child.nodeSize;
|
|
63
|
+
}
|
|
64
|
+
hole.removeAttribute(HOLE_MARKER_ATTR);
|
|
65
|
+
// Empty inline-content blocks (paragraph/heading/etc.) need a placeholder
|
|
66
|
+
// so the line has visible height and contenteditable can place a caret
|
|
67
|
+
// inside it. The doc node itself is exempt.
|
|
68
|
+
if (node.content.childCount === 0 &&
|
|
69
|
+
node.type.name !== 'doc' &&
|
|
70
|
+
acceptsInlineContent(node)) {
|
|
71
|
+
const br = doc.createElement('br');
|
|
72
|
+
br.setAttribute('data-dx-placeholder', 'true');
|
|
73
|
+
hole.appendChild(br);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
return root;
|
|
77
|
+
}
|
|
78
|
+
function acceptsInlineContent(node) {
|
|
79
|
+
const content = node.type.spec.content;
|
|
80
|
+
if (!content)
|
|
81
|
+
return false;
|
|
82
|
+
return content.includes('inline');
|
|
83
|
+
}
|
|
84
|
+
function findHole(el) {
|
|
85
|
+
if (el.hasAttribute(HOLE_MARKER_ATTR))
|
|
86
|
+
return el;
|
|
87
|
+
return el.querySelector(`[${HOLE_MARKER_ATTR}]`);
|
|
88
|
+
}
|
|
89
|
+
//# sourceMappingURL=renderer.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"renderer.js","sourceRoot":"","sources":["../../src/view/renderer.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import type { DxNode } from '../model/node.js';
|
|
2
|
+
import type { Selection } from '../state/selection.js';
|
|
3
|
+
/**
|
|
4
|
+
* Move the host window's selection to mirror `sel`. Walks the rendered DOM
|
|
5
|
+
* subtree under `root`, finds the text node and offset corresponding to
|
|
6
|
+
* sel.anchor/head, and calls Selection.setBaseAndExtent.
|
|
7
|
+
*
|
|
8
|
+
* Positions on a block boundary (depth 0 of the model) snap to the nearest
|
|
9
|
+
* inline text node.
|
|
10
|
+
*
|
|
11
|
+
* For a CellSelection, paints `dx-cell-selected` on every cell in the
|
|
12
|
+
* rectangle and clears the native DOM selection (no caret) instead.
|
|
13
|
+
*/
|
|
14
|
+
export declare function renderSelection(root: Element, sel: Selection, doc: DxNode): void;
|
|
15
|
+
export interface DomLoc {
|
|
16
|
+
readonly node: Node;
|
|
17
|
+
readonly offset: number;
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Find the DOM text node + offset that corresponds to model position `pos`.
|
|
21
|
+
* Strategy: walk the DOM in document order. Each text node carries a
|
|
22
|
+
* NodeRef (set by renderer.ts) pointing back at the model node and the
|
|
23
|
+
* model position of its [open] boundary. We match by model position.
|
|
24
|
+
*
|
|
25
|
+
* Exported as `locateDomFromModelPos` for collab presence overlays — see
|
|
26
|
+
* `@doxi/collab` `installRemoteCursors`.
|
|
27
|
+
*/
|
|
28
|
+
export declare function locateDomFromModelPos(root: Element, pos: number, doc: DxNode): DomLoc | null;
|
|
29
|
+
import { TextSelection } from '../state/text-selection.js';
|
|
30
|
+
/**
|
|
31
|
+
* Translate the host window's current selection into a model TextSelection.
|
|
32
|
+
* Returns null when the selection is not inside `root` (or there is none).
|
|
33
|
+
*/
|
|
34
|
+
export declare function resolveDOMSelection(root: Element, _doc: DxNode): TextSelection | null;
|
|
35
|
+
//# sourceMappingURL=selection-sync.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"selection-sync.d.ts","sourceRoot":"","sources":["../../src/view/selection-sync.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,kBAAkB,CAAA;AAI9C,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,uBAAuB,CAAA;AAMtD;;;;;;;;;;GAUG;AACH,wBAAgB,eAAe,CAAC,IAAI,EAAE,OAAO,EAAE,GAAG,EAAE,SAAS,EAAE,GAAG,EAAE,MAAM,GAAG,IAAI,CAyBhF;AA4FD,MAAM,WAAW,MAAM;IAAG,QAAQ,CAAC,IAAI,EAAE,IAAI,CAAC;IAAC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;CAAE;AAExE;;;;;;;;GAQG;AACH,wBAAgB,qBAAqB,CAAC,IAAI,EAAE,OAAO,EAAE,GAAG,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAE5F;AAsED,OAAO,EAAE,aAAa,EAAE,MAAM,4BAA4B,CAAA;AAE1D;;;GAGG;AACH,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,GAAG,aAAa,GAAG,IAAI,CAmCrF"}
|
|
@@ -0,0 +1,324 @@
|
|
|
1
|
+
import { tableGrid } from '../model/table-grid.js';
|
|
2
|
+
import { CellSelection } from '../state/cell-selection.js';
|
|
3
|
+
import { NodeSelection } from '../state/node-selection.js';
|
|
4
|
+
import { getNodeRef } from './renderer.js';
|
|
5
|
+
const CELL_SELECTED_CLASS = 'dx-cell-selected';
|
|
6
|
+
const IMAGE_SELECTED_CLASS = 'dx-image-selected';
|
|
7
|
+
/**
|
|
8
|
+
* Move the host window's selection to mirror `sel`. Walks the rendered DOM
|
|
9
|
+
* subtree under `root`, finds the text node and offset corresponding to
|
|
10
|
+
* sel.anchor/head, and calls Selection.setBaseAndExtent.
|
|
11
|
+
*
|
|
12
|
+
* Positions on a block boundary (depth 0 of the model) snap to the nearest
|
|
13
|
+
* inline text node.
|
|
14
|
+
*
|
|
15
|
+
* For a CellSelection, paints `dx-cell-selected` on every cell in the
|
|
16
|
+
* rectangle and clears the native DOM selection (no caret) instead.
|
|
17
|
+
*/
|
|
18
|
+
export function renderSelection(root, sel, doc) {
|
|
19
|
+
const win = root.ownerDocument.defaultView;
|
|
20
|
+
if (!win)
|
|
21
|
+
return;
|
|
22
|
+
if (sel instanceof CellSelection) {
|
|
23
|
+
paintCellSelection(root, sel, doc);
|
|
24
|
+
clearImageSelectionPainting(root);
|
|
25
|
+
win.getSelection()?.removeAllRanges();
|
|
26
|
+
return;
|
|
27
|
+
}
|
|
28
|
+
if (sel instanceof NodeSelection) {
|
|
29
|
+
// Only image NodeSelections get painted today; other node selections
|
|
30
|
+
// (hr, etc.) fall back to clearing the image highlight without painting.
|
|
31
|
+
paintImageSelection(root, sel);
|
|
32
|
+
clearCellSelectionPainting(root);
|
|
33
|
+
win.getSelection()?.removeAllRanges();
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
clearCellSelectionPainting(root);
|
|
37
|
+
clearImageSelectionPainting(root);
|
|
38
|
+
const anchor = locate(root, sel.anchor, doc);
|
|
39
|
+
const head = locate(root, sel.head, doc);
|
|
40
|
+
if (!anchor || !head)
|
|
41
|
+
return;
|
|
42
|
+
const sel2 = win.getSelection();
|
|
43
|
+
if (!sel2)
|
|
44
|
+
return;
|
|
45
|
+
sel2.setBaseAndExtent(anchor.node, anchor.offset, head.node, head.offset);
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Paint `dx-cell-selected` on every cell inside the bounding rectangle of
|
|
49
|
+
* the CellSelection, and remove the class from every other cell under `root`.
|
|
50
|
+
*
|
|
51
|
+
* Strategy: locate the anchor and head cell DOM elements via the NodeRef
|
|
52
|
+
* back-pointers attached by the renderer, walk up to their shared `<table>`,
|
|
53
|
+
* find the master positions in `tableGrid`, and call `cellPositionsInRect`
|
|
54
|
+
* over the bounding rectangle. Match those positions against the cell DOM
|
|
55
|
+
* elements inside the table.
|
|
56
|
+
*/
|
|
57
|
+
function paintCellSelection(root, sel, doc) {
|
|
58
|
+
// Find every cell DOM under the root once.
|
|
59
|
+
const allCells = Array.from(root.querySelectorAll('.dx-table-cell'));
|
|
60
|
+
const anchorEl = allCells.find((el) => getNodeRef(el)?.pos === sel.anchorCell) ?? null;
|
|
61
|
+
const headEl = allCells.find((el) => getNodeRef(el)?.pos === sel.headCell) ?? null;
|
|
62
|
+
if (!anchorEl || !headEl) {
|
|
63
|
+
// Couldn't locate the cells — clear stale highlighting and bail.
|
|
64
|
+
clearCellSelectionPainting(root);
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
67
|
+
const anchorTable = anchorEl.closest('table.dx-table');
|
|
68
|
+
const headTable = headEl.closest('table.dx-table');
|
|
69
|
+
if (!anchorTable || anchorTable !== headTable) {
|
|
70
|
+
clearCellSelectionPainting(root);
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
const tableRef = getNodeRef(anchorTable);
|
|
74
|
+
if (!tableRef || tableRef.pos < 0) {
|
|
75
|
+
clearCellSelectionPainting(root);
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
const grid = tableGrid(tableRef.node, tableRef.pos);
|
|
79
|
+
const anchorCell = grid.byPos(sel.anchorCell);
|
|
80
|
+
const headCell = grid.byPos(sel.headCell);
|
|
81
|
+
if (!anchorCell || !headCell) {
|
|
82
|
+
clearCellSelectionPainting(root);
|
|
83
|
+
return;
|
|
84
|
+
}
|
|
85
|
+
const r0 = Math.min(anchorCell.row, headCell.row);
|
|
86
|
+
const r1 = Math.max(anchorCell.row + anchorCell.rowspan - 1, headCell.row + headCell.rowspan - 1);
|
|
87
|
+
const c0 = Math.min(anchorCell.col, headCell.col);
|
|
88
|
+
const c1 = Math.max(anchorCell.col + anchorCell.colspan - 1, headCell.col + headCell.colspan - 1);
|
|
89
|
+
const selectedPositions = new Set(grid.cellPositionsInRect(r0, r1, c0, c1));
|
|
90
|
+
// Clear painting on cells outside the table; mark inside the table.
|
|
91
|
+
for (const el of allCells) {
|
|
92
|
+
const pos = getNodeRef(el)?.pos;
|
|
93
|
+
if (pos !== undefined && selectedPositions.has(pos) && el.closest('table.dx-table') === anchorTable) {
|
|
94
|
+
el.classList.add(CELL_SELECTED_CLASS);
|
|
95
|
+
}
|
|
96
|
+
else {
|
|
97
|
+
el.classList.remove(CELL_SELECTED_CLASS);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
// Reference doc to keep the signature stable for callers that pass it.
|
|
101
|
+
void doc;
|
|
102
|
+
}
|
|
103
|
+
function clearCellSelectionPainting(root) {
|
|
104
|
+
const painted = root.querySelectorAll(`.${CELL_SELECTED_CLASS}`);
|
|
105
|
+
for (let i = 0; i < painted.length; i++) {
|
|
106
|
+
painted.item(i).classList.remove(CELL_SELECTED_CLASS);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
110
|
+
* Paint `dx-image-selected` on the `.dx-image` whose NodeRef.pos matches
|
|
111
|
+
* `sel.anchor`, and remove it from every other image under `root`. Used by
|
|
112
|
+
* NodeSelection to give the user a visible affordance for the selected image.
|
|
113
|
+
*
|
|
114
|
+
* If no matching image is found (e.g., the NodeSelection targets a non-image
|
|
115
|
+
* atomic node), this still clears stale highlights and bails — the model
|
|
116
|
+
* selection remains valid even without DOM affordance.
|
|
117
|
+
*/
|
|
118
|
+
function paintImageSelection(root, sel) {
|
|
119
|
+
const allImages = Array.from(root.querySelectorAll('.dx-image'));
|
|
120
|
+
for (const el of allImages) {
|
|
121
|
+
if (getNodeRef(el)?.pos === sel.anchor) {
|
|
122
|
+
el.classList.add(IMAGE_SELECTED_CLASS);
|
|
123
|
+
}
|
|
124
|
+
else {
|
|
125
|
+
el.classList.remove(IMAGE_SELECTED_CLASS);
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
function clearImageSelectionPainting(root) {
|
|
130
|
+
const painted = root.querySelectorAll(`.${IMAGE_SELECTED_CLASS}`);
|
|
131
|
+
for (let i = 0; i < painted.length; i++) {
|
|
132
|
+
painted.item(i).classList.remove(IMAGE_SELECTED_CLASS);
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
/**
|
|
136
|
+
* Find the DOM text node + offset that corresponds to model position `pos`.
|
|
137
|
+
* Strategy: walk the DOM in document order. Each text node carries a
|
|
138
|
+
* NodeRef (set by renderer.ts) pointing back at the model node and the
|
|
139
|
+
* model position of its [open] boundary. We match by model position.
|
|
140
|
+
*
|
|
141
|
+
* Exported as `locateDomFromModelPos` for collab presence overlays — see
|
|
142
|
+
* `@doxi/collab` `installRemoteCursors`.
|
|
143
|
+
*/
|
|
144
|
+
export function locateDomFromModelPos(root, pos, doc) {
|
|
145
|
+
return locate(root, pos, doc);
|
|
146
|
+
}
|
|
147
|
+
function locate(root, pos, _doc) {
|
|
148
|
+
for (const textNode of collectTextNodes(root)) {
|
|
149
|
+
const ref = findRefForText(textNode);
|
|
150
|
+
if (!ref)
|
|
151
|
+
continue;
|
|
152
|
+
const start = ref.pos;
|
|
153
|
+
const end = start + textNode.data.length;
|
|
154
|
+
if (pos >= start && pos <= end) {
|
|
155
|
+
return { node: textNode, offset: pos - start };
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
// Fallback for positions inside empty blocks (e.g., the tail block produced
|
|
159
|
+
// by splitBlock when the cursor was at the end of a line). Walk elements
|
|
160
|
+
// with NodeRefs and pick the deepest one whose inside-range contains pos.
|
|
161
|
+
let best = null;
|
|
162
|
+
let bestStart = -1;
|
|
163
|
+
const walk = (el) => {
|
|
164
|
+
const ref = getNodeRef(el);
|
|
165
|
+
if (ref) {
|
|
166
|
+
const start = ref.pos;
|
|
167
|
+
const end = start + ref.node.nodeSize;
|
|
168
|
+
const insideStart = start + 1;
|
|
169
|
+
const insideEnd = end - 1;
|
|
170
|
+
if (pos >= insideStart && pos <= insideEnd && start > bestStart) {
|
|
171
|
+
best = el;
|
|
172
|
+
bestStart = start;
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
for (let i = 0; i < el.children.length; i++) {
|
|
176
|
+
const child = el.children[i];
|
|
177
|
+
if (child)
|
|
178
|
+
walk(child);
|
|
179
|
+
}
|
|
180
|
+
};
|
|
181
|
+
walk(root);
|
|
182
|
+
if (best) {
|
|
183
|
+
return { node: best, offset: 0 };
|
|
184
|
+
}
|
|
185
|
+
if (root.lastChild)
|
|
186
|
+
return { node: root.lastChild, offset: 0 };
|
|
187
|
+
return { node: root, offset: 0 };
|
|
188
|
+
}
|
|
189
|
+
function collectTextNodes(root) {
|
|
190
|
+
const out = [];
|
|
191
|
+
const recur = (n) => {
|
|
192
|
+
if (n.nodeType === 3 /* TEXT_NODE */) {
|
|
193
|
+
out.push(n);
|
|
194
|
+
return;
|
|
195
|
+
}
|
|
196
|
+
for (let i = 0; i < n.childNodes.length; i++) {
|
|
197
|
+
const c = n.childNodes[i];
|
|
198
|
+
if (c)
|
|
199
|
+
recur(c);
|
|
200
|
+
}
|
|
201
|
+
};
|
|
202
|
+
recur(root);
|
|
203
|
+
return out;
|
|
204
|
+
}
|
|
205
|
+
function findRefForText(textNode) {
|
|
206
|
+
// The renderer attached NodeRef to the OUTER text element (the unwrapped Text
|
|
207
|
+
// node OR the outermost mark span). Walk up the DOM until we find one.
|
|
208
|
+
let cur = textNode;
|
|
209
|
+
while (cur) {
|
|
210
|
+
const ref = getNodeRef(cur);
|
|
211
|
+
if (ref)
|
|
212
|
+
return ref;
|
|
213
|
+
cur = cur.parentNode;
|
|
214
|
+
}
|
|
215
|
+
return undefined;
|
|
216
|
+
}
|
|
217
|
+
import { TextSelection } from '../state/text-selection.js';
|
|
218
|
+
/**
|
|
219
|
+
* Translate the host window's current selection into a model TextSelection.
|
|
220
|
+
* Returns null when the selection is not inside `root` (or there is none).
|
|
221
|
+
*/
|
|
222
|
+
export function resolveDOMSelection(root, _doc) {
|
|
223
|
+
const win = root.ownerDocument.defaultView;
|
|
224
|
+
if (!win)
|
|
225
|
+
return null;
|
|
226
|
+
const domSel = win.getSelection();
|
|
227
|
+
if (!domSel || domSel.rangeCount === 0)
|
|
228
|
+
return null;
|
|
229
|
+
// Prefer anchor/focus so reverse selections (head < anchor when the user
|
|
230
|
+
// drags right-to-left or shift-arrows backward) are preserved in the
|
|
231
|
+
// model. Fall back to the range's start/end when anchor/focus aren't
|
|
232
|
+
// usable (e.g., happy-dom doesn't expose focusOffset reliably for
|
|
233
|
+
// selections set via setBaseAndExtent).
|
|
234
|
+
const range = domSel.getRangeAt(0);
|
|
235
|
+
let aNode = domSel.anchorNode;
|
|
236
|
+
let aOff = domSel.anchorOffset;
|
|
237
|
+
let fNode = domSel.focusNode;
|
|
238
|
+
let fOff = domSel.focusOffset;
|
|
239
|
+
const haveAnchorFocus = aNode != null && fNode != null && root.contains(aNode) && root.contains(fNode);
|
|
240
|
+
// happy-dom only updates anchor when a selection is created via addRange or
|
|
241
|
+
// setBaseAndExtent; focus stays equal to anchor. Detect that as "AF is
|
|
242
|
+
// collapsed but the range isn't" and fall back to range start/end (loses
|
|
243
|
+
// direction information, but is at least correct).
|
|
244
|
+
const afAppearsCollapsed = aNode === fNode && aOff === fOff;
|
|
245
|
+
const rangeNotCollapsed = !range.collapsed;
|
|
246
|
+
if (!haveAnchorFocus || (afAppearsCollapsed && rangeNotCollapsed)) {
|
|
247
|
+
aNode = range.startContainer;
|
|
248
|
+
aOff = range.startOffset;
|
|
249
|
+
fNode = range.endContainer;
|
|
250
|
+
fOff = range.endOffset;
|
|
251
|
+
}
|
|
252
|
+
if (!aNode || !fNode)
|
|
253
|
+
return null;
|
|
254
|
+
if (!root.contains(aNode) || !root.contains(fNode))
|
|
255
|
+
return null;
|
|
256
|
+
const anchor = domOffsetToModelPos(aNode, aOff);
|
|
257
|
+
const head = domOffsetToModelPos(fNode, fOff);
|
|
258
|
+
if (anchor === null || head === null)
|
|
259
|
+
return null;
|
|
260
|
+
return new TextSelection(anchor, head);
|
|
261
|
+
}
|
|
262
|
+
function domOffsetToModelPos(node, offset) {
|
|
263
|
+
if (node.nodeType === 3 /* TEXT_NODE */) {
|
|
264
|
+
const ref = findAncestorRef(node);
|
|
265
|
+
if (!ref)
|
|
266
|
+
return null;
|
|
267
|
+
return ref.pos + offset;
|
|
268
|
+
}
|
|
269
|
+
// Element selection. The browser uses element + child-offset when no text
|
|
270
|
+
// node is at the precise click point (e.g., user clicked past the end of a
|
|
271
|
+
// line, or the caret sits inside an empty block whose only DOM child is a
|
|
272
|
+
// <br> placeholder).
|
|
273
|
+
const childAt = node.childNodes[offset];
|
|
274
|
+
if (childAt) {
|
|
275
|
+
const tDesc = lastTextDescendant(childAt);
|
|
276
|
+
if (tDesc) {
|
|
277
|
+
const ref = findAncestorRef(tDesc);
|
|
278
|
+
if (ref)
|
|
279
|
+
return ref.pos;
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
const childBefore = node.childNodes[offset - 1];
|
|
283
|
+
if (childBefore) {
|
|
284
|
+
const last = lastTextDescendant(childBefore);
|
|
285
|
+
if (last) {
|
|
286
|
+
const ref = findAncestorRef(last);
|
|
287
|
+
if (ref)
|
|
288
|
+
return ref.pos + last.data.length;
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
// Fall back to the nearest ancestor ref. For text refs, `pos` is already
|
|
292
|
+
// the start of text content. For container refs (e.g., an empty <p>),
|
|
293
|
+
// `pos` is the container's [open] token — a depth-0 boundary that the
|
|
294
|
+
// selectionchange guard rejects. Nudge to the first inside position so the
|
|
295
|
+
// caret is on an editable spot inside the container.
|
|
296
|
+
const ref = findAncestorRef(node);
|
|
297
|
+
if (!ref)
|
|
298
|
+
return null;
|
|
299
|
+
return ref.node.isText ? ref.pos : ref.pos + 1;
|
|
300
|
+
}
|
|
301
|
+
function lastTextDescendant(node) {
|
|
302
|
+
if (node.nodeType === 3)
|
|
303
|
+
return node;
|
|
304
|
+
for (let i = node.childNodes.length - 1; i >= 0; i--) {
|
|
305
|
+
const c = node.childNodes[i];
|
|
306
|
+
if (!c)
|
|
307
|
+
continue;
|
|
308
|
+
const t = lastTextDescendant(c);
|
|
309
|
+
if (t)
|
|
310
|
+
return t;
|
|
311
|
+
}
|
|
312
|
+
return null;
|
|
313
|
+
}
|
|
314
|
+
function findAncestorRef(node) {
|
|
315
|
+
let cur = node;
|
|
316
|
+
while (cur) {
|
|
317
|
+
const ref = getNodeRef(cur);
|
|
318
|
+
if (ref)
|
|
319
|
+
return ref;
|
|
320
|
+
cur = cur.parentNode;
|
|
321
|
+
}
|
|
322
|
+
return undefined;
|
|
323
|
+
}
|
|
324
|
+
//# sourceMappingURL=selection-sync.js.map
|