UNPKG

rhino-editor

Version:

A custom element wrapped rich text editor

1,366 lines (1,365 loc) 350 kB
import { AllSelection, DOMParser, DOMSerializer, EditorState, Fragment, Mark, Node, NodeRange, NodeSelection, Plugin, PluginKey, RemoveMarkStep, ReplaceAroundStep, ReplaceStep, Schema, Selection, Slice, TextSelection, Transform, canJoin, canSplit, dropPoint, findWrapping, joinPoint, liftTarget, replaceStep } from "./chunk-XG3W5CFT.js"; // node_modules/.pnpm/prosemirror-view@1.28.3/node_modules/prosemirror-view/dist/index.js var domIndex = function(node) { for (var index = 0; ; index++) { node = node.previousSibling; if (!node) return index; } }; var parentNode = function(node) { let parent = node.assignedSlot || node.parentNode; return parent && parent.nodeType == 11 ? parent.host : parent; }; var reusedRange = null; var textRange = function(node, from, to) { let range = reusedRange || (reusedRange = document.createRange()); range.setEnd(node, to == null ? node.nodeValue.length : to); range.setStart(node, from || 0); return range; }; var isEquivalentPosition = function(node, off, targetNode, targetOff) { return targetNode && (scanFor(node, off, targetNode, targetOff, -1) || scanFor(node, off, targetNode, targetOff, 1)); }; var atomElements = /^(img|br|input|textarea|hr)$/i; function scanFor(node, off, targetNode, targetOff, dir) { for (; ; ) { if (node == targetNode && off == targetOff) return true; if (off == (dir < 0 ? 0 : nodeSize(node))) { let parent = node.parentNode; if (!parent || parent.nodeType != 1 || hasBlockDesc(node) || atomElements.test(node.nodeName) || node.contentEditable == "false") return false; off = domIndex(node) + (dir < 0 ? 0 : 1); node = parent; } else if (node.nodeType == 1) { node = node.childNodes[off + (dir < 0 ? -1 : 0)]; if (node.contentEditable == "false") return false; off = dir < 0 ? nodeSize(node) : 0; } else { return false; } } } function nodeSize(node) { return node.nodeType == 3 ? node.nodeValue.length : node.childNodes.length; } function isOnEdge(node, offset, parent) { for (let atStart = offset == 0, atEnd = offset == nodeSize(node); atStart || atEnd; ) { if (node == parent) return true; let index = domIndex(node); node = node.parentNode; if (!node) return false; atStart = atStart && index == 0; atEnd = atEnd && index == nodeSize(node); } } function hasBlockDesc(dom) { let desc; for (let cur = dom; cur; cur = cur.parentNode) if (desc = cur.pmViewDesc) break; return desc && desc.node && desc.node.isBlock && (desc.dom == dom || desc.contentDOM == dom); } var selectionCollapsed = function(domSel) { return domSel.focusNode && isEquivalentPosition(domSel.focusNode, domSel.focusOffset, domSel.anchorNode, domSel.anchorOffset); }; function keyEvent(keyCode, key) { let event = document.createEvent("Event"); event.initEvent("keydown", true, true); event.keyCode = keyCode; event.key = event.code = key; return event; } function deepActiveElement(doc2) { let elt = doc2.activeElement; while (elt && elt.shadowRoot) elt = elt.shadowRoot.activeElement; return elt; } var nav = typeof navigator != "undefined" ? navigator : null; var doc = typeof document != "undefined" ? document : null; var agent = nav && nav.userAgent || ""; var ie_edge = /Edge\/(\d+)/.exec(agent); var ie_upto10 = /MSIE \d/.exec(agent); var ie_11up = /Trident\/(?:[7-9]|\d{2,})\..*rv:(\d+)/.exec(agent); var ie = !!(ie_upto10 || ie_11up || ie_edge); var ie_version = ie_upto10 ? document.documentMode : ie_11up ? +ie_11up[1] : ie_edge ? +ie_edge[1] : 0; var gecko = !ie && /gecko\/(\d+)/i.test(agent); gecko && +(/Firefox\/(\d+)/.exec(agent) || [0, 0])[1]; var _chrome = !ie && /Chrome\/(\d+)/.exec(agent); var chrome = !!_chrome; var chrome_version = _chrome ? +_chrome[1] : 0; var safari = !ie && !!nav && /Apple Computer/.test(nav.vendor); var ios = safari && (/Mobile\/\w+/.test(agent) || !!nav && nav.maxTouchPoints > 2); var mac = ios || (nav ? /Mac/.test(nav.platform) : false); var android = /Android \d/.test(agent); var webkit = !!doc && "webkitFontSmoothing" in doc.documentElement.style; var webkit_version = webkit ? +(/\bAppleWebKit\/(\d+)/.exec(navigator.userAgent) || [0, 0])[1] : 0; function windowRect(doc2) { return { left: 0, right: doc2.documentElement.clientWidth, top: 0, bottom: doc2.documentElement.clientHeight }; } function getSide(value, side) { return typeof value == "number" ? value : value[side]; } function clientRect(node) { let rect = node.getBoundingClientRect(); let scaleX = rect.width / node.offsetWidth || 1; let scaleY = rect.height / node.offsetHeight || 1; return { left: rect.left, right: rect.left + node.clientWidth * scaleX, top: rect.top, bottom: rect.top + node.clientHeight * scaleY }; } function scrollRectIntoView(view, rect, startDOM) { let scrollThreshold = view.someProp("scrollThreshold") || 0, scrollMargin = view.someProp("scrollMargin") || 5; let doc2 = view.dom.ownerDocument; for (let parent = startDOM || view.dom; ; parent = parentNode(parent)) { if (!parent) break; if (parent.nodeType != 1) continue; let elt = parent; let atTop = elt == doc2.body; let bounding = atTop ? windowRect(doc2) : clientRect(elt); let moveX = 0, moveY = 0; if (rect.top < bounding.top + getSide(scrollThreshold, "top")) moveY = -(bounding.top - rect.top + getSide(scrollMargin, "top")); else if (rect.bottom > bounding.bottom - getSide(scrollThreshold, "bottom")) moveY = rect.bottom - bounding.bottom + getSide(scrollMargin, "bottom"); if (rect.left < bounding.left + getSide(scrollThreshold, "left")) moveX = -(bounding.left - rect.left + getSide(scrollMargin, "left")); else if (rect.right > bounding.right - getSide(scrollThreshold, "right")) moveX = rect.right - bounding.right + getSide(scrollMargin, "right"); if (moveX || moveY) { if (atTop) { doc2.defaultView.scrollBy(moveX, moveY); } else { let startX = elt.scrollLeft, startY = elt.scrollTop; if (moveY) elt.scrollTop += moveY; if (moveX) elt.scrollLeft += moveX; let dX = elt.scrollLeft - startX, dY = elt.scrollTop - startY; rect = { left: rect.left - dX, top: rect.top - dY, right: rect.right - dX, bottom: rect.bottom - dY }; } } if (atTop) break; } } function storeScrollPos(view) { let rect = view.dom.getBoundingClientRect(), startY = Math.max(0, rect.top); let refDOM, refTop; for (let x = (rect.left + rect.right) / 2, y = startY + 1; y < Math.min(innerHeight, rect.bottom); y += 5) { let dom = view.root.elementFromPoint(x, y); if (!dom || dom == view.dom || !view.dom.contains(dom)) continue; let localRect = dom.getBoundingClientRect(); if (localRect.top >= startY - 20) { refDOM = dom; refTop = localRect.top; break; } } return { refDOM, refTop, stack: scrollStack(view.dom) }; } function scrollStack(dom) { let stack = [], doc2 = dom.ownerDocument; for (let cur = dom; cur; cur = parentNode(cur)) { stack.push({ dom: cur, top: cur.scrollTop, left: cur.scrollLeft }); if (dom == doc2) break; } return stack; } function resetScrollPos({ refDOM, refTop, stack }) { let newRefTop = refDOM ? refDOM.getBoundingClientRect().top : 0; restoreScrollStack(stack, newRefTop == 0 ? 0 : newRefTop - refTop); } function restoreScrollStack(stack, dTop) { for (let i = 0; i < stack.length; i++) { let { dom, top, left } = stack[i]; if (dom.scrollTop != top + dTop) dom.scrollTop = top + dTop; if (dom.scrollLeft != left) dom.scrollLeft = left; } } var preventScrollSupported = null; function focusPreventScroll(dom) { if (dom.setActive) return dom.setActive(); if (preventScrollSupported) return dom.focus(preventScrollSupported); let stored = scrollStack(dom); dom.focus(preventScrollSupported == null ? { get preventScroll() { preventScrollSupported = { preventScroll: true }; return true; } } : void 0); if (!preventScrollSupported) { preventScrollSupported = false; restoreScrollStack(stored, 0); } } function findOffsetInNode(node, coords) { let closest, dxClosest = 2e8, coordsClosest, offset = 0; let rowBot = coords.top, rowTop = coords.top; for (let child = node.firstChild, childIndex = 0; child; child = child.nextSibling, childIndex++) { let rects; if (child.nodeType == 1) rects = child.getClientRects(); else if (child.nodeType == 3) rects = textRange(child).getClientRects(); else continue; for (let i = 0; i < rects.length; i++) { let rect = rects[i]; if (rect.top <= rowBot && rect.bottom >= rowTop) { rowBot = Math.max(rect.bottom, rowBot); rowTop = Math.min(rect.top, rowTop); let dx = rect.left > coords.left ? rect.left - coords.left : rect.right < coords.left ? coords.left - rect.right : 0; if (dx < dxClosest) { closest = child; dxClosest = dx; coordsClosest = dx && closest.nodeType == 3 ? { left: rect.right < coords.left ? rect.right : rect.left, top: coords.top } : coords; if (child.nodeType == 1 && dx) offset = childIndex + (coords.left >= (rect.left + rect.right) / 2 ? 1 : 0); continue; } } if (!closest && (coords.left >= rect.right && coords.top >= rect.top || coords.left >= rect.left && coords.top >= rect.bottom)) offset = childIndex + 1; } } if (closest && closest.nodeType == 3) return findOffsetInText(closest, coordsClosest); if (!closest || dxClosest && closest.nodeType == 1) return { node, offset }; return findOffsetInNode(closest, coordsClosest); } function findOffsetInText(node, coords) { let len = node.nodeValue.length; let range = document.createRange(); for (let i = 0; i < len; i++) { range.setEnd(node, i + 1); range.setStart(node, i); let rect = singleRect(range, 1); if (rect.top == rect.bottom) continue; if (inRect(coords, rect)) return { node, offset: i + (coords.left >= (rect.left + rect.right) / 2 ? 1 : 0) }; } return { node, offset: 0 }; } function inRect(coords, rect) { return coords.left >= rect.left - 1 && coords.left <= rect.right + 1 && coords.top >= rect.top - 1 && coords.top <= rect.bottom + 1; } function targetKludge(dom, coords) { let parent = dom.parentNode; if (parent && /^li$/i.test(parent.nodeName) && coords.left < dom.getBoundingClientRect().left) return parent; return dom; } function posFromElement(view, elt, coords) { let { node, offset } = findOffsetInNode(elt, coords), bias = -1; if (node.nodeType == 1 && !node.firstChild) { let rect = node.getBoundingClientRect(); bias = rect.left != rect.right && coords.left > (rect.left + rect.right) / 2 ? 1 : -1; } return view.docView.posFromDOM(node, offset, bias); } function posFromCaret(view, node, offset, coords) { let outside = -1; for (let cur = node; ; ) { if (cur == view.dom) break; let desc = view.docView.nearestDesc(cur, true); if (!desc) return null; if (desc.node.isBlock && desc.parent) { let rect = desc.dom.getBoundingClientRect(); if (rect.left > coords.left || rect.top > coords.top) outside = desc.posBefore; else if (rect.right < coords.left || rect.bottom < coords.top) outside = desc.posAfter; else break; } cur = desc.dom.parentNode; } return outside > -1 ? outside : view.docView.posFromDOM(node, offset, 1); } function elementFromPoint(element, coords, box) { let len = element.childNodes.length; if (len && box.top < box.bottom) { for (let startI = Math.max(0, Math.min(len - 1, Math.floor(len * (coords.top - box.top) / (box.bottom - box.top)) - 2)), i = startI; ; ) { let child = element.childNodes[i]; if (child.nodeType == 1) { let rects = child.getClientRects(); for (let j = 0; j < rects.length; j++) { let rect = rects[j]; if (inRect(coords, rect)) return elementFromPoint(child, coords, rect); } } if ((i = (i + 1) % len) == startI) break; } } return element; } function posAtCoords(view, coords) { let doc2 = view.dom.ownerDocument, node, offset = 0; if (doc2.caretPositionFromPoint) { try { let pos2 = doc2.caretPositionFromPoint(coords.left, coords.top); if (pos2) ({ offsetNode: node, offset } = pos2); } catch (_) { } } if (!node && doc2.caretRangeFromPoint) { let range = doc2.caretRangeFromPoint(coords.left, coords.top); if (range) ({ startContainer: node, startOffset: offset } = range); } let elt = (view.root.elementFromPoint ? view.root : doc2).elementFromPoint(coords.left, coords.top); let pos; if (!elt || !view.dom.contains(elt.nodeType != 1 ? elt.parentNode : elt)) { let box = view.dom.getBoundingClientRect(); if (!inRect(coords, box)) return null; elt = elementFromPoint(view.dom, coords, box); if (!elt) return null; } if (safari) { for (let p = elt; node && p; p = parentNode(p)) if (p.draggable) node = void 0; } elt = targetKludge(elt, coords); if (node) { if (gecko && node.nodeType == 1) { offset = Math.min(offset, node.childNodes.length); if (offset < node.childNodes.length) { let next = node.childNodes[offset], box; if (next.nodeName == "IMG" && (box = next.getBoundingClientRect()).right <= coords.left && box.bottom > coords.top) offset++; } } if (node == view.dom && offset == node.childNodes.length - 1 && node.lastChild.nodeType == 1 && coords.top > node.lastChild.getBoundingClientRect().bottom) pos = view.state.doc.content.size; else if (offset == 0 || node.nodeType != 1 || node.childNodes[offset - 1].nodeName != "BR") pos = posFromCaret(view, node, offset, coords); } if (pos == null) pos = posFromElement(view, elt, coords); let desc = view.docView.nearestDesc(elt, true); return { pos, inside: desc ? desc.posAtStart - desc.border : -1 }; } function singleRect(target, bias) { let rects = target.getClientRects(); return !rects.length ? target.getBoundingClientRect() : rects[bias < 0 ? 0 : rects.length - 1]; } var BIDI = /[\u0590-\u05f4\u0600-\u06ff\u0700-\u08ac]/; function coordsAtPos(view, pos, side) { let { node, offset, atom } = view.docView.domFromPos(pos, side < 0 ? -1 : 1); let supportEmptyRange = webkit || gecko; if (node.nodeType == 3) { if (supportEmptyRange && (BIDI.test(node.nodeValue) || (side < 0 ? !offset : offset == node.nodeValue.length))) { let rect = singleRect(textRange(node, offset, offset), side); if (gecko && offset && /\s/.test(node.nodeValue[offset - 1]) && offset < node.nodeValue.length) { let rectBefore = singleRect(textRange(node, offset - 1, offset - 1), -1); if (rectBefore.top == rect.top) { let rectAfter = singleRect(textRange(node, offset, offset + 1), -1); if (rectAfter.top != rect.top) return flattenV(rectAfter, rectAfter.left < rectBefore.left); } } return rect; } else { let from = offset, to = offset, takeSide = side < 0 ? 1 : -1; if (side < 0 && !offset) { to++; takeSide = -1; } else if (side >= 0 && offset == node.nodeValue.length) { from--; takeSide = 1; } else if (side < 0) { from--; } else { to++; } return flattenV(singleRect(textRange(node, from, to), 1), takeSide < 0); } } let $dom = view.state.doc.resolve(pos - (atom || 0)); if (!$dom.parent.inlineContent) { if (atom == null && offset && (side < 0 || offset == nodeSize(node))) { let before = node.childNodes[offset - 1]; if (before.nodeType == 1) return flattenH(before.getBoundingClientRect(), false); } if (atom == null && offset < nodeSize(node)) { let after = node.childNodes[offset]; if (after.nodeType == 1) return flattenH(after.getBoundingClientRect(), true); } return flattenH(node.getBoundingClientRect(), side >= 0); } if (atom == null && offset && (side < 0 || offset == nodeSize(node))) { let before = node.childNodes[offset - 1]; let target = before.nodeType == 3 ? textRange(before, nodeSize(before) - (supportEmptyRange ? 0 : 1)) : before.nodeType == 1 && (before.nodeName != "BR" || !before.nextSibling) ? before : null; if (target) return flattenV(singleRect(target, 1), false); } if (atom == null && offset < nodeSize(node)) { let after = node.childNodes[offset]; while (after.pmViewDesc && after.pmViewDesc.ignoreForCoords) after = after.nextSibling; let target = !after ? null : after.nodeType == 3 ? textRange(after, 0, supportEmptyRange ? 0 : 1) : after.nodeType == 1 ? after : null; if (target) return flattenV(singleRect(target, -1), true); } return flattenV(singleRect(node.nodeType == 3 ? textRange(node) : node, -side), side >= 0); } function flattenV(rect, left) { if (rect.width == 0) return rect; let x = left ? rect.left : rect.right; return { top: rect.top, bottom: rect.bottom, left: x, right: x }; } function flattenH(rect, top) { if (rect.height == 0) return rect; let y = top ? rect.top : rect.bottom; return { top: y, bottom: y, left: rect.left, right: rect.right }; } function withFlushedState(view, state, f) { let viewState = view.state, active = view.root.activeElement; if (viewState != state) view.updateState(state); if (active != view.dom) view.focus(); try { return f(); } finally { if (viewState != state) view.updateState(viewState); if (active != view.dom && active) active.focus(); } } function endOfTextblockVertical(view, state, dir) { let sel = state.selection; let $pos = dir == "up" ? sel.$from : sel.$to; return withFlushedState(view, state, () => { let { node: dom } = view.docView.domFromPos($pos.pos, dir == "up" ? -1 : 1); for (; ; ) { let nearest = view.docView.nearestDesc(dom, true); if (!nearest) break; if (nearest.node.isBlock) { dom = nearest.dom; break; } dom = nearest.dom.parentNode; } let coords = coordsAtPos(view, $pos.pos, 1); for (let child = dom.firstChild; child; child = child.nextSibling) { let boxes; if (child.nodeType == 1) boxes = child.getClientRects(); else if (child.nodeType == 3) boxes = textRange(child, 0, child.nodeValue.length).getClientRects(); else continue; for (let i = 0; i < boxes.length; i++) { let box = boxes[i]; if (box.bottom > box.top + 1 && (dir == "up" ? coords.top - box.top > (box.bottom - coords.top) * 2 : box.bottom - coords.bottom > (coords.bottom - box.top) * 2)) return false; } } return true; }); } var maybeRTL = /[\u0590-\u08ac]/; function endOfTextblockHorizontal(view, state, dir) { let { $head } = state.selection; if (!$head.parent.isTextblock) return false; let offset = $head.parentOffset, atStart = !offset, atEnd = offset == $head.parent.content.size; let sel = view.domSelection(); if (!maybeRTL.test($head.parent.textContent) || !sel.modify) return dir == "left" || dir == "backward" ? atStart : atEnd; return withFlushedState(view, state, () => { let { focusNode: oldNode, focusOffset: oldOff, anchorNode, anchorOffset } = view.domSelectionRange(); let oldBidiLevel = sel.caretBidiLevel; sel.modify("move", dir, "character"); let parentDOM = $head.depth ? view.docView.domAfterPos($head.before()) : view.dom; let { focusNode: newNode, focusOffset: newOff } = view.domSelectionRange(); let result = newNode && !parentDOM.contains(newNode.nodeType == 1 ? newNode : newNode.parentNode) || oldNode == newNode && oldOff == newOff; try { sel.collapse(anchorNode, anchorOffset); if (oldNode && (oldNode != anchorNode || oldOff != anchorOffset) && sel.extend) sel.extend(oldNode, oldOff); } catch (_) { } if (oldBidiLevel != null) sel.caretBidiLevel = oldBidiLevel; return result; }); } var cachedState = null; var cachedDir = null; var cachedResult = false; function endOfTextblock(view, state, dir) { if (cachedState == state && cachedDir == dir) return cachedResult; cachedState = state; cachedDir = dir; return cachedResult = dir == "up" || dir == "down" ? endOfTextblockVertical(view, state, dir) : endOfTextblockHorizontal(view, state, dir); } var NOT_DIRTY = 0; var CHILD_DIRTY = 1; var CONTENT_DIRTY = 2; var NODE_DIRTY = 3; var ViewDesc = class { constructor(parent, children, dom, contentDOM) { this.parent = parent; this.children = children; this.dom = dom; this.contentDOM = contentDOM; this.dirty = NOT_DIRTY; dom.pmViewDesc = this; } // Used to check whether a given description corresponds to a // widget/mark/node. matchesWidget(widget) { return false; } matchesMark(mark) { return false; } matchesNode(node, outerDeco, innerDeco) { return false; } matchesHack(nodeName) { return false; } // When parsing in-editor content (in domchange.js), we allow // descriptions to determine the parse rules that should be used to // parse them. parseRule() { return null; } // Used by the editor's event handler to ignore events that come // from certain descs. stopEvent(event) { return false; } // The size of the content represented by this desc. get size() { let size = 0; for (let i = 0; i < this.children.length; i++) size += this.children[i].size; return size; } // For block nodes, this represents the space taken up by their // start/end tokens. get border() { return 0; } destroy() { this.parent = void 0; if (this.dom.pmViewDesc == this) this.dom.pmViewDesc = void 0; for (let i = 0; i < this.children.length; i++) this.children[i].destroy(); } posBeforeChild(child) { for (let i = 0, pos = this.posAtStart; ; i++) { let cur = this.children[i]; if (cur == child) return pos; pos += cur.size; } } get posBefore() { return this.parent.posBeforeChild(this); } get posAtStart() { return this.parent ? this.parent.posBeforeChild(this) + this.border : 0; } get posAfter() { return this.posBefore + this.size; } get posAtEnd() { return this.posAtStart + this.size - 2 * this.border; } localPosFromDOM(dom, offset, bias) { if (this.contentDOM && this.contentDOM.contains(dom.nodeType == 1 ? dom : dom.parentNode)) { if (bias < 0) { let domBefore, desc; if (dom == this.contentDOM) { domBefore = dom.childNodes[offset - 1]; } else { while (dom.parentNode != this.contentDOM) dom = dom.parentNode; domBefore = dom.previousSibling; } while (domBefore && !((desc = domBefore.pmViewDesc) && desc.parent == this)) domBefore = domBefore.previousSibling; return domBefore ? this.posBeforeChild(desc) + desc.size : this.posAtStart; } else { let domAfter, desc; if (dom == this.contentDOM) { domAfter = dom.childNodes[offset]; } else { while (dom.parentNode != this.contentDOM) dom = dom.parentNode; domAfter = dom.nextSibling; } while (domAfter && !((desc = domAfter.pmViewDesc) && desc.parent == this)) domAfter = domAfter.nextSibling; return domAfter ? this.posBeforeChild(desc) : this.posAtEnd; } } let atEnd; if (dom == this.dom && this.contentDOM) { atEnd = offset > domIndex(this.contentDOM); } else if (this.contentDOM && this.contentDOM != this.dom && this.dom.contains(this.contentDOM)) { atEnd = dom.compareDocumentPosition(this.contentDOM) & 2; } else if (this.dom.firstChild) { if (offset == 0) for (let search = dom; ; search = search.parentNode) { if (search == this.dom) { atEnd = false; break; } if (search.previousSibling) break; } if (atEnd == null && offset == dom.childNodes.length) for (let search = dom; ; search = search.parentNode) { if (search == this.dom) { atEnd = true; break; } if (search.nextSibling) break; } } return (atEnd == null ? bias > 0 : atEnd) ? this.posAtEnd : this.posAtStart; } // Scan up the dom finding the first desc that is a descendant of // this one. nearestDesc(dom, onlyNodes = false) { for (let first2 = true, cur = dom; cur; cur = cur.parentNode) { let desc = this.getDesc(cur), nodeDOM; if (desc && (!onlyNodes || desc.node)) { if (first2 && (nodeDOM = desc.nodeDOM) && !(nodeDOM.nodeType == 1 ? nodeDOM.contains(dom.nodeType == 1 ? dom : dom.parentNode) : nodeDOM == dom)) first2 = false; else return desc; } } } getDesc(dom) { let desc = dom.pmViewDesc; for (let cur = desc; cur; cur = cur.parent) if (cur == this) return desc; } posFromDOM(dom, offset, bias) { for (let scan = dom; scan; scan = scan.parentNode) { let desc = this.getDesc(scan); if (desc) return desc.localPosFromDOM(dom, offset, bias); } return -1; } // Find the desc for the node after the given pos, if any. (When a // parent node overrode rendering, there might not be one.) descAt(pos) { for (let i = 0, offset = 0; i < this.children.length; i++) { let child = this.children[i], end = offset + child.size; if (offset == pos && end != offset) { while (!child.border && child.children.length) child = child.children[0]; return child; } if (pos < end) return child.descAt(pos - offset - child.border); offset = end; } } domFromPos(pos, side) { if (!this.contentDOM) return { node: this.dom, offset: 0, atom: pos + 1 }; let i = 0, offset = 0; for (let curPos = 0; i < this.children.length; i++) { let child = this.children[i], end = curPos + child.size; if (end > pos || child instanceof TrailingHackViewDesc) { offset = pos - curPos; break; } curPos = end; } if (offset) return this.children[i].domFromPos(offset - this.children[i].border, side); for (let prev; i && !(prev = this.children[i - 1]).size && prev instanceof WidgetViewDesc && prev.side >= 0; i--) { } if (side <= 0) { let prev, enter2 = true; for (; ; i--, enter2 = false) { prev = i ? this.children[i - 1] : null; if (!prev || prev.dom.parentNode == this.contentDOM) break; } if (prev && side && enter2 && !prev.border && !prev.domAtom) return prev.domFromPos(prev.size, side); return { node: this.contentDOM, offset: prev ? domIndex(prev.dom) + 1 : 0 }; } else { let next, enter2 = true; for (; ; i++, enter2 = false) { next = i < this.children.length ? this.children[i] : null; if (!next || next.dom.parentNode == this.contentDOM) break; } if (next && enter2 && !next.border && !next.domAtom) return next.domFromPos(0, side); return { node: this.contentDOM, offset: next ? domIndex(next.dom) : this.contentDOM.childNodes.length }; } } // Used to find a DOM range in a single parent for a given changed // range. parseRange(from, to, base2 = 0) { if (this.children.length == 0) return { node: this.contentDOM, from, to, fromOffset: 0, toOffset: this.contentDOM.childNodes.length }; let fromOffset = -1, toOffset = -1; for (let offset = base2, i = 0; ; i++) { let child = this.children[i], end = offset + child.size; if (fromOffset == -1 && from <= end) { let childBase = offset + child.border; if (from >= childBase && to <= end - child.border && child.node && child.contentDOM && this.contentDOM.contains(child.contentDOM)) return child.parseRange(from, to, childBase); from = offset; for (let j = i; j > 0; j--) { let prev = this.children[j - 1]; if (prev.size && prev.dom.parentNode == this.contentDOM && !prev.emptyChildAt(1)) { fromOffset = domIndex(prev.dom) + 1; break; } from -= prev.size; } if (fromOffset == -1) fromOffset = 0; } if (fromOffset > -1 && (end > to || i == this.children.length - 1)) { to = end; for (let j = i + 1; j < this.children.length; j++) { let next = this.children[j]; if (next.size && next.dom.parentNode == this.contentDOM && !next.emptyChildAt(-1)) { toOffset = domIndex(next.dom); break; } to += next.size; } if (toOffset == -1) toOffset = this.contentDOM.childNodes.length; break; } offset = end; } return { node: this.contentDOM, from, to, fromOffset, toOffset }; } emptyChildAt(side) { if (this.border || !this.contentDOM || !this.children.length) return false; let child = this.children[side < 0 ? 0 : this.children.length - 1]; return child.size == 0 || child.emptyChildAt(side); } domAfterPos(pos) { let { node, offset } = this.domFromPos(pos, 0); if (node.nodeType != 1 || offset == node.childNodes.length) throw new RangeError("No node after pos " + pos); return node.childNodes[offset]; } // View descs are responsible for setting any selection that falls // entirely inside of them, so that custom implementations can do // custom things with the selection. Note that this falls apart when // a selection starts in such a node and ends in another, in which // case we just use whatever domFromPos produces as a best effort. setSelection(anchor, head, root, force = false) { let from = Math.min(anchor, head), to = Math.max(anchor, head); for (let i = 0, offset = 0; i < this.children.length; i++) { let child = this.children[i], end = offset + child.size; if (from > offset && to < end) return child.setSelection(anchor - offset - child.border, head - offset - child.border, root, force); offset = end; } let anchorDOM = this.domFromPos(anchor, anchor ? -1 : 1); let headDOM = head == anchor ? anchorDOM : this.domFromPos(head, head ? -1 : 1); let domSel = root.getSelection(); let brKludge = false; if ((gecko || safari) && anchor == head) { let { node, offset } = anchorDOM; if (node.nodeType == 3) { brKludge = !!(offset && node.nodeValue[offset - 1] == "\n"); if (brKludge && offset == node.nodeValue.length) { for (let scan = node, after; scan; scan = scan.parentNode) { if (after = scan.nextSibling) { if (after.nodeName == "BR") anchorDOM = headDOM = { node: after.parentNode, offset: domIndex(after) + 1 }; break; } let desc = scan.pmViewDesc; if (desc && desc.node && desc.node.isBlock) break; } } } else { let prev = node.childNodes[offset - 1]; brKludge = prev && (prev.nodeName == "BR" || prev.contentEditable == "false"); } } if (gecko && domSel.focusNode && domSel.focusNode != headDOM.node && domSel.focusNode.nodeType == 1) { let after = domSel.focusNode.childNodes[domSel.focusOffset]; if (after && after.contentEditable == "false") force = true; } if (!(force || brKludge && safari) && isEquivalentPosition(anchorDOM.node, anchorDOM.offset, domSel.anchorNode, domSel.anchorOffset) && isEquivalentPosition(headDOM.node, headDOM.offset, domSel.focusNode, domSel.focusOffset)) return; let domSelExtended = false; if ((domSel.extend || anchor == head) && !brKludge) { domSel.collapse(anchorDOM.node, anchorDOM.offset); try { if (anchor != head) domSel.extend(headDOM.node, headDOM.offset); domSelExtended = true; } catch (_) { } } if (!domSelExtended) { if (anchor > head) { let tmp = anchorDOM; anchorDOM = headDOM; headDOM = tmp; } let range = document.createRange(); range.setEnd(headDOM.node, headDOM.offset); range.setStart(anchorDOM.node, anchorDOM.offset); domSel.removeAllRanges(); domSel.addRange(range); } } ignoreMutation(mutation) { return !this.contentDOM && mutation.type != "selection"; } get contentLost() { return this.contentDOM && this.contentDOM != this.dom && !this.dom.contains(this.contentDOM); } // Remove a subtree of the element tree that has been touched // by a DOM change, so that the next update will redraw it. markDirty(from, to) { for (let offset = 0, i = 0; i < this.children.length; i++) { let child = this.children[i], end = offset + child.size; if (offset == end ? from <= end && to >= offset : from < end && to > offset) { let startInside = offset + child.border, endInside = end - child.border; if (from >= startInside && to <= endInside) { this.dirty = from == offset || to == end ? CONTENT_DIRTY : CHILD_DIRTY; if (from == startInside && to == endInside && (child.contentLost || child.dom.parentNode != this.contentDOM)) child.dirty = NODE_DIRTY; else child.markDirty(from - startInside, to - startInside); return; } else { child.dirty = child.dom == child.contentDOM && child.dom.parentNode == this.contentDOM && !child.children.length ? CONTENT_DIRTY : NODE_DIRTY; } } offset = end; } this.dirty = CONTENT_DIRTY; } markParentsDirty() { let level = 1; for (let node = this.parent; node; node = node.parent, level++) { let dirty = level == 1 ? CONTENT_DIRTY : CHILD_DIRTY; if (node.dirty < dirty) node.dirty = dirty; } } get domAtom() { return false; } get ignoreForCoords() { return false; } }; var WidgetViewDesc = class extends ViewDesc { constructor(parent, widget, view, pos) { let self, dom = widget.type.toDOM; if (typeof dom == "function") dom = dom(view, () => { if (!self) return pos; if (self.parent) return self.parent.posBeforeChild(self); }); if (!widget.type.spec.raw) { if (dom.nodeType != 1) { let wrap = document.createElement("span"); wrap.appendChild(dom); dom = wrap; } dom.contentEditable = "false"; dom.classList.add("ProseMirror-widget"); } super(parent, [], dom, null); this.widget = widget; this.widget = widget; self = this; } matchesWidget(widget) { return this.dirty == NOT_DIRTY && widget.type.eq(this.widget.type); } parseRule() { return { ignore: true }; } stopEvent(event) { let stop = this.widget.spec.stopEvent; return stop ? stop(event) : false; } ignoreMutation(mutation) { return mutation.type != "selection" || this.widget.spec.ignoreSelection; } destroy() { this.widget.type.destroy(this.dom); super.destroy(); } get domAtom() { return true; } get side() { return this.widget.type.side; } }; var CompositionViewDesc = class extends ViewDesc { constructor(parent, dom, textDOM, text) { super(parent, [], dom, null); this.textDOM = textDOM; this.text = text; } get size() { return this.text.length; } localPosFromDOM(dom, offset) { if (dom != this.textDOM) return this.posAtStart + (offset ? this.size : 0); return this.posAtStart + offset; } domFromPos(pos) { return { node: this.textDOM, offset: pos }; } ignoreMutation(mut) { return mut.type === "characterData" && mut.target.nodeValue == mut.oldValue; } }; var MarkViewDesc = class _MarkViewDesc extends ViewDesc { constructor(parent, mark, dom, contentDOM) { super(parent, [], dom, contentDOM); this.mark = mark; } static create(parent, mark, inline, view) { let custom = view.nodeViews[mark.type.name]; let spec = custom && custom(mark, view, inline); if (!spec || !spec.dom) spec = DOMSerializer.renderSpec(document, mark.type.spec.toDOM(mark, inline)); return new _MarkViewDesc(parent, mark, spec.dom, spec.contentDOM || spec.dom); } parseRule() { if (this.dirty & NODE_DIRTY || this.mark.type.spec.reparseInView) return null; return { mark: this.mark.type.name, attrs: this.mark.attrs, contentElement: this.contentDOM || void 0 }; } matchesMark(mark) { return this.dirty != NODE_DIRTY && this.mark.eq(mark); } markDirty(from, to) { super.markDirty(from, to); if (this.dirty != NOT_DIRTY) { let parent = this.parent; while (!parent.node) parent = parent.parent; if (parent.dirty < this.dirty) parent.dirty = this.dirty; this.dirty = NOT_DIRTY; } } slice(from, to, view) { let copy = _MarkViewDesc.create(this.parent, this.mark, true, view); let nodes = this.children, size = this.size; if (to < size) nodes = replaceNodes(nodes, to, size, view); if (from > 0) nodes = replaceNodes(nodes, 0, from, view); for (let i = 0; i < nodes.length; i++) nodes[i].parent = copy; copy.children = nodes; return copy; } }; var NodeViewDesc = class _NodeViewDesc extends ViewDesc { constructor(parent, node, outerDeco, innerDeco, dom, contentDOM, nodeDOM, view, pos) { super(parent, [], dom, contentDOM); this.node = node; this.outerDeco = outerDeco; this.innerDeco = innerDeco; this.nodeDOM = nodeDOM; if (contentDOM) this.updateChildren(view, pos); } // By default, a node is rendered using the `toDOM` method from the // node type spec. But client code can use the `nodeViews` spec to // supply a custom node view, which can influence various aspects of // the way the node works. // // (Using subclassing for this was intentionally decided against, // since it'd require exposing a whole slew of finicky // implementation details to the user code that they probably will // never need.) static create(parent, node, outerDeco, innerDeco, view, pos) { let custom = view.nodeViews[node.type.name], descObj; let spec = custom && custom(node, view, () => { if (!descObj) return pos; if (descObj.parent) return descObj.parent.posBeforeChild(descObj); }, outerDeco, innerDeco); let dom = spec && spec.dom, contentDOM = spec && spec.contentDOM; if (node.isText) { if (!dom) dom = document.createTextNode(node.text); else if (dom.nodeType != 3) throw new RangeError("Text must be rendered as a DOM text node"); } else if (!dom) { ({ dom, contentDOM } = DOMSerializer.renderSpec(document, node.type.spec.toDOM(node))); } if (!contentDOM && !node.isText && dom.nodeName != "BR") { if (!dom.hasAttribute("contenteditable")) dom.contentEditable = "false"; if (node.type.spec.draggable) dom.draggable = true; } let nodeDOM = dom; dom = applyOuterDeco(dom, outerDeco, node); if (spec) return descObj = new CustomNodeViewDesc(parent, node, outerDeco, innerDeco, dom, contentDOM || null, nodeDOM, spec, view, pos + 1); else if (node.isText) return new TextViewDesc(parent, node, outerDeco, innerDeco, dom, nodeDOM, view); else return new _NodeViewDesc(parent, node, outerDeco, innerDeco, dom, contentDOM || null, nodeDOM, view, pos + 1); } parseRule() { if (this.node.type.spec.reparseInView) return null; let rule = { node: this.node.type.name, attrs: this.node.attrs }; if (this.node.type.whitespace == "pre") rule.preserveWhitespace = "full"; if (!this.contentDOM) { rule.getContent = () => this.node.content; } else if (!this.contentLost) { rule.contentElement = this.contentDOM; } else { for (let i = this.children.length - 1; i >= 0; i--) { let child = this.children[i]; if (this.dom.contains(child.dom.parentNode)) { rule.contentElement = child.dom.parentNode; break; } } if (!rule.contentElement) rule.getContent = () => Fragment.empty; } return rule; } matchesNode(node, outerDeco, innerDeco) { return this.dirty == NOT_DIRTY && node.eq(this.node) && sameOuterDeco(outerDeco, this.outerDeco) && innerDeco.eq(this.innerDeco); } get size() { return this.node.nodeSize; } get border() { return this.node.isLeaf ? 0 : 1; } // Syncs `this.children` to match `this.node.content` and the local // decorations, possibly introducing nesting for marks. Then, in a // separate step, syncs the DOM inside `this.contentDOM` to // `this.children`. updateChildren(view, pos) { let inline = this.node.inlineContent, off = pos; let composition = view.composing ? this.localCompositionInfo(view, pos) : null; let localComposition = composition && composition.pos > -1 ? composition : null; let compositionInChild = composition && composition.pos < 0; let updater = new ViewTreeUpdater(this, localComposition && localComposition.node, view); iterDeco(this.node, this.innerDeco, (widget, i, insideNode) => { if (widget.spec.marks) updater.syncToMarks(widget.spec.marks, inline, view); else if (widget.type.side >= 0 && !insideNode) updater.syncToMarks(i == this.node.childCount ? Mark.none : this.node.child(i).marks, inline, view); updater.placeWidget(widget, view, off); }, (child, outerDeco, innerDeco, i) => { updater.syncToMarks(child.marks, inline, view); let compIndex; if (updater.findNodeMatch(child, outerDeco, innerDeco, i)) ; else if (compositionInChild && view.state.selection.from > off && view.state.selection.to < off + child.nodeSize && (compIndex = updater.findIndexWithChild(composition.node)) > -1 && updater.updateNodeAt(child, outerDeco, innerDeco, compIndex, view)) ; else if (updater.updateNextNode(child, outerDeco, innerDeco, view, i)) ; else { updater.addNode(child, outerDeco, innerDeco, view, off); } off += child.nodeSize; }); updater.syncToMarks([], inline, view); if (this.node.isTextblock) updater.addTextblockHacks(); updater.destroyRest(); if (updater.changed || this.dirty == CONTENT_DIRTY) { if (localComposition) this.protectLocalComposition(view, localComposition); renderDescs(this.contentDOM, this.children, view); if (ios) iosHacks(this.dom); } } localCompositionInfo(view, pos) { 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); if (!textNode || !this.dom.contains(textNode.parentNode)) return null; if (this.node.inlineContent) { let text = textNode.nodeValue; let textPos = findTextInFragment(this.node.content, text, from - pos, to - pos); return textPos < 0 ? null : { node: textNode, pos: textPos, text }; } else { return { node: textNode, pos: -1, text: "" }; } } protectLocalComposition(view, { node, pos, text }) { if (this.getDesc(node)) return; let topNode = node; for (; ; topNode = topNode.parentNode) { if (topNode.parentNode == this.contentDOM) break; while (topNode.previousSibling) topNode.parentNode.removeChild(topNode.previousSibling); while (topNode.nextSibling) topNode.parentNode.removeChild(topNode.nextSibling); if (topNode.pmViewDesc) topNode.pmViewDesc = void 0; } let desc = new CompositionViewDesc(this, topNode, node, text); view.input.compositionNodes.push(desc); this.children = replaceNodes(this.children, pos, pos + text.length, view, desc); } // If this desc must be updated to match the given node decoration, // do so and return true. update(node, outerDeco, innerDeco, view) { if (this.dirty == NODE_DIRTY || !node.sameMarkup(this.node)) return false; this.updateInner(node, outerDeco, innerDeco, view); return true; } updateInner(node, outerDeco, innerDeco, view) { this.updateOuterDeco(outerDeco); this.node = node; this.innerDeco = innerDeco; if (this.contentDOM) this.updateChildren(view, this.posAtStart); this.dirty = NOT_DIRTY; } updateOuterDeco(outerDeco) { if (sameOuterDeco(outerDeco, this.outerDeco)) return; let needsWrap = this.nodeDOM.nodeType != 1; let oldDOM = this.dom; this.dom = patchOuterDeco(this.dom, this.nodeDOM, computeOuterDeco(this.outerDeco, this.node, needsWrap), computeOuterDeco(outerDeco, this.node, needsWrap)); if (this.dom != oldDOM) { oldDOM.pmViewDesc = void 0; this.dom.pmViewDesc = this; } this.outerDeco = outerDeco; } // Mark this node as being the selected node. selectNode() { if (this.nodeDOM.nodeType == 1) this.nodeDOM.classList.add("ProseMirror-selectednode"); if (this.contentDOM || !this.node.type.spec.draggable) this.dom.draggable = true; } // Remove selected node marking from this node. deselectNode() { if (this.nodeDOM.nodeType == 1) this.nodeDOM.classList.remove("ProseMirror-selectednode"); if (this.contentDOM || !this.node.type.spec.draggable) this.dom.removeAttribute("draggable"); } get domAtom() { return this.node.isAtom; } }; function docViewDesc(doc2, outerDeco, innerDeco, dom, view) { applyOuterDeco(dom, outerDeco, doc2); return new NodeViewDesc(void 0, doc2, outerDeco, innerDeco, dom, dom, dom, view, 0); } var TextViewDesc = class _TextViewDesc extends NodeViewDesc { constructor(parent, node, outerDeco, innerDeco, dom, nodeDOM, view) { super(parent, node, outerDeco, innerDeco, dom, null, nodeDOM, view, 0); } parseRule() { let skip = this.nodeDOM.parentNode; while (skip && skip != this.dom && !skip.pmIsDeco) skip = skip.parentNode; return { skip: skip || true }; } update(node, outerDeco, innerDeco, view) { if (this.dirty == NODE_DIRTY || this.dirty != NOT_DIRTY && !this.inParent() || !node.sameMarkup(this.node)) return false; this.updateOuterDeco(outerDeco); if ((this.dirty != NOT_DIRTY || node.text != this.node.text) && node.text != this.nodeDOM.nodeValue) { this.nodeDOM.nodeValue = node.text; if (view.trackWrites == this.nodeDOM) view.trackWrites = null; } this.node = node; this.dirty = NOT_DIRTY; return true; } inParent() { let parentDOM = this.parent.contentDOM; for (let n = this.nodeDOM; n; n = n.parentNode) if (n == parentDOM) return true; return false; } domFromPos(pos) { return { node: this.nodeDOM, offset: pos }; } localPosFromDOM(dom, offset, bias) { if (dom == this.nodeDOM) return this.posAtStart + Math.min(offset, this.node.text.length); return super.localPosFromDOM(dom, offset, bias); } ignoreMutation(mutation) { return mutation.type != "characterData" && mutation.type != "selection"; } slice(from, to, view) { let node = this.node.cut(from, to), dom = document.createTextNode(node.text); return new _TextViewDesc(this.parent, node, this.outerDeco, this.innerDeco, dom, dom, view); } markDirty(from, to) { super.markDirty(from, to); if (this.dom != this.nodeDOM && (from == 0 || to == this.nodeDOM.nodeValue.length)) this.dirty = NODE_DIRTY; } get domAtom() { return false; } }; var TrailingHackViewDesc = class extends ViewDesc { parseRule() { return { ignore: true }; } matchesHack(nodeName) { return this.dirty == NOT_DIRTY && this.dom.nodeName == nodeName; } get domAtom() { return true; } get ignoreForCoords() { return this.dom.nodeName == "IMG"; } }; var CustomNodeViewDesc = class extends NodeViewDesc { constructor(parent, node, outerDeco, innerDeco, dom, contentDOM, nodeDOM, spec, view, pos) { super(parent, node, outerDeco, innerDeco, dom, contentDOM, nodeDOM, view, pos); this.spec = spec; } // A custom `update` method gets to decide whether the update goes // through. If it does, and there's a `contentDOM` node, our logic // updates the children. update(node, outerDeco, innerDeco, view) { if (this.dirty == NODE_DIRTY) return false; if (this.spec.update) { let result = this.spec.update(node, outerDeco, innerDeco); if (result) this.updateInner(node, outerDeco, innerDeco, view); return result; } else if (!this.contentDOM && !node.isLeaf) { return false; } else { return super.update(node, outerDeco, innerDeco, view); } } selectNode() { this.spec.selectNode ? this.spec.selectNode() : super.selectNode(); } deselectNode() { this.spec.deselectNode ? this.spec.deselectNode() : super.deselectNode(); } setSelection(anchor, head, root, force) { this.spec.setSelection ? this.spec.setSelection(anchor, head, root) : super.setSelection(anchor, head, root, force); } destroy() { if (this.spec.destroy) this.spec.destroy(); super