diff --git a/src/ttfrog/themes/default/base.html b/src/ttfrog/themes/default/base.html index e50aa5f..bd4baa4 100644 --- a/src/ttfrog/themes/default/base.html +++ b/src/ttfrog/themes/default/base.html @@ -19,7 +19,7 @@ {% include "breadcrumbs.html" %} {% endblock %}
, when the -// paragraph on the left is open, "foo" can be placed (somewhere on -// the left side of the replacement gap) independently from p("bar"). -// -// This class tracks the state of the placement progress in the -// following properties: -// -// - `frontier` holds a stack of `{type, match}` objects that -// represent the open side of the replacement. It starts at -// `$from`, then moves forward as content is placed, and is finally -// reconciled with `$to`. -// -// - `unplaced` is a slice that represents the content that hasn't -// been placed yet. -// -// - `placed` is a fragment of placed content. Its open-start value -// is implicit in `$from`, and its open-end value in `frontier`. -class Fitter { - constructor($from, $to, unplaced) { - this.$from = $from; - this.$to = $to; - this.unplaced = unplaced; - this.frontier = []; - this.placed = Fragment.empty; - for (let i = 0; i <= $from.depth; i++) { - let node = $from.node(i); - this.frontier.push({ - type: node.type, - match: node.contentMatchAt($from.indexAfter(i)) - }); - } - for (let i = $from.depth; i > 0; i--) - this.placed = Fragment.from($from.node(i).copy(this.placed)); - } - get depth() { return this.frontier.length - 1; } - fit() { - // As long as there's unplaced content, try to place some of it. - // If that fails, either increase the open score of the unplaced - // slice, or drop nodes from it, and then try again. - while (this.unplaced.size) { - let fit = this.findFittable(); - if (fit) - this.placeNodes(fit); - else - this.openMore() || this.dropNode(); - } - // When there's inline content directly after the frontier _and_ - // directly after `this.$to`, we must generate a `ReplaceAround` - // step that pulls that content into the node after the frontier. - // That means the fitting must be done to the end of the textblock - // node after `this.$to`, not `this.$to` itself. - let moveInline = this.mustMoveInline(), placedSize = this.placed.size - this.depth - this.$from.depth; - let $from = this.$from, $to = this.close(moveInline < 0 ? this.$to : $from.doc.resolve(moveInline)); - if (!$to) - return null; - // If closing to `$to` succeeded, create a step - let content = this.placed, openStart = $from.depth, openEnd = $to.depth; - while (openStart && openEnd && content.childCount == 1) { // Normalize by dropping open parent nodes - content = content.firstChild.content; - openStart--; - openEnd--; - } - let slice = new Slice(content, openStart, openEnd); - if (moveInline > -1) - return new ReplaceAroundStep($from.pos, moveInline, this.$to.pos, this.$to.end(), slice, placedSize); - if (slice.size || $from.pos != this.$to.pos) // Don't generate no-op steps - return new ReplaceStep($from.pos, $to.pos, slice); - return null; - } - // Find a position on the start spine of `this.unplaced` that has - // content that can be moved somewhere on the frontier. Returns two - // depths, one for the slice and one for the frontier. - findFittable() { - // Only try wrapping nodes (pass 2) after finding a place without - // wrapping failed. - for (let pass = 1; pass <= 2; pass++) { - for (let sliceDepth = this.unplaced.openStart; sliceDepth >= 0; sliceDepth--) { - let fragment, parent = null; - if (sliceDepth) { - parent = contentAt(this.unplaced.content, sliceDepth - 1).firstChild; - fragment = parent.content; - } - else { - fragment = this.unplaced.content; - } - let first = fragment.firstChild; - for (let frontierDepth = this.depth; frontierDepth >= 0; frontierDepth--) { - let { type, match } = this.frontier[frontierDepth], wrap, inject = null; - // In pass 1, if the next node matches, or there is no next - // node but the parents look compatible, we've found a - // place. - if (pass == 1 && (first ? match.matchType(first.type) || (inject = match.fillBefore(Fragment.from(first), false)) - : parent && type.compatibleContent(parent.type))) - return { sliceDepth, frontierDepth, parent, inject }; - // In pass 2, look for a set of wrapping nodes that make - // `first` fit here. - else if (pass == 2 && first && (wrap = match.findWrapping(first.type))) - return { sliceDepth, frontierDepth, parent, wrap }; - // Don't continue looking further up if the parent node - // would fit here. - if (parent && match.matchType(parent.type)) - break; - } - } - } - } - openMore() { - let { content, openStart, openEnd } = this.unplaced; - let inner = contentAt(content, openStart); - if (!inner.childCount || inner.firstChild.isLeaf) - return false; - this.unplaced = new Slice(content, openStart + 1, Math.max(openEnd, inner.size + openStart >= content.size - openEnd ? openStart + 1 : 0)); - return true; - } - dropNode() { - let { content, openStart, openEnd } = this.unplaced; - let inner = contentAt(content, openStart); - if (inner.childCount <= 1 && openStart > 0) { - let openAtEnd = content.size - openStart <= openStart + inner.size; - this.unplaced = new Slice(dropFromFragment(content, openStart - 1, 1), openStart - 1, openAtEnd ? openStart - 1 : openEnd); - } - else { - this.unplaced = new Slice(dropFromFragment(content, openStart, 1), openStart, openEnd); - } - } - // Move content from the unplaced slice at `sliceDepth` to the - // frontier node at `frontierDepth`. Close that frontier node when - // applicable. - placeNodes({ sliceDepth, frontierDepth, parent, inject, wrap }) { - while (this.depth > frontierDepth) - this.closeFrontierNode(); - if (wrap) - for (let i = 0; i < wrap.length; i++) - this.openFrontierNode(wrap[i]); - let slice = this.unplaced, fragment = parent ? parent.content : slice.content; - let openStart = slice.openStart - sliceDepth; - let taken = 0, add = []; - let { match, type } = this.frontier[frontierDepth]; - if (inject) { - for (let i = 0; i < inject.childCount; i++) - add.push(inject.child(i)); - match = match.matchFragment(inject); - } - // Computes the amount of (end) open nodes at the end of the - // fragment. When 0, the parent is open, but no more. When - // negative, nothing is open. - let openEndCount = (fragment.size + sliceDepth) - (slice.content.size - slice.openEnd); - // Scan over the fragment, fitting as many child nodes as - // possible. - while (taken < fragment.childCount) { - let next = fragment.child(taken), matches = match.matchType(next.type); - if (!matches) - break; - taken++; - if (taken > 1 || openStart == 0 || next.content.size) { // Drop empty open nodes - match = matches; - add.push(closeNodeStart(next.mark(type.allowedMarks(next.marks)), taken == 1 ? openStart : 0, taken == fragment.childCount ? openEndCount : -1)); - } - } - let toEnd = taken == fragment.childCount; - if (!toEnd) - openEndCount = -1; - this.placed = addToFragment(this.placed, frontierDepth, Fragment.from(add)); - this.frontier[frontierDepth].match = match; - // If the parent types match, and the entire node was moved, and - // it's not open, close this frontier node right away. - if (toEnd && openEndCount < 0 && parent && parent.type == this.frontier[this.depth].type && this.frontier.length > 1) - this.closeFrontierNode(); - // Add new frontier nodes for any open nodes at the end. - for (let i = 0, cur = fragment; i < openEndCount; i++) { - let node = cur.lastChild; - this.frontier.push({ type: node.type, match: node.contentMatchAt(node.childCount) }); - cur = node.content; - } - // Update `this.unplaced`. Drop the entire node from which we - // placed it we got to its end, otherwise just drop the placed - // nodes. - this.unplaced = !toEnd ? new Slice(dropFromFragment(slice.content, sliceDepth, taken), slice.openStart, slice.openEnd) - : sliceDepth == 0 ? Slice.empty - : new Slice(dropFromFragment(slice.content, sliceDepth - 1, 1), sliceDepth - 1, openEndCount < 0 ? slice.openEnd : sliceDepth - 1); - } - mustMoveInline() { - if (!this.$to.parent.isTextblock) - return -1; - let top = this.frontier[this.depth], level; - if (!top.type.isTextblock || !contentAfterFits(this.$to, this.$to.depth, top.type, top.match, false) || - (this.$to.depth == this.depth && (level = this.findCloseLevel(this.$to)) && level.depth == this.depth)) - return -1; - let { depth } = this.$to, after = this.$to.after(depth); - while (depth > 1 && after == this.$to.end(--depth)) - ++after; - return after; - } - findCloseLevel($to) { - scan: for (let i = Math.min(this.depth, $to.depth); i >= 0; i--) { - let { match, type } = this.frontier[i]; - let dropInner = i < $to.depth && $to.end(i + 1) == $to.pos + ($to.depth - (i + 1)); - let fit = contentAfterFits($to, i, type, match, dropInner); - if (!fit) - continue; - for (let d = i - 1; d >= 0; d--) { - let { match, type } = this.frontier[d]; - let matches = contentAfterFits($to, d, type, match, true); - if (!matches || matches.childCount) - continue scan; - } - return { depth: i, fit, move: dropInner ? $to.doc.resolve($to.after(i + 1)) : $to }; - } - } - close($to) { - let close = this.findCloseLevel($to); - if (!close) - return null; - while (this.depth > close.depth) - this.closeFrontierNode(); - if (close.fit.childCount) - this.placed = addToFragment(this.placed, close.depth, close.fit); - $to = close.move; - for (let d = close.depth + 1; d <= $to.depth; d++) { - let node = $to.node(d), add = node.type.contentMatch.fillBefore(node.content, true, $to.index(d)); - this.openFrontierNode(node.type, node.attrs, add); - } - return $to; - } - openFrontierNode(type, attrs = null, content) { - let top = this.frontier[this.depth]; - top.match = top.match.matchType(type); - this.placed = addToFragment(this.placed, this.depth, Fragment.from(type.create(attrs, content))); - this.frontier.push({ type, match: type.contentMatch }); - } - closeFrontierNode() { - let open = this.frontier.pop(); - let add = open.match.fillBefore(Fragment.empty, true); - if (add.childCount) - this.placed = addToFragment(this.placed, this.frontier.length, add); - } -} -function dropFromFragment(fragment, depth, count) { - if (depth == 0) - return fragment.cutByIndex(count, fragment.childCount); - return fragment.replaceChild(0, fragment.firstChild.copy(dropFromFragment(fragment.firstChild.content, depth - 1, count))); -} -function addToFragment(fragment, depth, content) { - if (depth == 0) - return fragment.append(content); - return fragment.replaceChild(fragment.childCount - 1, fragment.lastChild.copy(addToFragment(fragment.lastChild.content, depth - 1, content))); -} -function contentAt(fragment, depth) { - for (let i = 0; i < depth; i++) - fragment = fragment.firstChild.content; - return fragment; -} -function closeNodeStart(node, openStart, openEnd) { - if (openStart <= 0) - return node; - let frag = node.content; - if (openStart > 1) - frag = frag.replaceChild(0, closeNodeStart(frag.firstChild, openStart - 1, frag.childCount == 1 ? openEnd - 1 : 0)); - if (openStart > 0) { - frag = node.type.contentMatch.fillBefore(frag).append(frag); - if (openEnd <= 0) - frag = frag.append(node.type.contentMatch.matchFragment(frag).fillBefore(Fragment.empty, true)); - } - return node.copy(frag); -} -function contentAfterFits($to, depth, type, match, open) { - let node = $to.node(depth), index = open ? $to.indexAfter(depth) : $to.index(depth); - if (index == node.childCount && !type.compatibleContent(node.type)) - return null; - let fit = match.fillBefore(node.content, true, index); - return fit && !invalidMarks(type, node.content, index) ? fit : null; -} -function invalidMarks(type, fragment, start) { - for (let i = start; i < fragment.childCount; i++) - if (!type.allowsMarks(fragment.child(i).marks)) - return true; - return false; -} -function definesContent(type) { - return type.spec.defining || type.spec.definingForContent; -} -function replaceRange(tr, from, to, slice) { - if (!slice.size) - return tr.deleteRange(from, to); - let $from = tr.doc.resolve(from), $to = tr.doc.resolve(to); - if (fitsTrivially($from, $to, slice)) - return tr.step(new ReplaceStep(from, to, slice)); - let targetDepths = coveredDepths($from, tr.doc.resolve(to)); - // Can't replace the whole document, so remove 0 if it's present - if (targetDepths[targetDepths.length - 1] == 0) - targetDepths.pop(); - // Negative numbers represent not expansion over the whole node at - // that depth, but replacing from $from.before(-D) to $to.pos. - let preferredTarget = -($from.depth + 1); - targetDepths.unshift(preferredTarget); - // This loop picks a preferred target depth, if one of the covering - // depths is not outside of a defining node, and adds negative - // depths for any depth that has $from at its start and does not - // cross a defining node. - for (let d = $from.depth, pos = $from.pos - 1; d > 0; d--, pos--) { - let spec = $from.node(d).type.spec; - if (spec.defining || spec.definingAsContext || spec.isolating) - break; - if (targetDepths.indexOf(d) > -1) - preferredTarget = d; - else if ($from.before(d) == pos) - targetDepths.splice(1, 0, -d); - } - // Try to fit each possible depth of the slice into each possible - // target depth, starting with the preferred depths. - let preferredTargetIndex = targetDepths.indexOf(preferredTarget); - let leftNodes = [], preferredDepth = slice.openStart; - for (let content = slice.content, i = 0;; i++) { - let node = content.firstChild; - leftNodes.push(node); - if (i == slice.openStart) - break; - content = node.content; - } - // Back up preferredDepth to cover defining textblocks directly - // above it, possibly skipping a non-defining textblock. - for (let d = preferredDepth - 1; d >= 0; d--) { - let type = leftNodes[d].type, def = definesContent(type); - if (def && $from.node(preferredTargetIndex).type != type) - preferredDepth = d; - else if (def || !type.isTextblock) - break; - } - for (let j = slice.openStart; j >= 0; j--) { - let openDepth = (j + preferredDepth + 1) % (slice.openStart + 1); - let insert = leftNodes[openDepth]; - if (!insert) - continue; - for (let i = 0; i < targetDepths.length; i++) { - // Loop over possible expansion levels, starting with the - // preferred one - let targetDepth = targetDepths[(i + preferredTargetIndex) % targetDepths.length], expand = true; - if (targetDepth < 0) { - expand = false; - targetDepth = -targetDepth; - } - let parent = $from.node(targetDepth - 1), index = $from.index(targetDepth - 1); - if (parent.canReplaceWith(index, index, insert.type, insert.marks)) - return tr.replace($from.before(targetDepth), expand ? $to.after(targetDepth) : to, new Slice(closeFragment(slice.content, 0, slice.openStart, openDepth), openDepth, slice.openEnd)); - } - } - let startSteps = tr.steps.length; - for (let i = targetDepths.length - 1; i >= 0; i--) { - tr.replace(from, to, slice); - if (tr.steps.length > startSteps) - break; - let depth = targetDepths[i]; - if (depth < 0) - continue; - from = $from.before(depth); - to = $to.after(depth); - } -} -function closeFragment(fragment, depth, oldOpen, newOpen, parent) { - if (depth < oldOpen) { - let first = fragment.firstChild; - fragment = fragment.replaceChild(0, first.copy(closeFragment(first.content, depth + 1, oldOpen, newOpen, first))); - } - if (depth > newOpen) { - let match = parent.contentMatchAt(0); - let start = match.fillBefore(fragment).append(fragment); - fragment = start.append(match.matchFragment(start).fillBefore(Fragment.empty, true)); - } - return fragment; -} -function replaceRangeWith(tr, from, to, node) { - if (!node.isInline && from == to && tr.doc.resolve(from).parent.content.size) { - let point = insertPoint(tr.doc, from, node.type); - if (point != null) - from = to = point; - } - tr.replaceRange(from, to, new Slice(Fragment.from(node), 0, 0)); -} -function deleteRange(tr, from, to) { - let $from = tr.doc.resolve(from), $to = tr.doc.resolve(to); - let covered = coveredDepths($from, $to); - for (let i = 0; i < covered.length; i++) { - let depth = covered[i], last = i == covered.length - 1; - if ((last && depth == 0) || $from.node(depth).type.contentMatch.validEnd) - return tr.delete($from.start(depth), $to.end(depth)); - if (depth > 0 && (last || $from.node(depth - 1).canReplace($from.index(depth - 1), $to.indexAfter(depth - 1)))) - return tr.delete($from.before(depth), $to.after(depth)); - } - for (let d = 1; d <= $from.depth && d <= $to.depth; d++) { - if (from - $from.start(d) == $from.depth - d && to > $from.end(d) && $to.end(d) - to != $to.depth - d) - return tr.delete($from.before(d), to); - } - tr.delete(from, to); -} -// Returns an array of all depths for which $from - $to spans the -// whole content of the nodes at that depth. -function coveredDepths($from, $to) { - let result = [], minDepth = Math.min($from.depth, $to.depth); - for (let d = minDepth; d >= 0; d--) { - let start = $from.start(d); - if (start < $from.pos - ($from.depth - d) || - $to.end(d) > $to.pos + ($to.depth - d) || - $from.node(d).type.spec.isolating || - $to.node(d).type.spec.isolating) - break; - if (start == $to.start(d) || - (d == $from.depth && d == $to.depth && $from.parent.inlineContent && $to.parent.inlineContent && - d && $to.start(d - 1) == start - 1)) - result.push(d); - } - return result; -} - -/** -@internal -*/ -let TransformError = class extends Error { -}; -TransformError = function TransformError(message) { - let err = Error.call(this, message); - err.__proto__ = TransformError.prototype; - return err; -}; -TransformError.prototype = Object.create(Error.prototype); -TransformError.prototype.constructor = TransformError; -TransformError.prototype.name = "TransformError"; -/** -Abstraction to build up and track an array of -[steps](https://prosemirror.net/docs/ref/#transform.Step) representing a document transformation. - -Most transforming methods return the `Transform` object itself, so -that they can be chained. -*/ -class Transform { - /** - Create a transform that starts with the given document. - */ - constructor( - /** - The current document (the result of applying the steps in the - transform). - */ - doc) { - this.doc = doc; - /** - The steps in this transform. - */ - this.steps = []; - /** - The documents before each of the steps. - */ - this.docs = []; - /** - A mapping with the maps for each of the steps in this transform. - */ - this.mapping = new Mapping; - } - /** - The starting document. - */ - get before() { return this.docs.length ? this.docs[0] : this.doc; } - /** - Apply a new step in this transform, saving the result. Throws an - error when the step fails. - */ - step(step) { - let result = this.maybeStep(step); - if (result.failed) - throw new TransformError(result.failed); - return this; - } - /** - Try to apply a step in this transformation, ignoring it if it - fails. Returns the step result. - */ - maybeStep(step) { - let result = step.apply(this.doc); - if (!result.failed) - this.addStep(step, result.doc); - return result; - } - /** - True when the document has been changed (when there are any - steps). - */ - get docChanged() { - return this.steps.length > 0; - } - /** - @internal - */ - addStep(step, doc) { - this.docs.push(this.doc); - this.steps.push(step); - this.mapping.appendMap(step.getMap()); - this.doc = doc; - } - /** - Replace the part of the document between `from` and `to` with the - given `slice`. - */ - replace(from, to = from, slice = Slice.empty) { - let step = replaceStep(this.doc, from, to, slice); - if (step) - this.step(step); - return this; - } - /** - Replace the given range with the given content, which may be a - fragment, node, or array of nodes. - */ - replaceWith(from, to, content) { - return this.replace(from, to, new Slice(Fragment.from(content), 0, 0)); - } - /** - Delete the content between the given positions. - */ - delete(from, to) { - return this.replace(from, to, Slice.empty); - } - /** - Insert the given content at the given position. - */ - insert(pos, content) { - return this.replaceWith(pos, pos, content); - } - /** - Replace a range of the document with a given slice, using - `from`, `to`, and the slice's - [`openStart`](https://prosemirror.net/docs/ref/#model.Slice.openStart) property as hints, rather - than fixed start and end points. This method may grow the - replaced area or close open nodes in the slice in order to get a - fit that is more in line with WYSIWYG expectations, by dropping - fully covered parent nodes of the replaced region when they are - marked [non-defining as - context](https://prosemirror.net/docs/ref/#model.NodeSpec.definingAsContext), or including an - open parent node from the slice that _is_ marked as [defining - its content](https://prosemirror.net/docs/ref/#model.NodeSpec.definingForContent). - - This is the method, for example, to handle paste. The similar - [`replace`](https://prosemirror.net/docs/ref/#transform.Transform.replace) method is a more - primitive tool which will _not_ move the start and end of its given - range, and is useful in situations where you need more precise - control over what happens. - */ - replaceRange(from, to, slice) { - replaceRange(this, from, to, slice); - return this; - } - /** - Replace the given range with a node, but use `from` and `to` as - hints, rather than precise positions. When from and to are the same - and are at the start or end of a parent node in which the given - node doesn't fit, this method may _move_ them out towards a parent - that does allow the given node to be placed. When the given range - completely covers a parent node, this method may completely replace - that parent node. - */ - replaceRangeWith(from, to, node) { - replaceRangeWith(this, from, to, node); - return this; - } - /** - Delete the given range, expanding it to cover fully covered - parent nodes until a valid replace is found. - */ - deleteRange(from, to) { - deleteRange(this, from, to); - return this; - } - /** - Split the content in the given range off from its parent, if there - is sibling content before or after it, and move it up the tree to - the depth specified by `target`. You'll probably want to use - [`liftTarget`](https://prosemirror.net/docs/ref/#transform.liftTarget) to compute `target`, to make - sure the lift is valid. - */ - lift(range, target) { - lift(this, range, target); - return this; - } - /** - Join the blocks around the given position. If depth is 2, their - last and first siblings are also joined, and so on. - */ - join(pos, depth = 1) { - join(this, pos, depth); - return this; - } - /** - Wrap the given [range](https://prosemirror.net/docs/ref/#model.NodeRange) in the given set of wrappers. - The wrappers are assumed to be valid in this position, and should - probably be computed with [`findWrapping`](https://prosemirror.net/docs/ref/#transform.findWrapping). - */ - wrap(range, wrappers) { - wrap(this, range, wrappers); - return this; - } - /** - Set the type of all textblocks (partly) between `from` and `to` to - the given node type with the given attributes. - */ - setBlockType(from, to = from, type, attrs = null) { - setBlockType(this, from, to, type, attrs); - return this; - } - /** - Change the type, attributes, and/or marks of the node at `pos`. - When `type` isn't given, the existing node type is preserved, - */ - setNodeMarkup(pos, type, attrs = null, marks = []) { - setNodeMarkup(this, pos, type, attrs, marks); - return this; - } - /** - Split the node at the given position, and optionally, if `depth` is - greater than one, any number of nodes above that. By default, the - parts split off will inherit the node type of the original node. - This can be changed by passing an array of types and attributes to - use after the split. - */ - split(pos, depth = 1, typesAfter) { - split(this, pos, depth, typesAfter); - return this; - } - /** - Add the given mark to the inline content between `from` and `to`. - */ - addMark(from, to, mark) { - addMark(this, from, to, mark); - return this; - } - /** - Remove marks from inline nodes between `from` and `to`. When - `mark` is a single mark, remove precisely that mark. When it is - a mark type, remove all marks of that type. When it is null, - remove all marks of any type. - */ - removeMark(from, to, mark) { - removeMark(this, from, to, mark); - return this; - } - /** - Removes all marks and nodes from the content of the node at - `pos` that don't match the given new parent node type. Accepts - an optional starting [content match](https://prosemirror.net/docs/ref/#model.ContentMatch) as - third argument. - */ - clearIncompatible(pos, parentType, match) { - clearIncompatible(this, pos, parentType, match); - return this; - } -} - - - -;// CONCATENATED MODULE: ../../node_modules/prosemirror-state/dist/index.js - - - -const classesById = Object.create(null); -/** -Superclass for editor selections. Every selection type should -extend this. Should not be instantiated directly. -*/ -class Selection { - /** - Initialize a selection with the head and anchor and ranges. If no - ranges are given, constructs a single range across `$anchor` and - `$head`. - */ - constructor( - /** - The resolved anchor of the selection (the side that stays in - place when the selection is modified). - */ - $anchor, - /** - The resolved head of the selection (the side that moves when - the selection is modified). - */ - $head, ranges) { - this.$anchor = $anchor; - this.$head = $head; - this.ranges = ranges || [new SelectionRange($anchor.min($head), $anchor.max($head))]; - } - /** - The selection's anchor, as an unresolved position. - */ - get anchor() { return this.$anchor.pos; } - /** - The selection's head. - */ - get head() { return this.$head.pos; } - /** - The lower bound of the selection's main range. - */ - get from() { return this.$from.pos; } - /** - The upper bound of the selection's main range. - */ - get to() { return this.$to.pos; } - /** - The resolved lower bound of the selection's main range. - */ - get $from() { - return this.ranges[0].$from; - } - /** - The resolved upper bound of the selection's main range. - */ - get $to() { - return this.ranges[0].$to; - } - /** - Indicates whether the selection contains any content. - */ - get empty() { - let ranges = this.ranges; - for (let i = 0; i < ranges.length; i++) - if (ranges[i].$from.pos != ranges[i].$to.pos) - return false; - return true; - } - /** - Get the content of this selection as a slice. - */ - content() { - return this.$from.doc.slice(this.from, this.to, true); - } - /** - Replace the selection with a slice or, if no slice is given, - delete the selection. Will append to the given transaction. - */ - replace(tr, content = Slice.empty) { - // Put the new selection at the position after the inserted - // content. When that ended in an inline node, search backwards, - // to get the position after that node. If not, search forward. - let lastNode = content.content.lastChild, lastParent = null; - for (let i = 0; i < content.openEnd; i++) { - lastParent = lastNode; - lastNode = lastNode.lastChild; - } - let mapFrom = tr.steps.length, ranges = this.ranges; - for (let i = 0; i < ranges.length; i++) { - let { $from, $to } = ranges[i], mapping = tr.mapping.slice(mapFrom); - tr.replaceRange(mapping.map($from.pos), mapping.map($to.pos), i ? Slice.empty : content); - if (i == 0) - selectionToInsertionEnd(tr, mapFrom, (lastNode ? lastNode.isInline : lastParent && lastParent.isTextblock) ? -1 : 1); - } - } - /** - Replace the selection with the given node, appending the changes - to the given transaction. - */ - replaceWith(tr, node) { - let mapFrom = tr.steps.length, ranges = this.ranges; - for (let i = 0; i < ranges.length; i++) { - let { $from, $to } = ranges[i], mapping = tr.mapping.slice(mapFrom); - let from = mapping.map($from.pos), to = mapping.map($to.pos); - if (i) { - tr.deleteRange(from, to); - } - else { - tr.replaceRangeWith(from, to, node); - selectionToInsertionEnd(tr, mapFrom, node.isInline ? -1 : 1); - } - } - } - /** - Find a valid cursor or leaf node selection starting at the given - position and searching back if `dir` is negative, and forward if - positive. When `textOnly` is true, only consider cursor - selections. Will return null when no valid selection position is - found. - */ - static findFrom($pos, dir, textOnly = false) { - let inner = $pos.parent.inlineContent ? new TextSelection($pos) - : findSelectionIn($pos.node(0), $pos.parent, $pos.pos, $pos.index(), dir, textOnly); - if (inner) - return inner; - for (let depth = $pos.depth - 1; depth >= 0; depth--) { - let found = dir < 0 - ? findSelectionIn($pos.node(0), $pos.node(depth), $pos.before(depth + 1), $pos.index(depth), dir, textOnly) - : findSelectionIn($pos.node(0), $pos.node(depth), $pos.after(depth + 1), $pos.index(depth) + 1, dir, textOnly); - if (found) - return found; - } - return null; - } - /** - Find a valid cursor or leaf node selection near the given - position. Searches forward first by default, but if `bias` is - negative, it will search backwards first. - */ - static near($pos, bias = 1) { - return this.findFrom($pos, bias) || this.findFrom($pos, -bias) || new AllSelection($pos.node(0)); - } - /** - Find the cursor or leaf node selection closest to the start of - the given document. Will return an - [`AllSelection`](https://prosemirror.net/docs/ref/#state.AllSelection) if no valid position - exists. - */ - static atStart(doc) { - return findSelectionIn(doc, doc, 0, 0, 1) || new AllSelection(doc); - } - /** - Find the cursor or leaf node selection closest to the end of the - given document. - */ - static atEnd(doc) { - return findSelectionIn(doc, doc, doc.content.size, doc.childCount, -1) || new AllSelection(doc); - } - /** - Deserialize the JSON representation of a selection. Must be - implemented for custom classes (as a static class method). - */ - static fromJSON(doc, json) { - if (!json || !json.type) - throw new RangeError("Invalid input for Selection.fromJSON"); - let cls = classesById[json.type]; - if (!cls) - throw new RangeError(`No selection type ${json.type} defined`); - return cls.fromJSON(doc, json); - } - /** - To be able to deserialize selections from JSON, custom selection - classes must register themselves with an ID string, so that they - can be disambiguated. Try to pick something that's unlikely to - clash with classes from other modules. - */ - static jsonID(id, selectionClass) { - if (id in classesById) - throw new RangeError("Duplicate use of selection JSON ID " + id); - classesById[id] = selectionClass; - selectionClass.prototype.jsonID = id; - return selectionClass; - } - /** - Get a [bookmark](https://prosemirror.net/docs/ref/#state.SelectionBookmark) for this selection, - which is a value that can be mapped without having access to a - current document, and later resolved to a real selection for a - given document again. (This is used mostly by the history to - track and restore old selections.) The default implementation of - this method just converts the selection to a text selection and - returns the bookmark for that. - */ - getBookmark() { - return TextSelection.between(this.$anchor, this.$head).getBookmark(); - } -} -Selection.prototype.visible = true; -/** -Represents a selected range in a document. -*/ -class SelectionRange { - /** - Create a range. - */ - constructor( - /** - The lower bound of the range. - */ - $from, - /** - The upper bound of the range. - */ - $to) { - this.$from = $from; - this.$to = $to; - } -} -let warnedAboutTextSelection = false; -function checkTextSelection($pos) { - if (!warnedAboutTextSelection && !$pos.parent.inlineContent) { - warnedAboutTextSelection = true; - console["warn"]("TextSelection endpoint not pointing into a node with inline content (" + $pos.parent.type.name + ")"); - } -} -/** -A text selection represents a classical editor selection, with a -head (the moving side) and anchor (immobile side), both of which -point into textblock nodes. It can be empty (a regular cursor -position). -*/ -class TextSelection extends Selection { - /** - Construct a text selection between the given points. - */ - constructor($anchor, $head = $anchor) { - checkTextSelection($anchor); - checkTextSelection($head); - super($anchor, $head); - } - /** - Returns a resolved position if this is a cursor selection (an - empty text selection), and null otherwise. - */ - get $cursor() { return this.$anchor.pos == this.$head.pos ? this.$head : null; } - map(doc, mapping) { - let $head = doc.resolve(mapping.map(this.head)); - if (!$head.parent.inlineContent) - return Selection.near($head); - let $anchor = doc.resolve(mapping.map(this.anchor)); - return new TextSelection($anchor.parent.inlineContent ? $anchor : $head, $head); - } - replace(tr, content = Slice.empty) { - super.replace(tr, content); - if (content == Slice.empty) { - let marks = this.$from.marksAcross(this.$to); - if (marks) - tr.ensureMarks(marks); - } - } - eq(other) { - return other instanceof TextSelection && other.anchor == this.anchor && other.head == this.head; - } - getBookmark() { - return new TextBookmark(this.anchor, this.head); - } - toJSON() { - return { type: "text", anchor: this.anchor, head: this.head }; - } - /** - @internal - */ - static fromJSON(doc, json) { - if (typeof json.anchor != "number" || typeof json.head != "number") - throw new RangeError("Invalid input for TextSelection.fromJSON"); - return new TextSelection(doc.resolve(json.anchor), doc.resolve(json.head)); - } - /** - Create a text selection from non-resolved positions. - */ - static create(doc, anchor, head = anchor) { - let $anchor = doc.resolve(anchor); - return new this($anchor, head == anchor ? $anchor : doc.resolve(head)); - } - /** - Return a text selection that spans the given positions or, if - they aren't text positions, find a text selection near them. - `bias` determines whether the method searches forward (default) - or backwards (negative number) first. Will fall back to calling - [`Selection.near`](https://prosemirror.net/docs/ref/#state.Selection^near) when the document - doesn't contain a valid text position. - */ - static between($anchor, $head, bias) { - let dPos = $anchor.pos - $head.pos; - if (!bias || dPos) - bias = dPos >= 0 ? 1 : -1; - if (!$head.parent.inlineContent) { - let found = Selection.findFrom($head, bias, true) || Selection.findFrom($head, -bias, true); - if (found) - $head = found.$head; - else - return Selection.near($head, bias); - } - if (!$anchor.parent.inlineContent) { - if (dPos == 0) { - $anchor = $head; - } - else { - $anchor = (Selection.findFrom($anchor, -bias, true) || Selection.findFrom($anchor, bias, true)).$anchor; - if (($anchor.pos < $head.pos) != (dPos < 0)) - $anchor = $head; - } - } - return new TextSelection($anchor, $head); - } -} -Selection.jsonID("text", TextSelection); -class TextBookmark { - constructor(anchor, head) { - this.anchor = anchor; - this.head = head; - } - map(mapping) { - return new TextBookmark(mapping.map(this.anchor), mapping.map(this.head)); - } - resolve(doc) { - return TextSelection.between(doc.resolve(this.anchor), doc.resolve(this.head)); - } -} -/** -A node selection is a selection that points at a single node. All -nodes marked [selectable](https://prosemirror.net/docs/ref/#model.NodeSpec.selectable) can be the -target of a node selection. In such a selection, `from` and `to` -point directly before and after the selected node, `anchor` equals -`from`, and `head` equals `to`.. -*/ -class dist_NodeSelection extends Selection { - /** - Create a node selection. Does not verify the validity of its - argument. - */ - constructor($pos) { - let node = $pos.nodeAfter; - let $end = $pos.node(0).resolve($pos.pos + node.nodeSize); - super($pos, $end); - this.node = node; - } - map(doc, mapping) { - let { deleted, pos } = mapping.mapResult(this.anchor); - let $pos = doc.resolve(pos); - if (deleted) - return Selection.near($pos); - return new dist_NodeSelection($pos); - } - content() { - return new Slice(Fragment.from(this.node), 0, 0); - } - eq(other) { - return other instanceof dist_NodeSelection && other.anchor == this.anchor; - } - toJSON() { - return { type: "node", anchor: this.anchor }; - } - getBookmark() { return new NodeBookmark(this.anchor); } - /** - @internal - */ - static fromJSON(doc, json) { - if (typeof json.anchor != "number") - throw new RangeError("Invalid input for NodeSelection.fromJSON"); - return new dist_NodeSelection(doc.resolve(json.anchor)); - } - /** - Create a node selection from non-resolved positions. - */ - static create(doc, from) { - return new dist_NodeSelection(doc.resolve(from)); - } - /** - Determines whether the given node may be selected as a node - selection. - */ - static isSelectable(node) { - return !node.isText && node.type.spec.selectable !== false; - } -} -dist_NodeSelection.prototype.visible = false; -Selection.jsonID("node", dist_NodeSelection); -class NodeBookmark { - constructor(anchor) { - this.anchor = anchor; - } - map(mapping) { - let { deleted, pos } = mapping.mapResult(this.anchor); - return deleted ? new TextBookmark(pos, pos) : new NodeBookmark(pos); - } - resolve(doc) { - let $pos = doc.resolve(this.anchor), node = $pos.nodeAfter; - if (node && dist_NodeSelection.isSelectable(node)) - return new dist_NodeSelection($pos); - return Selection.near($pos); - } -} -/** -A selection type that represents selecting the whole document -(which can not necessarily be expressed with a text selection, when -there are for example leaf block nodes at the start or end of the -document). -*/ -class AllSelection extends Selection { - /** - Create an all-selection over the given document. - */ - constructor(doc) { - super(doc.resolve(0), doc.resolve(doc.content.size)); - } - replace(tr, content = Slice.empty) { - if (content == Slice.empty) { - tr.delete(0, tr.doc.content.size); - let sel = Selection.atStart(tr.doc); - if (!sel.eq(tr.selection)) - tr.setSelection(sel); - } - else { - super.replace(tr, content); - } - } - toJSON() { return { type: "all" }; } - /** - @internal - */ - static fromJSON(doc) { return new AllSelection(doc); } - map(doc) { return new AllSelection(doc); } - eq(other) { return other instanceof AllSelection; } - getBookmark() { return AllBookmark; } -} -Selection.jsonID("all", AllSelection); -const AllBookmark = { - map() { return this; }, - resolve(doc) { return new AllSelection(doc); } -}; -// FIXME we'll need some awareness of text direction when scanning for selections -// Try to find a selection inside the given node. `pos` points at the -// position where the search starts. When `text` is true, only return -// text selections. -function findSelectionIn(doc, node, pos, index, dir, text = false) { - if (node.inlineContent) - return TextSelection.create(doc, pos); - for (let i = index - (dir > 0 ? 0 : 1); dir > 0 ? i < node.childCount : i >= 0; i += dir) { - let child = node.child(i); - if (!child.isAtom) { - let inner = findSelectionIn(doc, child, pos + dir, dir < 0 ? child.childCount : 0, dir, text); - if (inner) - return inner; - } - else if (!text && dist_NodeSelection.isSelectable(child)) { - return dist_NodeSelection.create(doc, pos - (dir < 0 ? child.nodeSize : 0)); - } - pos += child.nodeSize * dir; - } - return null; -} -function selectionToInsertionEnd(tr, startLen, bias) { - let last = tr.steps.length - 1; - if (last < startLen) - return; - let step = tr.steps[last]; - if (!(step instanceof ReplaceStep || step instanceof ReplaceAroundStep)) - return; - let map = tr.mapping.maps[last], end; - map.forEach((_from, _to, _newFrom, newTo) => { if (end == null) - end = newTo; }); - tr.setSelection(Selection.near(tr.doc.resolve(end), bias)); -} - -const UPDATED_SEL = 1, UPDATED_MARKS = 2, UPDATED_SCROLL = 4; -/** -An editor state transaction, which can be applied to a state to -create an updated state. Use -[`EditorState.tr`](https://prosemirror.net/docs/ref/#state.EditorState.tr) to create an instance. - -Transactions track changes to the document (they are a subclass of -[`Transform`](https://prosemirror.net/docs/ref/#transform.Transform)), but also other state changes, -like selection updates and adjustments of the set of [stored -marks](https://prosemirror.net/docs/ref/#state.EditorState.storedMarks). In addition, you can store -metadata properties in a transaction, which are extra pieces of -information that client code or plugins can use to describe what a -transaction represents, so that they can update their [own -state](https://prosemirror.net/docs/ref/#state.StateField) accordingly. - -The [editor view](https://prosemirror.net/docs/ref/#view.EditorView) uses a few metadata properties: -it will attach a property `"pointer"` with the value `true` to -selection transactions directly caused by mouse or touch input, and -a `"uiEvent"` property of that may be `"paste"`, `"cut"`, or `"drop"`. -*/ -class Transaction extends Transform { - /** - @internal - */ - constructor(state) { - super(state.doc); - // The step count for which the current selection is valid. - this.curSelectionFor = 0; - // Bitfield to track which aspects of the state were updated by - // this transaction. - this.updated = 0; - // Object used to store metadata properties for the transaction. - this.meta = Object.create(null); - this.time = Date.now(); - this.curSelection = state.selection; - this.storedMarks = state.storedMarks; - } - /** - The transaction's current selection. This defaults to the editor - selection [mapped](https://prosemirror.net/docs/ref/#state.Selection.map) through the steps in the - transaction, but can be overwritten with - [`setSelection`](https://prosemirror.net/docs/ref/#state.Transaction.setSelection). - */ - get selection() { - if (this.curSelectionFor < this.steps.length) { - this.curSelection = this.curSelection.map(this.doc, this.mapping.slice(this.curSelectionFor)); - this.curSelectionFor = this.steps.length; - } - return this.curSelection; - } - /** - Update the transaction's current selection. Will determine the - selection that the editor gets when the transaction is applied. - */ - setSelection(selection) { - if (selection.$from.doc != this.doc) - throw new RangeError("Selection passed to setSelection must point at the current document"); - this.curSelection = selection; - this.curSelectionFor = this.steps.length; - this.updated = (this.updated | UPDATED_SEL) & ~UPDATED_MARKS; - this.storedMarks = null; - return this; - } - /** - Whether the selection was explicitly updated by this transaction. - */ - get selectionSet() { - return (this.updated & UPDATED_SEL) > 0; - } - /** - Set the current stored marks. - */ - setStoredMarks(marks) { - this.storedMarks = marks; - this.updated |= UPDATED_MARKS; - return this; - } - /** - Make sure the current stored marks or, if that is null, the marks - at the selection, match the given set of marks. Does nothing if - this is already the case. - */ - ensureMarks(marks) { - if (!Mark.sameSet(this.storedMarks || this.selection.$from.marks(), marks)) - this.setStoredMarks(marks); - return this; - } - /** - Add a mark to the set of stored marks. - */ - addStoredMark(mark) { - return this.ensureMarks(mark.addToSet(this.storedMarks || this.selection.$head.marks())); - } - /** - Remove a mark or mark type from the set of stored marks. - */ - removeStoredMark(mark) { - return this.ensureMarks(mark.removeFromSet(this.storedMarks || this.selection.$head.marks())); - } - /** - Whether the stored marks were explicitly set for this transaction. - */ - get storedMarksSet() { - return (this.updated & UPDATED_MARKS) > 0; - } - /** - @internal - */ - addStep(step, doc) { - super.addStep(step, doc); - this.updated = this.updated & ~UPDATED_MARKS; - this.storedMarks = null; - } - /** - Update the timestamp for the transaction. - */ - setTime(time) { - this.time = time; - return this; - } - /** - Replace the current selection with the given slice. - */ - replaceSelection(slice) { - this.selection.replace(this, slice); - return this; - } - /** - Replace the selection with the given node. When `inheritMarks` is - true and the content is inline, it inherits the marks from the - place where it is inserted. - */ - replaceSelectionWith(node, inheritMarks = true) { - let selection = this.selection; - if (inheritMarks) - node = node.mark(this.storedMarks || (selection.empty ? selection.$from.marks() : (selection.$from.marksAcross(selection.$to) || Mark.none))); - selection.replaceWith(this, node); - return this; - } - /** - Delete the selection. - */ - deleteSelection() { - this.selection.replace(this); - return this; - } - /** - Replace the given range, or the selection if no range is given, - with a text node containing the given string. - */ - insertText(text, from, to) { - let schema = this.doc.type.schema; - if (from == null) { - if (!text) - return this.deleteSelection(); - return this.replaceSelectionWith(schema.text(text), true); - } - else { - if (to == null) - to = from; - to = to == null ? from : to; - if (!text) - return this.deleteRange(from, to); - let marks = this.storedMarks; - if (!marks) { - let $from = this.doc.resolve(from); - marks = to == from ? $from.marks() : $from.marksAcross(this.doc.resolve(to)); - } - this.replaceRangeWith(from, to, schema.text(text, marks)); - if (!this.selection.empty) - this.setSelection(Selection.near(this.selection.$to)); - return this; - } - } - /** - Store a metadata property in this transaction, keyed either by - name or by plugin. - */ - setMeta(key, value) { - this.meta[typeof key == "string" ? key : key.key] = value; - return this; - } - /** - Retrieve a metadata property for a given name or plugin. - */ - getMeta(key) { - return this.meta[typeof key == "string" ? key : key.key]; - } - /** - Returns true if this transaction doesn't contain any metadata, - and can thus safely be extended. - */ - get isGeneric() { - for (let _ in this.meta) - return false; - return true; - } - /** - Indicate that the editor should scroll the selection into view - when updated to the state produced by this transaction. - */ - scrollIntoView() { - this.updated |= UPDATED_SCROLL; - return this; - } - /** - True when this transaction has had `scrollIntoView` called on it. - */ - get scrolledIntoView() { - return (this.updated & UPDATED_SCROLL) > 0; - } -} - -function bind(f, self) { - return !self || !f ? f : f.bind(self); -} -class FieldDesc { - constructor(name, desc, self) { - this.name = name; - this.init = bind(desc.init, self); - this.apply = bind(desc.apply, self); - } -} -const baseFields = [ - new FieldDesc("doc", { - init(config) { return config.doc || config.schema.topNodeType.createAndFill(); }, - apply(tr) { return tr.doc; } - }), - new FieldDesc("selection", { - init(config, instance) { return config.selection || Selection.atStart(instance.doc); }, - apply(tr) { return tr.selection; } - }), - new FieldDesc("storedMarks", { - init(config) { return config.storedMarks || null; }, - apply(tr, _marks, _old, state) { return state.selection.$cursor ? tr.storedMarks : null; } - }), - new FieldDesc("scrollToSelection", { - init() { return 0; }, - apply(tr, prev) { return tr.scrolledIntoView ? prev + 1 : prev; } - }) -]; -// Object wrapping the part of a state object that stays the same -// across transactions. Stored in the state's `config` property. -class Configuration { - constructor(schema, plugins) { - this.schema = schema; - this.plugins = []; - this.pluginsByKey = Object.create(null); - this.fields = baseFields.slice(); - if (plugins) - plugins.forEach(plugin => { - if (this.pluginsByKey[plugin.key]) - throw new RangeError("Adding different instances of a keyed plugin (" + plugin.key + ")"); - this.plugins.push(plugin); - this.pluginsByKey[plugin.key] = plugin; - if (plugin.spec.state) - this.fields.push(new FieldDesc(plugin.key, plugin.spec.state, plugin)); - }); - } -} -/** -The state of a ProseMirror editor is represented by an object of -this type. A state is a persistent data structure—it isn't -updated, but rather a new state value is computed from an old one -using the [`apply`](https://prosemirror.net/docs/ref/#state.EditorState.apply) method. - -A state holds a number of built-in fields, and plugins can -[define](https://prosemirror.net/docs/ref/#state.PluginSpec.state) additional fields. -*/ -class EditorState { - /** - @internal - */ - constructor( - /** - @internal - */ - config) { - this.config = config; - } - /** - The schema of the state's document. - */ - get schema() { - return this.config.schema; - } - /** - The plugins that are active in this state. - */ - get plugins() { - return this.config.plugins; - } - /** - Apply the given transaction to produce a new state. - */ - apply(tr) { - return this.applyTransaction(tr).state; - } - /** - @ignore - */ - filterTransaction(tr, ignore = -1) { - for (let i = 0; i < this.config.plugins.length; i++) - if (i != ignore) { - let plugin = this.config.plugins[i]; - if (plugin.spec.filterTransaction && !plugin.spec.filterTransaction.call(plugin, tr, this)) - return false; - } - return true; - } - /** - Verbose variant of [`apply`](https://prosemirror.net/docs/ref/#state.EditorState.apply) that - returns the precise transactions that were applied (which might - be influenced by the [transaction - hooks](https://prosemirror.net/docs/ref/#state.PluginSpec.filterTransaction) of - plugins) along with the new state. - */ - applyTransaction(rootTr) { - if (!this.filterTransaction(rootTr)) - return { state: this, transactions: [] }; - let trs = [rootTr], newState = this.applyInner(rootTr), seen = null; - // This loop repeatedly gives plugins a chance to respond to - // transactions as new transactions are added, making sure to only - // pass the transactions the plugin did not see before. - for (;;) { - let haveNew = false; - for (let i = 0; i < this.config.plugins.length; i++) { - let plugin = this.config.plugins[i]; - if (plugin.spec.appendTransaction) { - let n = seen ? seen[i].n : 0, oldState = seen ? seen[i].state : this; - let tr = n < trs.length && - plugin.spec.appendTransaction.call(plugin, n ? trs.slice(n) : trs, oldState, newState); - if (tr && newState.filterTransaction(tr, i)) { - tr.setMeta("appendedTransaction", rootTr); - if (!seen) { - seen = []; - for (let j = 0; j < this.config.plugins.length; j++) - seen.push(j < i ? { state: newState, n: trs.length } : { state: this, n: 0 }); - } - trs.push(tr); - newState = newState.applyInner(tr); - haveNew = true; - } - if (seen) - seen[i] = { state: newState, n: trs.length }; - } - } - if (!haveNew) - return { state: newState, transactions: trs }; - } - } - /** - @internal - */ - applyInner(tr) { - if (!tr.before.eq(this.doc)) - throw new RangeError("Applying a mismatched transaction"); - let newInstance = new EditorState(this.config), fields = this.config.fields; - for (let i = 0; i < fields.length; i++) { - let field = fields[i]; - newInstance[field.name] = field.apply(tr, this[field.name], this, newInstance); - } - return newInstance; - } - /** - Start a [transaction](https://prosemirror.net/docs/ref/#state.Transaction) from this state. - */ - get tr() { return new Transaction(this); } - /** - Create a new state. - */ - static create(config) { - let $config = new Configuration(config.doc ? config.doc.type.schema : config.schema, config.plugins); - let instance = new EditorState($config); - for (let i = 0; i < $config.fields.length; i++) - instance[$config.fields[i].name] = $config.fields[i].init(config, instance); - return instance; - } - /** - Create a new state based on this one, but with an adjusted set - of active plugins. State fields that exist in both sets of - plugins are kept unchanged. Those that no longer exist are - dropped, and those that are new are initialized using their - [`init`](https://prosemirror.net/docs/ref/#state.StateField.init) method, passing in the new - configuration object.. - */ - reconfigure(config) { - let $config = new Configuration(this.schema, config.plugins); - let fields = $config.fields, instance = new EditorState($config); - for (let i = 0; i < fields.length; i++) { - let name = fields[i].name; - instance[name] = this.hasOwnProperty(name) ? this[name] : fields[i].init(config, instance); - } - return instance; - } - /** - Serialize this state to JSON. If you want to serialize the state - of plugins, pass an object mapping property names to use in the - resulting JSON object to plugin objects. The argument may also be - a string or number, in which case it is ignored, to support the - way `JSON.stringify` calls `toString` methods. - */ - toJSON(pluginFields) { - let result = { doc: this.doc.toJSON(), selection: this.selection.toJSON() }; - if (this.storedMarks) - result.storedMarks = this.storedMarks.map(m => m.toJSON()); - if (pluginFields && typeof pluginFields == 'object') - for (let prop in pluginFields) { - if (prop == "doc" || prop == "selection") - throw new RangeError("The JSON fields `doc` and `selection` are reserved"); - let plugin = pluginFields[prop], state = plugin.spec.state; - if (state && state.toJSON) - result[prop] = state.toJSON.call(plugin, this[plugin.key]); - } - return result; - } - /** - Deserialize a JSON representation of a state. `config` should - have at least a `schema` field, and should contain array of - plugins to initialize the state with. `pluginFields` can be used - to deserialize the state of plugins, by associating plugin - instances with the property names they use in the JSON object. - */ - static fromJSON(config, json, pluginFields) { - if (!json) - throw new RangeError("Invalid input for EditorState.fromJSON"); - if (!config.schema) - throw new RangeError("Required config field 'schema' missing"); - let $config = new Configuration(config.schema, config.plugins); - let instance = new EditorState($config); - $config.fields.forEach(field => { - if (field.name == "doc") { - instance.doc = dist_Node.fromJSON(config.schema, json.doc); - } - else if (field.name == "selection") { - instance.selection = Selection.fromJSON(instance.doc, json.selection); - } - else if (field.name == "storedMarks") { - if (json.storedMarks) - instance.storedMarks = json.storedMarks.map(config.schema.markFromJSON); - } - else { - if (pluginFields) - for (let prop in pluginFields) { - let plugin = pluginFields[prop], state = plugin.spec.state; - if (plugin.key == field.name && state && state.fromJSON && - Object.prototype.hasOwnProperty.call(json, prop)) { - instance[field.name] = state.fromJSON.call(plugin, config, json[prop], instance); - return; - } - } - instance[field.name] = field.init(config, instance); - } - }); - return instance; - } -} - -function bindProps(obj, self, target) { - for (let prop in obj) { - let val = obj[prop]; - if (val instanceof Function) - val = val.bind(self); - else if (prop == "handleDOMEvents") - val = bindProps(val, self, {}); - target[prop] = val; - } - return target; -} -/** -Plugins bundle functionality that can be added to an editor. -They are part of the [editor state](https://prosemirror.net/docs/ref/#state.EditorState) and -may influence that state and the view that contains it. -*/ -class Plugin { - /** - Create a plugin. - */ - constructor( - /** - The plugin's [spec object](https://prosemirror.net/docs/ref/#state.PluginSpec). - */ - spec) { - this.spec = spec; - /** - The [props](https://prosemirror.net/docs/ref/#view.EditorProps) exported by this plugin. - */ - this.props = {}; - if (spec.props) - bindProps(spec.props, this, this.props); - this.key = spec.key ? spec.key.key : createKey("plugin"); - } - /** - Extract the plugin's state field from an editor state. - */ - getState(state) { return state[this.key]; } -} -const keys = Object.create(null); -function createKey(name) { - if (name in keys) - return name + "$" + ++keys[name]; - keys[name] = 0; - return name + "$"; -} -/** -A key is used to [tag](https://prosemirror.net/docs/ref/#state.PluginSpec.key) plugins in a way -that makes it possible to find them, given an editor state. -Assigning a key does mean only one plugin of that type can be -active in a state. -*/ -class PluginKey { - /** - Create a plugin key. - */ - constructor(name = "key") { this.key = createKey(name); } - /** - Get the active plugin with this key, if any, from an editor - state. - */ - get(state) { return state.config.pluginsByKey[this.key]; } - /** - Get the plugin's state from an editor state. - */ - getState(state) { return state[this.key]; } -} - - - -;// CONCATENATED MODULE: ../../node_modules/prosemirror-view/dist/index.js - - - - -const nav = typeof navigator != "undefined" ? navigator : null; -const dist_doc = typeof document != "undefined" ? document : null; -const agent = (nav && nav.userAgent) || ""; -const ie_edge = /Edge\/(\d+)/.exec(agent); -const ie_upto10 = /MSIE \d/.exec(agent); -const ie_11up = /Trident\/(?:[7-9]|\d{2,})\..*rv:(\d+)/.exec(agent); -const ie = !!(ie_upto10 || ie_11up || ie_edge); -const ie_version = ie_upto10 ? document.documentMode : ie_11up ? +ie_11up[1] : ie_edge ? +ie_edge[1] : 0; -const gecko = !ie && /gecko\/(\d+)/i.test(agent); -gecko && +(/Firefox\/(\d+)/.exec(agent) || [0, 0])[1]; -const _chrome = !ie && /Chrome\/(\d+)/.exec(agent); -const chrome = !!_chrome; -const chrome_version = _chrome ? +_chrome[1] : 0; -const safari = !ie && !!nav && /Apple Computer/.test(nav.vendor); -// Is true for both iOS and iPadOS for convenience -const ios = safari && (/Mobile\/\w+/.test(agent) || !!nav && nav.maxTouchPoints > 2); -const mac = ios || (nav ? /Mac/.test(nav.platform) : false); -const android = /Android \d/.test(agent); -const webkit = !!dist_doc && "webkitFontSmoothing" in dist_doc.documentElement.style; -const webkit_version = webkit ? +(/\bAppleWebKit\/(\d+)/.exec(navigator.userAgent) || [0, 0])[1] : 0; - -const domIndex = function (node) { - for (var index = 0;; index++) { - node = node.previousSibling; - if (!node) - return index; - } -}; -const parentNode = function (node) { - let parent = node.assignedSlot || node.parentNode; - return parent && parent.nodeType == 11 ? parent.host : parent; -}; -let reusedRange = null; -// Note that this will always return the same range, because DOM range -// objects are every expensive, and keep slowing down subsequent DOM -// updates, for some reason. -const 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; -}; -// Scans forward and backward through DOM positions equivalent to the -// given one to see if the two are in the same place (i.e. after a -// text node vs at the end of that text node) -const isEquivalentPosition = function (node, off, targetNode, targetOff) { - return targetNode && (scanFor(node, off, targetNode, targetOff, -1) || - scanFor(node, off, targetNode, targetOff, 1)); -}; -const 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); -} -// Work around Chrome issue https://bugs.chromium.org/p/chromium/issues/detail?id=447523 -// (isCollapsed inappropriately returns true in shadow dom) -const selectionCollapsed = function (domSel) { - let collapsed = domSel.isCollapsed; - if (collapsed && chrome && domSel.rangeCount && !domSel.getRangeAt(0).collapsed) - collapsed = false; - return collapsed; -}; -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 windowRect(doc) { - return { left: 0, right: doc.documentElement.clientWidth, - top: 0, bottom: doc.documentElement.clientHeight }; -} -function getSide(value, side) { - return typeof value == "number" ? value : value[side]; -} -function clientRect(node) { - let rect = node.getBoundingClientRect(); - // Adjust for elements with style "transform: scale()" - let scaleX = (rect.width / node.offsetWidth) || 1; - let scaleY = (rect.height / node.offsetHeight) || 1; - // Make sure scrollbar width isn't included in the rectangle - 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 doc = 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 == doc.body; - let bounding = atTop ? windowRect(doc) : 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) { - doc.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; - } -} -// Store the scroll position of the editor's parent nodes, along with -// the top position of an element near the top of the editor, which -// will be used to make sure the visible viewport remains stable even -// when the size of the content above changes. -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: refDOM, refTop: refTop, stack: scrollStack(view.dom) }; -} -function scrollStack(dom) { - let stack = [], doc = dom.ownerDocument; - for (let cur = dom; cur; cur = parentNode(cur)) { - stack.push({ dom: cur, top: cur.scrollTop, left: cur.scrollLeft }); - if (dom == doc) - break; - } - return stack; -} -// Reset the scroll position of the editor's parent nodes to that what -// it was before, when storeScrollPos was called. -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; - } -} -let preventScrollSupported = null; -// Feature-detects support for .focus({preventScroll: true}), and uses -// a fallback kludge when not supported. -function focusPreventScroll(dom) { - if (dom.setActive) - return dom.setActive(); // in IE - if (preventScrollSupported) - return dom.focus(preventScrollSupported); - let stored = scrollStack(dom); - dom.focus(preventScrollSupported == null ? { - get preventScroll() { - preventScrollSupported = { preventScroll: true }; - return true; - } - } : undefined); - 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) { - // Browser (in caretPosition/RangeFromPoint) will agressively - // normalize towards nearby inline nodes. Since we are interested in - // positions between block nodes too, we first walk up the hierarchy - // of nodes to see if there are block nodes that the coordinates - // fall outside of. If so, we take the position before/after that - // block. If not, we call `posFromDOM` on the raw node/offset. - 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; -} -// Given an x,y position on the editor, get the position in the document. -function posAtCoords(view, coords) { - let doc = view.dom.ownerDocument, node, offset = 0; - if (doc.caretPositionFromPoint) { - try { // Firefox throws for this call in hard-to-predict circumstances (#994) - let pos = doc.caretPositionFromPoint(coords.left, coords.top); - if (pos) - ({ offsetNode: node, offset } = pos); - } - catch (_) { } - } - if (!node && doc.caretRangeFromPoint) { - let range = doc.caretRangeFromPoint(coords.left, coords.top); - if (range) - ({ startContainer: node, startOffset: offset } = range); - } - let elt = (view.root.elementFromPoint ? view.root : doc) - .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; - } - // Safari's caretRangeFromPoint returns nonsense when on a draggable element - if (safari) { - for (let p = elt; node && p; p = parentNode(p)) - if (p.draggable) - node = undefined; - } - elt = targetKludge(elt, coords); - if (node) { - if (gecko && node.nodeType == 1) { - // Firefox will sometimes return offsets into nodes, which - // have no actual children, from caretPositionFromPoint (#953) - offset = Math.min(offset, node.childNodes.length); - // It'll also move the returned position before image nodes, - // even if those are behind it. - 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++; - } - } - // Suspiciously specific kludge to work around caret*FromPoint - // never returning a position at the end of the document - 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; - // Ignore positions directly after a BR, since caret*FromPoint - // 'round up' positions that would be more accurately placed - // before the BR node. - 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]; -} -const BIDI = /[\u0590-\u05f4\u0600-\u06ff\u0700-\u08ac]/; -// Given a position in the document model, get a bounding box of the -// character at that position, relative to the window. -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) { - // These browsers support querying empty text ranges. Prefer that in - // bidi context or when at the end of a node. - if (supportEmptyRange && (BIDI.test(node.nodeValue) || (side < 0 ? !offset : offset == node.nodeValue.length))) { - let rect = singleRect(textRange(node, offset, offset), side); - // Firefox returns bad results (the position before the space) - // when querying a position directly after line-broken - // whitespace. Detect this situation and and kludge around it - 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), takeSide), takeSide < 0); - } - } - let $dom = view.state.doc.resolve(pos - (atom || 0)); - // Return a horizontal line in block context - 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); - } - // Inline, not in text node (this is not Bidi-safe) - 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)) - // BR nodes tend to only return the rectangle before them. - // Only use them if they are the last element in their parent - : 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); - } - // All else failed, just try to get a rectangle for the target node - 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(); - } -} -// Whether vertical position motion in a given direction -// from a position would leave a text block. -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; - }); -} -const 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 the textblock is all LTR, or the browser doesn't support - // Selection.modify (Edge), fall back to a primitive approach - if (!maybeRTL.test($head.parent.textContent) || !sel.modify) - return dir == "left" || dir == "backward" ? atStart : atEnd; - return withFlushedState(view, state, () => { - // This is a huge hack, but appears to be the best we can - // currently do: use `Selection.modify` to move the selection by - // one character, and see if that moves the cursor out of the - // textblock (or doesn't move it at all, when at the start/end of - // the document). - let oldRange = sel.getRangeAt(0), oldNode = sel.focusNode, oldOff = sel.focusOffset; - let oldBidiLevel = sel.caretBidiLevel // Only for Firefox - ; - sel.modify("move", dir, "character"); - let parentDOM = $head.depth ? view.docView.domAfterPos($head.before()) : view.dom; - let result = !parentDOM.contains(sel.focusNode.nodeType == 1 ? sel.focusNode : sel.focusNode.parentNode) || - (oldNode == sel.focusNode && oldOff == sel.focusOffset); - // Restore the previous selection - sel.removeAllRanges(); - sel.addRange(oldRange); - if (oldBidiLevel != null) - sel.caretBidiLevel = oldBidiLevel; - return result; - }); -} -let cachedState = null; -let cachedDir = null; -let 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); -} - -// View descriptions are data structures that describe the DOM that is -// used to represent the editor's content. They are used for: -// -// - Incremental redrawing when the document changes -// -// - Figuring out what part of the document a given DOM position -// corresponds to -// -// - Wiring in custom implementations of the editing interface for a -// given node -// -// They form a doubly-linked mutable tree, starting at `view.docView`. -const NOT_DIRTY = 0, CHILD_DIRTY = 1, CONTENT_DIRTY = 2, NODE_DIRTY = 3; -// Superclass for the various kinds of descriptions. Defines their -// basic structure and shared methods. -class ViewDesc { - constructor(parent, children, dom, - // This is the node that holds the child views. It may be null for - // descs that don't have children. - contentDOM) { - this.parent = parent; - this.children = children; - this.dom = dom; - this.contentDOM = contentDOM; - this.dirty = NOT_DIRTY; - // An expando property on the DOM node provides a link back to its - // description. - 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 = undefined; - if (this.dom.pmViewDesc == this) - this.dom.pmViewDesc = undefined; - 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 the DOM position is in the content, use the child desc after - // it to figure out a position. - 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; - } - } - // Otherwise, use various heuristics, falling back on the bias - // parameter, to determine whether to return the position at the - // start or at the end of this view desc. - 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 first = true, cur = dom; cur; cur = cur.parentNode) { - let desc = this.getDesc(cur), nodeDOM; - if (desc && (!onlyNodes || desc.node)) { - // If dom is outside of this desc's nodeDOM, don't count it. - if (first && (nodeDOM = desc.nodeDOM) && - !(nodeDOM.nodeType == 1 ? nodeDOM.contains(dom.nodeType == 1 ? dom : dom.parentNode) : nodeDOM == dom)) - first = 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 }; - // First find the position in the child array - 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 this points into the middle of a child, call through - if (offset) - return this.children[i].domFromPos(offset - this.children[i].border, side); - // Go back if there were any zero-length widgets with side >= 0 before this point - for (let prev; i && !(prev = this.children[i - 1]).size && prev instanceof WidgetViewDesc && prev.side >= 0; i--) { } - // Scan towards the first useable node - if (side <= 0) { - let prev, enter = true; - for (;; i--, enter = false) { - prev = i ? this.children[i - 1] : null; - if (!prev || prev.dom.parentNode == this.contentDOM) - break; - } - if (prev && side && enter && !prev.border && !prev.domAtom) - return prev.domFromPos(prev.size, side); - return { node: this.contentDOM, offset: prev ? domIndex(prev.dom) + 1 : 0 }; - } - else { - let next, enter = true; - for (;; i++, enter = false) { - next = i < this.children.length ? this.children[i] : null; - if (!next || next.dom.parentNode == this.contentDOM) - break; - } - if (next && enter && !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, base = 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 = base, i = 0;; i++) { - let child = this.children[i], end = offset + child.size; - if (fromOffset == -1 && from <= end) { - let childBase = offset + child.border; - // FIXME maybe descend mark views to parse a narrower range? - 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) { - // If the selection falls entirely in a child, give it to that child - 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; - // On Firefox, using Selection.collapse to put the cursor after a - // BR node for some reason doesn't always work (#1073). On Safari, - // the cursor sometimes inexplicable visually lags behind its - // reported position in such situations (#1092). - if ((gecko || safari) && anchor == head) { - let { node, offset } = anchorDOM; - if (node.nodeType == 3) { - brKludge = !!(offset && node.nodeValue[offset - 1] == "\n"); - // Issue #1128 - 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"); - } - } - // Firefox can act strangely when the selection is in front of an - // uneditable node. See #1163 and https://bugzilla.mozilla.org/show_bug.cgi?id=1709536 - 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; - // Selection.extend can be used to create an 'inverted' selection - // (one where the focus is before the anchor), but not all - // browsers support it yet. - 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 (err) { - // In some cases with Chrome the selection is empty after calling - // collapse, even when it should be valid. This appears to be a bug, but - // it is difficult to isolate. If this happens fallback to the old path - // without using extend. - if (!(err instanceof DOMException)) - throw err; - // declare global: DOMException - } - } - 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; } -} -// A widget desc represents a widget decoration, which is a DOM node -// drawn between the document nodes. -class WidgetViewDesc 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; } -} -class CompositionViewDesc 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; - } -} -// A mark desc represents a mark. May have multiple children, -// depending on how the mark is split. Note that marks are drawn using -// a fixed nesting order, for simplicity and predictability, so in -// some cases they will be split more often than would appear -// necessary. -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 || undefined }; - } - matchesMark(mark) { return this.dirty != NODE_DIRTY && this.mark.eq(mark); } - markDirty(from, to) { - super.markDirty(from, to); - // Move dirty info to nearest node view - 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; - } -} -// Node view descs are the main, most common type of view desc, and -// correspond to an actual node in the document. Unlike mark descs, -// they populate their child array themselves. -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, () => { - // (This is a function that allows the custom view to find its - // own position) - 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") { // Chrome gets confused by - 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() { - // Experimental kludge to allow opt-in re-parsing of nodes - if (this.node.type.spec.reparseInView) - return null; - // FIXME the assumption that this can always return the current - // attrs means that if the user somehow manages to change the - // attrs in the dom, that won't be picked up. Not entirely sure - // whether this is a problem - 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 { - // Chrome likes to randomly recreate parent nodes when - // backspacing things. When that happens, this tries to find the - // new parent. - 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); - 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); - // If the next node is a desc matching this widget, reuse it, - // otherwise insert the widget as a new view desc. - updater.placeWidget(widget, view, off); - }, (child, outerDeco, innerDeco, i) => { - // Make sure the wrapping mark descs match the node's marks. - updater.syncToMarks(child.marks, inline, view); - // Try several strategies for drawing this node - 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 { - // Add it as a new view - updater.addNode(child, outerDeco, innerDeco, view, off); - } - off += child.nodeSize; - }); - // Drop all remaining descs after the current position. - updater.syncToMarks([], inline, view); - if (this.node.isTextblock) - updater.addTextblockHacks(); - updater.destroyRest(); - // Sync the DOM if anything changed - if (updater.changed || this.dirty == CONTENT_DIRTY) { - // May have to protect focused DOM from being changed if a composition is active - if (localComposition) - this.protectLocalComposition(view, localComposition); - renderDescs(this.contentDOM, this.children, view); - if (ios) - iosHacks(this.dom); - } - } - localCompositionInfo(view, pos) { - // Only do something if both the selection and a focused text node - // are inside of this node - 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.domSelection(); - let textNode = nearbyTextNode(sel.focusNode, sel.focusOffset); - if (!textNode || !this.dom.contains(textNode.parentNode)) - return null; - if (this.node.inlineContent) { - // Find the text in the focused node in the node, stop if it's not - // there (may have been modified through other means, in which - // case it should overwritten) - 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 }) { - // The node is already part of a local view desc, leave it there - if (this.getDesc(node)) - return; - // Create a composition view for the orphaned nodes - 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 = undefined; - } - let desc = new CompositionViewDesc(this, topNode, node, text); - view.input.compositionNodes.push(desc); - // Patch up this.children to contain the composition view - 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 = undefined; - 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; } -} -// Create a view desc for the top-level document node, to be exported -// and used by the view class. -function docViewDesc(doc, outerDeco, innerDeco, dom, view) { - applyOuterDeco(dom, outerDeco, doc); - return new NodeViewDesc(undefined, doc, outerDeco, innerDeco, dom, dom, dom, view, 0); -} -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; } -} -// A dummy desc used to tag trailing BR or IMG nodes created to work -// around contentEditable terribleness. -class TrailingHackViewDesc 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"; } -} -// A separate subclass is used for customized node views, so that the -// extra checks only have to be made for nodes that are actually -// customized. -class CustomNodeViewDesc 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.destroy(); - } - stopEvent(event) { - return this.spec.stopEvent ? this.spec.stopEvent(event) : false; - } - ignoreMutation(mutation) { - return this.spec.ignoreMutation ? this.spec.ignoreMutation(mutation) : super.ignoreMutation(mutation); - } -} -// Sync the content of the given DOM node with the nodes associated -// with the given array of view descs, recursing into mark descs -// because this should sync the subtree for a whole node at a time. -function renderDescs(parentDOM, descs, view) { - let dom = parentDOM.firstChild, written = false; - for (let i = 0; i < descs.length; i++) { - let desc = descs[i], childDOM = desc.dom; - if (childDOM.parentNode == parentDOM) { - while (childDOM != dom) { - dom = rm(dom); - written = true; - } - dom = dom.nextSibling; - } - else { - written = true; - parentDOM.insertBefore(childDOM, dom); - } - if (desc instanceof MarkViewDesc) { - let pos = dom ? dom.previousSibling : parentDOM.lastChild; - renderDescs(desc.contentDOM, desc.children, view); - dom = pos ? pos.nextSibling : parentDOM.firstChild; - } - } - while (dom) { - dom = rm(dom); - written = true; - } - if (written && view.trackWrites == parentDOM) - view.trackWrites = null; -} -const OuterDecoLevel = function (nodeName) { - if (nodeName) - this.nodeName = nodeName; -}; -OuterDecoLevel.prototype = Object.create(null); -const noDeco = [new OuterDecoLevel]; -function computeOuterDeco(outerDeco, node, needsWrap) { - if (outerDeco.length == 0) - return noDeco; - let top = needsWrap ? noDeco[0] : new OuterDecoLevel, result = [top]; - for (let i = 0; i < outerDeco.length; i++) { - let attrs = outerDeco[i].type.attrs; - if (!attrs) - continue; - if (attrs.nodeName) - result.push(top = new OuterDecoLevel(attrs.nodeName)); - for (let name in attrs) { - let val = attrs[name]; - if (val == null) - continue; - if (needsWrap && result.length == 1) - result.push(top = new OuterDecoLevel(node.isInline ? "span" : "div")); - if (name == "class") - top.class = (top.class ? top.class + " " : "") + val; - else if (name == "style") - top.style = (top.style ? top.style + ";" : "") + val; - else if (name != "nodeName") - top[name] = val; - } - } - return result; -} -function patchOuterDeco(outerDOM, nodeDOM, prevComputed, curComputed) { - // Shortcut for trivial case - if (prevComputed == noDeco && curComputed == noDeco) - return nodeDOM; - let curDOM = nodeDOM; - for (let i = 0; i < curComputed.length; i++) { - let deco = curComputed[i], prev = prevComputed[i]; - if (i) { - let parent; - if (prev && prev.nodeName == deco.nodeName && curDOM != outerDOM && - (parent = curDOM.parentNode) && parent.nodeName.toLowerCase() == deco.nodeName) { - curDOM = parent; - } - else { - parent = document.createElement(deco.nodeName); - parent.pmIsDeco = true; - parent.appendChild(curDOM); - prev = noDeco[0]; - curDOM = parent; - } - } - patchAttributes(curDOM, prev || noDeco[0], deco); - } - return curDOM; -} -function patchAttributes(dom, prev, cur) { - for (let name in prev) - if (name != "class" && name != "style" && name != "nodeName" && !(name in cur)) - dom.removeAttribute(name); - for (let name in cur) - if (name != "class" && name != "style" && name != "nodeName" && cur[name] != prev[name]) - dom.setAttribute(name, cur[name]); - if (prev.class != cur.class) { - let prevList = prev.class ? prev.class.split(" ").filter(Boolean) : []; - let curList = cur.class ? cur.class.split(" ").filter(Boolean) : []; - for (let i = 0; i < prevList.length; i++) - if (curList.indexOf(prevList[i]) == -1) - dom.classList.remove(prevList[i]); - for (let i = 0; i < curList.length; i++) - if (prevList.indexOf(curList[i]) == -1) - dom.classList.add(curList[i]); - if (dom.classList.length == 0) - dom.removeAttribute("class"); - } - if (prev.style != cur.style) { - if (prev.style) { - let prop = /\s*([\w\-\xa1-\uffff]+)\s*:(?:"(?:\\.|[^"])*"|'(?:\\.|[^'])*'|\(.*?\)|[^;])*/g, m; - while (m = prop.exec(prev.style)) - dom.style.removeProperty(m[1]); - } - if (cur.style) - dom.style.cssText += cur.style; - } -} -function applyOuterDeco(dom, deco, node) { - return patchOuterDeco(dom, dom, noDeco, computeOuterDeco(deco, node, dom.nodeType != 1)); -} -function sameOuterDeco(a, b) { - if (a.length != b.length) - return false; - for (let i = 0; i < a.length; i++) - if (!a[i].type.eq(b[i].type)) - return false; - return true; -} -// Remove a DOM node and return its next sibling. -function rm(dom) { - let next = dom.nextSibling; - dom.parentNode.removeChild(dom); - return next; -} -// Helper class for incrementally updating a tree of mark descs and -// the widget and node descs inside of them. -class ViewTreeUpdater { - constructor(top, lock) { - this.lock = lock; - // Index into `this.top`'s child array, represents the current - // update position. - this.index = 0; - // When entering a mark, the current top and index are pushed - // onto this. - this.stack = []; - // Tracks whether anything was changed - this.changed = false; - this.top = top; - this.preMatch = preMatch(top.node.content, top); - } - // Destroy and remove the children between the given indices in - // `this.top`. - destroyBetween(start, end) { - if (start == end) - return; - for (let i = start; i < end; i++) - this.top.children[i].destroy(); - this.top.children.splice(start, end - start); - this.changed = true; - } - // Destroy all remaining children in `this.top`. - destroyRest() { - this.destroyBetween(this.index, this.top.children.length); - } - // Sync the current stack of mark descs with the given array of - // marks, reusing existing mark descs when possible. - syncToMarks(marks, inline, view) { - let keep = 0, depth = this.stack.length >> 1; - let maxKeep = Math.min(depth, marks.length); - while (keep < maxKeep && - (keep == depth - 1 ? this.top : this.stack[(keep + 1) << 1]) - .matchesMark(marks[keep]) && marks[keep].type.spec.spanning !== false) - keep++; - while (keep < depth) { - this.destroyRest(); - this.top.dirty = NOT_DIRTY; - this.index = this.stack.pop(); - this.top = this.stack.pop(); - depth--; - } - while (depth < marks.length) { - this.stack.push(this.top, this.index + 1); - let found = -1; - for (let i = this.index; i < Math.min(this.index + 3, this.top.children.length); i++) { - if (this.top.children[i].matchesMark(marks[depth])) { - found = i; - break; - } - } - if (found > -1) { - if (found > this.index) { - this.changed = true; - this.destroyBetween(this.index, found); - } - this.top = this.top.children[this.index]; - } - else { - let markDesc = MarkViewDesc.create(this.top, marks[depth], inline, view); - this.top.children.splice(this.index, 0, markDesc); - this.top = markDesc; - this.changed = true; - } - this.index = 0; - depth++; - } - } - // Try to find a node desc matching the given data. Skip over it and - // return true when successful. - findNodeMatch(node, outerDeco, innerDeco, index) { - let found = -1, targetDesc; - if (index >= this.preMatch.index && - (targetDesc = this.preMatch.matches[index - this.preMatch.index]).parent == this.top && - targetDesc.matchesNode(node, outerDeco, innerDeco)) { - found = this.top.children.indexOf(targetDesc, this.index); - } - else { - for (let i = this.index, e = Math.min(this.top.children.length, i + 5); i < e; i++) { - let child = this.top.children[i]; - if (child.matchesNode(node, outerDeco, innerDeco) && !this.preMatch.matched.has(child)) { - found = i; - break; - } - } - } - if (found < 0) - return false; - this.destroyBetween(this.index, found); - this.index++; - return true; - } - updateNodeAt(node, outerDeco, innerDeco, index, view) { - let child = this.top.children[index]; - if (child.dirty == NODE_DIRTY && child.dom == child.contentDOM) - child.dirty = CONTENT_DIRTY; - if (!child.update(node, outerDeco, innerDeco, view)) - return false; - this.destroyBetween(this.index, index); - this.index++; - return true; - } - findIndexWithChild(domNode) { - for (;;) { - let parent = domNode.parentNode; - if (!parent) - return -1; - if (parent == this.top.contentDOM) { - let desc = domNode.pmViewDesc; - if (desc) - for (let i = this.index; i < this.top.children.length; i++) { - if (this.top.children[i] == desc) - return i; - } - return -1; - } - domNode = parent; - } - } - // Try to update the next node, if any, to the given data. Checks - // pre-matches to avoid overwriting nodes that could still be used. - updateNextNode(node, outerDeco, innerDeco, view, index) { - for (let i = this.index; i < this.top.children.length; i++) { - let next = this.top.children[i]; - if (next instanceof NodeViewDesc) { - let preMatch = this.preMatch.matched.get(next); - if (preMatch != null && preMatch != index) - return false; - let nextDOM = next.dom; - // Can't update if nextDOM is or contains this.lock, except if - // it's a text node whose content already matches the new text - // and whose decorations match the new ones. - let locked = this.lock && (nextDOM == this.lock || nextDOM.nodeType == 1 && nextDOM.contains(this.lock.parentNode)) && - !(node.isText && next.node && next.node.isText && next.nodeDOM.nodeValue == node.text && - next.dirty != NODE_DIRTY && sameOuterDeco(outerDeco, next.outerDeco)); - if (!locked && next.update(node, outerDeco, innerDeco, view)) { - this.destroyBetween(this.index, i); - if (next.dom != nextDOM) - this.changed = true; - this.index++; - return true; - } - break; - } - } - return false; - } - // Insert the node as a newly created node desc. - addNode(node, outerDeco, innerDeco, view, pos) { - this.top.children.splice(this.index++, 0, NodeViewDesc.create(this.top, node, outerDeco, innerDeco, view, pos)); - this.changed = true; - } - placeWidget(widget, view, pos) { - let next = this.index < this.top.children.length ? this.top.children[this.index] : null; - if (next && next.matchesWidget(widget) && - (widget == next.widget || !next.widget.type.toDOM.parentNode)) { - this.index++; - } - else { - let desc = new WidgetViewDesc(this.top, widget, view, pos); - this.top.children.splice(this.index++, 0, desc); - this.changed = true; - } - } - // Make sure a textblock looks and behaves correctly in - // contentEditable. - addTextblockHacks() { - let lastChild = this.top.children[this.index - 1], parent = this.top; - while (lastChild instanceof MarkViewDesc) { - parent = lastChild; - lastChild = parent.children[parent.children.length - 1]; - } - if (!lastChild || // Empty textblock - !(lastChild instanceof TextViewDesc) || - /\n$/.test(lastChild.node.text)) { - // Avoid bugs in Safari's cursor drawing (#1165) and Chrome's mouse selection (#1152) - if ((safari || chrome) && lastChild && lastChild.dom.contentEditable == "false") - this.addHackNode("IMG", parent); - this.addHackNode("BR", this.top); - } - } - addHackNode(nodeName, parent) { - if (parent == this.top && this.index < parent.children.length && parent.children[this.index].matchesHack(nodeName)) { - this.index++; - } - else { - let dom = document.createElement(nodeName); - if (nodeName == "IMG") { - dom.className = "ProseMirror-separator"; - dom.alt = ""; - } - if (nodeName == "BR") - dom.className = "ProseMirror-trailingBreak"; - let hack = new TrailingHackViewDesc(this.top, [], dom, null); - if (parent != this.top) - parent.children.push(hack); - else - parent.children.splice(this.index++, 0, hack); - this.changed = true; - } - } -} -// Iterate from the end of the fragment and array of descs to find -// directly matching ones, in order to avoid overeagerly reusing those -// for other nodes. Returns the fragment index of the first node that -// is part of the sequence of matched nodes at the end of the -// fragment. -function preMatch(frag, parentDesc) { - let curDesc = parentDesc, descI = curDesc.children.length; - let fI = frag.childCount, matched = new Map, matches = []; - outer: while (fI > 0) { - let desc; - for (;;) { - if (descI) { - let next = curDesc.children[descI - 1]; - if (next instanceof MarkViewDesc) { - curDesc = next; - descI = next.children.length; - } - else { - desc = next; - descI--; - break; - } - } - else if (curDesc == parentDesc) { - break outer; - } - else { - // FIXME - descI = curDesc.parent.children.indexOf(curDesc); - curDesc = curDesc.parent; - } - } - let node = desc.node; - if (!node) - continue; - if (node != frag.child(fI - 1)) - break; - --fI; - matched.set(desc, fI); - matches.push(desc); - } - return { index: fI, matched, matches: matches.reverse() }; -} -function compareSide(a, b) { - return a.type.side - b.type.side; -} -// This function abstracts iterating over the nodes and decorations in -// a fragment. Calls `onNode` for each node, with its local and child -// decorations. Splits text nodes when there is a decoration starting -// or ending inside of them. Calls `onWidget` for each widget. -function iterDeco(parent, deco, onWidget, onNode) { - let locals = deco.locals(parent), offset = 0; - // Simple, cheap variant for when there are no local decorations - if (locals.length == 0) { - for (let i = 0; i < parent.childCount; i++) { - let child = parent.child(i); - onNode(child, locals, deco.forChild(offset, child), i); - offset += child.nodeSize; - } - return; - } - let decoIndex = 0, active = [], restNode = null; - for (let parentIndex = 0;;) { - if (decoIndex < locals.length && locals[decoIndex].to == offset) { - let widget = locals[decoIndex++], widgets; - while (decoIndex < locals.length && locals[decoIndex].to == offset) - (widgets || (widgets = [widget])).push(locals[decoIndex++]); - if (widgets) { - widgets.sort(compareSide); - for (let i = 0; i < widgets.length; i++) - onWidget(widgets[i], parentIndex, !!restNode); - } - else { - onWidget(widget, parentIndex, !!restNode); - } - } - let child, index; - if (restNode) { - index = -1; - child = restNode; - restNode = null; - } - else if (parentIndex < parent.childCount) { - index = parentIndex; - child = parent.child(parentIndex++); - } - else { - break; - } - for (let i = 0; i < active.length; i++) - if (active[i].to <= offset) - active.splice(i--, 1); - while (decoIndex < locals.length && locals[decoIndex].from <= offset && locals[decoIndex].to > offset) - active.push(locals[decoIndex++]); - let end = offset + child.nodeSize; - if (child.isText) { - let cutAt = end; - if (decoIndex < locals.length && locals[decoIndex].from < cutAt) - cutAt = locals[decoIndex].from; - for (let i = 0; i < active.length; i++) - if (active[i].to < cutAt) - cutAt = active[i].to; - if (cutAt < end) { - restNode = child.cut(cutAt - offset); - child = child.cut(0, cutAt - offset); - end = cutAt; - index = -1; - } - } - let outerDeco = child.isInline && !child.isLeaf ? active.filter(d => !d.inline) : active.slice(); - onNode(child, outerDeco, deco.forChild(offset, child), index); - offset = end; - } -} -// List markers in Mobile Safari will mysteriously disappear -// sometimes. This works around that. -function iosHacks(dom) { - if (dom.nodeName == "UL" || dom.nodeName == "OL") { - let oldCSS = dom.style.cssText; - dom.style.cssText = oldCSS + "; list-style: square !important"; - window.getComputedStyle(dom).listStyle; - dom.style.cssText = oldCSS; - } -} -function nearbyTextNode(node, offset) { - for (;;) { - if (node.nodeType == 3) - return node; - if (node.nodeType == 1 && offset > 0) { - if (node.childNodes.length > offset && node.childNodes[offset].nodeType == 3) - return node.childNodes[offset]; - node = node.childNodes[offset - 1]; - offset = nodeSize(node); - } - else if (node.nodeType == 1 && offset < node.childNodes.length) { - node = node.childNodes[offset]; - offset = 0; - } - else { - return null; - } - } -} -// Find a piece of text in an inline fragment, overlapping from-to -function findTextInFragment(frag, text, from, to) { - for (let i = 0, pos = 0; i < frag.childCount && pos <= to;) { - let child = frag.child(i++), childStart = pos; - pos += child.nodeSize; - if (!child.isText) - continue; - let str = child.text; - while (i < frag.childCount) { - let next = frag.child(i++); - pos += next.nodeSize; - if (!next.isText) - break; - str += next.text; - } - if (pos >= from) { - let found = childStart < to ? str.lastIndexOf(text, to - childStart - 1) : -1; - if (found >= 0 && found + text.length + childStart >= from) - return childStart + found; - if (from == to && str.length >= (to + text.length) - childStart && - str.slice(to - childStart, to - childStart + text.length) == text) - return to; - } - } - return -1; -} -// Replace range from-to in an array of view descs with replacement -// (may be null to just delete). This goes very much against the grain -// of the rest of this code, which tends to create nodes with the -// right shape in one go, rather than messing with them after -// creation, but is necessary in the composition hack. -function replaceNodes(nodes, from, to, view, replacement) { - let result = []; - for (let i = 0, off = 0; i < nodes.length; i++) { - let child = nodes[i], start = off, end = off += child.size; - if (start >= to || end <= from) { - result.push(child); - } - else { - if (start < from) - result.push(child.slice(0, from - start, view)); - if (replacement) { - result.push(replacement); - replacement = undefined; - } - if (end > to) - result.push(child.slice(to - start, child.size, view)); - } - } - return result; -} - -function selectionFromDOM(view, origin = null) { - let domSel = view.domSelection(), doc = view.state.doc; - if (!domSel.focusNode) - return null; - let nearestDesc = view.docView.nearestDesc(domSel.focusNode), inWidget = nearestDesc && nearestDesc.size == 0; - let head = view.docView.posFromDOM(domSel.focusNode, domSel.focusOffset, 1); - if (head < 0) - return null; - let $head = doc.resolve(head), $anchor, selection; - if (selectionCollapsed(domSel)) { - $anchor = $head; - while (nearestDesc && !nearestDesc.node) - nearestDesc = nearestDesc.parent; - let nearestDescNode = nearestDesc.node; - if (nearestDesc && nearestDescNode.isAtom && dist_NodeSelection.isSelectable(nearestDescNode) && nearestDesc.parent - && !(nearestDescNode.isInline && isOnEdge(domSel.focusNode, domSel.focusOffset, nearestDesc.dom))) { - let pos = nearestDesc.posBefore; - selection = new dist_NodeSelection(head == pos ? $head : doc.resolve(pos)); - } - } - else { - let anchor = view.docView.posFromDOM(domSel.anchorNode, domSel.anchorOffset, 1); - if (anchor < 0) - return null; - $anchor = doc.resolve(anchor); - } - if (!selection) { - let bias = origin == "pointer" || (view.state.selection.head < $head.pos && !inWidget) ? 1 : -1; - selection = selectionBetween(view, $anchor, $head, bias); - } - return selection; -} -function editorOwnsSelection(view) { - return view.editable ? view.hasFocus() : - hasSelection(view) && document.activeElement && document.activeElement.contains(view.dom); -} -function selectionToDOM(view, force = false) { - let sel = view.state.selection; - syncNodeSelection(view, sel); - if (!editorOwnsSelection(view)) - return; - // The delayed drag selection causes issues with Cell Selections - // in Safari. And the drag selection delay is to workarond issues - // which only present in Chrome. - if (!force && view.input.mouseDown && view.input.mouseDown.allowDefault && chrome) { - let domSel = view.domSelection(), curSel = view.domObserver.currentSelection; - if (domSel.anchorNode && curSel.anchorNode && - isEquivalentPosition(domSel.anchorNode, domSel.anchorOffset, curSel.anchorNode, curSel.anchorOffset)) { - view.input.mouseDown.delayedSelectionSync = true; - view.domObserver.setCurSelection(); - return; - } - } - view.domObserver.disconnectSelection(); - if (view.cursorWrapper) { - selectCursorWrapper(view); - } - else { - let { anchor, head } = sel, resetEditableFrom, resetEditableTo; - if (brokenSelectBetweenUneditable && !(sel instanceof TextSelection)) { - if (!sel.$from.parent.inlineContent) - resetEditableFrom = temporarilyEditableNear(view, sel.from); - if (!sel.empty && !sel.$from.parent.inlineContent) - resetEditableTo = temporarilyEditableNear(view, sel.to); - } - view.docView.setSelection(anchor, head, view.root, force); - if (brokenSelectBetweenUneditable) { - if (resetEditableFrom) - resetEditable(resetEditableFrom); - if (resetEditableTo) - resetEditable(resetEditableTo); - } - if (sel.visible) { - view.dom.classList.remove("ProseMirror-hideselection"); - } - else { - view.dom.classList.add("ProseMirror-hideselection"); - if ("onselectionchange" in document) - removeClassOnSelectionChange(view); - } - } - view.domObserver.setCurSelection(); - view.domObserver.connectSelection(); -} -// Kludge to work around Webkit not allowing a selection to start/end -// between non-editable block nodes. We briefly make something -// editable, set the selection, then set it uneditable again. -const brokenSelectBetweenUneditable = safari || chrome && chrome_version < 63; -function temporarilyEditableNear(view, pos) { - let { node, offset } = view.docView.domFromPos(pos, 0); - let after = offset < node.childNodes.length ? node.childNodes[offset] : null; - let before = offset ? node.childNodes[offset - 1] : null; - if (safari && after && after.contentEditable == "false") - return setEditable(after); - if ((!after || after.contentEditable == "false") && - (!before || before.contentEditable == "false")) { - if (after) - return setEditable(after); - else if (before) - return setEditable(before); - } -} -function setEditable(element) { - element.contentEditable = "true"; - if (safari && element.draggable) { - element.draggable = false; - element.wasDraggable = true; - } - return element; -} -function resetEditable(element) { - element.contentEditable = "false"; - if (element.wasDraggable) { - element.draggable = true; - element.wasDraggable = null; - } -} -function removeClassOnSelectionChange(view) { - let doc = view.dom.ownerDocument; - doc.removeEventListener("selectionchange", view.input.hideSelectionGuard); - let domSel = view.domSelection(); - let node = domSel.anchorNode, offset = domSel.anchorOffset; - doc.addEventListener("selectionchange", view.input.hideSelectionGuard = () => { - if (domSel.anchorNode != node || domSel.anchorOffset != offset) { - doc.removeEventListener("selectionchange", view.input.hideSelectionGuard); - setTimeout(() => { - if (!editorOwnsSelection(view) || view.state.selection.visible) - view.dom.classList.remove("ProseMirror-hideselection"); - }, 20); - } - }); -} -function selectCursorWrapper(view) { - let domSel = view.domSelection(), range = document.createRange(); - let node = view.cursorWrapper.dom, img = node.nodeName == "IMG"; - if (img) - range.setEnd(node.parentNode, domIndex(node) + 1); - else - range.setEnd(node, 0); - range.collapse(false); - domSel.removeAllRanges(); - domSel.addRange(range); - // Kludge to kill 'control selection' in IE11 when selecting an - // invisible cursor wrapper, since that would result in those weird - // resize handles and a selection that considers the absolutely - // positioned wrapper, rather than the root editable node, the - // focused element. - if (!img && !view.state.selection.visible && ie && ie_version <= 11) { - node.disabled = true; - node.disabled = false; - } -} -function syncNodeSelection(view, sel) { - if (sel instanceof dist_NodeSelection) { - let desc = view.docView.descAt(sel.from); - if (desc != view.lastSelectedViewDesc) { - clearNodeSelection(view); - if (desc) - desc.selectNode(); - view.lastSelectedViewDesc = desc; - } - } - else { - clearNodeSelection(view); - } -} -// Clear all DOM statefulness of the last node selection. -function clearNodeSelection(view) { - if (view.lastSelectedViewDesc) { - if (view.lastSelectedViewDesc.parent) - view.lastSelectedViewDesc.deselectNode(); - view.lastSelectedViewDesc = undefined; - } -} -function selectionBetween(view, $anchor, $head, bias) { - return view.someProp("createSelectionBetween", f => f(view, $anchor, $head)) - || TextSelection.between($anchor, $head, bias); -} -function hasFocusAndSelection(view) { - if (view.editable && view.root.activeElement != view.dom) - return false; - return hasSelection(view); -} -function hasSelection(view) { - let sel = view.domSelection(); - if (!sel.anchorNode) - return false; - try { - // Firefox will raise 'permission denied' errors when accessing - // properties of `sel.anchorNode` when it's in a generated CSS - // element. - return view.dom.contains(sel.anchorNode.nodeType == 3 ? sel.anchorNode.parentNode : sel.anchorNode) && - (view.editable || view.dom.contains(sel.focusNode.nodeType == 3 ? sel.focusNode.parentNode : sel.focusNode)); - } - catch (_) { - return false; - } -} -function anchorInRightPlace(view) { - let anchorDOM = view.docView.domFromPos(view.state.selection.anchor, 0); - let domSel = view.domSelection(); - return isEquivalentPosition(anchorDOM.node, anchorDOM.offset, domSel.anchorNode, domSel.anchorOffset); -} - -function moveSelectionBlock(state, dir) { - let { $anchor, $head } = state.selection; - let $side = dir > 0 ? $anchor.max($head) : $anchor.min($head); - let $start = !$side.parent.inlineContent ? $side : $side.depth ? state.doc.resolve(dir > 0 ? $side.after() : $side.before()) : null; - return $start && Selection.findFrom($start, dir); -} -function apply(view, sel) { - view.dispatch(view.state.tr.setSelection(sel).scrollIntoView()); - return true; -} -function selectHorizontally(view, dir, mods) { - let sel = view.state.selection; - if (sel instanceof TextSelection) { - if (!sel.empty || mods.indexOf("s") > -1) { - return false; - } - else if (view.endOfTextblock(dir > 0 ? "right" : "left")) { - let next = moveSelectionBlock(view.state, dir); - if (next && (next instanceof dist_NodeSelection)) - return apply(view, next); - return false; - } - else if (!(mac && mods.indexOf("m") > -1)) { - let $head = sel.$head, node = $head.textOffset ? null : dir < 0 ? $head.nodeBefore : $head.nodeAfter, desc; - if (!node || node.isText) - return false; - let nodePos = dir < 0 ? $head.pos - node.nodeSize : $head.pos; - if (!(node.isAtom || (desc = view.docView.descAt(nodePos)) && !desc.contentDOM)) - return false; - if (dist_NodeSelection.isSelectable(node)) { - return apply(view, new dist_NodeSelection(dir < 0 ? view.state.doc.resolve($head.pos - node.nodeSize) : $head)); - } - else if (webkit) { - // Chrome and Safari will introduce extra pointless cursor - // positions around inline uneditable nodes, so we have to - // take over and move the cursor past them (#937) - return apply(view, new TextSelection(view.state.doc.resolve(dir < 0 ? nodePos : nodePos + node.nodeSize))); - } - else { - return false; - } - } - } - else if (sel instanceof dist_NodeSelection && sel.node.isInline) { - return apply(view, new TextSelection(dir > 0 ? sel.$to : sel.$from)); - } - else { - let next = moveSelectionBlock(view.state, dir); - if (next) - return apply(view, next); - return false; - } -} -function nodeLen(node) { - return node.nodeType == 3 ? node.nodeValue.length : node.childNodes.length; -} -function isIgnorable(dom) { - let desc = dom.pmViewDesc; - return desc && desc.size == 0 && (dom.nextSibling || dom.nodeName != "BR"); -} -// Make sure the cursor isn't directly after one or more ignored -// nodes, which will confuse the browser's cursor motion logic. -function skipIgnoredNodesLeft(view) { - let sel = view.domSelection(); - let node = sel.focusNode, offset = sel.focusOffset; - if (!node) - return; - let moveNode, moveOffset, force = false; - // Gecko will do odd things when the selection is directly in front - // of a non-editable node, so in that case, move it into the next - // node if possible. Issue prosemirror/prosemirror#832. - if (gecko && node.nodeType == 1 && offset < nodeLen(node) && isIgnorable(node.childNodes[offset])) - force = true; - for (;;) { - if (offset > 0) { - if (node.nodeType != 1) { - break; - } - else { - let before = node.childNodes[offset - 1]; - if (isIgnorable(before)) { - moveNode = node; - moveOffset = --offset; - } - else if (before.nodeType == 3) { - node = before; - offset = node.nodeValue.length; - } - else - break; - } - } - else if (isBlockNode(node)) { - break; - } - else { - let prev = node.previousSibling; - while (prev && isIgnorable(prev)) { - moveNode = node.parentNode; - moveOffset = domIndex(prev); - prev = prev.previousSibling; - } - if (!prev) { - node = node.parentNode; - if (node == view.dom) - break; - offset = 0; - } - else { - node = prev; - offset = nodeLen(node); - } - } - } - if (force) - setSelFocus(view, sel, node, offset); - else if (moveNode) - setSelFocus(view, sel, moveNode, moveOffset); -} -// Make sure the cursor isn't directly before one or more ignored -// nodes. -function skipIgnoredNodesRight(view) { - let sel = view.domSelection(); - let node = sel.focusNode, offset = sel.focusOffset; - if (!node) - return; - let len = nodeLen(node); - let moveNode, moveOffset; - for (;;) { - if (offset < len) { - if (node.nodeType != 1) - break; - let after = node.childNodes[offset]; - if (isIgnorable(after)) { - moveNode = node; - moveOffset = ++offset; - } - else - break; - } - else if (isBlockNode(node)) { - break; - } - else { - let next = node.nextSibling; - while (next && isIgnorable(next)) { - moveNode = next.parentNode; - moveOffset = domIndex(next) + 1; - next = next.nextSibling; - } - if (!next) { - node = node.parentNode; - if (node == view.dom) - break; - offset = len = 0; - } - else { - node = next; - offset = 0; - len = nodeLen(node); - } - } - } - if (moveNode) - setSelFocus(view, sel, moveNode, moveOffset); -} -function isBlockNode(dom) { - let desc = dom.pmViewDesc; - return desc && desc.node && desc.node.isBlock; -} -function setSelFocus(view, sel, node, offset) { - if (selectionCollapsed(sel)) { - let range = document.createRange(); - range.setEnd(node, offset); - range.setStart(node, offset); - sel.removeAllRanges(); - sel.addRange(range); - } - else if (sel.extend) { - sel.extend(node, offset); - } - view.domObserver.setCurSelection(); - let { state } = view; - // If no state update ends up happening, reset the selection. - setTimeout(() => { - if (view.state == state) - selectionToDOM(view); - }, 50); -} -// Check whether vertical selection motion would involve node -// selections. If so, apply it (if not, the result is left to the -// browser) -function selectVertically(view, dir, mods) { - let sel = view.state.selection; - if (sel instanceof TextSelection && !sel.empty || mods.indexOf("s") > -1) - return false; - if (mac && mods.indexOf("m") > -1) - return false; - let { $from, $to } = sel; - if (!$from.parent.inlineContent || view.endOfTextblock(dir < 0 ? "up" : "down")) { - let next = moveSelectionBlock(view.state, dir); - if (next && (next instanceof dist_NodeSelection)) - return apply(view, next); - } - if (!$from.parent.inlineContent) { - let side = dir < 0 ? $from : $to; - let beyond = sel instanceof AllSelection ? Selection.near(side, dir) : Selection.findFrom(side, dir); - return beyond ? apply(view, beyond) : false; - } - return false; -} -function stopNativeHorizontalDelete(view, dir) { - if (!(view.state.selection instanceof TextSelection)) - return true; - let { $head, $anchor, empty } = view.state.selection; - if (!$head.sameParent($anchor)) - return true; - if (!empty) - return false; - if (view.endOfTextblock(dir > 0 ? "forward" : "backward")) - return true; - let nextNode = !$head.textOffset && (dir < 0 ? $head.nodeBefore : $head.nodeAfter); - if (nextNode && !nextNode.isText) { - let tr = view.state.tr; - if (dir < 0) - tr.delete($head.pos - nextNode.nodeSize, $head.pos); - else - tr.delete($head.pos, $head.pos + nextNode.nodeSize); - view.dispatch(tr); - return true; - } - return false; -} -function switchEditable(view, node, state) { - view.domObserver.stop(); - node.contentEditable = state; - view.domObserver.start(); -} -// Issue #867 / #1090 / https://bugs.chromium.org/p/chromium/issues/detail?id=903821 -// In which Safari (and at some point in the past, Chrome) does really -// wrong things when the down arrow is pressed when the cursor is -// directly at the start of a textblock and has an uneditable node -// after it -function safariDownArrowBug(view) { - if (!safari || view.state.selection.$head.parentOffset > 0) - return false; - let { focusNode, focusOffset } = view.domSelection(); - if (focusNode && focusNode.nodeType == 1 && focusOffset == 0 && - focusNode.firstChild && focusNode.firstChild.contentEditable == "false") { - let child = focusNode.firstChild; - switchEditable(view, child, "true"); - setTimeout(() => switchEditable(view, child, "false"), 20); - } - return false; -} -// A backdrop key mapping used to make sure we always suppress keys -// that have a dangerous default effect, even if the commands they are -// bound to return false, and to make sure that cursor-motion keys -// find a cursor (as opposed to a node selection) when pressed. For -// cursor-motion keys, the code in the handlers also takes care of -// block selections. -function getMods(event) { - let result = ""; - if (event.ctrlKey) - result += "c"; - if (event.metaKey) - result += "m"; - if (event.altKey) - result += "a"; - if (event.shiftKey) - result += "s"; - return result; -} -function captureKeyDown(view, event) { - let code = event.keyCode, mods = getMods(event); - if (code == 8 || (mac && code == 72 && mods == "c")) { // Backspace, Ctrl-h on Mac - return stopNativeHorizontalDelete(view, -1) || skipIgnoredNodesLeft(view); - } - else if (code == 46 || (mac && code == 68 && mods == "c")) { // Delete, Ctrl-d on Mac - return stopNativeHorizontalDelete(view, 1) || skipIgnoredNodesRight(view); - } - else if (code == 13 || code == 27) { // Enter, Esc - return true; - } - else if (code == 37 || (mac && code == 66 && mods == "c")) { // Left arrow, Ctrl-b on Mac - return selectHorizontally(view, -1, mods) || skipIgnoredNodesLeft(view); - } - else if (code == 39 || (mac && code == 70 && mods == "c")) { // Right arrow, Ctrl-f on Mac - return selectHorizontally(view, 1, mods) || skipIgnoredNodesRight(view); - } - else if (code == 38 || (mac && code == 80 && mods == "c")) { // Up arrow, Ctrl-p on Mac - return selectVertically(view, -1, mods) || skipIgnoredNodesLeft(view); - } - else if (code == 40 || (mac && code == 78 && mods == "c")) { // Down arrow, Ctrl-n on Mac - return safariDownArrowBug(view) || selectVertically(view, 1, mods) || skipIgnoredNodesRight(view); - } - else if (mods == (mac ? "m" : "c") && - (code == 66 || code == 73 || code == 89 || code == 90)) { // Mod-[biyz] - return true; - } - return false; -} - -function serializeForClipboard(view, slice) { - let context = [], { content, openStart, openEnd } = slice; - while (openStart > 1 && openEnd > 1 && content.childCount == 1 && content.firstChild.childCount == 1) { - openStart--; - openEnd--; - let node = content.firstChild; - context.push(node.type.name, node.attrs != node.type.defaultAttrs ? node.attrs : null); - content = node.content; - } - let serializer = view.someProp("clipboardSerializer") || DOMSerializer.fromSchema(view.state.schema); - let doc = detachedDoc(), wrap = doc.createElement("div"); - wrap.appendChild(serializer.serializeFragment(content, { document: doc })); - let firstChild = wrap.firstChild, needsWrap, wrappers = 0; - while (firstChild && firstChild.nodeType == 1 && (needsWrap = wrapMap[firstChild.nodeName.toLowerCase()])) { - for (let i = needsWrap.length - 1; i >= 0; i--) { - let wrapper = doc.createElement(needsWrap[i]); - while (wrap.firstChild) - wrapper.appendChild(wrap.firstChild); - wrap.appendChild(wrapper); - wrappers++; - } - firstChild = wrap.firstChild; - } - if (firstChild && firstChild.nodeType == 1) - firstChild.setAttribute("data-pm-slice", `${openStart} ${openEnd}${wrappers ? ` -${wrappers}` : ""} ${JSON.stringify(context)}`); - let text = view.someProp("clipboardTextSerializer", f => f(slice)) || - slice.content.textBetween(0, slice.content.size, "\n\n"); - return { dom: wrap, text }; -} -// Read a slice of content from the clipboard (or drop data). -function parseFromClipboard(view, text, html, plainText, $context) { - let inCode = $context.parent.type.spec.code; - let dom, slice; - if (!html && !text) - return null; - let asText = text && (plainText || inCode || !html); - if (asText) { - view.someProp("transformPastedText", f => { text = f(text, inCode || plainText); }); - if (inCode) - return text ? new Slice(Fragment.from(view.state.schema.text(text.replace(/\r\n?/g, "\n"))), 0, 0) : Slice.empty; - let parsed = view.someProp("clipboardTextParser", f => f(text, $context, plainText)); - if (parsed) { - slice = parsed; - } - else { - let marks = $context.marks(); - let { schema } = view.state, serializer = DOMSerializer.fromSchema(schema); - dom = document.createElement("div"); - text.split(/(?:\r\n?|\n)+/).forEach(block => { - let p = dom.appendChild(document.createElement("p")); - if (block) - p.appendChild(serializer.serializeNode(schema.text(block, marks))); - }); - } - } - else { - view.someProp("transformPastedHTML", f => { html = f(html); }); - dom = readHTML(html); - if (webkit) - restoreReplacedSpaces(dom); - } - let contextNode = dom && dom.querySelector("[data-pm-slice]"); - let sliceData = contextNode && /^(\d+) (\d+)(?: -(\d+))? (.*)/.exec(contextNode.getAttribute("data-pm-slice") || ""); - if (sliceData && sliceData[3]) - for (let i = +sliceData[3]; i > 0 && dom.firstChild; i--) - dom = dom.firstChild; - if (!slice) { - let parser = view.someProp("clipboardParser") || view.someProp("domParser") || DOMParser.fromSchema(view.state.schema); - slice = parser.parseSlice(dom, { - preserveWhitespace: !!(asText || sliceData), - context: $context, - ruleFromNode(dom) { - if (dom.nodeName == "BR" && !dom.nextSibling && - dom.parentNode && !inlineParents.test(dom.parentNode.nodeName)) - return { ignore: true }; - return null; - } - }); - } - if (sliceData) { - slice = addContext(closeSlice(slice, +sliceData[1], +sliceData[2]), sliceData[4]); - } - else { // HTML wasn't created by ProseMirror. Make sure top-level siblings are coherent - slice = Slice.maxOpen(normalizeSiblings(slice.content, $context), true); - if (slice.openStart || slice.openEnd) { - let openStart = 0, openEnd = 0; - for (let node = slice.content.firstChild; openStart < slice.openStart && !node.type.spec.isolating; openStart++, node = node.firstChild) { } - for (let node = slice.content.lastChild; openEnd < slice.openEnd && !node.type.spec.isolating; openEnd++, node = node.lastChild) { } - slice = closeSlice(slice, openStart, openEnd); - } - } - view.someProp("transformPasted", f => { slice = f(slice); }); - return slice; -} -const inlineParents = /^(a|abbr|acronym|b|cite|code|del|em|i|ins|kbd|label|output|q|ruby|s|samp|span|strong|sub|sup|time|u|tt|var)$/i; -// Takes a slice parsed with parseSlice, which means there hasn't been -// any content-expression checking done on the top nodes, tries to -// find a parent node in the current context that might fit the nodes, -// and if successful, rebuilds the slice so that it fits into that parent. -// -// This addresses the problem that Transform.replace expects a -// coherent slice, and will fail to place a set of siblings that don't -// fit anywhere in the schema. -function normalizeSiblings(fragment, $context) { - if (fragment.childCount < 2) - return fragment; - for (let d = $context.depth; d >= 0; d--) { - let parent = $context.node(d); - let match = parent.contentMatchAt($context.index(d)); - let lastWrap, result = []; - fragment.forEach(node => { - if (!result) - return; - let wrap = match.findWrapping(node.type), inLast; - if (!wrap) - return result = null; - if (inLast = result.length && lastWrap.length && addToSibling(wrap, lastWrap, node, result[result.length - 1], 0)) { - result[result.length - 1] = inLast; - } - else { - if (result.length) - result[result.length - 1] = closeRight(result[result.length - 1], lastWrap.length); - let wrapped = withWrappers(node, wrap); - result.push(wrapped); - match = match.matchType(wrapped.type); - lastWrap = wrap; - } - }); - if (result) - return Fragment.from(result); - } - return fragment; -} -function withWrappers(node, wrap, from = 0) { - for (let i = wrap.length - 1; i >= from; i--) - node = wrap[i].create(null, Fragment.from(node)); - return node; -} -// Used to group adjacent nodes wrapped in similar parents by -// normalizeSiblings into the same parent node -function addToSibling(wrap, lastWrap, node, sibling, depth) { - if (depth < wrap.length && depth < lastWrap.length && wrap[depth] == lastWrap[depth]) { - let inner = addToSibling(wrap, lastWrap, node, sibling.lastChild, depth + 1); - if (inner) - return sibling.copy(sibling.content.replaceChild(sibling.childCount - 1, inner)); - let match = sibling.contentMatchAt(sibling.childCount); - if (match.matchType(depth == wrap.length - 1 ? node.type : wrap[depth + 1])) - return sibling.copy(sibling.content.append(Fragment.from(withWrappers(node, wrap, depth + 1)))); - } -} -function closeRight(node, depth) { - if (depth == 0) - return node; - let fragment = node.content.replaceChild(node.childCount - 1, closeRight(node.lastChild, depth - 1)); - let fill = node.contentMatchAt(node.childCount).fillBefore(Fragment.empty, true); - return node.copy(fragment.append(fill)); -} -function closeRange(fragment, side, from, to, depth, openEnd) { - let node = side < 0 ? fragment.firstChild : fragment.lastChild, inner = node.content; - if (depth < to - 1) - inner = closeRange(inner, side, from, to, depth + 1, openEnd); - if (depth >= from) - inner = side < 0 ? node.contentMatchAt(0).fillBefore(inner, fragment.childCount > 1 || openEnd <= depth).append(inner) - : inner.append(node.contentMatchAt(node.childCount).fillBefore(Fragment.empty, true)); - return fragment.replaceChild(side < 0 ? 0 : fragment.childCount - 1, node.copy(inner)); -} -function closeSlice(slice, openStart, openEnd) { - if (openStart < slice.openStart) - slice = new Slice(closeRange(slice.content, -1, openStart, slice.openStart, 0, slice.openEnd), openStart, slice.openEnd); - if (openEnd < slice.openEnd) - slice = new Slice(closeRange(slice.content, 1, openEnd, slice.openEnd, 0, 0), slice.openStart, openEnd); - return slice; -} -// Trick from jQuery -- some elements must be wrapped in other -// elements for innerHTML to work. I.e. if you do `div.innerHTML = -// "
'; - -;// CONCATENATED MODULE: ./src/utils/dom.ts - - - - - - - - - - -function isPositionInBox(style, offsetX, offsetY) { - var left = parseInt(style.left, 10); - var top = parseInt(style.top, 10); - var width = parseInt(style.width, 10) + parseInt(style.paddingLeft, 10) + parseInt(style.paddingRight, 10); - var height = parseInt(style.height, 10) + parseInt(style.paddingTop, 10) + parseInt(style.paddingBottom, 10); - return offsetX >= left && offsetX <= left + width && offsetY >= top && offsetY <= top + height; -} -var CLS_PREFIX = 'toastui-editor-'; -function cls() { - var names = []; - for (var _i = 0; _i < arguments.length; _i++) { - names[_i] = arguments[_i]; - } - var result = []; - for (var _a = 0, names_1 = names; _a < names_1.length; _a++) { - var name = names_1[_a]; - var className = void 0; - if (Array.isArray(name)) { - className = name[0] ? name[1] : null; - } - else { - className = name; - } - if (className) { - result.push("" + CLS_PREFIX + className); - } - } - return result.join(' '); -} -function clsWithMdPrefix() { - var names = []; - for (var _i = 0; _i < arguments.length; _i++) { - names[_i] = arguments[_i]; - } - return names.map(function (className) { return CLS_PREFIX + "md-" + className; }).join(' '); -} -function isTextNode(node) { - return (node === null || node === void 0 ? void 0 : node.nodeType) === Node.TEXT_NODE; -} -function isElemNode(node) { - return node && node.nodeType === Node.ELEMENT_NODE; -} -function findNodes(element, selector) { - var nodeList = toArray_default()(element.querySelectorAll(selector)); - if (nodeList.length) { - return nodeList; - } - return []; -} -function appendNodes(node, nodesToAppend) { - nodesToAppend = isArray_default()(nodesToAppend) ? toArray_default()(nodesToAppend) : [nodesToAppend]; - nodesToAppend.forEach(function (nodeToAppend) { - node.appendChild(nodeToAppend); - }); -} -function insertBeforeNode(insertedNode, node) { - if (node.parentNode) { - node.parentNode.insertBefore(insertedNode, node); - } -} -function removeNode(node) { - if (node.parentNode) { - node.parentNode.removeChild(node); - } -} -function unwrapNode(node) { - var result = []; - while (node.firstChild) { - result.push(node.firstChild); - if (node.parentNode) { - node.parentNode.insertBefore(node.firstChild, node); - } - } - removeNode(node); - return result; -} -function toggleClass(element, className, state) { - if (isUndefined_default()(state)) { - state = !hasClass_default()(element, className); - } - var toggleFn = state ? (addClass_default()) : (removeClass_default()); - toggleFn(element, className); -} -function createElementWith(contents, target) { - var container = document.createElement('div'); - if (isString_default()(contents)) { - container.innerHTML = contents; - } - else { - container.appendChild(contents); - } - var firstChild = container.firstChild; - if (target) { - target.appendChild(firstChild); - } - return firstChild; -} -function getOuterWidth(el) { - var computed = window.getComputedStyle(el); - return (['margin-left', 'margin-right'].reduce(function (acc, type) { return acc + parseInt(computed.getPropertyValue(type), 10); }, 0) + el.offsetWidth); -} -function closest(node, found) { - var condition; - if (isString_default()(found)) { - condition = function (target) { return matches_default()(target, found); }; - } - else { - condition = function (target) { return target === found; }; - } - while (node && node !== document) { - if (isElemNode(node) && condition(node)) { - return node; - } - node = node.parentNode; - } - return null; -} -function getTotalOffset(el, root) { - var offsetTop = 0; - var offsetLeft = 0; - while (el && el !== root) { - var top = el.offsetTop, left = el.offsetLeft, offsetParent = el.offsetParent; - offsetTop += top; - offsetLeft += left; - if (offsetParent === root.offsetParent) { - break; - } - el = el.offsetParent; - } - return { offsetTop: offsetTop, offsetLeft: offsetLeft }; -} -function finalizeHtml(html, needHtmlText) { - var result; - if (needHtmlText) { - result = html.innerHTML; - } - else { - var frag = document.createDocumentFragment(); - var childNodes = toArray(html.childNodes); - var length = childNodes.length; - for (var i = 0; i < length; i += 1) { - frag.appendChild(childNodes[i]); - } - result = frag; - } - return result; -} -function dom_empty(node) { - while (node.firstChild) { - node.removeChild(node.firstChild); - } -} -function appendNode(node, appended) { - if (isString(appended)) { - node.insertAdjacentHTML('beforeend', appended); - } - else { - var nodes = appended.length - ? toArray(appended) - : [appended]; - for (var i = 0, len = nodes.length; i < len; i += 1) { - node.appendChild(nodes[i]); - } - } -} -function prependNode(node, appended) { - if (isString(appended)) { - node.insertAdjacentHTML('afterbegin', appended); - } - else { - var nodes = appended.length - ? toArray(appended) - : [appended]; - for (var i = nodes.length - 1, len = 0; i >= len; i -= 1) { - node.insertBefore(nodes[i], node.firstChild); - } - } -} -function setAttributes(attributes, element) { - Object.keys(attributes).forEach(function (attrName) { - if (isNil(attributes[attrName])) { - element.removeAttribute(attrName); - } - else { - element.setAttribute(attrName, attributes[attrName]); - } - }); -} -function replaceBRWithEmptyBlock(html) { - // remove br in paragraph to compatible with markdown - var replacedHTML = html.replace(/
<\/p>/gi, '
- var inList = paraDepth >= listDepth; - var curDepth = inList ? cellDepth + 1 : paraDepth; - var moveBeforeCell = canMoveToBeforeCell(direction, [paraDepth, listDepth, curDepth], from, doc, inList); - var moveAfterCell = canMoveToAfterCell(direction, curDepth, from, doc, inList); - return moveBeforeCell && moveAfterCell; -} -function canBeOutOfTable(direction, map, _a) { - var rowIdx = _a[0], colIdx = _a[1]; - var rowspanInfo = map.getRowspanStartInfo(rowIdx, colIdx); - var inFirstRow = direction === Direction.UP && rowIdx === 0; - var inLastRow = direction === Direction.DOWN && - ((rowspanInfo === null || rowspanInfo === void 0 ? void 0 : rowspanInfo.count) > 1 ? rowIdx + rowspanInfo.count - 1 : rowIdx) === map.totalRowCount - 1; - return inFirstRow || inLastRow; -} -function addParagraphBeforeTable(tr, map, schema) { - var tableStartPos = tr.doc.resolve(map.tableStartOffset - 1); - if (!tableStartPos.nodeBefore) { - return addParagraph(tr, tableStartPos, schema); - } - return tr.setSelection(Selection.near(tableStartPos, -1)); -} -function addParagraphAfterTable(tr, map, schema, forcedAddtion) { - if (forcedAddtion === void 0) { forcedAddtion = false; } - var tableEndPos = tr.doc.resolve(map.tableEndOffset); - if (forcedAddtion || !tableEndPos.nodeAfter) { - return addParagraph(tr, tableEndPos, schema); - } - return tr.setSelection(Selection.near(tableEndPos, 1)); -} -function getRightCellOffset(_a, map) { - var rowIdx = _a[0], colIdx = _a[1]; - var totalRowCount = map.totalRowCount, totalColumnCount = map.totalColumnCount; - var lastCellInRow = colIdx === totalColumnCount - 1; - var lastCellInTable = rowIdx === totalRowCount - 1 && lastCellInRow; - if (!lastCellInTable) { - var nextColIdx = colIdx + 1; - var colspanInfo = map.getColspanStartInfo(rowIdx, colIdx); - if ((colspanInfo === null || colspanInfo === void 0 ? void 0 : colspanInfo.count) > 1) { - nextColIdx += colspanInfo.count - 1; - } - if (lastCellInRow || nextColIdx === totalColumnCount) { - rowIdx += 1; - nextColIdx = 0; - } - var offset = map.getCellInfo(rowIdx, nextColIdx).offset; - return offset + 2; - } - return null; -} -function getLeftCellOffset(_a, map) { - var rowIdx = _a[0], colIdx = _a[1]; - var totalColumnCount = map.totalColumnCount; - var firstCellInRow = colIdx === 0; - var firstCellInTable = rowIdx === 0 && firstCellInRow; - if (!firstCellInTable) { - colIdx -= 1; - if (firstCellInRow) { - rowIdx -= 1; - colIdx = totalColumnCount - 1; - } - var _b = map.getCellInfo(rowIdx, colIdx), offset = _b.offset, nodeSize = _b.nodeSize; - return offset + nodeSize - 2; - } - return null; -} -function getUpCellOffset(_a, map) { - var rowIdx = _a[0], colIdx = _a[1]; - if (rowIdx > 0) { - var _b = map.getCellInfo(rowIdx - 1, colIdx), offset = _b.offset, nodeSize = _b.nodeSize; - return offset + nodeSize - 2; - } - return null; -} -function getDownCellOffset(_a, map) { - var rowIdx = _a[0], colIdx = _a[1]; - var totalRowCount = map.totalRowCount; - if (rowIdx < totalRowCount - 1) { - var nextRowIdx = rowIdx + 1; - var rowspanInfo = map.getRowspanStartInfo(rowIdx, colIdx); - if ((rowspanInfo === null || rowspanInfo === void 0 ? void 0 : rowspanInfo.count) > 1) { - nextRowIdx += rowspanInfo.count - 1; - } - var offset = map.getCellInfo(nextRowIdx, colIdx).offset; - return offset + 2; - } - return null; -} -function moveToCell(direction, tr, cellIndex, map) { - var cellOffsetFn = cellOffsetFnMap[direction]; - var offset = cellOffsetFn(cellIndex, map); - if (offset) { - var dir = direction === Direction.RIGHT || direction === Direction.DOWN ? 1 : -1; - return tr.setSelection(Selection.near(tr.doc.resolve(offset), dir)); - } - return null; -} -function canSelectTableNode(direction, map, _a) { - var rowIdx = _a[0], colIdx = _a[1]; - if (direction === Direction.UP || direction === Direction.DOWN) { - return false; - } - var tableStartOffset = map.tableStartOffset, tableEndOffset = map.tableEndOffset; - var _b = map.getCellInfo(rowIdx, colIdx), offset = _b.offset, nodeSize = _b.nodeSize; - var pos = direction === Direction.LEFT ? tableStartOffset : tableEndOffset; - var curPos = direction === Direction.LEFT ? offset - 2 : offset + nodeSize + 3; - return pos === curPos; -} -function selectNode(tr, pos, depth) { - var tablePos = tr.doc.resolve(pos.before(depth - 3)); - return tr.setSelection(new dist_NodeSelection(tablePos)); -} - -;// CONCATENATED MODULE: ./src/wysiwyg/nodes/table.ts - - - - - - - - -// eslint-disable-next-line no-shadow -var Direction; -(function (Direction) { - Direction["LEFT"] = "left"; - Direction["RIGHT"] = "right"; - Direction["UP"] = "up"; - Direction["DOWN"] = "down"; -})(Direction || (Direction = {})); -function getTargetRowInfo(direction, map, selectionInfo) { - var targetRowIdx; - var insertColIdx; - var nodeSize; - if (direction === Direction.UP) { - targetRowIdx = selectionInfo.startRowIdx; - insertColIdx = 0; - nodeSize = -1; - } - else { - targetRowIdx = selectionInfo.endRowIdx; - insertColIdx = map.totalColumnCount - 1; - nodeSize = map.getCellInfo(targetRowIdx, insertColIdx).nodeSize + 1; - } - return { targetRowIdx: targetRowIdx, insertColIdx: insertColIdx, nodeSize: nodeSize }; -} -function getRowRanges(map, rowIdx, totalColumnCount) { - var startOffset = map.getCellInfo(rowIdx, 0).offset; - var _a = map.getCellInfo(rowIdx, totalColumnCount - 1), offset = _a.offset, nodeSize = _a.nodeSize; - return { from: startOffset, to: offset + nodeSize }; -} -var table_Table = /** @class */ (function (_super) { - __extends(Table, _super); - function Table() { - return _super !== null && _super.apply(this, arguments) || this; - } - Object.defineProperty(Table.prototype, "name", { - get: function () { - return 'table'; - }, - enumerable: false, - configurable: true - }); - Object.defineProperty(Table.prototype, "schema", { - get: function () { - return { - content: 'tableHead{1} tableBody{1}', - group: 'block', - attrs: __assign({ rawHTML: { default: null } }, getDefaultCustomAttrs()), - parseDOM: [createDOMInfoParsedRawHTML('table')], - toDOM: function (_a) { - var attrs = _a.attrs; - return ['table', getCustomAttrs(attrs), 0]; - }, - }; - }, - enumerable: false, - configurable: true - }); - Table.prototype.addTable = function () { - return function (payload) { - if (payload === void 0) { payload = { rowCount: 2, columnCount: 1, data: [] }; } - return function (state, dispatch) { - var rowCount = payload.rowCount, columnCount = payload.columnCount, data = payload.data; - var schema = state.schema, selection = state.selection, tr = state.tr; - var from = selection.from, to = selection.to, $from = selection.$from; - var collapsed = from === to; - if (collapsed && !isInTableNode($from)) { - var _a = schema.nodes, tableHead = _a.tableHead, tableBody = _a.tableBody; - var theadData = data === null || data === void 0 ? void 0 : data.slice(0, columnCount); - var tbodyData = data === null || data === void 0 ? void 0 : data.slice(columnCount, data.length); - var tableHeadRow = createTableHeadRow(columnCount, schema, theadData); - var tableBodyRows = createTableBodyRows(rowCount - 1, columnCount, schema, tbodyData); - var table = schema.nodes.table.create(null, [ - tableHead.create(null, tableHeadRow), - tableBody.create(null, tableBodyRows), - ]); - dispatch(tr.replaceSelectionWith(table)); - return true; - } - return false; - }; - }; - }; - Table.prototype.removeTable = function () { - return function () { return function (state, dispatch) { - var selection = state.selection, tr = state.tr; - var map = TableOffsetMap.create(selection.$anchor); - if (map) { - var tableStartOffset = map.tableStartOffset, tableEndOffset = map.tableEndOffset; - var startOffset = tableStartOffset - 1; - var cursorPos = createTextSelection(tr.delete(startOffset, tableEndOffset), startOffset); - dispatch(tr.setSelection(cursorPos)); - return true; - } - return false; - }; }; - }; - Table.prototype.addColumn = function (direction) { - return function () { return function (state, dispatch) { - var selection = state.selection, tr = state.tr, schema = state.schema; - var _a = getResolvedSelection(selection), anchor = _a.anchor, head = _a.head; - if (anchor && head) { - var map = TableOffsetMap.create(anchor); - var selectionInfo = map.getRectOffsets(anchor, head); - var targetColIdx = direction === Direction.LEFT ? selectionInfo.startColIdx : selectionInfo.endColIdx + 1; - var columnCount = getRowAndColumnCount(selectionInfo).columnCount; - var totalRowCount = map.totalRowCount; - for (var rowIdx = 0; rowIdx < totalRowCount; rowIdx += 1) { - var cells = createDummyCells(columnCount, rowIdx, schema); - tr.insert(tr.mapping.map(map.posAt(rowIdx, targetColIdx)), cells); - } - dispatch(tr); - return true; - } - return false; - }; }; - }; - Table.prototype.removeColumn = function () { - return function () { return function (state, dispatch) { - var selection = state.selection, tr = state.tr; - var _a = getResolvedSelection(selection), anchor = _a.anchor, head = _a.head; - if (anchor && head) { - var map = TableOffsetMap.create(anchor); - var selectionInfo = map.getRectOffsets(anchor, head); - var totalColumnCount = map.totalColumnCount, totalRowCount = map.totalRowCount; - var columnCount = getRowAndColumnCount(selectionInfo).columnCount; - var selectedAllColumn = columnCount === totalColumnCount; - if (selectedAllColumn) { - return false; - } - var startColIdx = selectionInfo.startColIdx, endColIdx = selectionInfo.endColIdx; - var mapStart = tr.mapping.maps.length; - for (var rowIdx = 0; rowIdx < totalRowCount; rowIdx += 1) { - for (var colIdx = endColIdx; colIdx >= startColIdx; colIdx -= 1) { - var _b = map.getCellInfo(rowIdx, colIdx), offset = _b.offset, nodeSize = _b.nodeSize; - var from = tr.mapping.slice(mapStart).map(offset); - var to = from + nodeSize; - tr.delete(from, to); - } - } - dispatch(tr); - return true; - } - return false; - }; }; - }; - Table.prototype.addRow = function (direction) { - return function () { return function (state, dispatch) { - var selection = state.selection, schema = state.schema, tr = state.tr; - var _a = getResolvedSelection(selection), anchor = _a.anchor, head = _a.head; - if (anchor && head) { - var map = TableOffsetMap.create(anchor); - var totalColumnCount = map.totalColumnCount; - var selectionInfo = map.getRectOffsets(anchor, head); - var rowCount = getRowAndColumnCount(selectionInfo).rowCount; - var _b = getTargetRowInfo(direction, map, selectionInfo), targetRowIdx = _b.targetRowIdx, insertColIdx = _b.insertColIdx, nodeSize = _b.nodeSize; - var selectedThead = targetRowIdx === 0; - if (!selectedThead) { - var rows = []; - var from = tr.mapping.map(map.posAt(targetRowIdx, insertColIdx)) + nodeSize; - var cells = []; - for (var colIdx = 0; colIdx < totalColumnCount; colIdx += 1) { - cells = cells.concat(createDummyCells(1, targetRowIdx, schema)); - } - for (var i = 0; i < rowCount; i += 1) { - rows.push(schema.nodes.tableRow.create(null, cells)); - } - dispatch(tr.insert(from, rows)); - return true; - } - } - return false; - }; }; - }; - Table.prototype.removeRow = function () { - return function () { return function (state, dispatch) { - var selection = state.selection, tr = state.tr; - var _a = getResolvedSelection(selection), anchor = _a.anchor, head = _a.head; - if (anchor && head) { - var map = TableOffsetMap.create(anchor); - var totalRowCount = map.totalRowCount, totalColumnCount = map.totalColumnCount; - var selectionInfo = map.getRectOffsets(anchor, head); - var rowCount = getRowAndColumnCount(selectionInfo).rowCount; - var startRowIdx = selectionInfo.startRowIdx, endRowIdx = selectionInfo.endRowIdx; - var selectedThead = startRowIdx === 0; - var selectedAllTbodyRow = rowCount === totalRowCount - 1; - if (selectedAllTbodyRow || selectedThead) { - return false; - } - for (var rowIdx = endRowIdx; rowIdx >= startRowIdx; rowIdx -= 1) { - var _b = getRowRanges(map, rowIdx, totalColumnCount), from = _b.from, to = _b.to; - // delete table row - tr.delete(from - 1, to + 1); - } - dispatch(tr); - return true; - } - return false; - }; }; - }; - Table.prototype.alignColumn = function () { - return function (payload) { - if (payload === void 0) { payload = { align: 'center' }; } - return function (state, dispatch) { - var align = payload.align; - var selection = state.selection, tr = state.tr; - var _a = getResolvedSelection(selection), anchor = _a.anchor, head = _a.head; - if (anchor && head) { - var map = TableOffsetMap.create(anchor); - var totalRowCount = map.totalRowCount; - var selectionInfo = map.getRectOffsets(anchor, head); - var startColIdx = selectionInfo.startColIdx, endColIdx = selectionInfo.endColIdx; - for (var rowIdx = 0; rowIdx < totalRowCount; rowIdx += 1) { - for (var colIdx = startColIdx; colIdx <= endColIdx; colIdx += 1) { - if (!map.extendedRowspan(rowIdx, colIdx) && !map.extendedColspan(rowIdx, colIdx)) { - var _b = map.getNodeAndPos(rowIdx, colIdx), node = _b.node, pos = _b.pos; - var attrs = setAttrs(node, { align: align }); - tr.setNodeMarkup(pos, null, attrs); - } - } - } - dispatch(tr); - return true; - } - return false; - }; - }; - }; - Table.prototype.moveToCell = function (direction) { - return function (state, dispatch) { - var selection = state.selection, tr = state.tr, schema = state.schema; - var _a = getResolvedSelection(selection), anchor = _a.anchor, head = _a.head; - if (anchor && head) { - var map = TableOffsetMap.create(anchor); - var cellIndex = map.getCellIndex(anchor); - var newTr = void 0; - if (canBeOutOfTable(direction, map, cellIndex)) { - // When there is no content before or after the table, - // an empty line('paragraph') is created by pressing the arrow keys. - newTr = addParagraphAfterTable(tr, map, schema); - } - else { - newTr = moveToCell(direction, tr, cellIndex, map); - } - if (newTr) { - dispatch(newTr); - return true; - } - } - return false; - }; - }; - Table.prototype.moveInCell = function (direction) { - var _this = this; - return function (state, dispatch) { - var selection = state.selection, tr = state.tr, doc = state.doc, schema = state.schema; - var $from = selection.$from; - var view = _this.context.view; - if (!view.endOfTextblock(direction)) { - return false; - } - var cell = findNodeBy($from, function (_a) { - var type = _a.type; - return type.name === 'tableHeadCell' || type.name === 'tableBodyCell'; - }); - if (cell) { - var para = findNodeBy($from, function (_a) { - var type = _a.type; - return type.name === 'paragraph'; - }); - var cellDepth = cell.depth; - if (para && canMoveBetweenCells(direction, [cellDepth, para.depth], $from, doc)) { - var anchor = getResolvedSelection(selection).anchor; - var map = TableOffsetMap.create(anchor); - var cellIndex = map.getCellIndex(anchor); - var newTr = void 0; - if (canSelectTableNode(direction, map, cellIndex)) { - // When the cursor position is at the end of the cell, - // the table is selected when the left / right arrow keys are pressed. - newTr = selectNode(tr, $from, cellDepth); - } - else if (canBeOutOfTable(direction, map, cellIndex)) { - // When there is no content before or after the table, - // an empty line('paragraph') is created by pressing the arrow keys. - if (direction === Direction.UP) { - newTr = addParagraphBeforeTable(tr, map, schema); - } - else if (direction === Direction.DOWN) { - newTr = addParagraphAfterTable(tr, map, schema); - } - } - else { - newTr = moveToCell(direction, tr, cellIndex, map); - } - if (newTr) { - dispatch(newTr); - return true; - } - } - } - return false; - }; - }; - Table.prototype.deleteCells = function () { - return function (state, dispatch) { - var schema = state.schema, selection = state.selection, tr = state.tr; - var _a = getResolvedSelection(selection), anchor = _a.anchor, head = _a.head; - var textSelection = selection instanceof TextSelection; - if (anchor && head && !textSelection) { - var map = TableOffsetMap.create(anchor); - var _b = map.getRectOffsets(anchor, head), startRowIdx = _b.startRowIdx, startColIdx = _b.startColIdx, endRowIdx = _b.endRowIdx, endColIdx = _b.endColIdx; - for (var rowIdx = startRowIdx; rowIdx <= endRowIdx; rowIdx += 1) { - for (var colIdx = startColIdx; colIdx <= endColIdx; colIdx += 1) { - if (!map.extendedRowspan(rowIdx, colIdx) && !map.extendedColspan(rowIdx, colIdx)) { - var _c = map.getNodeAndPos(rowIdx, colIdx), node = _c.node, pos = _c.pos; - var cells = createDummyCells(1, rowIdx, schema, node.attrs); - tr.replaceWith(tr.mapping.map(pos), tr.mapping.map(pos + node.nodeSize), cells); - } - } - } - dispatch(tr); - return true; - } - return false; - }; - }; - Table.prototype.exitTable = function () { - return function (state, dispatch) { - var selection = state.selection, tr = state.tr, schema = state.schema; - var $from = selection.$from; - var cell = findNodeBy($from, function (_a) { - var type = _a.type; - return type.name === 'tableHeadCell' || type.name === 'tableBodyCell'; - }); - if (cell) { - var para = findNodeBy($from, function (_a) { - var type = _a.type; - return type.name === 'paragraph'; - }); - if (para) { - var anchor = getResolvedSelection(selection).anchor; - var map = TableOffsetMap.create(anchor); - dispatch(addParagraphAfterTable(tr, map, schema, true)); - return true; - } - } - return false; - }; - }; - Table.prototype.commands = function () { - return { - addTable: this.addTable(), - removeTable: this.removeTable(), - addColumnToLeft: this.addColumn(Direction.LEFT), - addColumnToRight: this.addColumn(Direction.RIGHT), - removeColumn: this.removeColumn(), - addRowToUp: this.addRow(Direction.UP), - addRowToDown: this.addRow(Direction.DOWN), - removeRow: this.removeRow(), - alignColumn: this.alignColumn(), - }; - }; - Table.prototype.keymaps = function () { - var deleteCellContent = this.deleteCells(); - return { - Tab: this.moveToCell(Direction.RIGHT), - 'Shift-Tab': this.moveToCell(Direction.LEFT), - ArrowUp: this.moveInCell(Direction.UP), - ArrowDown: this.moveInCell(Direction.DOWN), - ArrowLeft: this.moveInCell(Direction.LEFT), - ArrowRight: this.moveInCell(Direction.RIGHT), - Backspace: deleteCellContent, - 'Mod-Backspace': deleteCellContent, - Delete: deleteCellContent, - 'Mod-Delete': deleteCellContent, - 'Mod-Enter': this.exitTable(), - }; - }; - return Table; -}(node)); - - -;// CONCATENATED MODULE: ./src/wysiwyg/nodes/tableHead.ts - - - -var TableHead = /** @class */ (function (_super) { - __extends(TableHead, _super); - function TableHead() { - return _super !== null && _super.apply(this, arguments) || this; - } - Object.defineProperty(TableHead.prototype, "name", { - get: function () { - return 'tableHead'; - }, - enumerable: false, - configurable: true - }); - Object.defineProperty(TableHead.prototype, "schema", { - get: function () { - return { - content: 'tableRow{1}', - attrs: __assign({ rawHTML: { default: null } }, getDefaultCustomAttrs()), - parseDOM: [createDOMInfoParsedRawHTML('thead')], - toDOM: function (_a) { - var attrs = _a.attrs; - return ['thead', getCustomAttrs(attrs), 0]; - }, - }; - }, - enumerable: false, - configurable: true - }); - return TableHead; -}(node)); - - -;// CONCATENATED MODULE: ./src/wysiwyg/nodes/tableBody.ts - - - -var TableBody = /** @class */ (function (_super) { - __extends(TableBody, _super); - function TableBody() { - return _super !== null && _super.apply(this, arguments) || this; - } - Object.defineProperty(TableBody.prototype, "name", { - get: function () { - return 'tableBody'; - }, - enumerable: false, - configurable: true - }); - Object.defineProperty(TableBody.prototype, "schema", { - get: function () { - return { - content: 'tableRow+', - attrs: __assign({ rawHTML: { default: null } }, getDefaultCustomAttrs()), - parseDOM: [ - { - tag: 'tbody', - getAttrs: function (dom) { - var rows = dom.querySelectorAll('tr'); - var columns = rows[0].children.length; - var rawHTML = dom.getAttribute('data-raw-html'); - if (!columns) { - return false; - } - return __assign({}, (rawHTML && { rawHTML: rawHTML })); - }, - }, - ], - toDOM: function (_a) { - var attrs = _a.attrs; - return ['tbody', getCustomAttrs(attrs), 0]; - }, - }; - }, - enumerable: false, - configurable: true - }); - return TableBody; -}(node)); - - -;// CONCATENATED MODULE: ./src/wysiwyg/nodes/tableRow.ts - - - -var TableRow = /** @class */ (function (_super) { - __extends(TableRow, _super); - function TableRow() { - return _super !== null && _super.apply(this, arguments) || this; - } - Object.defineProperty(TableRow.prototype, "name", { - get: function () { - return 'tableRow'; - }, - enumerable: false, - configurable: true - }); - Object.defineProperty(TableRow.prototype, "schema", { - get: function () { - return { - content: '(tableHeadCell | tableBodyCell)*', - attrs: __assign({ rawHTML: { default: null } }, getDefaultCustomAttrs()), - parseDOM: [ - { - tag: 'tr', - getAttrs: function (dom) { - var columns = dom.children.length; - var rawHTML = dom.getAttribute('data-raw-html'); - if (!columns) { - return false; - } - return __assign({}, (rawHTML && { rawHTML: rawHTML })); - }, - }, - ], - toDOM: function (_a) { - var attrs = _a.attrs; - return ['tr', getCustomAttrs(attrs), 0]; - }, - }; - }, - enumerable: false, - configurable: true - }); - return TableRow; -}(node)); - - -;// CONCATENATED MODULE: ./src/wysiwyg/nodes/tableHeadCell.ts - - - -var TableHeadCell = /** @class */ (function (_super) { - __extends(TableHeadCell, _super); - function TableHeadCell() { - return _super !== null && _super.apply(this, arguments) || this; - } - Object.defineProperty(TableHeadCell.prototype, "name", { - get: function () { - return 'tableHeadCell'; - }, - enumerable: false, - configurable: true - }); - Object.defineProperty(TableHeadCell.prototype, "schema", { - get: function () { - return { - content: 'paragraph+', - attrs: __assign({ align: { default: null }, className: { default: null }, rawHTML: { default: null }, colspan: { default: null }, extended: { default: null } }, getDefaultCustomAttrs()), - isolating: true, - parseDOM: [createParsedCellDOM('th')], - toDOM: function (_a) { - var attrs = _a.attrs; - var cellAttrs = createCellAttrs(attrs); - return ['th', __assign(__assign({}, cellAttrs), getCustomAttrs(attrs)), 0]; - }, - }; - }, - enumerable: false, - configurable: true - }); - return TableHeadCell; -}(node)); - - -;// CONCATENATED MODULE: ./src/wysiwyg/nodes/tableBodyCell.ts - - - -var TableBodyCell = /** @class */ (function (_super) { - __extends(TableBodyCell, _super); - function TableBodyCell() { - return _super !== null && _super.apply(this, arguments) || this; - } - Object.defineProperty(TableBodyCell.prototype, "name", { - get: function () { - return 'tableBodyCell'; - }, - enumerable: false, - configurable: true - }); - Object.defineProperty(TableBodyCell.prototype, "schema", { - get: function () { - return { - content: '(paragraph | bulletList | orderedList)+', - attrs: { - align: { default: null }, - className: { default: null }, - rawHTML: { default: null }, - colspan: { default: null }, - rowspan: { default: null }, - extended: { default: null }, - }, - isolating: true, - parseDOM: [createParsedCellDOM('td')], - toDOM: function (_a) { - var attrs = _a.attrs; - var cellAttrs = createCellAttrs(attrs); - return ['td', cellAttrs, 0]; - }, - }; - }, - enumerable: false, - configurable: true - }); - return TableBodyCell; -}(node)); - - -;// CONCATENATED MODULE: ./src/wysiwyg/nodes/image.ts - - - - - -var Image = /** @class */ (function (_super) { - __extends(Image, _super); - function Image() { - return _super !== null && _super.apply(this, arguments) || this; - } - Object.defineProperty(Image.prototype, "name", { - get: function () { - return 'image'; - }, - enumerable: false, - configurable: true - }); - Object.defineProperty(Image.prototype, "schema", { - get: function () { - return { - inline: true, - attrs: __assign({ imageUrl: { default: '' }, altText: { default: null }, rawHTML: { default: null } }, getDefaultCustomAttrs()), - group: 'inline', - selectable: false, - parseDOM: [ - { - tag: 'img[src]', - getAttrs: function (dom) { - var sanitizedDOM = sanitizeHTML(dom, { RETURN_DOM_FRAGMENT: true }) - .firstChild; - var imageUrl = sanitizedDOM.getAttribute('src') || ''; - var rawHTML = sanitizedDOM.getAttribute('data-raw-html'); - var altText = sanitizedDOM.getAttribute('alt'); - return __assign({ imageUrl: imageUrl, - altText: altText }, (rawHTML && { rawHTML: rawHTML })); - }, - }, - ], - toDOM: function (_a) { - var attrs = _a.attrs; - return [ - attrs.rawHTML || 'img', - __assign(__assign({ src: escapeXml(attrs.imageUrl) }, (attrs.altText && { alt: attrs.altText })), getCustomAttrs(attrs)), - ]; - }, - }; - }, - enumerable: false, - configurable: true - }); - Image.prototype.addImage = function () { - return function (payload) { return function (_a, dispatch) { - var schema = _a.schema, tr = _a.tr; - var _b = payload, imageUrl = _b.imageUrl, altText = _b.altText; - if (!imageUrl) { - return false; - } - var node = schema.nodes.image.createAndFill(__assign({ imageUrl: imageUrl }, (altText && { altText: altText }))); - dispatch(tr.replaceSelectionWith(node).scrollIntoView()); - return true; - }; }; - }; - Image.prototype.commands = function () { - return { - addImage: this.addImage(), - }; - }; - return Image; -}(node)); - - -;// CONCATENATED MODULE: ./src/wysiwyg/nodes/thematicBreak.ts - - - -var ROOT_BLOCK_DEPTH = 1; -var thematicBreak_ThematicBreak = /** @class */ (function (_super) { - __extends(ThematicBreak, _super); - function ThematicBreak() { - return _super !== null && _super.apply(this, arguments) || this; - } - Object.defineProperty(ThematicBreak.prototype, "name", { - get: function () { - return 'thematicBreak'; - }, - enumerable: false, - configurable: true - }); - Object.defineProperty(ThematicBreak.prototype, "schema", { - get: function () { - return { - attrs: __assign({ rawHTML: { default: null } }, getDefaultCustomAttrs()), - group: 'block', - parseDOM: [{ tag: 'hr' }], - selectable: false, - toDOM: function (_a) { - var attrs = _a.attrs; - return ['div', getCustomAttrs(attrs), [attrs.rawHTML || 'hr']]; - }, - }; - }, - enumerable: false, - configurable: true - }); - ThematicBreak.prototype.hr = function () { - var _this = this; - return function () { return function (state, dispatch) { - var _a; - var _b = state.selection, $from = _b.$from, $to = _b.$to; - if ($from === $to) { - var doc = state.doc; - var _c = state.schema.nodes, thematicBreak = _c.thematicBreak, paragraph = _c.paragraph; - var nodes = [thematicBreak.create()]; - var rootBlock = $from.node(ROOT_BLOCK_DEPTH); - var lastBlock = doc.child(doc.childCount - 1) === rootBlock; - var blockEnd = doc.resolve($from.after(ROOT_BLOCK_DEPTH)); - var nextHr = ((_a = $from.nodeAfter) === null || _a === void 0 ? void 0 : _a.type.name) === _this.name; - if (lastBlock || nextHr) { - nodes.push(paragraph.create()); - } - dispatch(state.tr.insert(blockEnd.pos, nodes).scrollIntoView()); - return true; - } - return false; - }; }; - }; - ThematicBreak.prototype.commands = function () { - return { hr: this.hr() }; - }; - ThematicBreak.prototype.keymaps = function () { - var hrCommand = this.hr()(); - return { - 'Mod-l': hrCommand, - 'Mod-L': hrCommand, - }; - }; - return ThematicBreak; -}(node)); - - -;// CONCATENATED MODULE: ./src/wysiwyg/marks/strong.ts - - - - -var strong_Strong = /** @class */ (function (_super) { - __extends(Strong, _super); - function Strong() { - return _super !== null && _super.apply(this, arguments) || this; - } - Object.defineProperty(Strong.prototype, "name", { - get: function () { - return 'strong'; - }, - enumerable: false, - configurable: true - }); - Object.defineProperty(Strong.prototype, "schema", { - get: function () { - var parseDOM = ['b', 'strong'].map(function (tag) { - return { - tag: tag, - getAttrs: function (dom) { - var rawHTML = dom.getAttribute('data-raw-html'); - return __assign({}, (rawHTML && { rawHTML: rawHTML })); - }, - }; - }); - return { - attrs: __assign({ rawHTML: { default: null } }, getDefaultCustomAttrs()), - parseDOM: parseDOM, - toDOM: function (_a) { - var attrs = _a.attrs; - return [attrs.rawHTML || 'strong', getCustomAttrs(attrs)]; - }, - }; - }, - enumerable: false, - configurable: true - }); - Strong.prototype.bold = function () { - return function () { return function (state, dispatch) { return toggleMark(state.schema.marks.strong)(state, dispatch); }; }; - }; - Strong.prototype.commands = function () { - return { bold: this.bold() }; - }; - Strong.prototype.keymaps = function () { - var boldCommand = this.bold()(); - return { - 'Mod-b': boldCommand, - 'Mod-B': boldCommand, - }; - }; - return Strong; -}(mark)); - - -;// CONCATENATED MODULE: ./src/wysiwyg/marks/emph.ts - - - - -var emph_Emph = /** @class */ (function (_super) { - __extends(Emph, _super); - function Emph() { - return _super !== null && _super.apply(this, arguments) || this; - } - Object.defineProperty(Emph.prototype, "name", { - get: function () { - return 'emph'; - }, - enumerable: false, - configurable: true - }); - Object.defineProperty(Emph.prototype, "schema", { - get: function () { - var parseDOM = ['i', 'em'].map(function (tag) { - return { - tag: tag, - getAttrs: function (dom) { - var rawHTML = dom.getAttribute('data-raw-html'); - return __assign({}, (rawHTML && { rawHTML: rawHTML })); - }, - }; - }); - return { - attrs: __assign({ rawHTML: { default: null } }, getDefaultCustomAttrs()), - parseDOM: parseDOM, - toDOM: function (_a) { - var attrs = _a.attrs; - return [attrs.rawHTML || 'em', getCustomAttrs(attrs)]; - }, - }; - }, - enumerable: false, - configurable: true - }); - Emph.prototype.italic = function () { - return function () { return function (state, dispatch) { return toggleMark(state.schema.marks.emph)(state, dispatch); }; }; - }; - Emph.prototype.commands = function () { - return { italic: this.italic() }; - }; - Emph.prototype.keymaps = function () { - var italicCommand = this.italic()(); - return { - 'Mod-i': italicCommand, - 'Mod-I': italicCommand, - }; - }; - return Emph; -}(mark)); - - -;// CONCATENATED MODULE: ./src/wysiwyg/marks/strike.ts - - - - -var strike_Strike = /** @class */ (function (_super) { - __extends(Strike, _super); - function Strike() { - return _super !== null && _super.apply(this, arguments) || this; - } - Object.defineProperty(Strike.prototype, "name", { - get: function () { - return 'strike'; - }, - enumerable: false, - configurable: true - }); - Object.defineProperty(Strike.prototype, "schema", { - get: function () { - var parseDOM = ['s', 'del'].map(function (tag) { - return { - tag: tag, - getAttrs: function (dom) { - var rawHTML = dom.getAttribute('data-raw-html'); - return __assign({}, (rawHTML && { rawHTML: rawHTML })); - }, - }; - }); - return { - attrs: __assign({ rawHTML: { default: null } }, getDefaultCustomAttrs()), - parseDOM: parseDOM, - toDOM: function (_a) { - var attrs = _a.attrs; - return [attrs.rawHTML || 'del', getCustomAttrs(attrs)]; - }, - }; - }, - enumerable: false, - configurable: true - }); - Strike.prototype.commands = function () { - return function () { return function (state, dispatch) { return toggleMark(state.schema.marks.strike)(state, dispatch); }; }; - }; - Strike.prototype.keymaps = function () { - var strikeCommand = this.commands()(); - return { - 'Mod-s': strikeCommand, - 'Mod-S': strikeCommand, - }; - }; - return Strike; -}(mark)); - - -;// CONCATENATED MODULE: ./src/wysiwyg/marks/link.ts - - - - - - - -var link_Link = /** @class */ (function (_super) { - __extends(Link, _super); - function Link(linkAttributes) { - var _this = _super.call(this) || this; - _this.linkAttributes = linkAttributes; - return _this; - } - Object.defineProperty(Link.prototype, "name", { - get: function () { - return 'link'; - }, - enumerable: false, - configurable: true - }); - Object.defineProperty(Link.prototype, "schema", { - get: function () { - var _this = this; - return { - attrs: __assign({ linkUrl: { default: '' }, title: { default: null }, rawHTML: { default: null } }, getDefaultCustomAttrs()), - inclusive: false, - parseDOM: [ - { - tag: 'a[href]', - getAttrs: function (dom) { - var sanitizedDOM = sanitizeHTML(dom, { RETURN_DOM_FRAGMENT: true }) - .firstChild; - var href = sanitizedDOM.getAttribute('href') || ''; - var title = sanitizedDOM.getAttribute('title') || ''; - var rawHTML = sanitizedDOM.getAttribute('data-raw-html'); - return __assign({ linkUrl: href, title: title }, (rawHTML && { rawHTML: rawHTML })); - }, - }, - ], - toDOM: function (_a) { - var attrs = _a.attrs; - return [ - attrs.rawHTML || 'a', - __assign(__assign({ href: escapeXml(attrs.linkUrl) }, _this.linkAttributes), getCustomAttrs(attrs)), - ]; - }, - }; - }, - enumerable: false, - configurable: true - }); - Link.prototype.addLink = function () { - return function (payload) { return function (state, dispatch) { - var _a = payload, linkUrl = _a.linkUrl, _b = _a.linkText, linkText = _b === void 0 ? '' : _b; - var schema = state.schema, tr = state.tr, selection = state.selection; - var empty = selection.empty, from = selection.from, to = selection.to; - if (from && to && linkUrl) { - var attrs = { linkUrl: linkUrl }; - var mark = schema.mark('link', attrs); - if (empty && linkText) { - var node = createTextNode(schema, linkText, mark); - tr.replaceRangeWith(from, to, node); - } - else { - tr.addMark(from, to, mark); - } - dispatch(tr.scrollIntoView()); - return true; - } - return false; - }; }; - }; - Link.prototype.toggleLink = function () { - return function (payload) { return function (state, dispatch) { - return toggleMark(state.schema.marks.link, payload)(state, dispatch); - }; }; - }; - Link.prototype.commands = function () { - return { - addLink: this.addLink(), - toggleLink: this.toggleLink(), - }; - }; - return Link; -}(mark)); - - -;// CONCATENATED MODULE: ./src/wysiwyg/marks/code.ts - - - - -var code_Code = /** @class */ (function (_super) { - __extends(Code, _super); - function Code() { - return _super !== null && _super.apply(this, arguments) || this; - } - Object.defineProperty(Code.prototype, "name", { - get: function () { - return 'code'; - }, - enumerable: false, - configurable: true - }); - Object.defineProperty(Code.prototype, "schema", { - get: function () { - return { - attrs: __assign({ rawHTML: { default: null } }, getDefaultCustomAttrs()), - parseDOM: [ - { - tag: 'code', - getAttrs: function (dom) { - var rawHTML = dom.getAttribute('data-raw-html'); - return __assign({}, (rawHTML && { rawHTML: rawHTML })); - }, - }, - ], - toDOM: function (_a) { - var attrs = _a.attrs; - return [attrs.rawHTML || 'code', getCustomAttrs(attrs)]; - }, - }; - }, - enumerable: false, - configurable: true - }); - Code.prototype.commands = function () { - return function () { return function (state, dispatch) { return toggleMark(state.schema.marks.code)(state, dispatch); }; }; - }; - Code.prototype.keymaps = function () { - var codeCommand = this.commands()(); - return { - 'Shift-Mod-c': codeCommand, - 'Shift-Mod-C': codeCommand, - }; - }; - return Code; -}(mark)); - - -;// CONCATENATED MODULE: ./src/wysiwyg/nodes/customBlock.ts - - - -var customBlock_CustomBlock = /** @class */ (function (_super) { - __extends(CustomBlock, _super); - function CustomBlock() { - return _super !== null && _super.apply(this, arguments) || this; - } - Object.defineProperty(CustomBlock.prototype, "name", { - get: function () { - return 'customBlock'; - }, - enumerable: false, - configurable: true - }); - Object.defineProperty(CustomBlock.prototype, "schema", { - get: function () { - return { - content: 'text*', - group: 'block', - attrs: { - info: { default: null }, - }, - atom: true, - code: true, - defining: true, - parseDOM: [ - { - tag: 'div[data-custom-info]', - getAttrs: function (dom) { - var info = dom.getAttribute('data-custom-info'); - return { info: info }; - }, - }, - ], - toDOM: function (_a) { - var attrs = _a.attrs; - return ['div', { 'data-custom-info': attrs.info || null }, 0]; - }, - }; - }, - enumerable: false, - configurable: true - }); - CustomBlock.prototype.commands = function () { - return function (payload) { return function (state, dispatch) { - return (payload === null || payload === void 0 ? void 0 : payload.info) - ? dist_setBlockType(state.schema.nodes.customBlock, payload)(state, dispatch) - : false; - }; }; - }; - return CustomBlock; -}(node)); - - -;// CONCATENATED MODULE: ./src/wysiwyg/nodes/frontMatter.ts - - - -var FrontMatter = /** @class */ (function (_super) { - __extends(FrontMatter, _super); - function FrontMatter() { - return _super !== null && _super.apply(this, arguments) || this; - } - Object.defineProperty(FrontMatter.prototype, "name", { - get: function () { - return 'frontMatter'; - }, - enumerable: false, - configurable: true - }); - Object.defineProperty(FrontMatter.prototype, "schema", { - get: function () { - return { - content: 'text*', - group: 'block', - code: true, - defining: true, - parseDOM: [ - { - preserveWhitespace: 'full', - tag: 'div[data-front-matter]', - }, - ], - toDOM: function () { - return ['div', { 'data-front-matter': 'true' }, 0]; - }, - }; - }, - enumerable: false, - configurable: true - }); - FrontMatter.prototype.commands = function () { - return function () { return function (state, dispatch, view) { - var $from = state.selection.$from; - if (view.endOfTextblock('down') && $from.node().type.name === 'frontMatter') { - return exitCode(state, dispatch); - } - return false; - }; }; - }; - FrontMatter.prototype.keymaps = function () { - return { - Enter: this.commands()(), - }; - }; - return FrontMatter; -}(node)); - - -;// CONCATENATED MODULE: ./src/wysiwyg/nodes/htmlComment.ts - - - -var HTMLComment = /** @class */ (function (_super) { - __extends(HTMLComment, _super); - function HTMLComment() { - return _super !== null && _super.apply(this, arguments) || this; - } - Object.defineProperty(HTMLComment.prototype, "name", { - get: function () { - return 'htmlComment'; - }, - enumerable: false, - configurable: true - }); - Object.defineProperty(HTMLComment.prototype, "schema", { - get: function () { - return { - content: 'text*', - group: 'block', - code: true, - defining: true, - parseDOM: [{ preserveWhitespace: 'full', tag: 'div[data-html-comment]' }], - toDOM: function () { - return ['div', { 'data-html-comment': 'true' }, 0]; - }, - }; - }, - enumerable: false, - configurable: true - }); - HTMLComment.prototype.commands = function () { - return function () { return function (state, dispatch, view) { - var $from = state.selection.$from; - if (view.endOfTextblock('down') && $from.node().type.name === 'htmlComment') { - return exitCode(state, dispatch); - } - return false; - }; }; - }; - HTMLComment.prototype.keymaps = function () { - return { - Enter: this.commands()(), - }; - }; - return HTMLComment; -}(node)); - - -;// CONCATENATED MODULE: ./src/wysiwyg/specCreator.ts - - - - - - - - - - - - - - - - - - - - - - - - - - - -function createSpecs(linkAttributes) { - return new specManager([ - new doc_Doc(), - new paragraph_Paragraph(), - new text_Text(), - new heading_Heading(), - new codeBlock_CodeBlock(), - new BulletList(), - new OrderedList(), - new listItem_ListItem(), - new blockQuote_BlockQuote(), - new table_Table(), - new TableHead(), - new TableBody(), - new TableRow(), - new TableHeadCell(), - new TableBodyCell(), - new Image(), - new thematicBreak_ThematicBreak(), - new strong_Strong(), - new emph_Emph(), - new strike_Strike(), - new link_Link(linkAttributes), - new code_Code(), - new customBlock_CustomBlock(), - new FrontMatter(), - new Widget(), - new HTMLComment(), - ]); -} - -;// CONCATENATED MODULE: ./src/wysiwyg/wwEditor.ts - - - - - - - - - - - - - - - - - - - - - - - - -var CONTENTS_CLASS_NAME = cls('contents'); -var WysiwygEditor = /** @class */ (function (_super) { - __extends(WysiwygEditor, _super); - function WysiwygEditor(eventEmitter, options) { - var _this = _super.call(this, eventEmitter) || this; - var toDOMAdaptor = options.toDOMAdaptor, _a = options.htmlSchemaMap, htmlSchemaMap = _a === void 0 ? {} : _a, _b = options.linkAttributes, linkAttributes = _b === void 0 ? {} : _b, _c = options.useCommandShortcut, useCommandShortcut = _c === void 0 ? true : _c, _d = options.wwPlugins, wwPlugins = _d === void 0 ? [] : _d, _e = options.wwNodeViews, wwNodeViews = _e === void 0 ? {} : _e; - _this.editorType = 'wysiwyg'; - _this.el.classList.add('ww-mode'); - _this.toDOMAdaptor = toDOMAdaptor; - _this.linkAttributes = linkAttributes; - _this.extraPlugins = wwPlugins; - _this.pluginNodeViews = wwNodeViews; - _this.specs = _this.createSpecs(); - _this.schema = _this.createSchema(htmlSchemaMap); - _this.context = _this.createContext(); - _this.keymaps = _this.createKeymaps(useCommandShortcut); - _this.view = _this.createView(); - _this.commands = _this.createCommands(); - _this.specs.setContext(__assign(__assign({}, _this.context), { view: _this.view })); - _this.initEvent(); - return _this; - } - WysiwygEditor.prototype.createSpecs = function () { - return createSpecs(this.linkAttributes); - }; - WysiwygEditor.prototype.createContext = function () { - return { - schema: this.schema, - eventEmitter: this.eventEmitter, - }; - }; - WysiwygEditor.prototype.createSchema = function (htmlSchemaMap) { - return new Schema({ - nodes: __assign(__assign({}, this.specs.nodes), htmlSchemaMap.nodes), - marks: __assign(__assign({}, this.specs.marks), htmlSchemaMap.marks), - }); - }; - WysiwygEditor.prototype.createPlugins = function () { - return __spreadArray([ - tableSelection(), - tableContextMenu(this.eventEmitter), - task(), - toolbarStateHighlight(this.eventEmitter) - ], this.createPluginProps()).concat(this.defaultPlugins); - }; - WysiwygEditor.prototype.createPluginNodeViews = function () { - var _a = this, eventEmitter = _a.eventEmitter, pluginNodeViews = _a.pluginNodeViews; - var pluginNodeViewMap = {}; - if (pluginNodeViews) { - Object.keys(pluginNodeViews).forEach(function (key) { - pluginNodeViewMap[key] = function (node, view, getPos) { - return pluginNodeViews[key](node, view, getPos, eventEmitter); - }; - }); - } - return pluginNodeViewMap; - }; - WysiwygEditor.prototype.createView = function () { - var _this = this; - var _a = this, toDOMAdaptor = _a.toDOMAdaptor, eventEmitter = _a.eventEmitter; - return new EditorView(this.el, { - state: this.createState(), - attributes: { - class: CONTENTS_CLASS_NAME, - }, - nodeViews: __assign({ customBlock: function (node, view, getPos) { - return new CustomBlockView(node, view, getPos, toDOMAdaptor); - }, - image: function (node, view, getPos) { - return new ImageView(node, view, getPos, eventEmitter); - }, - codeBlock: function (node, view, getPos) { - return new CodeBlockView(node, view, getPos, eventEmitter); - }, widget: widgetNodeView }, this.createPluginNodeViews()), - dispatchTransaction: function (tr) { - var state = _this.view.state.applyTransaction(tr).state; - _this.view.updateState(state); - _this.emitChangeEvent(tr.scrollIntoView()); - _this.eventEmitter.emit('setFocusedNode', state.selection.$from.node(1)); - }, - transformPastedHTML: changePastedHTML, - transformPasted: function (slice) { - return changePastedSlice(slice, _this.schema, isInTableNode(_this.view.state.selection.$from)); - }, - handlePaste: function (view, _, slice) { return pasteToTable(view, slice); }, - handleKeyDown: function (_, ev) { - _this.eventEmitter.emit('keydown', _this.editorType, ev); - return false; - }, - handleDOMEvents: { - paste: function (_, ev) { - var clipboardData = ev.clipboardData || window.clipboardData; - var items = clipboardData === null || clipboardData === void 0 ? void 0 : clipboardData.items; - if (items) { - var containRtfItem = toArray_default()(items).some(function (item) { return item.kind === 'string' && item.type === 'text/rtf'; }); - // if it contains rtf, it's most likely copy paste from office -> no image - if (!containRtfItem) { - var imageBlob = pasteImageOnly(items); - if (imageBlob) { - ev.preventDefault(); - emitImageBlobHook(_this.eventEmitter, imageBlob, ev.type); - } - } - } - return false; - }, - keyup: function (_, ev) { - _this.eventEmitter.emit('keyup', _this.editorType, ev); - return false; - }, - scroll: function () { - _this.eventEmitter.emit('scroll', 'editor'); - return true; - }, - }, - }); - }; - WysiwygEditor.prototype.createCommands = function () { - return this.specs.commands(this.view, getWwCommands()); - }; - WysiwygEditor.prototype.getHTML = function () { - return removeProseMirrorHackNodes(this.view.dom.innerHTML); - }; - WysiwygEditor.prototype.getModel = function () { - return this.view.state.doc; - }; - WysiwygEditor.prototype.getSelection = function () { - var _a = this.view.state.selection, from = _a.from, to = _a.to; - return [from, to]; - }; - WysiwygEditor.prototype.getSchema = function () { - return this.view.state.schema; - }; - WysiwygEditor.prototype.replaceSelection = function (text, start, end) { - var _a = this.view.state, schema = _a.schema, tr = _a.tr; - var lineTexts = text.split('\n'); - var paras = lineTexts.map(function (lineText) { - return createParagraph(schema, createNodesWithWidget(lineText, schema)); - }); - var slice = new Slice(Fragment.from(paras), 1, 1); - var newTr = isNumber_default()(start) && isNumber_default()(end) - ? tr.replaceRange(start, end, slice) - : tr.replaceSelection(slice); - this.view.dispatch(newTr); - this.focus(); - }; - WysiwygEditor.prototype.deleteSelection = function (start, end) { - var tr = this.view.state.tr; - var newTr = isNumber_default()(start) && isNumber_default()(end) ? tr.deleteRange(start, end) : tr.deleteSelection(); - this.view.dispatch(newTr.scrollIntoView()); - }; - WysiwygEditor.prototype.getSelectedText = function (start, end) { - var _a = this.view.state, doc = _a.doc, selection = _a.selection; - var from = selection.from, to = selection.to; - if (isNumber_default()(start) && isNumber_default()(end)) { - from = start; - to = end; - } - return doc.textBetween(from, to, '\n'); - }; - WysiwygEditor.prototype.setModel = function (newDoc, cursorToEnd) { - if (cursorToEnd === void 0) { cursorToEnd = false; } - var _a = this.view.state, tr = _a.tr, doc = _a.doc; - this.view.dispatch(tr.replaceWith(0, doc.content.size, newDoc)); - if (cursorToEnd) { - this.moveCursorToEnd(true); - } - }; - WysiwygEditor.prototype.setSelection = function (start, end) { - if (end === void 0) { end = start; } - var tr = this.view.state.tr; - var selection = createTextSelection(tr, start, end); - this.view.dispatch(tr.setSelection(selection).scrollIntoView()); - }; - WysiwygEditor.prototype.addWidget = function (node, style, pos) { - var _a = this.view, dispatch = _a.dispatch, state = _a.state; - dispatch(state.tr.setMeta('widget', { pos: pos !== null && pos !== void 0 ? pos : state.selection.to, node: node, style: style })); - }; - WysiwygEditor.prototype.replaceWithWidget = function (start, end, text) { - var _a = this.view.state, tr = _a.tr, schema = _a.schema; - var nodes = createNodesWithWidget(text, schema); - this.view.dispatch(tr.replaceWith(start, end, nodes)); - }; - WysiwygEditor.prototype.getRangeInfoOfNode = function (pos) { - var _a = this.view.state, doc = _a.doc, selection = _a.selection; - var $pos = pos ? doc.resolve(pos) : selection.$from; - var marks = $pos.marks(); - var node = $pos.node(); - var start = $pos.start(); - var end = $pos.end(); - var type = node.type.name; - if (marks.length || type === 'paragraph') { - var mark_1 = marks[marks.length - 1]; - var maybeHasMark_1 = function (nodeMarks) { - return nodeMarks.length ? common_includes(nodeMarks, mark_1) : true; - }; - type = mark_1 ? mark_1.type.name : 'text'; - node.forEach(function (child, offset) { - var isText = child.isText, nodeSize = child.nodeSize, nodeMarks = child.marks; - var startOffset = $pos.pos - start; - if (isText && - offset <= startOffset && - offset + nodeSize >= startOffset && - maybeHasMark_1(nodeMarks)) { - start = start + offset; - end = start + nodeSize; - } - }); - } - return { range: [start, end], type: type }; - }; - return WysiwygEditor; -}(src_base)); -/* harmony default export */ var wwEditor = (WysiwygEditor); - -// EXTERNAL MODULE: ../../node_modules/tui-code-snippet/type/isFalsy.js -var isFalsy = __webpack_require__(404); -var isFalsy_default = /*#__PURE__*/__webpack_require__.n(isFalsy); -;// CONCATENATED MODULE: ./src/event/eventEmitter.ts - - - - -var eventTypeList = [ - 'afterPreviewRender', - 'updatePreview', - 'changeMode', - 'needChangeMode', - 'command', - 'changePreviewStyle', - 'changePreviewTabPreview', - 'changePreviewTabWrite', - 'scroll', - 'contextmenu', - 'show', - 'hide', - 'changeLanguage', - 'changeToolbarState', - 'toggleScrollSync', - 'mixinTableOffsetMapPrototype', - 'setFocusedNode', - 'removePopupWidget', - 'query', - // provide event for user - 'openPopup', - 'closePopup', - 'addImageBlobHook', - 'beforePreviewRender', - 'beforeConvertWysiwygToMarkdown', - 'load', - 'loadUI', - 'change', - 'caretChange', - 'destroy', - 'focus', - 'blur', - 'keydown', - 'keyup', -]; -/** - * Class EventEmitter - * @ignore - */ -var EventEmitter = /** @class */ (function () { - function EventEmitter() { - var _this = this; - this.events = new utils_map(); - this.eventTypes = eventTypeList.reduce(function (types, type) { - return __assign(__assign({}, types), { type: type }); - }, {}); - this.hold = false; - eventTypeList.forEach(function (eventType) { - _this.addEventType(eventType); - }); - } - /** - * Listen event and bind event handler - * @param {string} type Event type string - * @param {function} handler Event handler - */ - EventEmitter.prototype.listen = function (type, handler) { - var typeInfo = this.getTypeInfo(type); - var eventHandlers = this.events.get(typeInfo.type) || []; - if (!this.hasEventType(typeInfo.type)) { - throw new Error("There is no event type " + typeInfo.type); - } - if (typeInfo.namespace) { - handler.namespace = typeInfo.namespace; - } - eventHandlers.push(handler); - this.events.set(typeInfo.type, eventHandlers); - }; - /** - * Emit event - * @param {string} eventName Event name to emit - * @returns {Array} - */ - EventEmitter.prototype.emit = function (type) { - var args = []; - for (var _i = 1; _i < arguments.length; _i++) { - args[_i - 1] = arguments[_i]; - } - var typeInfo = this.getTypeInfo(type); - var eventHandlers = this.events.get(typeInfo.type); - var results = []; - if (!this.hold && eventHandlers) { - eventHandlers.forEach(function (handler) { - var result = handler.apply(void 0, args); - if (!isUndefined_default()(result)) { - results.push(result); - } - }); - } - return results; - }; - /** - * Emit given event and return result - * @param {string} eventName Event name to emit - * @param {any} source Source to change - * @returns {string} - */ - EventEmitter.prototype.emitReduce = function (type, source) { - var args = []; - for (var _i = 2; _i < arguments.length; _i++) { - args[_i - 2] = arguments[_i]; - } - var eventHandlers = this.events.get(type); - if (!this.hold && eventHandlers) { - eventHandlers.forEach(function (handler) { - var result = handler.apply(void 0, __spreadArray([source], args)); - if (!isFalsy_default()(result)) { - source = result; - } - }); - } - return source; - }; - /** - * Get event type and namespace - * @param {string} type Event type name - * @returns {{type: string, namespace: string}} - * @private - */ - EventEmitter.prototype.getTypeInfo = function (type) { - var splited = type.split('.'); - return { - type: splited[0], - namespace: splited[1], - }; - }; - /** - * Check whether event type exists or not - * @param {string} type Event type name - * @returns {boolean} - * @private - */ - EventEmitter.prototype.hasEventType = function (type) { - return !isUndefined_default()(this.eventTypes[this.getTypeInfo(type).type]); - }; - /** - * Add event type when given event not exists - * @param {string} type Event type name - */ - EventEmitter.prototype.addEventType = function (type) { - if (this.hasEventType(type)) { - throw new Error("There is already have event type " + type); - } - this.eventTypes[type] = type; - }; - /** - * Remove event handler from given event type - * @param {string} eventType Event type name - * @param {function} [handler] - registered event handler - */ - EventEmitter.prototype.removeEventHandler = function (eventType, handler) { - var _this = this; - var _a = this.getTypeInfo(eventType), type = _a.type, namespace = _a.namespace; - if (type && handler) { - this.removeEventHandlerWithHandler(type, handler); - } - else if (type && !namespace) { - this.events.delete(type); - } - else if (!type && namespace) { - this.events.forEach(function (_, evtType) { - _this.removeEventHandlerWithTypeInfo(evtType, namespace); - }); - } - else if (type && namespace) { - this.removeEventHandlerWithTypeInfo(type, namespace); - } - }; - /** - * Remove event handler with event handler - * @param {string} type - event type name - * @param {function} handler - event handler - * @private - */ - EventEmitter.prototype.removeEventHandlerWithHandler = function (type, handler) { - var eventHandlers = this.events.get(type); - if (eventHandlers) { - var handlerIndex = eventHandlers.indexOf(handler); - if (eventHandlers.indexOf(handler) >= 0) { - eventHandlers.splice(handlerIndex, 1); - } - } - }; - /** - * Remove event handler with event type information - * @param {string} type Event type name - * @param {string} namespace Event namespace - * @private - */ - EventEmitter.prototype.removeEventHandlerWithTypeInfo = function (type, namespace) { - var handlersToSurvive = []; - var eventHandlers = this.events.get(type); - if (!eventHandlers) { - return; - } - eventHandlers.map(function (handler) { - if (handler.namespace !== namespace) { - handlersToSurvive.push(handler); - } - return null; - }); - this.events.set(type, handlersToSurvive); - }; - EventEmitter.prototype.getEvents = function () { - return this.events; - }; - EventEmitter.prototype.holdEventInvoke = function (fn) { - this.hold = true; - fn(); - this.hold = false; - }; - return EventEmitter; -}()); -/* harmony default export */ var eventEmitter = (EventEmitter); - -;// CONCATENATED MODULE: ./src/commands/commandManager.ts -var CommandManager = /** @class */ (function () { - function CommandManager(eventEmitter, mdCommands, wwCommands, getEditorType) { - this.eventEmitter = eventEmitter; - this.mdCommands = mdCommands; - this.wwCommands = wwCommands; - this.getEditorType = getEditorType; - this.initEvent(); - } - CommandManager.prototype.initEvent = function () { - var _this = this; - this.eventEmitter.listen('command', function (command, payload) { - _this.exec(command, payload); - }); - }; - CommandManager.prototype.addCommand = function (type, name, command) { - if (type === 'markdown') { - this.mdCommands[name] = command; - } - else { - this.wwCommands[name] = command; - } - }; - CommandManager.prototype.deleteCommand = function (type, name) { - if (type === 'markdown') { - delete this.mdCommands[name]; - } - else { - delete this.wwCommands[name]; - } - }; - CommandManager.prototype.exec = function (name, payload) { - var type = this.getEditorType(); - if (type === 'markdown') { - this.mdCommands[name](payload); - } - else { - this.wwCommands[name](payload); - } - }; - return CommandManager; -}()); -/* harmony default export */ var commandManager = (CommandManager); - -;// CONCATENATED MODULE: ./src/convertors/toWysiwyg/htmlToWwConvertors.ts - - - - -function getTextWithoutTrailingNewline(text) { - return text[text.length - 1] === '\n' ? text.slice(0, text.length - 1) : text; -} -function isCustomHTMLInlineNode(_a, node) { - var schema = _a.schema; - var html = node.literal; - var matched = html.match(reHTMLTag); - if (matched) { - var openTagName = matched[1], closeTagName = matched[3]; - var typeName = (openTagName || closeTagName).toLowerCase(); - return node.type === 'htmlInline' && !!(schema.marks[typeName] || schema.nodes[typeName]); - } - return false; -} -function htmlToWwConvertors_isInlineNode(_a) { - var type = _a.type; - return common_includes(['text', 'strong', 'emph', 'strike', 'image', 'link', 'code'], type); -} -function isSoftbreak(mdNode) { - return (mdNode === null || mdNode === void 0 ? void 0 : mdNode.type) === 'softbreak'; -} -function htmlToWwConvertors_isListNode(_a) { - var type = _a.type, literal = _a.literal; - var matched = type === 'htmlInline' && literal.match(reHTMLTag); - if (matched) { - var openTagName = matched[1], closeTagName = matched[3]; - var tagName = openTagName || closeTagName; - if (tagName) { - return common_includes(['ul', 'ol', 'li'], tagName.toLowerCase()); - } - } - return false; -} -function getListItemAttrs(_a) { - var literal = _a.literal; - var task = /data-task/.test(literal); - var checked = /data-task-checked/.test(literal); - return { task: task, checked: checked }; -} -function getMatchedAttributeValue(rawHTML) { - var attrNames = []; - for (var _i = 1; _i < arguments.length; _i++) { - attrNames[_i - 1] = arguments[_i]; - } - var wrapper = document.createElement('div'); - wrapper.innerHTML = sanitizeHTML(rawHTML); - var el = wrapper.firstChild; - return attrNames.map(function (attrName) { return el.getAttribute(attrName) || ''; }); -} -function createConvertors(convertors) { - var convertorMap = {}; - Object.keys(convertors).forEach(function (key) { - var tagNames = key.split(', '); - tagNames.forEach(function (tagName) { - var name = tagName.toLowerCase(); - convertorMap[name] = convertors[key]; - }); - }); - return convertorMap; -} -var convertors = { - 'b, strong': function (state, _, openTagName) { - var strong = state.schema.marks.strong; - if (openTagName) { - state.openMark(strong.create({ rawHTML: openTagName })); - } - else { - state.closeMark(strong); - } - }, - 'i, em': function (state, _, openTagName) { - var emph = state.schema.marks.emph; - if (openTagName) { - state.openMark(emph.create({ rawHTML: openTagName })); - } - else { - state.closeMark(emph); - } - }, - 's, del': function (state, _, openTagName) { - var strike = state.schema.marks.strike; - if (openTagName) { - state.openMark(strike.create({ rawHTML: openTagName })); - } - else { - state.closeMark(strike); - } - }, - code: function (state, _, openTagName) { - var code = state.schema.marks.code; - if (openTagName) { - state.openMark(code.create({ rawHTML: openTagName })); - } - else { - state.closeMark(code); - } - }, - a: function (state, node, openTagName) { - var tag = node.literal; - var link = state.schema.marks.link; - if (openTagName) { - var linkUrl = getMatchedAttributeValue(tag, 'href')[0]; - state.openMark(link.create({ - linkUrl: linkUrl, - rawHTML: openTagName, - })); - } - else { - state.closeMark(link); - } - }, - img: function (state, node, openTagName) { - var tag = node.literal; - if (openTagName) { - var _a = getMatchedAttributeValue(tag, 'src', 'alt'), imageUrl = _a[0], altText = _a[1]; - var image = state.schema.nodes.image; - state.addNode(image, __assign({ rawHTML: openTagName, imageUrl: imageUrl }, (altText && { altText: altText }))); - } - }, - hr: function (state, _, openTagName) { - state.addNode(state.schema.nodes.thematicBreak, { rawHTML: openTagName }); - }, - br: function (state, node) { - var paragraph = state.schema.nodes.paragraph; - var parent = node.parent, prev = node.prev, next = node.next; - if ((parent === null || parent === void 0 ? void 0 : parent.type) === 'paragraph') { - // should open a paragraph node when line text has only tag - // ex) first line\n\n\nfourth line - if (isSoftbreak(prev)) { - state.openNode(paragraph); - } - // should close a paragraph node when line text has only tag - // ex) first line\n\n\nfourth line - if (isSoftbreak(next)) { - state.closeNode(); - // should close a paragraph node and open a paragraph node to separate between blocks - // when tag is in the middle of the paragraph - // ex) first line\nthird line - } - else if (next) { - state.closeNode(); - state.openNode(paragraph); - } - } - else if ((parent === null || parent === void 0 ? void 0 : parent.type) === 'tableCell') { - if (prev && (htmlToWwConvertors_isInlineNode(prev) || isCustomHTMLInlineNode(state, prev))) { - state.closeNode(); - } - if (next && (htmlToWwConvertors_isInlineNode(next) || isCustomHTMLInlineNode(state, next))) { - state.openNode(paragraph); - } - } - }, - pre: function (state, node, openTagName) { - var _a, _b; - var container = document.createElement('div'); - container.innerHTML = node.literal; - var literal = (_b = (_a = container.firstChild) === null || _a === void 0 ? void 0 : _a.firstChild) === null || _b === void 0 ? void 0 : _b.textContent; - state.openNode(state.schema.nodes.codeBlock, { rawHTML: openTagName }); - state.addText(getTextWithoutTrailingNewline(literal)); - state.closeNode(); - }, - 'ul, ol': function (state, node, openTagName) { - // in the table cell, '
", "