Skip to content

Commit 216449a

Browse files
author
Zaydek Michels-Gualtieri
committed
Add beginning of the reducer pattern
1 parent 1b9a1df commit 216449a

File tree

11 files changed

+595
-41
lines changed

11 files changed

+595
-41
lines changed

src/Editor/Editor.js

Lines changed: 66 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,11 @@
11
import React from "react"
22
import ReactDOM from "react-dom"
33

4+
import {
5+
computeEditorRangeFromCurrentDOMRange,
6+
convertEditorRangeToDOMRange,
7+
} from "./model/Range"
8+
49
// const MemoElements = React.memo(({ elements }) => (
510
// elements.map(each => (
611
// React.createElement(componentMap[each.type], {
@@ -11,6 +16,24 @@ import ReactDOM from "react-dom"
1116
// ))
1217
// ))
1318

19+
const Paragraph = ({ children }) => (
20+
<p>
21+
{children || (
22+
<br />
23+
)}
24+
</p>
25+
)
26+
27+
const Render = ({ renderable }) => (
28+
renderable.map(each => (
29+
React.createElement(Paragraph /* componentMap[each.type] */, {
30+
...each.props,
31+
key: each.key, // React key
32+
id: each.key, // DOM ID
33+
})
34+
))
35+
)
36+
1437
const Editor = ({
1538
id,
1639
className,
@@ -33,20 +56,19 @@ const Editor = ({
3356
selection.removeAllRanges()
3457
}
3558
ReactDOM.render(
36-
// <MemoElements elements={state.elements} />,
37-
"Hello, world!",
59+
<Render renderable={state.renderable} />,
3860
articleRef.current,
3961
() => {
4062
if (!state.focused) {
4163
// No-op
4264
return
4365
}
44-
// try {
45-
// const userRange = convRangeToUserLiteral(state.range)
46-
// selection.addRange(userRange)
47-
// } catch (error) {
48-
// console.error(error)
49-
// }
66+
try {
67+
const domRange = convertEditorRangeToDOMRange(state.range)
68+
selection.addRange(domRange)
69+
} catch (error) {
70+
console.error(error)
71+
}
5072
},
5173
)
5274
}, [state]),
@@ -62,15 +84,15 @@ const Editor = ({
6284
style={style}
6385

6486
onFocus={e => {
65-
// dispatch({
66-
// type: "FOCUS",
67-
// })
87+
dispatch({
88+
type: "FOCUS",
89+
})
6890
}}
6991

7092
onBlur={e => {
71-
// dispatch({
72-
// type: "BLUR",
73-
// })
93+
dispatch({
94+
type: "BLUR",
95+
})
7496
}}
7597

7698
onPointerDown={e => {
@@ -84,37 +106,39 @@ const Editor = ({
84106
// }
85107
// return
86108
// }
87-
// const range = getCurrentRange(ref.current)
109+
// const range = computeEditorRangeFromCurrentDOMRange(articleRef.current)
88110
// if (!range) {
89111
// // No-op
90112
// return
91113
// }
92-
// dispatch({
93-
// type: "SELECT",
94-
// range,
95-
// })
114+
// // dispatch({
115+
// // type: "SELECT",
116+
// // range,
117+
// // })
96118
}}
97119

98-
onPointerUp={e => {
99-
// isPointerDownRef.current = false
100-
}}
120+
// onPointerUp={e => {
121+
// isPointerDownRef.current = false
122+
// }}
101123

102124
// TODO: Add COMPAT guard for select-all or prevent
103125
// default?
104126
onSelect={e => {
105-
// try {
106-
// const range = getCurrentRange(ref.current)
107-
// if (!range) {
108-
// // No-op
109-
// return
110-
// }
111-
// dispatch({
112-
// type: "SELECT",
113-
// range,
114-
// })
115-
// } catch (error) {
116-
// console.error(error)
117-
// }
127+
try {
128+
const range = computeEditorRangeFromCurrentDOMRange(articleRef.current)
129+
if (!range) {
130+
// No-op
131+
return
132+
}
133+
console.log(range)
134+
135+
// dispatch({
136+
// type: "SELECT",
137+
// range,
138+
// })
139+
} catch (error) {
140+
console.error(`onSelect: error=${error}`)
141+
}
118142
}}
119143

120144
onKeyDown={e => {
@@ -166,7 +190,7 @@ const Editor = ({
166190
// if (formatType !== "plaintext") {
167191
// // types[formatType] = {}
168192
// types[formatType] = formatType !== "a" ? {} : {
169-
// href: "TODO",
193+
// harticleRef: "TODO",
170194
// }
171195
// }
172196
// dispatch({
@@ -203,8 +227,8 @@ const Editor = ({
203227
// end: state.range.start,
204228
// }
205229
// setTimeout(() => {
206-
// const userRange = convRangeToUserLiteral(range)
207-
// selection.addRange(userRange)
230+
// const domRange = convertEditorRangeToDOMRange(range)
231+
// selection.addRange(domRange)
208232
// }, 0)
209233
// }
210234
// dispatch({
@@ -253,7 +277,7 @@ const Editor = ({
253277
}}
254278

255279
onCompositionEnd={e => {
256-
// const range = getCurrentRange(ref.current)
280+
// const range = computeEditorRangeFromCurrentDOMRange(articleRef.current)
257281
// const children = parseRenderedChildren(document.getElementById(range.start.key))
258282
// dispatch({
259283
// type: "UNCONTROLLED_INPUT",
@@ -264,7 +288,7 @@ const Editor = ({
264288
}}
265289

266290
onInput={e => {
267-
// const range = getCurrentRange(ref.current)
291+
// const range = computeEditorRangeFromCurrentDOMRange(articleRef.current)
268292
// const children = parseRenderedChildren(document.getElementById(range.start.key))
269293
// dispatch({
270294
// type: "UNCONTROLLED_INPUT",
@@ -303,11 +327,12 @@ const tabSize = n => ({
303327
tabSize: n,
304328
})
305329

306-
const EditorWithDebugger = ({ state, dispatch }) => (
330+
const EditorWithDebugger = ({ state, dispatch, ...props }) => (
307331
<>
308332
<Editor
309333
state={state}
310334
dispatch={dispatch}
335+
{...props}
311336
/>
312337
{process.env.NODE_ENV !== "production" && (
313338
<pre className="mt-6 text-xs whitespace-pre-wrap break-words" style={tabSize(2)}>

src/Editor/model/Range/Position.js

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
import domUtils from "lib/DOM/domUtils"
2+
3+
export function computeEditorPositionFromDOMPosition({ node, offset: originalOffset }) {
4+
// Guard non-contenteditable descendants:
5+
if (!domUtils.ascendElement(node).closest("[contenteditable='true']")) {
6+
return null
7+
}
8+
// Guard node and originalOffset (1 of 2):
9+
while (!domUtils.isTextNodeOrBrElement(node)) {
10+
if (originalOffset && originalOffset === node.childNodes.length) {
11+
originalOffset = node.childNodes.length - 1
12+
}
13+
node = node.childNodes[originalOffset]
14+
originalOffset = 0
15+
}
16+
// Guard originalOffset (2 of 2):
17+
if (originalOffset > (node.nodeValue || "").length) {
18+
// TODO
19+
originalOffset = Math.max(0, (node.nodeValue || "").length)
20+
}
21+
let key = ""
22+
let offset = 0
23+
const recurse = on => {
24+
if (on === node) {
25+
key = domUtils.ascendElementID(on).id
26+
offset += originalOffset
27+
return true
28+
}
29+
for (const each of on.childNodes) {
30+
if (recurse(each)) {
31+
return true
32+
}
33+
offset += domUtils.isTextNode(each) &&
34+
each.nodeValue.length
35+
}
36+
return false
37+
}
38+
recurse(domUtils.ascendElementID(node))
39+
return { key, offset }
40+
}
41+
42+
export function convertEditorPositionToDOMPosition(pos) {
43+
let { key, offset: originalOffset } = pos
44+
45+
let node = null
46+
let offset = 0
47+
const recurse = on => {
48+
if (domUtils.isTextNodeOrBrElement(on) && originalOffset - (on.nodeValue || "").length <= 0) {
49+
node = on
50+
offset = originalOffset
51+
return true
52+
}
53+
for (const each of on.childNodes) {
54+
if (recurse(each)) {
55+
return true
56+
}
57+
originalOffset -= domUtils.isTextNode(each) &&
58+
each.nodeValue.length
59+
}
60+
return false
61+
}
62+
recurse(document.getElementById(key))
63+
return { node, offset }
64+
}

0 commit comments

Comments
 (0)