diff options
Diffstat (limited to 'src/rich-text.js')
| -rw-r--r-- | src/rich-text.js | 38 |
1 files changed, 26 insertions, 12 deletions
diff --git a/src/rich-text.js b/src/rich-text.js index 7003f5c..b766767 100644 --- a/src/rich-text.js +++ b/src/rich-text.js @@ -46,20 +46,34 @@ function offsetOf(root, node, offset) { return t.length; } -// (node, offset) for a text position; the field is normalised to text nodes -// and <br>s, so that is all this walks. +// (node, offset) for a text position, counted the way textOf counts: text, +// <br>s, and the line break a new block (<div>, <p>) stands for — Chrome +// wraps lines in <div>s once Enter has been pressed, and a walk of the top +// level alone put every later position at the end of the field. function pointAt(root, pos) { - let left = pos; - for (const c of root.childNodes) { - if (c.nodeType === 3) { - if (left <= c.data.length) return [c, left]; - left -= c.data.length; - } else if (c.nodeName === 'BR') { - if (left === 0) return [root, [...root.childNodes].indexOf(c)]; - left -= 1; + let left = pos, out = ''; + const walk = (n) => { + const kids = [...n.childNodes]; + for (let i = 0; i < kids.length; i++) { + const c = kids[i]; + if (c.nodeType === 3) { + if (left <= c.data.length) return [c, left]; + left -= c.data.length; out += c.data; + } else if (c.nodeName === 'BR') { + if (left === 0) return [n, i]; + left -= 1; out += '\n'; + } else if (c.nodeType === 1) { + if (BLOCK.has(c.nodeName) && out && !out.endsWith('\n')) { + if (left === 0) return [n, i]; + left -= 1; out += '\n'; + } + const got = walk(c); + if (got) return got; + } } - } - return [root, root.childNodes.length]; + return null; + }; + return walk(root) || [root, root.childNodes.length]; } const mediaOf = (dt) => [...((dt && dt.files) || [])].filter((f) => /^(image|video)\//.test(f.type)); |
