commit 9cb3e30d6196792680795d2e19fa20f766fde36b
parent 2239f1d5ffbac29deec1e16f87b87d151f1085bd
Author: Florian Zia <zia.florian@gmail.com>
Date: Fri, 12 Dec 2025 11:08:52 +0000
Bug 2000992 - Vendor ProseMirror r=sylvestre,mak,Gijs
Differential Revision: https://phabricator.services.mozilla.com/D274588
Diffstat:
107 files changed, 17966 insertions(+), 0 deletions(-)
diff --git a/third_party/js/prosemirror/.gitignore b/third_party/js/prosemirror/.gitignore
@@ -0,0 +1,3 @@
+package-lock.json
+**/node_modules/
+**/dist/
diff --git a/third_party/js/prosemirror/LICENSE b/third_party/js/prosemirror/LICENSE
@@ -0,0 +1,19 @@
+Copyright (C) 2015-2016 by Marijn Haverbeke <marijnh@gmail.com> and others
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
diff --git a/third_party/js/prosemirror/README.md b/third_party/js/prosemirror/README.md
@@ -0,0 +1,32 @@
+# ProseMirror vendoring
+
+This directory vendors a ProseMirror bundle. Individual ProseMirror packages are vendored into subfolders. The bundle is built by Rollup from the entry point in `bundle_entry.mjs` and exported via `moz.build`.
+
+## Updating the bundle
+
+### Using moz.sh
+
+To vendor all packages and rebuild the bundle at once you can run:
+
+```bash
+./third_party/js/prosemirror/moz.sh [--mach-vendor-flags]
+```
+
+`--mach-vendor-flags`: Any `mach vendor` flags (like `--ignore-modified`, `--force`, etc.) will be passed along to each vendoring operation.
+
+The script:
+- Vendors the root `moz.yaml` (only fetches a LICENSE file)
+- Vendors each `prosemirror-*/moz.yaml` in turn with `--ignore-modified` to handle spurious changes
+- Rebuild the bundle by running `make_bundle.sh`
+
+### Manual options
+
+1. Per-package vendoring: `mach vendor third_party/js/prosemirror/prosemirror-*/moz.yaml`
+2. Bundle only: Run `./third_party/js/prosemirror/moz.sh`
+
+After vendoring, commit the updated files.
+
+## Development notes
+
+- If you need a bundle that is not minified for stack traces during development you can generate one with `npm run build:dev`.
+- Individual package metadata and sources live under `third_party/js/prosemirror/prosemirror-*` with separate `moz.yaml` files. The bundle consumes these via `file:` dependencies in `package.json`.
diff --git a/third_party/js/prosemirror/bundle_entry.mjs b/third_party/js/prosemirror/bundle_entry.mjs
@@ -0,0 +1,22 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// This file exports only the ProseMirror APIs that are being used.
+// When adding new functionality the required exports can be added here before
+// rebuilding the bundle with.
+
+// Core
+export { baseKeymap } from "prosemirror-commands";
+export { history, undo, redo } from "prosemirror-history";
+export { keymap } from "prosemirror-keymap";
+export { Schema, DOMSerializer, DOMParser } from "prosemirror-model";
+export { schema as basicSchema } from "prosemirror-schema-basic";
+export { EditorState, Plugin, PluginKey, TextSelection } from "prosemirror-state";
+export { EditorView, Decoration, DecorationSet } from "prosemirror-view";
+
+// Non-core
+export { defaultMarkdownParser } from "prosemirror-markdown";
+
+// Third-party
+export { suggestionsPlugin, triggerCharacter } from "./prosemirror-suggestions/src/index.js";
diff --git a/third_party/js/prosemirror/make_bundle.sh b/third_party/js/prosemirror/make_bundle.sh
@@ -0,0 +1,26 @@
+#!/usr/bin/env bash
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+# This script bundles all vendored ProseMirror packages in this directory.
+
+set -euo pipefail
+
+cd "$(dirname "${BASH_SOURCE[0]}")"
+MACH=$(realpath "../../../mach")
+
+# Install local dependencies
+"$MACH" npm install
+
+# Build the bundle
+"$MACH" npm run build
+
+# Remove artifacts
+rm -rf node_modules package-lock.json prosemirror-*/{dist,node_modules,package-lock.json}
+
+# Report bundle size
+if [[ -f prosemirror.bundle.mjs ]]; then
+ BUNDLE_SIZE=$(ls -lh prosemirror.bundle.mjs | awk '{print $5}')
+ echo "Bundle size: $BUNDLE_SIZE"
+fi
diff --git a/third_party/js/prosemirror/moz.build b/third_party/js/prosemirror/moz.build
@@ -0,0 +1,12 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+with Files("**"):
+ BUG_COMPONENT = ("Firefox", "General")
+
+MOZ_SRC_FILES += [
+ "prosemirror.bundle.mjs",
+]
diff --git a/third_party/js/prosemirror/moz.sh b/third_party/js/prosemirror/moz.sh
@@ -0,0 +1,37 @@
+#!/usr/bin/env bash
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+# This script vendors all ProseMirror packages in this directory.
+
+set -euo pipefail
+
+SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
+MACH=$(realpath "$SCRIPT_DIR/../../../mach")
+ROOT_MOZ_YAML="$SCRIPT_DIR/moz.yaml"
+
+# Vendor root moz.yaml first
+if [[ -f "$ROOT_MOZ_YAML" ]]; then
+ echo "Vendoring $ROOT_MOZ_YAML"
+ "$MACH" vendor "$ROOT_MOZ_YAML" "$@" || true
+fi
+
+for manifest in "$SCRIPT_DIR"/prosemirror-*/moz.yaml; do
+ echo "Vendoring $manifest"
+ # Always pass --ignore-modified to subfolder vendoring since a previous run
+ # might have modified `moz.yaml` files.
+ if ! "$MACH" vendor "$manifest" --ignore-modified "$@"; then
+ code=$?
+ # Continue on spurious-check exit
+ if [[ $code -eq 254 ]]; then
+ echo "Vendored $manifest with success code $code; continuing"
+ else
+ echo "Vendoring failed for $manifest with exit code $code"
+ exit "$code"
+ fi
+ fi
+done
+
+# Build the bundle
+"$SCRIPT_DIR/make_bundle.sh"
diff --git a/third_party/js/prosemirror/moz.yaml b/third_party/js/prosemirror/moz.yaml
@@ -0,0 +1,58 @@
+# Version of this schema
+schema: 1
+
+bugzilla:
+ # Bugzilla product and component for this directory and subdirectories
+ product: Firefox
+ component: General
+
+# Document the source of externally hosted code
+origin:
+
+ # Short name of the package/library
+ name: prosemirror
+
+ description: ProseMirror is a content editor based on contentEditable.
+
+ # Full URL for the package's homepage/etc
+ # Usually different from repository url
+ url: https://prosemirror.net/
+
+ # Human-readable identifier for this version/release
+ # Generally "version NNN", "tag SSS", "bookmark SSS"
+ release: 0126a40c14d408a275743058d8cd04d3fc64834c (2025-04-22T12:43:21Z).
+
+ # Revision to pull in
+ # Must be a long or short commit SHA (long preferred) or a tag
+ revision: 0126a40c14d408a275743058d8cd04d3fc64834c
+
+ # The package's license, where possible using the mnemonic from
+ # https://spdx.org/licenses/
+ # Multiple licenses can be specified (as a YAML list)
+ # A "LICENSE" file must exist containing the full license text
+ license: MIT
+
+ # If the package's license is specified in a particular file,
+ # this is the name of the file.
+ # optional
+ license-file: LICENSE
+
+# Configuration for the automated vendoring system.
+# optional
+vendoring:
+
+ # Repository URL to vendor from
+ # eg. https://github.com/kinetiknz/nestegg
+ # Any repository host can be specified here, however initially we'll only
+ # support automated vendoring from selected sources.
+ url: https://github.com/ProseMirror/prosemirror
+
+ # Type of hosting for the upstream repository
+ # Valid values are 'gitlab', 'github', googlesource
+ source-hosting: github
+
+ flavor: individual-files
+
+ individual-files:
+ - upstream: LICENSE
+ destination: LICENSE
diff --git a/third_party/js/prosemirror/package.json b/third_party/js/prosemirror/package.json
@@ -0,0 +1,26 @@
+{
+ "description": "ProseMirror bundle of core, non-core and third-party packages",
+ "scripts": {
+ "build": "rollup -c --configMinify rollup.config.mjs",
+ "build:dev": "rollup -c rollup.config.mjs"
+ },
+ "dependencies": {
+ "prosemirror-commands": "file:./prosemirror-commands",
+ "prosemirror-history": "file:./prosemirror-history",
+ "prosemirror-keymap": "file:./prosemirror-keymap",
+ "prosemirror-markdown": "file:./prosemirror-markdown",
+ "prosemirror-model": "file:./prosemirror-model",
+ "prosemirror-schema-basic": "file:./prosemirror-schema-basic",
+ "prosemirror-state": "file:./prosemirror-state",
+ "prosemirror-suggestions": "file:./prosemirror-suggestions",
+ "prosemirror-transform": "file:./prosemirror-transform",
+ "prosemirror-view": "file:./prosemirror-view"
+ },
+ "devDependencies": {
+ "@rollup/plugin-commonjs": "^29.0.0",
+ "@rollup/plugin-json": "^6.1.0",
+ "@rollup/plugin-node-resolve": "^15.2.1",
+ "@rollup/plugin-terser": "^0.4.4",
+ "rollup": "^3.29.4"
+ }
+}
diff --git a/third_party/js/prosemirror/prosemirror-commands/LICENSE b/third_party/js/prosemirror/prosemirror-commands/LICENSE
@@ -0,0 +1,19 @@
+Copyright (C) 2015-2017 by Marijn Haverbeke <marijn@haverbeke.berlin> and others
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
diff --git a/third_party/js/prosemirror/prosemirror-commands/moz.yaml b/third_party/js/prosemirror/prosemirror-commands/moz.yaml
@@ -0,0 +1,63 @@
+# Version of this schema
+schema: 1
+
+bugzilla:
+ # Bugzilla product and component for this directory and subdirectories
+ product: Firefox
+ component: General
+
+# Document the source of externally hosted code
+origin:
+
+ # Short name of the package/library
+ name: prosemirror-commands
+
+ description: Editing commands for ProseMirror
+
+ # Full URL for the package's homepage/etc
+ # Usually different from repository url
+ url: https://prosemirror.net/
+
+ # Human-readable identifier for this version/release
+ # Generally "version NNN", "tag SSS", "bookmark SSS"
+ release: 1.7.1 (2025-04-13T21:37:45+02:00).
+
+ # Revision to pull in
+ # Must be a long or short commit SHA (long preferred) or a tag
+ revision: 1.7.1
+
+ # The package's license, where possible using the mnemonic from
+ # https://spdx.org/licenses/
+ # Multiple licenses can be specified (as a YAML list)
+ # A "LICENSE" file must exist containing the full license text
+ license: MIT
+
+ # If the package's license is specified in a particular file,
+ # this is the name of the file.
+ # optional
+ license-file: LICENSE
+
+# Configuration for the automated vendoring system.
+# optional
+vendoring:
+
+ # Repository URL to vendor from
+ # eg. https://github.com/kinetiknz/nestegg
+ # Any repository host can be specified here, however initially we'll only
+ # support automated vendoring from selected sources.
+ url: https://github.com/ProseMirror/prosemirror-commands
+
+ # Type of hosting for the upstream repository
+ # Valid values are 'gitlab', 'github', googlesource
+ source-hosting: github
+
+ # Whether to track by commit or tag
+ tracking: tag
+
+ exclude:
+ - "**"
+
+ include:
+ - LICENSE
+ - src/
+ - package.json
diff --git a/third_party/js/prosemirror/prosemirror-commands/package.json b/third_party/js/prosemirror/prosemirror-commands/package.json
@@ -0,0 +1,39 @@
+{
+ "name": "prosemirror-commands",
+ "version": "1.7.1",
+ "description": "Editing commands for ProseMirror",
+ "type": "module",
+ "main": "dist/index.cjs",
+ "module": "dist/index.js",
+ "types": "dist/index.d.ts",
+ "exports": {
+ "import": "./dist/index.js",
+ "require": "./dist/index.cjs"
+ },
+ "sideEffects": false,
+ "license": "MIT",
+ "maintainers": [
+ {
+ "name": "Marijn Haverbeke",
+ "email": "marijn@haverbeke.berlin",
+ "web": "http://marijnhaverbeke.nl"
+ }
+ ],
+ "repository": {
+ "type": "git",
+ "url": "git://github.com/prosemirror/prosemirror-commands.git"
+ },
+ "dependencies": {
+ "prosemirror-model": "^1.0.0",
+ "prosemirror-transform": "^1.10.2",
+ "prosemirror-state": "^1.0.0"
+ },
+ "devDependencies": {
+ "@prosemirror/buildhelper": "^0.1.5",
+ "prosemirror-test-builder": "^1.0.0"
+ },
+ "scripts": {
+ "test": "pm-runtests",
+ "prepare": "pm-buildhelper src/commands.ts"
+ }
+}
diff --git a/third_party/js/prosemirror/prosemirror-commands/src/README.md b/third_party/js/prosemirror/prosemirror-commands/src/README.md
@@ -0,0 +1,40 @@
+This module exports a number of _commands_, which are building block
+functions that encapsulate an editing action. A command function takes
+an editor state, _optionally_ a `dispatch` function that it can use
+to dispatch a transaction and _optionally_ an `EditorView` instance.
+It should return a boolean that indicates whether it could perform any
+action. When no `dispatch` callback is passed, the command should do a
+'dry run', determining whether it is applicable, but not actually doing
+anything.
+
+These are mostly used to bind keys and define menu items.
+
+@chainCommands
+@deleteSelection
+@joinBackward
+@selectNodeBackward
+@joinTextblockBackward
+@joinForward
+@selectNodeForward
+@joinTextblockForward
+@joinUp
+@joinDown
+@lift
+@newlineInCode
+@exitCode
+@createParagraphNear
+@liftEmptyBlock
+@splitBlock
+@splitBlockAs
+@splitBlockKeepMarks
+@selectParentNode
+@selectAll
+@selectTextblockStart
+@selectTextblockEnd
+@wrapIn
+@setBlockType
+@toggleMark
+@autoJoin
+@baseKeymap
+@pcBaseKeymap
+@macBaseKeymap
diff --git a/third_party/js/prosemirror/prosemirror-commands/src/commands.ts b/third_party/js/prosemirror/prosemirror-commands/src/commands.ts
@@ -0,0 +1,783 @@
+import {joinPoint, canJoin, findWrapping, liftTarget, canSplit,
+ ReplaceStep, ReplaceAroundStep, replaceStep} from "prosemirror-transform"
+import {Slice, Fragment, Node, NodeType, Attrs, MarkType, ResolvedPos, ContentMatch} from "prosemirror-model"
+import {Selection, EditorState, Transaction, TextSelection, NodeSelection,
+ SelectionRange, AllSelection, Command} from "prosemirror-state"
+import {EditorView} from "prosemirror-view"
+
+/// Delete the selection, if there is one.
+export const deleteSelection: Command = (state, dispatch) => {
+ if (state.selection.empty) return false
+ if (dispatch) dispatch(state.tr.deleteSelection().scrollIntoView())
+ return true
+}
+
+function atBlockStart(state: EditorState, view?: EditorView): ResolvedPos | null {
+ let {$cursor} = state.selection as TextSelection
+ if (!$cursor || (view ? !view.endOfTextblock("backward", state)
+ : $cursor.parentOffset > 0))
+ return null
+ return $cursor
+}
+
+/// If the selection is empty and at the start of a textblock, try to
+/// reduce the distance between that block and the one before it—if
+/// there's a block directly before it that can be joined, join them.
+/// If not, try to move the selected block closer to the next one in
+/// the document structure by lifting it out of its parent or moving it
+/// into a parent of the previous block. Will use the view for accurate
+/// (bidi-aware) start-of-textblock detection if given.
+export const joinBackward: Command = (state, dispatch, view) => {
+ let $cursor = atBlockStart(state, view)
+ if (!$cursor) return false
+
+ let $cut = findCutBefore($cursor)
+
+ // If there is no node before this, try to lift
+ if (!$cut) {
+ let range = $cursor.blockRange(), target = range && liftTarget(range)
+ if (target == null) return false
+ if (dispatch) dispatch(state.tr.lift(range!, target).scrollIntoView())
+ return true
+ }
+
+ let before = $cut.nodeBefore!
+ // Apply the joining algorithm
+ if (deleteBarrier(state, $cut, dispatch, -1)) return true
+
+ // If the node below has no content and the node above is
+ // selectable, delete the node below and select the one above.
+ if ($cursor.parent.content.size == 0 &&
+ (textblockAt(before, "end") || NodeSelection.isSelectable(before))) {
+ for (let depth = $cursor.depth;; depth--) {
+ let delStep = replaceStep(state.doc, $cursor.before(depth), $cursor.after(depth), Slice.empty)
+ if (delStep && (delStep as ReplaceStep).slice.size < (delStep as ReplaceStep).to - (delStep as ReplaceStep).from) {
+ if (dispatch) {
+ let tr = state.tr.step(delStep)
+ tr.setSelection(textblockAt(before, "end")
+ ? Selection.findFrom(tr.doc.resolve(tr.mapping.map($cut.pos, -1)), -1)!
+ : NodeSelection.create(tr.doc, $cut.pos - before.nodeSize))
+ dispatch(tr.scrollIntoView())
+ }
+ return true
+ }
+ if (depth == 1 || $cursor.node(depth - 1).childCount > 1) break
+ }
+ }
+
+ // If the node before is an atom, delete it
+ if (before.isAtom && $cut.depth == $cursor.depth - 1) {
+ if (dispatch) dispatch(state.tr.delete($cut.pos - before.nodeSize, $cut.pos).scrollIntoView())
+ return true
+ }
+
+ return false
+}
+
+/// A more limited form of [`joinBackward`](#commands.joinBackward)
+/// that only tries to join the current textblock to the one before
+/// it, if the cursor is at the start of a textblock.
+export const joinTextblockBackward: Command = (state, dispatch, view) => {
+ let $cursor = atBlockStart(state, view)
+ if (!$cursor) return false
+ let $cut = findCutBefore($cursor)
+ return $cut ? joinTextblocksAround(state, $cut, dispatch) : false
+}
+
+/// A more limited form of [`joinForward`](#commands.joinForward)
+/// that only tries to join the current textblock to the one after
+/// it, if the cursor is at the end of a textblock.
+export const joinTextblockForward: Command = (state, dispatch, view) => {
+ let $cursor = atBlockEnd(state, view)
+ if (!$cursor) return false
+ let $cut = findCutAfter($cursor)
+ return $cut ? joinTextblocksAround(state, $cut, dispatch) : false
+}
+
+function joinTextblocksAround(state: EditorState, $cut: ResolvedPos, dispatch?: (tr: Transaction) => void) {
+ let before = $cut.nodeBefore!, beforeText = before, beforePos = $cut.pos - 1
+ for (; !beforeText.isTextblock; beforePos--) {
+ if (beforeText.type.spec.isolating) return false
+ let child = beforeText.lastChild
+ if (!child) return false
+ beforeText = child
+ }
+ let after = $cut.nodeAfter!, afterText = after, afterPos = $cut.pos + 1
+ for (; !afterText.isTextblock; afterPos++) {
+ if (afterText.type.spec.isolating) return false
+ let child = afterText.firstChild
+ if (!child) return false
+ afterText = child
+ }
+ let step = replaceStep(state.doc, beforePos, afterPos, Slice.empty) as ReplaceStep | null
+ if (!step || step.from != beforePos ||
+ step instanceof ReplaceStep && step.slice.size >= afterPos - beforePos) return false
+ if (dispatch) {
+ let tr = state.tr.step(step)
+ tr.setSelection(TextSelection.create(tr.doc, beforePos))
+ dispatch(tr.scrollIntoView())
+ }
+ return true
+
+}
+
+function textblockAt(node: Node, side: "start" | "end", only = false) {
+ for (let scan: Node | null = node; scan; scan = (side == "start" ? scan.firstChild : scan.lastChild)) {
+ if (scan.isTextblock) return true
+ if (only && scan.childCount != 1) return false
+ }
+ return false
+}
+
+/// When the selection is empty and at the start of a textblock, select
+/// the node before that textblock, if possible. This is intended to be
+/// bound to keys like backspace, after
+/// [`joinBackward`](#commands.joinBackward) or other deleting
+/// commands, as a fall-back behavior when the schema doesn't allow
+/// deletion at the selected point.
+export const selectNodeBackward: Command = (state, dispatch, view) => {
+ let {$head, empty} = state.selection, $cut: ResolvedPos | null = $head
+ if (!empty) return false
+
+ if ($head.parent.isTextblock) {
+ if (view ? !view.endOfTextblock("backward", state) : $head.parentOffset > 0) return false
+ $cut = findCutBefore($head)
+ }
+ let node = $cut && $cut.nodeBefore
+ if (!node || !NodeSelection.isSelectable(node)) return false
+ if (dispatch)
+ dispatch(state.tr.setSelection(NodeSelection.create(state.doc, $cut!.pos - node.nodeSize)).scrollIntoView())
+ return true
+}
+
+function findCutBefore($pos: ResolvedPos): ResolvedPos | null {
+ if (!$pos.parent.type.spec.isolating) for (let i = $pos.depth - 1; i >= 0; i--) {
+ if ($pos.index(i) > 0) return $pos.doc.resolve($pos.before(i + 1))
+ if ($pos.node(i).type.spec.isolating) break
+ }
+ return null
+}
+
+function atBlockEnd(state: EditorState, view?: EditorView): ResolvedPos | null {
+ let {$cursor} = state.selection as TextSelection
+ if (!$cursor || (view ? !view.endOfTextblock("forward", state)
+ : $cursor.parentOffset < $cursor.parent.content.size))
+ return null
+ return $cursor
+}
+
+/// If the selection is empty and the cursor is at the end of a
+/// textblock, try to reduce or remove the boundary between that block
+/// and the one after it, either by joining them or by moving the other
+/// block closer to this one in the tree structure. Will use the view
+/// for accurate start-of-textblock detection if given.
+export const joinForward: Command = (state, dispatch, view) => {
+ let $cursor = atBlockEnd(state, view)
+ if (!$cursor) return false
+
+ let $cut = findCutAfter($cursor)
+ // If there is no node after this, there's nothing to do
+ if (!$cut) return false
+
+ let after = $cut.nodeAfter!
+ // Try the joining algorithm
+ if (deleteBarrier(state, $cut, dispatch, 1)) return true
+
+ // If the node above has no content and the node below is
+ // selectable, delete the node above and select the one below.
+ if ($cursor.parent.content.size == 0 &&
+ (textblockAt(after, "start") || NodeSelection.isSelectable(after))) {
+ let delStep = replaceStep(state.doc, $cursor.before(), $cursor.after(), Slice.empty)
+ if (delStep && (delStep as ReplaceStep).slice.size < (delStep as ReplaceStep).to - (delStep as ReplaceStep).from) {
+ if (dispatch) {
+ let tr = state.tr.step(delStep)
+ tr.setSelection(textblockAt(after, "start") ? Selection.findFrom(tr.doc.resolve(tr.mapping.map($cut.pos)), 1)!
+ : NodeSelection.create(tr.doc, tr.mapping.map($cut.pos)))
+ dispatch(tr.scrollIntoView())
+ }
+ return true
+ }
+ }
+
+ // If the next node is an atom, delete it
+ if (after.isAtom && $cut.depth == $cursor.depth - 1) {
+ if (dispatch) dispatch(state.tr.delete($cut.pos, $cut.pos + after.nodeSize).scrollIntoView())
+ return true
+ }
+
+ return false
+}
+
+/// When the selection is empty and at the end of a textblock, select
+/// the node coming after that textblock, if possible. This is intended
+/// to be bound to keys like delete, after
+/// [`joinForward`](#commands.joinForward) and similar deleting
+/// commands, to provide a fall-back behavior when the schema doesn't
+/// allow deletion at the selected point.
+export const selectNodeForward: Command = (state, dispatch, view) => {
+ let {$head, empty} = state.selection, $cut: ResolvedPos | null = $head
+ if (!empty) return false
+ if ($head.parent.isTextblock) {
+ if (view ? !view.endOfTextblock("forward", state) : $head.parentOffset < $head.parent.content.size)
+ return false
+ $cut = findCutAfter($head)
+ }
+ let node = $cut && $cut.nodeAfter
+ if (!node || !NodeSelection.isSelectable(node)) return false
+ if (dispatch)
+ dispatch(state.tr.setSelection(NodeSelection.create(state.doc, $cut!.pos)).scrollIntoView())
+ return true
+}
+
+function findCutAfter($pos: ResolvedPos) {
+ if (!$pos.parent.type.spec.isolating) for (let i = $pos.depth - 1; i >= 0; i--) {
+ let parent = $pos.node(i)
+ if ($pos.index(i) + 1 < parent.childCount) return $pos.doc.resolve($pos.after(i + 1))
+ if (parent.type.spec.isolating) break
+ }
+ return null
+}
+
+/// Join the selected block or, if there is a text selection, the
+/// closest ancestor block of the selection that can be joined, with
+/// the sibling above it.
+export const joinUp: Command = (state, dispatch) => {
+ let sel = state.selection, nodeSel = sel instanceof NodeSelection, point
+ if (nodeSel) {
+ if ((sel as NodeSelection).node.isTextblock || !canJoin(state.doc, sel.from)) return false
+ point = sel.from
+ } else {
+ point = joinPoint(state.doc, sel.from, -1)
+ if (point == null) return false
+ }
+ if (dispatch) {
+ let tr = state.tr.join(point)
+ if (nodeSel) tr.setSelection(NodeSelection.create(tr.doc, point - state.doc.resolve(point).nodeBefore!.nodeSize))
+ dispatch(tr.scrollIntoView())
+ }
+ return true
+}
+
+/// Join the selected block, or the closest ancestor of the selection
+/// that can be joined, with the sibling after it.
+export const joinDown: Command = (state, dispatch) => {
+ let sel = state.selection, point
+ if (sel instanceof NodeSelection) {
+ if (sel.node.isTextblock || !canJoin(state.doc, sel.to)) return false
+ point = sel.to
+ } else {
+ point = joinPoint(state.doc, sel.to, 1)
+ if (point == null) return false
+ }
+ if (dispatch)
+ dispatch(state.tr.join(point).scrollIntoView())
+ return true
+}
+
+/// Lift the selected block, or the closest ancestor block of the
+/// selection that can be lifted, out of its parent node.
+export const lift: Command = (state, dispatch) => {
+ let {$from, $to} = state.selection
+ let range = $from.blockRange($to), target = range && liftTarget(range)
+ if (target == null) return false
+ if (dispatch) dispatch(state.tr.lift(range!, target).scrollIntoView())
+ return true
+}
+
+/// If the selection is in a node whose type has a truthy
+/// [`code`](#model.NodeSpec.code) property in its spec, replace the
+/// selection with a newline character.
+export const newlineInCode: Command = (state, dispatch) => {
+ let {$head, $anchor} = state.selection
+ if (!$head.parent.type.spec.code || !$head.sameParent($anchor)) return false
+ if (dispatch) dispatch(state.tr.insertText("\n").scrollIntoView())
+ return true
+}
+
+function defaultBlockAt(match: ContentMatch) {
+ for (let i = 0; i < match.edgeCount; i++) {
+ let {type} = match.edge(i)
+ if (type.isTextblock && !type.hasRequiredAttrs()) return type
+ }
+ return null
+}
+
+/// When the selection is in a node with a truthy
+/// [`code`](#model.NodeSpec.code) property in its spec, create a
+/// default block after the code block, and move the cursor there.
+export const exitCode: Command = (state, dispatch) => {
+ let {$head, $anchor} = state.selection
+ if (!$head.parent.type.spec.code || !$head.sameParent($anchor)) return false
+ let above = $head.node(-1), after = $head.indexAfter(-1), type = defaultBlockAt(above.contentMatchAt(after))
+ if (!type || !above.canReplaceWith(after, after, type)) return false
+ if (dispatch) {
+ let pos = $head.after(), tr = state.tr.replaceWith(pos, pos, type.createAndFill()!)
+ tr.setSelection(Selection.near(tr.doc.resolve(pos), 1))
+ dispatch(tr.scrollIntoView())
+ }
+ return true
+}
+
+/// If a block node is selected, create an empty paragraph before (if
+/// it is its parent's first child) or after it.
+export const createParagraphNear: Command = (state, dispatch) => {
+ let sel = state.selection, {$from, $to} = sel
+ if (sel instanceof AllSelection || $from.parent.inlineContent || $to.parent.inlineContent) return false
+ let type = defaultBlockAt($to.parent.contentMatchAt($to.indexAfter()))
+ if (!type || !type.isTextblock) return false
+ if (dispatch) {
+ let side = (!$from.parentOffset && $to.index() < $to.parent.childCount ? $from : $to).pos
+ let tr = state.tr.insert(side, type.createAndFill()!)
+ tr.setSelection(TextSelection.create(tr.doc, side + 1))
+ dispatch(tr.scrollIntoView())
+ }
+ return true
+}
+
+/// If the cursor is in an empty textblock that can be lifted, lift the
+/// block.
+export const liftEmptyBlock: Command = (state, dispatch) => {
+ let {$cursor} = state.selection as TextSelection
+ if (!$cursor || $cursor.parent.content.size) return false
+ if ($cursor.depth > 1 && $cursor.after() != $cursor.end(-1)) {
+ let before = $cursor.before()
+ if (canSplit(state.doc, before)) {
+ if (dispatch) dispatch(state.tr.split(before).scrollIntoView())
+ return true
+ }
+ }
+ let range = $cursor.blockRange(), target = range && liftTarget(range)
+ if (target == null) return false
+ if (dispatch) dispatch(state.tr.lift(range!, target).scrollIntoView())
+ return true
+}
+
+/// Create a variant of [`splitBlock`](#commands.splitBlock) that uses
+/// a custom function to determine the type of the newly split off block.
+export function splitBlockAs(
+ splitNode?: (node: Node, atEnd: boolean, $from: ResolvedPos) => {type: NodeType, attrs?: Attrs} | null
+): Command {
+ return (state, dispatch) => {
+ let {$from, $to} = state.selection
+ if (state.selection instanceof NodeSelection && state.selection.node.isBlock) {
+ if (!$from.parentOffset || !canSplit(state.doc, $from.pos)) return false
+ if (dispatch) dispatch(state.tr.split($from.pos).scrollIntoView())
+ return true
+ }
+
+ if (!$from.depth) return false
+ let types: (null | {type: NodeType, attrs?: Attrs | null})[] = []
+ let splitDepth, deflt, atEnd = false, atStart = false
+ for (let d = $from.depth;; d--) {
+ let node = $from.node(d)
+ if (node.isBlock) {
+ atEnd = $from.end(d) == $from.pos + ($from.depth - d)
+ atStart = $from.start(d) == $from.pos - ($from.depth - d)
+ deflt = defaultBlockAt($from.node(d - 1).contentMatchAt($from.indexAfter(d - 1)))
+ let splitType = splitNode && splitNode($to.parent, atEnd, $from)
+ types.unshift(splitType || (atEnd && deflt ? {type: deflt} : null))
+ splitDepth = d
+ break
+ } else {
+ if (d == 1) return false
+ types.unshift(null)
+ }
+ }
+
+ let tr = state.tr
+ if (state.selection instanceof TextSelection || state.selection instanceof AllSelection) tr.deleteSelection()
+ let splitPos = tr.mapping.map($from.pos)
+ let can = canSplit(tr.doc, splitPos, types.length, types)
+ if (!can) {
+ types[0] = deflt ? {type: deflt} : null
+ can = canSplit(tr.doc, splitPos, types.length, types)
+ }
+ if (!can) return false
+ tr.split(splitPos, types.length, types)
+ if (!atEnd && atStart && $from.node(splitDepth).type != deflt) {
+ let first = tr.mapping.map($from.before(splitDepth)), $first = tr.doc.resolve(first)
+ if (deflt && $from.node(splitDepth - 1).canReplaceWith($first.index(), $first.index() + 1, deflt))
+ tr.setNodeMarkup(tr.mapping.map($from.before(splitDepth)), deflt)
+ }
+ if (dispatch) dispatch(tr.scrollIntoView())
+ return true
+ }
+}
+
+/// Split the parent block of the selection. If the selection is a text
+/// selection, also delete its content.
+export const splitBlock: Command = splitBlockAs()
+
+/// Acts like [`splitBlock`](#commands.splitBlock), but without
+/// resetting the set of active marks at the cursor.
+export const splitBlockKeepMarks: Command = (state, dispatch) => {
+ return splitBlock(state, dispatch && (tr => {
+ let marks = state.storedMarks || (state.selection.$to.parentOffset && state.selection.$from.marks())
+ if (marks) tr.ensureMarks(marks)
+ dispatch(tr)
+ }))
+}
+
+/// Move the selection to the node wrapping the current selection, if
+/// any. (Will not select the document node.)
+export const selectParentNode: Command = (state, dispatch) => {
+ let {$from, to} = state.selection, pos
+ let same = $from.sharedDepth(to)
+ if (same == 0) return false
+ pos = $from.before(same)
+ if (dispatch) dispatch(state.tr.setSelection(NodeSelection.create(state.doc, pos)))
+ return true
+}
+
+/// Select the whole document.
+export const selectAll: Command = (state, dispatch) => {
+ if (dispatch) dispatch(state.tr.setSelection(new AllSelection(state.doc)))
+ return true
+}
+
+function joinMaybeClear(state: EditorState, $pos: ResolvedPos, dispatch: ((tr: Transaction) => void) | undefined) {
+ let before = $pos.nodeBefore, after = $pos.nodeAfter, index = $pos.index()
+ if (!before || !after || !before.type.compatibleContent(after.type)) return false
+ if (!before.content.size && $pos.parent.canReplace(index - 1, index)) {
+ if (dispatch) dispatch(state.tr.delete($pos.pos - before.nodeSize, $pos.pos).scrollIntoView())
+ return true
+ }
+ if (!$pos.parent.canReplace(index, index + 1) || !(after.isTextblock || canJoin(state.doc, $pos.pos)))
+ return false
+ if (dispatch)
+ dispatch(state.tr.join($pos.pos).scrollIntoView())
+ return true
+}
+
+function deleteBarrier(state: EditorState, $cut: ResolvedPos, dispatch: ((tr: Transaction) => void) | undefined, dir: number) {
+ let before = $cut.nodeBefore!, after = $cut.nodeAfter!, conn, match
+ let isolated = before.type.spec.isolating || after.type.spec.isolating
+ if (!isolated && joinMaybeClear(state, $cut, dispatch)) return true
+
+ let canDelAfter = !isolated && $cut.parent.canReplace($cut.index(), $cut.index() + 1)
+ if (canDelAfter &&
+ (conn = (match = before.contentMatchAt(before.childCount)).findWrapping(after.type)) &&
+ match.matchType(conn[0] || after.type)!.validEnd) {
+ if (dispatch) {
+ let end = $cut.pos + after.nodeSize, wrap = Fragment.empty
+ for (let i = conn.length - 1; i >= 0; i--)
+ wrap = Fragment.from(conn[i].create(null, wrap))
+ wrap = Fragment.from(before.copy(wrap))
+ let tr = state.tr.step(new ReplaceAroundStep($cut.pos - 1, end, $cut.pos, end, new Slice(wrap, 1, 0), conn.length, true))
+ let $joinAt = tr.doc.resolve(end + 2 * conn.length)
+ if ($joinAt.nodeAfter && $joinAt.nodeAfter.type == before.type &&
+ canJoin(tr.doc, $joinAt.pos)) tr.join($joinAt.pos)
+ dispatch(tr.scrollIntoView())
+ }
+ return true
+ }
+
+ let selAfter = after.type.spec.isolating || (dir > 0 && isolated) ? null : Selection.findFrom($cut, 1)
+ let range = selAfter && selAfter.$from.blockRange(selAfter.$to), target = range && liftTarget(range)
+ if (target != null && target >= $cut.depth) {
+ if (dispatch) dispatch(state.tr.lift(range!, target).scrollIntoView())
+ return true
+ }
+
+ if (canDelAfter && textblockAt(after, "start", true) && textblockAt(before, "end")) {
+ let at = before, wrap = []
+ for (;;) {
+ wrap.push(at)
+ if (at.isTextblock) break
+ at = at.lastChild!
+ }
+ let afterText = after, afterDepth = 1
+ for (; !afterText.isTextblock; afterText = afterText.firstChild!) afterDepth++
+ if (at.canReplace(at.childCount, at.childCount, afterText.content)) {
+ if (dispatch) {
+ let end = Fragment.empty
+ for (let i = wrap.length - 1; i >= 0; i--) end = Fragment.from(wrap[i].copy(end))
+ let tr = state.tr.step(new ReplaceAroundStep($cut.pos - wrap.length, $cut.pos + after.nodeSize,
+ $cut.pos + afterDepth, $cut.pos + after.nodeSize - afterDepth,
+ new Slice(end, wrap.length, 0), 0, true))
+ dispatch(tr.scrollIntoView())
+ }
+ return true
+ }
+ }
+
+ return false
+}
+
+function selectTextblockSide(side: number): Command {
+ return function(state, dispatch) {
+ let sel = state.selection, $pos = side < 0 ? sel.$from : sel.$to
+ let depth = $pos.depth
+ while ($pos.node(depth).isInline) {
+ if (!depth) return false
+ depth--
+ }
+ if (!$pos.node(depth).isTextblock) return false
+ if (dispatch)
+ dispatch(state.tr.setSelection(TextSelection.create(
+ state.doc, side < 0 ? $pos.start(depth) : $pos.end(depth))))
+ return true
+ }
+}
+
+/// Moves the cursor to the start of current text block.
+export const selectTextblockStart = selectTextblockSide(-1)
+
+/// Moves the cursor to the end of current text block.
+export const selectTextblockEnd = selectTextblockSide(1)
+
+// Parameterized commands
+
+/// Wrap the selection in a node of the given type with the given
+/// attributes.
+export function wrapIn(nodeType: NodeType, attrs: Attrs | null = null): Command {
+ return function(state, dispatch) {
+ let {$from, $to} = state.selection
+ let range = $from.blockRange($to), wrapping = range && findWrapping(range, nodeType, attrs)
+ if (!wrapping) return false
+ if (dispatch) dispatch(state.tr.wrap(range!, wrapping).scrollIntoView())
+ return true
+ }
+}
+
+/// Returns a command that tries to set the selected textblocks to the
+/// given node type with the given attributes.
+export function setBlockType(nodeType: NodeType, attrs: Attrs | null = null): Command {
+ return function(state, dispatch) {
+ let applicable = false
+ for (let i = 0; i < state.selection.ranges.length && !applicable; i++) {
+ let {$from: {pos: from}, $to: {pos: to}} = state.selection.ranges[i]
+ state.doc.nodesBetween(from, to, (node, pos) => {
+ if (applicable) return false
+ if (!node.isTextblock || node.hasMarkup(nodeType, attrs)) return
+ if (node.type == nodeType) {
+ applicable = true
+ } else {
+ let $pos = state.doc.resolve(pos), index = $pos.index()
+ applicable = $pos.parent.canReplaceWith(index, index + 1, nodeType)
+ }
+ })
+ }
+ if (!applicable) return false
+ if (dispatch) {
+ let tr = state.tr
+ for (let i = 0; i < state.selection.ranges.length; i++) {
+ let {$from: {pos: from}, $to: {pos: to}} = state.selection.ranges[i]
+ tr.setBlockType(from, to, nodeType, attrs)
+ }
+ dispatch(tr.scrollIntoView())
+ }
+ return true
+ }
+}
+
+function markApplies(doc: Node, ranges: readonly SelectionRange[], type: MarkType, enterAtoms: boolean) {
+ for (let i = 0; i < ranges.length; i++) {
+ let {$from, $to} = ranges[i]
+ let can = $from.depth == 0 ? doc.inlineContent && doc.type.allowsMarkType(type) : false
+ doc.nodesBetween($from.pos, $to.pos, (node, pos) => {
+ if (can || !enterAtoms && node.isAtom && node.isInline && pos >= $from.pos && pos + node.nodeSize <= $to.pos)
+ return false
+ can = node.inlineContent && node.type.allowsMarkType(type)
+ })
+ if (can) return true
+ }
+ return false
+}
+
+function removeInlineAtoms(ranges: readonly SelectionRange[]): readonly SelectionRange[] {
+ let result = []
+ for (let i = 0; i < ranges.length; i++) {
+ let {$from, $to} = ranges[i]
+ $from.doc.nodesBetween($from.pos, $to.pos, (node, pos) => {
+ if (node.isAtom && node.content.size && node.isInline && pos >= $from.pos && pos + node.nodeSize <= $to.pos) {
+ if (pos + 1 > $from.pos) result.push(new SelectionRange($from, $from.doc.resolve(pos + 1)))
+ $from = $from.doc.resolve(pos + 1 + node.content.size)
+ return false
+ }
+ })
+ if ($from.pos < $to.pos) result.push(new SelectionRange($from, $to))
+ }
+ return result
+}
+
+/// Create a command function that toggles the given mark with the
+/// given attributes. Will return `false` when the current selection
+/// doesn't support that mark. This will remove the mark if any marks
+/// of that type exist in the selection, or add it otherwise. If the
+/// selection is empty, this applies to the [stored
+/// marks](#state.EditorState.storedMarks) instead of a range of the
+/// document.
+export function toggleMark(markType: MarkType, attrs: Attrs | null = null, options?: {
+ /// Controls whether, when part of the selected range has the mark
+ /// already and part doesn't, the mark is removed (`true`, the
+ /// default) or added (`false`).
+ removeWhenPresent?: boolean
+ /// When set to false, this will prevent the command from acting on
+ /// the content of inline nodes marked as
+ /// [atoms](#model.NodeSpec.atom) that are completely covered by a
+ /// selection range.
+ enterInlineAtoms?: boolean
+ /// By default, this command doesn't apply to leading and trailing
+ /// whitespace in the selection. Set this to `true` to change that.
+ includeWhitespace?: boolean
+}): Command {
+ let removeWhenPresent = (options && options.removeWhenPresent) !== false
+ let enterAtoms = (options && options.enterInlineAtoms) !== false
+ let dropSpace = !(options && options.includeWhitespace)
+ return function(state, dispatch) {
+ let {empty, $cursor, ranges} = state.selection as TextSelection
+ if ((empty && !$cursor) || !markApplies(state.doc, ranges, markType, enterAtoms)) return false
+ if (dispatch) {
+ if ($cursor) {
+ if (markType.isInSet(state.storedMarks || $cursor.marks()))
+ dispatch(state.tr.removeStoredMark(markType))
+ else
+ dispatch(state.tr.addStoredMark(markType.create(attrs)))
+ } else {
+ let add, tr = state.tr
+ if (!enterAtoms) ranges = removeInlineAtoms(ranges)
+ if (removeWhenPresent) {
+ add = !ranges.some(r => state.doc.rangeHasMark(r.$from.pos, r.$to.pos, markType))
+ } else {
+ add = !ranges.every(r => {
+ let missing = false
+ tr.doc.nodesBetween(r.$from.pos, r.$to.pos, (node, pos, parent) => {
+ if (missing) return false
+ missing = !markType.isInSet(node.marks) && !!parent && parent.type.allowsMarkType(markType) &&
+ !(node.isText && /^\s*$/.test(node.textBetween(Math.max(0, r.$from.pos - pos),
+ Math.min(node.nodeSize, r.$to.pos - pos))))
+ })
+ return !missing
+ })
+ }
+ for (let i = 0; i < ranges.length; i++) {
+ let {$from, $to} = ranges[i]
+ if (!add) {
+ tr.removeMark($from.pos, $to.pos, markType)
+ } else {
+ let from = $from.pos, to = $to.pos, start = $from.nodeAfter, end = $to.nodeBefore
+ let spaceStart = dropSpace && start && start.isText ? /^\s*/.exec(start.text!)![0].length : 0
+ let spaceEnd = dropSpace && end && end.isText ? /\s*$/.exec(end.text!)![0].length : 0
+ if (from + spaceStart < to) { from += spaceStart; to -= spaceEnd }
+ tr.addMark(from, to, markType.create(attrs))
+ }
+ }
+ dispatch(tr.scrollIntoView())
+ }
+ }
+ return true
+ }
+}
+
+function wrapDispatchForJoin(dispatch: (tr: Transaction) => void, isJoinable: (a: Node, b: Node) => boolean) {
+ return (tr: Transaction) => {
+ if (!tr.isGeneric) return dispatch(tr)
+
+ let ranges: number[] = []
+ for (let i = 0; i < tr.mapping.maps.length; i++) {
+ let map = tr.mapping.maps[i]
+ for (let j = 0; j < ranges.length; j++)
+ ranges[j] = map.map(ranges[j])
+ map.forEach((_s, _e, from, to) => ranges.push(from, to))
+ }
+
+ // Figure out which joinable points exist inside those ranges,
+ // by checking all node boundaries in their parent nodes.
+ let joinable = []
+ for (let i = 0; i < ranges.length; i += 2) {
+ let from = ranges[i], to = ranges[i + 1]
+ let $from = tr.doc.resolve(from), depth = $from.sharedDepth(to), parent = $from.node(depth)
+ for (let index = $from.indexAfter(depth), pos = $from.after(depth + 1); pos <= to; ++index) {
+ let after = parent.maybeChild(index)
+ if (!after) break
+ if (index && joinable.indexOf(pos) == -1) {
+ let before = parent.child(index - 1)
+ if (before.type == after.type && isJoinable(before, after))
+ joinable.push(pos)
+ }
+ pos += after.nodeSize
+ }
+ }
+ // Join the joinable points
+ joinable.sort((a, b) => a - b)
+ for (let i = joinable.length - 1; i >= 0; i--) {
+ if (canJoin(tr.doc, joinable[i])) tr.join(joinable[i])
+ }
+ dispatch(tr)
+ }
+}
+
+/// Wrap a command so that, when it produces a transform that causes
+/// two joinable nodes to end up next to each other, those are joined.
+/// Nodes are considered joinable when they are of the same type and
+/// when the `isJoinable` predicate returns true for them or, if an
+/// array of strings was passed, if their node type name is in that
+/// array.
+export function autoJoin(
+ command: Command,
+ isJoinable: ((before: Node, after: Node) => boolean) | readonly string[]
+): Command {
+ let canJoin = Array.isArray(isJoinable) ? (node: Node) => isJoinable.indexOf(node.type.name) > -1
+ : isJoinable as (a: Node, b: Node) => boolean
+ return (state, dispatch, view) => command(state, dispatch && wrapDispatchForJoin(dispatch, canJoin), view)
+}
+
+/// Combine a number of command functions into a single function (which
+/// calls them one by one until one returns true).
+export function chainCommands(...commands: readonly Command[]): Command {
+ return function(state, dispatch, view) {
+ for (let i = 0; i < commands.length; i++)
+ if (commands[i](state, dispatch, view)) return true
+ return false
+ }
+}
+
+let backspace = chainCommands(deleteSelection, joinBackward, selectNodeBackward)
+let del = chainCommands(deleteSelection, joinForward, selectNodeForward)
+
+/// A basic keymap containing bindings not specific to any schema.
+/// Binds the following keys (when multiple commands are listed, they
+/// are chained with [`chainCommands`](#commands.chainCommands)):
+///
+/// * **Enter** to `newlineInCode`, `createParagraphNear`, `liftEmptyBlock`, `splitBlock`
+/// * **Mod-Enter** to `exitCode`
+/// * **Backspace** and **Mod-Backspace** to `deleteSelection`, `joinBackward`, `selectNodeBackward`
+/// * **Delete** and **Mod-Delete** to `deleteSelection`, `joinForward`, `selectNodeForward`
+/// * **Mod-Delete** to `deleteSelection`, `joinForward`, `selectNodeForward`
+/// * **Mod-a** to `selectAll`
+export const pcBaseKeymap: {[key: string]: Command} = {
+ "Enter": chainCommands(newlineInCode, createParagraphNear, liftEmptyBlock, splitBlock),
+ "Mod-Enter": exitCode,
+ "Backspace": backspace,
+ "Mod-Backspace": backspace,
+ "Shift-Backspace": backspace,
+ "Delete": del,
+ "Mod-Delete": del,
+ "Mod-a": selectAll
+}
+
+/// A copy of `pcBaseKeymap` that also binds **Ctrl-h** like Backspace,
+/// **Ctrl-d** like Delete, **Alt-Backspace** like Ctrl-Backspace, and
+/// **Ctrl-Alt-Backspace**, **Alt-Delete**, and **Alt-d** like
+/// Ctrl-Delete.
+export const macBaseKeymap: {[key: string]: Command} = {
+ "Ctrl-h": pcBaseKeymap["Backspace"],
+ "Alt-Backspace": pcBaseKeymap["Mod-Backspace"],
+ "Ctrl-d": pcBaseKeymap["Delete"],
+ "Ctrl-Alt-Backspace": pcBaseKeymap["Mod-Delete"],
+ "Alt-Delete": pcBaseKeymap["Mod-Delete"],
+ "Alt-d": pcBaseKeymap["Mod-Delete"],
+ "Ctrl-a": selectTextblockStart,
+ "Ctrl-e": selectTextblockEnd
+}
+for (let key in pcBaseKeymap) (macBaseKeymap as any)[key] = pcBaseKeymap[key]
+
+const mac = typeof navigator != "undefined" ? /Mac|iP(hone|[oa]d)/.test(navigator.platform)
+ // @ts-ignore
+ : typeof os != "undefined" && os.platform ? os.platform() == "darwin" : false
+
+/// Depending on the detected platform, this will hold
+/// [`pcBasekeymap`](#commands.pcBaseKeymap) or
+/// [`macBaseKeymap`](#commands.macBaseKeymap).
+export const baseKeymap: {[key: string]: Command} = mac ? macBaseKeymap : pcBaseKeymap
diff --git a/third_party/js/prosemirror/prosemirror-history/LICENSE b/third_party/js/prosemirror/prosemirror-history/LICENSE
@@ -0,0 +1,19 @@
+Copyright (C) 2015-2017 by Marijn Haverbeke <marijn@haverbeke.berlin> and others
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
diff --git a/third_party/js/prosemirror/prosemirror-history/moz.yaml b/third_party/js/prosemirror/prosemirror-history/moz.yaml
@@ -0,0 +1,63 @@
+# Version of this schema
+schema: 1
+
+bugzilla:
+ # Bugzilla product and component for this directory and subdirectories
+ product: Firefox
+ component: General
+
+# Document the source of externally hosted code
+origin:
+
+ # Short name of the package/library
+ name: prosemirror-history
+
+ description: Undo history for ProseMirror
+
+ # Full URL for the package's homepage/etc
+ # Usually different from repository url
+ url: https://prosemirror.net/
+
+ # Human-readable identifier for this version/release
+ # Generally "version NNN", "tag SSS", "bookmark SSS"
+ release: 1.5.0 (2025-11-13T08:22:24+01:00).
+
+ # Revision to pull in
+ # Must be a long or short commit SHA (long preferred) or a tag
+ revision: 1.5.0
+
+ # The package's license, where possible using the mnemonic from
+ # https://spdx.org/licenses/
+ # Multiple licenses can be specified (as a YAML list)
+ # A "LICENSE" file must exist containing the full license text
+ license: MIT
+
+ # If the package's license is specified in a particular file,
+ # this is the name of the file.
+ # optional
+ license-file: LICENSE
+
+# Configuration for the automated vendoring system.
+# optional
+vendoring:
+
+ # Repository URL to vendor from
+ # eg. https://github.com/kinetiknz/nestegg
+ # Any repository host can be specified here, however initially we'll only
+ # support automated vendoring from selected sources.
+ url: https://github.com/ProseMirror/prosemirror-history
+
+ # Type of hosting for the upstream repository
+ # Valid values are 'gitlab', 'github', googlesource
+ source-hosting: github
+
+ # Whether to track by commit or tag
+ tracking: tag
+
+ exclude:
+ - "**"
+
+ include:
+ - LICENSE
+ - src/
+ - package.json
diff --git a/third_party/js/prosemirror/prosemirror-history/package.json b/third_party/js/prosemirror/prosemirror-history/package.json
@@ -0,0 +1,40 @@
+{
+ "name": "prosemirror-history",
+ "version": "1.5.0",
+ "description": "Undo history for ProseMirror",
+ "type": "module",
+ "main": "dist/index.cjs",
+ "module": "dist/index.js",
+ "types": "dist/index.d.ts",
+ "exports": {
+ "import": "./dist/index.js",
+ "require": "./dist/index.cjs"
+ },
+ "sideEffects": false,
+ "license": "MIT",
+ "maintainers": [
+ {
+ "name": "Marijn Haverbeke",
+ "email": "marijn@haverbeke.berlin",
+ "web": "http://marijnhaverbeke.nl"
+ }
+ ],
+ "repository": {
+ "type": "git",
+ "url": "git://github.com/prosemirror/prosemirror-history.git"
+ },
+ "dependencies": {
+ "prosemirror-state": "^1.2.2",
+ "prosemirror-transform": "^1.0.0",
+ "prosemirror-view": "^1.31.0",
+ "rope-sequence": "^1.3.0"
+ },
+ "devDependencies": {
+ "@prosemirror/buildhelper": "^0.1.5",
+ "prosemirror-test-builder": "^1.0.0"
+ },
+ "scripts": {
+ "test": "pm-runtests",
+ "prepare": "pm-buildhelper src/history.ts"
+ }
+}
diff --git a/third_party/js/prosemirror/prosemirror-history/src/README.md b/third_party/js/prosemirror/prosemirror-history/src/README.md
@@ -0,0 +1,23 @@
+An implementation of an undo/redo history for ProseMirror. This
+history is _selective_, meaning it does not just roll back to a
+previous state but can undo some changes while keeping other, later
+changes intact. (This is necessary for collaborative editing, and
+comes up in other situations as well.)
+
+@history
+
+@undo
+
+@redo
+
+@undoNoScroll
+
+@redoNoScroll
+
+@undoDepth
+
+@redoDepth
+
+@closeHistory
+
+@isHistoryTransaction
diff --git a/third_party/js/prosemirror/prosemirror-history/src/history.ts b/third_party/js/prosemirror/prosemirror-history/src/history.ts
@@ -0,0 +1,465 @@
+import RopeSequence from "rope-sequence"
+import {Mapping, Step, StepMap, Transform} from "prosemirror-transform"
+import {Plugin, Command, PluginKey, EditorState, Transaction, SelectionBookmark} from "prosemirror-state"
+
+// ProseMirror's history isn't simply a way to roll back to a previous
+// state, because ProseMirror supports applying changes without adding
+// them to the history (for example during collaboration).
+//
+// To this end, each 'Branch' (one for the undo history and one for
+// the redo history) keeps an array of 'Items', which can optionally
+// hold a step (an actual undoable change), and always hold a position
+// map (which is needed to move changes below them to apply to the
+// current document).
+//
+// An item that has both a step and a selection bookmark is the start
+// of an 'event' — a group of changes that will be undone or redone at
+// once. (It stores only the bookmark, since that way we don't have to
+// provide a document until the selection is actually applied, which
+// is useful when compressing.)
+
+// Used to schedule history compression
+const max_empty_items = 500
+
+class Branch {
+ constructor(readonly items: RopeSequence<Item>, readonly eventCount: number) {}
+
+ // Pop the latest event off the branch's history and apply it
+ // to a document transform.
+ popEvent(state: EditorState, preserveItems: boolean) {
+ if (this.eventCount == 0) return null
+
+ let end = this.items.length
+ for (;; end--) {
+ let next = this.items.get(end - 1)
+ if (next.selection) { --end; break }
+ }
+
+ let remap: Mapping | undefined, mapFrom: number | undefined
+ if (preserveItems) {
+ remap = this.remapping(end, this.items.length)
+ mapFrom = remap.maps.length
+ }
+ let transform = state.tr
+ let selection: SelectionBookmark | undefined, remaining: Branch | undefined
+ let addAfter: Item[] = [], addBefore: Item[] = []
+
+ this.items.forEach((item, i) => {
+ if (!item.step) {
+ if (!remap) {
+ remap = this.remapping(end, i + 1)
+ mapFrom = remap.maps.length
+ }
+ mapFrom!--
+ addBefore.push(item)
+ return
+ }
+
+ if (remap) {
+ addBefore.push(new Item(item.map))
+ let step = item.step.map(remap.slice(mapFrom)), map
+
+ if (step && transform.maybeStep(step).doc) {
+ map = transform.mapping.maps[transform.mapping.maps.length - 1]
+ addAfter.push(new Item(map, undefined, undefined, addAfter.length + addBefore.length))
+ }
+ mapFrom!--
+ if (map) remap.appendMap(map, mapFrom)
+ } else {
+ transform.maybeStep(item.step)
+ }
+
+ if (item.selection) {
+ selection = remap ? item.selection.map(remap.slice(mapFrom)) : item.selection
+ remaining = new Branch(this.items.slice(0, end).append(addBefore.reverse().concat(addAfter)), this.eventCount - 1)
+ return false
+ }
+ }, this.items.length, 0)
+
+ return {remaining: remaining!, transform, selection: selection!}
+ }
+
+ // Create a new branch with the given transform added.
+ addTransform(transform: Transform, selection: SelectionBookmark | undefined,
+ histOptions: Required<HistoryOptions>, preserveItems: boolean) {
+ let newItems = [], eventCount = this.eventCount
+ let oldItems = this.items, lastItem = !preserveItems && oldItems.length ? oldItems.get(oldItems.length - 1) : null
+
+ for (let i = 0; i < transform.steps.length; i++) {
+ let step = transform.steps[i].invert(transform.docs[i])
+ let item = new Item(transform.mapping.maps[i], step, selection), merged
+ if (merged = lastItem && lastItem.merge(item)) {
+ item = merged
+ if (i) newItems.pop()
+ else oldItems = oldItems.slice(0, oldItems.length - 1)
+ }
+ newItems.push(item)
+ if (selection) {
+ eventCount++
+ selection = undefined
+ }
+ if (!preserveItems) lastItem = item
+ }
+ let overflow = eventCount - histOptions.depth
+ if (overflow > DEPTH_OVERFLOW) {
+ oldItems = cutOffEvents(oldItems, overflow)
+ eventCount -= overflow
+ }
+ return new Branch(oldItems.append(newItems), eventCount)
+ }
+
+ remapping(from: number, to: number): Mapping {
+ let maps = new Mapping
+ this.items.forEach((item, i) => {
+ let mirrorPos = item.mirrorOffset != null && i - item.mirrorOffset >= from
+ ? maps.maps.length - item.mirrorOffset : undefined
+ maps.appendMap(item.map, mirrorPos)
+ }, from, to)
+ return maps
+ }
+
+ addMaps(array: readonly StepMap[]) {
+ if (this.eventCount == 0) return this
+ return new Branch(this.items.append(array.map(map => new Item(map))), this.eventCount)
+ }
+
+ // When the collab module receives remote changes, the history has
+ // to know about those, so that it can adjust the steps that were
+ // rebased on top of the remote changes, and include the position
+ // maps for the remote changes in its array of items.
+ rebased(rebasedTransform: Transform, rebasedCount: number) {
+ if (!this.eventCount) return this
+
+ let rebasedItems: Item[] = [], start = Math.max(0, this.items.length - rebasedCount)
+
+ let mapping = rebasedTransform.mapping
+ let newUntil = rebasedTransform.steps.length
+ let eventCount = this.eventCount
+ this.items.forEach(item => { if (item.selection) eventCount-- }, start)
+
+ let iRebased = rebasedCount
+ this.items.forEach(item => {
+ let pos = mapping.getMirror(--iRebased)
+ if (pos == null) return
+ newUntil = Math.min(newUntil, pos)
+ let map = mapping.maps[pos]
+ if (item.step) {
+ let step = rebasedTransform.steps[pos].invert(rebasedTransform.docs[pos])
+ let selection = item.selection && item.selection.map(mapping.slice(iRebased + 1, pos))
+ if (selection) eventCount++
+ rebasedItems.push(new Item(map, step, selection))
+ } else {
+ rebasedItems.push(new Item(map))
+ }
+ }, start)
+
+ let newMaps = []
+ for (let i = rebasedCount; i < newUntil; i++)
+ newMaps.push(new Item(mapping.maps[i]))
+ let items = this.items.slice(0, start).append(newMaps).append(rebasedItems)
+ let branch = new Branch(items, eventCount)
+
+ if (branch.emptyItemCount() > max_empty_items)
+ branch = branch.compress(this.items.length - rebasedItems.length)
+ return branch
+ }
+
+ emptyItemCount() {
+ let count = 0
+ this.items.forEach(item => { if (!item.step) count++ })
+ return count
+ }
+
+ // Compressing a branch means rewriting it to push the air (map-only
+ // items) out. During collaboration, these naturally accumulate
+ // because each remote change adds one. The `upto` argument is used
+ // to ensure that only the items below a given level are compressed,
+ // because `rebased` relies on a clean, untouched set of items in
+ // order to associate old items with rebased steps.
+ compress(upto = this.items.length) {
+ let remap = this.remapping(0, upto), mapFrom = remap.maps.length
+ let items: Item[] = [], events = 0
+ this.items.forEach((item, i) => {
+ if (i >= upto) {
+ items.push(item)
+ if (item.selection) events++
+ } else if (item.step) {
+ let step = item.step.map(remap.slice(mapFrom)), map = step && step.getMap()
+ mapFrom--
+ if (map) remap.appendMap(map, mapFrom)
+ if (step) {
+ let selection = item.selection && item.selection.map(remap.slice(mapFrom))
+ if (selection) events++
+ let newItem = new Item(map!.invert(), step, selection), merged, last = items.length - 1
+ if (merged = items.length && items[last].merge(newItem))
+ items[last] = merged
+ else
+ items.push(newItem)
+ }
+ } else if (item.map) {
+ mapFrom--
+ }
+ }, this.items.length, 0)
+ return new Branch(RopeSequence.from(items.reverse()), events)
+ }
+
+ static empty = new Branch(RopeSequence.empty, 0)
+}
+
+function cutOffEvents(items: RopeSequence<Item>, n: number) {
+ let cutPoint: number | undefined
+ items.forEach((item, i) => {
+ if (item.selection && (n-- == 0)) {
+ cutPoint = i
+ return false
+ }
+ })
+ return items.slice(cutPoint!)
+}
+
+class Item {
+ constructor(
+ // The (forward) step map for this item.
+ readonly map: StepMap,
+ // The inverted step
+ readonly step?: Step,
+ // If this is non-null, this item is the start of a group, and
+ // this selection is the starting selection for the group (the one
+ // that was active before the first step was applied)
+ readonly selection?: SelectionBookmark,
+ // If this item is the inverse of a previous mapping on the stack,
+ // this points at the inverse's offset
+ readonly mirrorOffset?: number
+ ) {}
+
+ merge(other: Item) {
+ if (this.step && other.step && !other.selection) {
+ let step = other.step.merge(this.step)
+ if (step) return new Item(step.getMap().invert(), step, this.selection)
+ }
+ }
+}
+
+// The value of the state field that tracks undo/redo history for that
+// state. Will be stored in the plugin state when the history plugin
+// is active.
+class HistoryState {
+ constructor(
+ readonly done: Branch,
+ readonly undone: Branch,
+ readonly prevRanges: readonly number[] | null,
+ readonly prevTime: number,
+ readonly prevComposition: number
+ ) {}
+}
+
+const DEPTH_OVERFLOW = 20
+
+// Record a transformation in undo history.
+function applyTransaction(history: HistoryState, state: EditorState, tr: Transaction, options: Required<HistoryOptions>) {
+ let historyTr = tr.getMeta(historyKey), rebased
+ if (historyTr) return historyTr.historyState
+
+ if (tr.getMeta(closeHistoryKey)) history = new HistoryState(history.done, history.undone, null, 0, -1)
+
+ let appended = tr.getMeta("appendedTransaction")
+
+ if (tr.steps.length == 0) {
+ return history
+ } else if (appended && appended.getMeta(historyKey)) {
+ if (appended.getMeta(historyKey).redo)
+ return new HistoryState(history.done.addTransform(tr, undefined, options, mustPreserveItems(state)),
+ history.undone, rangesFor(tr.mapping.maps),
+ history.prevTime, history.prevComposition)
+ else
+ return new HistoryState(history.done, history.undone.addTransform(tr, undefined, options, mustPreserveItems(state)),
+ null, history.prevTime, history.prevComposition)
+ } else if (tr.getMeta("addToHistory") !== false && !(appended && appended.getMeta("addToHistory") === false)) {
+ // Group transforms that occur in quick succession into one event.
+ let composition = tr.getMeta("composition")
+ let newGroup = history.prevTime == 0 ||
+ (!appended && history.prevComposition != composition &&
+ (history.prevTime < (tr.time || 0) - options.newGroupDelay || !isAdjacentTo(tr, history.prevRanges!)))
+ let prevRanges = appended ? mapRanges(history.prevRanges!, tr.mapping) : rangesFor(tr.mapping.maps)
+ return new HistoryState(history.done.addTransform(tr, newGroup ? state.selection.getBookmark() : undefined,
+ options, mustPreserveItems(state)),
+ Branch.empty, prevRanges, tr.time, composition == null ? history.prevComposition : composition)
+ } else if (rebased = tr.getMeta("rebased")) {
+ // Used by the collab module to tell the history that some of its
+ // content has been rebased.
+ return new HistoryState(history.done.rebased(tr, rebased),
+ history.undone.rebased(tr, rebased),
+ mapRanges(history.prevRanges!, tr.mapping), history.prevTime, history.prevComposition)
+ } else {
+ return new HistoryState(history.done.addMaps(tr.mapping.maps),
+ history.undone.addMaps(tr.mapping.maps),
+ mapRanges(history.prevRanges!, tr.mapping), history.prevTime, history.prevComposition)
+ }
+}
+
+function isAdjacentTo(transform: Transform, prevRanges: readonly number[]) {
+ if (!prevRanges) return false
+ if (!transform.docChanged) return true
+ let adjacent = false
+ transform.mapping.maps[0].forEach((start, end) => {
+ for (let i = 0; i < prevRanges.length; i += 2)
+ if (start <= prevRanges[i + 1] && end >= prevRanges[i])
+ adjacent = true
+ })
+ return adjacent
+}
+
+function rangesFor(maps: readonly StepMap[]) {
+ let result: number[] = []
+ for (let i = maps.length - 1; i >= 0 && result.length == 0; i--)
+ maps[i].forEach((_from, _to, from, to) => result.push(from, to))
+ return result
+}
+
+function mapRanges(ranges: readonly number[], mapping: Mapping) {
+ if (!ranges) return null
+ let result = []
+ for (let i = 0; i < ranges.length; i += 2) {
+ let from = mapping.map(ranges[i], 1), to = mapping.map(ranges[i + 1], -1)
+ if (from <= to) result.push(from, to)
+ }
+ return result
+}
+
+// Apply the latest event from one branch to the document and shift the event
+// onto the other branch.
+function histTransaction(history: HistoryState, state: EditorState, redo: boolean): Transaction | null {
+ let preserveItems = mustPreserveItems(state)
+ let histOptions = (historyKey.get(state)!.spec as any).config as Required<HistoryOptions>
+ let pop = (redo ? history.undone : history.done).popEvent(state, preserveItems)
+ if (!pop) return null
+
+ let selection = pop.selection!.resolve(pop.transform.doc)
+ let added = (redo ? history.done : history.undone).addTransform(pop.transform, state.selection.getBookmark(),
+ histOptions, preserveItems)
+
+ let newHist = new HistoryState(redo ? added : pop.remaining, redo ? pop.remaining : added, null, 0, -1)
+ return pop.transform.setSelection(selection).setMeta(historyKey, {redo, historyState: newHist})
+}
+
+let cachedPreserveItems = false, cachedPreserveItemsPlugins: readonly Plugin[] | null = null
+// Check whether any plugin in the given state has a
+// `historyPreserveItems` property in its spec, in which case we must
+// preserve steps exactly as they came in, so that they can be
+// rebased.
+function mustPreserveItems(state: EditorState) {
+ let plugins = state.plugins
+ if (cachedPreserveItemsPlugins != plugins) {
+ cachedPreserveItems = false
+ cachedPreserveItemsPlugins = plugins
+ for (let i = 0; i < plugins.length; i++) if ((plugins[i].spec as any).historyPreserveItems) {
+ cachedPreserveItems = true
+ break
+ }
+ }
+ return cachedPreserveItems
+}
+
+/// Set a flag on the given transaction that will prevent further steps
+/// from being appended to an existing history event (so that they
+/// require a separate undo command to undo).
+export function closeHistory(tr: Transaction) {
+ return tr.setMeta(closeHistoryKey, true)
+}
+
+const historyKey = new PluginKey("history")
+const closeHistoryKey = new PluginKey("closeHistory")
+
+interface HistoryOptions {
+ /// The amount of history events that are collected before the
+ /// oldest events are discarded. Defaults to 100.
+ depth?: number
+
+ /// The delay between changes after which a new group should be
+ /// started. Defaults to 500 (milliseconds). Note that when changes
+ /// aren't adjacent, a new group is always started.
+ newGroupDelay?: number
+}
+
+/// Returns a plugin that enables the undo history for an editor. The
+/// plugin will track undo and redo stacks, which can be used with the
+/// [`undo`](#history.undo) and [`redo`](#history.redo) commands.
+///
+/// You can set an `"addToHistory"` [metadata
+/// property](#state.Transaction.setMeta) of `false` on a transaction
+/// to prevent it from being rolled back by undo.
+export function history(config: HistoryOptions = {}): Plugin {
+ config = {depth: config.depth || 100,
+ newGroupDelay: config.newGroupDelay || 500}
+
+ return new Plugin({
+ key: historyKey,
+
+ state: {
+ init() {
+ return new HistoryState(Branch.empty, Branch.empty, null, 0, -1)
+ },
+ apply(tr, hist, state) {
+ return applyTransaction(hist, state, tr, config as Required<HistoryOptions>)
+ }
+ },
+
+ config,
+
+ props: {
+ handleDOMEvents: {
+ beforeinput(view, e: Event) {
+ let inputType = (e as InputEvent).inputType
+ let command = inputType == "historyUndo" ? undo : inputType == "historyRedo" ? redo : null
+ if (!command || !view.editable) return false
+ e.preventDefault()
+ return command(view.state, view.dispatch)
+ }
+ }
+ }
+ })
+}
+
+function buildCommand(redo: boolean, scroll: boolean): Command {
+ return (state, dispatch) => {
+ let hist = historyKey.getState(state)
+ if (!hist || (redo ? hist.undone : hist.done).eventCount == 0) return false
+ if (dispatch) {
+ let tr = histTransaction(hist, state, redo)
+ if (tr) dispatch(scroll ? tr.scrollIntoView() : tr)
+ }
+ return true
+ }
+}
+
+/// A command function that undoes the last change, if any.
+export const undo = buildCommand(false, true)
+
+/// A command function that redoes the last undone change, if any.
+export const redo = buildCommand(true, true)
+
+/// A command function that undoes the last change. Don't scroll the
+/// selection into view.
+export const undoNoScroll = buildCommand(false, false)
+
+/// A command function that redoes the last undone change. Don't
+/// scroll the selection into view.
+export const redoNoScroll = buildCommand(true, false)
+
+/// The amount of undoable events available in a given state.
+export function undoDepth(state: EditorState) {
+ let hist = historyKey.getState(state)
+ return hist ? hist.done.eventCount : 0
+}
+
+/// The amount of redoable events available in a given editor state.
+export function redoDepth(state: EditorState) {
+ let hist = historyKey.getState(state)
+ return hist ? hist.undone.eventCount : 0
+}
+
+/// Returns true if the given transaction was generated by the history
+/// plugin.
+export function isHistoryTransaction(tr: Transaction) {
+ return tr.getMeta(historyKey) != null
+}
diff --git a/third_party/js/prosemirror/prosemirror-keymap/LICENSE b/third_party/js/prosemirror/prosemirror-keymap/LICENSE
@@ -0,0 +1,19 @@
+Copyright (C) 2015-2017 by Marijn Haverbeke <marijn@haverbeke.berlin> and others
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
diff --git a/third_party/js/prosemirror/prosemirror-keymap/moz.yaml b/third_party/js/prosemirror/prosemirror-keymap/moz.yaml
@@ -0,0 +1,63 @@
+# Version of this schema
+schema: 1
+
+bugzilla:
+ # Bugzilla product and component for this directory and subdirectories
+ product: Firefox
+ component: General
+
+# Document the source of externally hosted code
+origin:
+
+ # Short name of the package/library
+ name: prosemirror-keymap
+
+ description: Keymap plugin for ProseMirror
+
+ # Full URL for the package's homepage/etc
+ # Usually different from repository url
+ url: https://prosemirror.net/
+
+ # Human-readable identifier for this version/release
+ # Generally "version NNN", "tag SSS", "bookmark SSS"
+ release: 1.2.3 (2025-05-04T13:17:40+02:00).
+
+ # Revision to pull in
+ # Must be a long or short commit SHA (long preferred) or a tag
+ revision: 1.2.3
+
+ # The package's license, where possible using the mnemonic from
+ # https://spdx.org/licenses/
+ # Multiple licenses can be specified (as a YAML list)
+ # A "LICENSE" file must exist containing the full license text
+ license: MIT
+
+ # If the package's license is specified in a particular file,
+ # this is the name of the file.
+ # optional
+ license-file: LICENSE
+
+# Configuration for the automated vendoring system.
+# optional
+vendoring:
+
+ # Repository URL to vendor from
+ # eg. https://github.com/kinetiknz/nestegg
+ # Any repository host can be specified here, however initially we'll only
+ # support automated vendoring from selected sources.
+ url: https://github.com/ProseMirror/prosemirror-keymap
+
+ # Type of hosting for the upstream repository
+ # Valid values are 'gitlab', 'github', googlesource
+ source-hosting: github
+
+ # Whether to track by commit or tag
+ tracking: tag
+
+ exclude:
+ - "**"
+
+ include:
+ - LICENSE
+ - src/
+ - package.json
diff --git a/third_party/js/prosemirror/prosemirror-keymap/package.json b/third_party/js/prosemirror/prosemirror-keymap/package.json
@@ -0,0 +1,38 @@
+{
+ "name": "prosemirror-keymap",
+ "version": "1.2.3",
+ "description": "Keymap plugin for ProseMirror",
+ "type": "module",
+ "main": "dist/index.cjs",
+ "module": "dist/index.js",
+ "types": "dist/index.d.ts",
+ "exports": {
+ "import": "./dist/index.js",
+ "require": "./dist/index.cjs"
+ },
+ "sideEffects": false,
+ "license": "MIT",
+ "maintainers": [
+ {
+ "name": "Marijn Haverbeke",
+ "email": "marijn@haverbeke.berlin",
+ "web": "http://marijnhaverbeke.nl"
+ }
+ ],
+ "repository": {
+ "type": "git",
+ "url": "git://github.com/prosemirror/prosemirror-keymap.git"
+ },
+ "dependencies": {
+ "w3c-keyname": "^2.2.0",
+ "prosemirror-state": "^1.0.0"
+ },
+ "devDependencies": {
+ "@prosemirror/buildhelper": "^0.1.5",
+ "prosemirror-test-builder": "^1.0.0"
+ },
+ "scripts": {
+ "test": "pm-runtests",
+ "prepare": "pm-buildhelper src/keymap.ts"
+ }
+}
diff --git a/third_party/js/prosemirror/prosemirror-keymap/src/README.md b/third_party/js/prosemirror/prosemirror-keymap/src/README.md
@@ -0,0 +1,5 @@
+A plugin for conveniently defining key bindings.
+
+@keymap
+
+@keydownHandler
diff --git a/third_party/js/prosemirror/prosemirror-keymap/src/keymap.ts b/third_party/js/prosemirror/prosemirror-keymap/src/keymap.ts
@@ -0,0 +1,105 @@
+import {base, keyName} from "w3c-keyname"
+import {Plugin, Command} from "prosemirror-state"
+import {EditorView} from "prosemirror-view"
+
+const mac = typeof navigator != "undefined" && /Mac|iP(hone|[oa]d)/.test(navigator.platform)
+const windows = typeof navigator != "undefined" && /Win/.test(navigator.platform)
+
+function normalizeKeyName(name: string) {
+ let parts = name.split(/-(?!$)/), result = parts[parts.length - 1]
+ if (result == "Space") result = " "
+ let alt, ctrl, shift, meta
+ for (let i = 0; i < parts.length - 1; i++) {
+ let mod = parts[i]
+ if (/^(cmd|meta|m)$/i.test(mod)) meta = true
+ else if (/^a(lt)?$/i.test(mod)) alt = true
+ else if (/^(c|ctrl|control)$/i.test(mod)) ctrl = true
+ else if (/^s(hift)?$/i.test(mod)) shift = true
+ else if (/^mod$/i.test(mod)) { if (mac) meta = true; else ctrl = true }
+ else throw new Error("Unrecognized modifier name: " + mod)
+ }
+ if (alt) result = "Alt-" + result
+ if (ctrl) result = "Ctrl-" + result
+ if (meta) result = "Meta-" + result
+ if (shift) result = "Shift-" + result
+ return result
+}
+
+function normalize(map: {[key: string]: Command}) {
+ let copy: {[key: string]: Command} = Object.create(null)
+ for (let prop in map) copy[normalizeKeyName(prop)] = map[prop]
+ return copy
+}
+
+function modifiers(name: string, event: KeyboardEvent, shift = true) {
+ if (event.altKey) name = "Alt-" + name
+ if (event.ctrlKey) name = "Ctrl-" + name
+ if (event.metaKey) name = "Meta-" + name
+ if (shift && event.shiftKey) name = "Shift-" + name
+ return name
+}
+
+/// Create a keymap plugin for the given set of bindings.
+///
+/// Bindings should map key names to [command](#commands)-style
+/// functions, which will be called with `(EditorState, dispatch,
+/// EditorView)` arguments, and should return true when they've handled
+/// the key. Note that the view argument isn't part of the command
+/// protocol, but can be used as an escape hatch if a binding needs to
+/// directly interact with the UI.
+///
+/// Key names may be strings like `"Shift-Ctrl-Enter"`—a key
+/// identifier prefixed with zero or more modifiers. Key identifiers
+/// are based on the strings that can appear in
+/// [`KeyEvent.key`](https:///developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/key).
+/// Use lowercase letters to refer to letter keys (or uppercase letters
+/// if you want shift to be held). You may use `"Space"` as an alias
+/// for the `" "` name.
+///
+/// Modifiers can be given in any order. `Shift-` (or `s-`), `Alt-` (or
+/// `a-`), `Ctrl-` (or `c-` or `Control-`) and `Cmd-` (or `m-` or
+/// `Meta-`) are recognized. For characters that are created by holding
+/// shift, the `Shift-` prefix is implied, and should not be added
+/// explicitly.
+///
+/// You can use `Mod-` as a shorthand for `Cmd-` on Mac and `Ctrl-` on
+/// other platforms.
+///
+/// You can add multiple keymap plugins to an editor. The order in
+/// which they appear determines their precedence (the ones early in
+/// the array get to dispatch first).
+export function keymap(bindings: {[key: string]: Command}): Plugin {
+ return new Plugin({props: {handleKeyDown: keydownHandler(bindings)}})
+}
+
+/// Given a set of bindings (using the same format as
+/// [`keymap`](#keymap.keymap)), return a [keydown
+/// handler](#view.EditorProps.handleKeyDown) that handles them.
+export function keydownHandler(bindings: {[key: string]: Command}): (view: EditorView, event: KeyboardEvent) => boolean {
+ let map = normalize(bindings)
+ return function(view, event) {
+ let name = keyName(event), baseName, direct = map[modifiers(name, event)]
+ if (direct && direct(view.state, view.dispatch, view)) return true
+ // A character key
+ if (name.length == 1 && name != " ") {
+ if (event.shiftKey) {
+ // In case the name was already modified by shift, try looking
+ // it up without its shift modifier
+ let noShift = map[modifiers(name, event, false)]
+ if (noShift && noShift(view.state, view.dispatch, view)) return true
+ }
+ if ((event.altKey || event.metaKey || event.ctrlKey) &&
+ // Ctrl-Alt may be used for AltGr on Windows
+ !(windows && event.ctrlKey && event.altKey) &&
+ (baseName = base[event.keyCode]) && baseName != name) {
+ // Try falling back to the keyCode when there's a modifier
+ // active or the character produced isn't ASCII, and our table
+ // produces a different name from the the keyCode. See #668,
+ // #1060, #1529.
+ let fromCode = map[modifiers(baseName, event)]
+ if (fromCode && fromCode(view.state, view.dispatch, view)) return true
+ }
+ }
+ return false
+ }
+}
diff --git a/third_party/js/prosemirror/prosemirror-markdown/LICENSE b/third_party/js/prosemirror/prosemirror-markdown/LICENSE
@@ -0,0 +1,19 @@
+Copyright (C) 2015-2017 by Marijn Haverbeke <marijn@haverbeke.berlin> and others
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
diff --git a/third_party/js/prosemirror/prosemirror-markdown/moz.yaml b/third_party/js/prosemirror/prosemirror-markdown/moz.yaml
@@ -0,0 +1,63 @@
+# Version of this schema
+schema: 1
+
+bugzilla:
+ # Bugzilla product and component for this directory and subdirectories
+ product: Firefox
+ component: General
+
+# Document the source of externally hosted code
+origin:
+
+ # Short name of the package/library
+ name: prosemirror-transform
+
+ description: ProseMirror Markdown integration
+
+ # Full URL for the package's homepage/etc
+ # Usually different from repository url
+ url: https://prosemirror.net/
+
+ # Human-readable identifier for this version/release
+ # Generally "version NNN", "tag SSS", "bookmark SSS"
+ release: 1.13.2 (2025-03-18T09:15:25+01:00).
+
+ # Revision to pull in
+ # Must be a long or short commit SHA (long preferred) or a tag
+ revision: 1.13.2
+
+ # The package's license, where possible using the mnemonic from
+ # https://spdx.org/licenses/
+ # Multiple licenses can be specified (as a YAML list)
+ # A "LICENSE" file must exist containing the full license text
+ license: MIT
+
+ # If the package's license is specified in a particular file,
+ # this is the name of the file.
+ # optional
+ license-file: LICENSE
+
+# Configuration for the automated vendoring system.
+# optional
+vendoring:
+
+ # Repository URL to vendor from
+ # eg. https://github.com/kinetiknz/nestegg
+ # Any repository host can be specified here, however initially we'll only
+ # support automated vendoring from selected sources.
+ url: https://github.com/ProseMirror/prosemirror-markdown
+
+ # Type of hosting for the upstream repository
+ # Valid values are 'gitlab', 'github', googlesource
+ source-hosting: github
+
+ # Whether to track by commit or tag
+ tracking: tag
+
+ exclude:
+ - "**"
+
+ include:
+ - LICENSE
+ - src/
+ - package.json
diff --git a/third_party/js/prosemirror/prosemirror-markdown/package.json b/third_party/js/prosemirror/prosemirror-markdown/package.json
@@ -0,0 +1,40 @@
+{
+ "name": "prosemirror-markdown",
+ "version": "1.13.2",
+ "description": "ProseMirror Markdown integration",
+ "type": "module",
+ "main": "dist/index.cjs",
+ "module": "dist/index.js",
+ "types": "dist/index.d.ts",
+ "exports": {
+ "import": "./dist/index.js",
+ "require": "./dist/index.cjs"
+ },
+ "sideEffects": false,
+ "license": "MIT",
+ "maintainers": [
+ {
+ "name": "Marijn Haverbeke",
+ "email": "marijn@haverbeke.berlin",
+ "web": "http://marijnhaverbeke.nl"
+ }
+ ],
+ "repository": {
+ "type": "git",
+ "url": "git://github.com/prosemirror/prosemirror-markdown.git"
+ },
+ "dependencies": {
+ "markdown-it": "^14.0.0",
+ "prosemirror-model": "^1.25.0",
+ "@types/markdown-it": "^14.0.0"
+ },
+ "devDependencies": {
+ "@prosemirror/buildhelper": "^0.1.5",
+ "prosemirror-test-builder": "^1.0.0",
+ "punycode": "^1.4.0"
+ },
+ "scripts": {
+ "test": "pm-runtests",
+ "prepare": "pm-buildhelper src/index.ts"
+ }
+}
diff --git a/third_party/js/prosemirror/prosemirror-markdown/src/README.md b/third_party/js/prosemirror/prosemirror-markdown/src/README.md
@@ -0,0 +1,42 @@
+# prosemirror-markdown
+
+[ [**WEBSITE**](http://prosemirror.net) | [**ISSUES**](https://github.com/prosemirror/prosemirror-markdown/issues) | [**FORUM**](https://discuss.prosemirror.net) | [**GITTER**](https://gitter.im/ProseMirror/prosemirror) ]
+
+This is a (non-core) module for [ProseMirror](http://prosemirror.net).
+ProseMirror is a well-behaved rich semantic content editor based on
+contentEditable, with support for collaborative editing and custom
+document schemas.
+
+This module implements a ProseMirror
+[schema](https://prosemirror.net/docs/guide/#schema) that corresponds to
+the document schema used by [CommonMark](http://commonmark.org/), and
+a parser and serializer to convert between ProseMirror documents in
+that schema and CommonMark/Markdown text.
+
+This code is released under an
+[MIT license](https://github.com/prosemirror/prosemirror/tree/master/LICENSE).
+There's a [forum](http://discuss.prosemirror.net) for general
+discussion and support requests, and the
+[Github bug tracker](https://github.com/prosemirror/prosemirror/issues)
+is the place to report issues.
+
+We aim to be an inclusive, welcoming community. To make that explicit,
+we have a [code of
+conduct](http://contributor-covenant.org/version/1/1/0/) that applies
+to communication around the project.
+
+## Documentation
+
+@schema
+
+@MarkdownParser
+
+@ParseSpec
+
+@defaultMarkdownParser
+
+@MarkdownSerializer
+
+@MarkdownSerializerState
+
+@defaultMarkdownSerializer
diff --git a/third_party/js/prosemirror/prosemirror-markdown/src/from_markdown.ts b/third_party/js/prosemirror/prosemirror-markdown/src/from_markdown.ts
@@ -0,0 +1,272 @@
+// @ts-ignore
+import MarkdownIt from "markdown-it"
+import Token from "markdown-it/lib/token.mjs"
+import {schema} from "./schema"
+import {Mark, MarkType, Node, Attrs, Schema, NodeType} from "prosemirror-model"
+
+function maybeMerge(a: Node, b: Node): Node | undefined {
+ if (a.isText && b.isText && Mark.sameSet(a.marks, b.marks))
+ return (a as any).withText(a.text! + b.text!)
+}
+
+// Object used to track the context of a running parse.
+class MarkdownParseState {
+ stack: {type: NodeType, attrs: Attrs | null, content: Node[], marks: readonly Mark[]}[]
+
+ constructor(
+ readonly schema: Schema,
+ readonly tokenHandlers: {[token: string]: (stat: MarkdownParseState, token: Token, tokens: Token[], i: number) => void}
+ ) {
+ this.stack = [{type: schema.topNodeType, attrs: null, content: [], marks: Mark.none}]
+ }
+
+ top() {
+ return this.stack[this.stack.length - 1]
+ }
+
+ push(elt: Node) {
+ if (this.stack.length) this.top().content.push(elt)
+ }
+
+ // Adds the given text to the current position in the document,
+ // using the current marks as styling.
+ addText(text: string) {
+ if (!text) return
+ let top = this.top(), nodes = top.content, last = nodes[nodes.length - 1]
+ let node = this.schema.text(text, top.marks), merged
+ if (last && (merged = maybeMerge(last, node))) nodes[nodes.length - 1] = merged
+ else nodes.push(node)
+ }
+
+ // Adds the given mark to the set of active marks.
+ openMark(mark: Mark) {
+ let top = this.top()
+ top.marks = mark.addToSet(top.marks)
+ }
+
+ // Removes the given mark from the set of active marks.
+ closeMark(mark: MarkType) {
+ let top = this.top()
+ top.marks = mark.removeFromSet(top.marks)
+ }
+
+ parseTokens(toks: Token[]) {
+ for (let i = 0; i < toks.length; i++) {
+ let tok = toks[i]
+ let handler = this.tokenHandlers[tok.type]
+ if (!handler)
+ throw new Error("Token type `" + tok.type + "` not supported by Markdown parser")
+ handler(this, tok, toks, i)
+ }
+ }
+
+ // Add a node at the current position.
+ addNode(type: NodeType, attrs: Attrs | null, content?: readonly Node[]) {
+ let top = this.top()
+ let node = type.createAndFill(attrs, content, top ? top.marks : [])
+ if (!node) return null
+ this.push(node)
+ return node
+ }
+
+ // Wrap subsequent content in a node of the given type.
+ openNode(type: NodeType, attrs: Attrs | null) {
+ this.stack.push({type: type, attrs: attrs, content: [], marks: Mark.none})
+ }
+
+ // Close and return the node that is currently on top of the stack.
+ closeNode() {
+ let info = this.stack.pop()!
+ return this.addNode(info.type, info.attrs, info.content)
+ }
+}
+
+function attrs(spec: ParseSpec, token: Token, tokens: Token[], i: number) {
+ if (spec.getAttrs) return spec.getAttrs(token, tokens, i)
+ // For backwards compatibility when `attrs` is a Function
+ else if (spec.attrs instanceof Function) return spec.attrs(token)
+ else return spec.attrs
+}
+
+// Code content is represented as a single token with a `content`
+// property in Markdown-it.
+function noCloseToken(spec: ParseSpec, type: string) {
+ return spec.noCloseToken || type == "code_inline" || type == "code_block" || type == "fence"
+}
+
+function withoutTrailingNewline(str: string) {
+ return str[str.length - 1] == "\n" ? str.slice(0, str.length - 1) : str
+}
+
+function noOp() {}
+
+function tokenHandlers(schema: Schema, tokens: {[token: string]: ParseSpec}) {
+ let handlers: {[token: string]: (stat: MarkdownParseState, token: Token, tokens: Token[], i: number) => void} =
+ Object.create(null)
+ for (let type in tokens) {
+ let spec = tokens[type]
+ if (spec.block) {
+ let nodeType = schema.nodeType(spec.block)
+ if (noCloseToken(spec, type)) {
+ handlers[type] = (state, tok, tokens, i) => {
+ state.openNode(nodeType, attrs(spec, tok, tokens, i))
+ state.addText(withoutTrailingNewline(tok.content))
+ state.closeNode()
+ }
+ } else {
+ handlers[type + "_open"] = (state, tok, tokens, i) => state.openNode(nodeType, attrs(spec, tok, tokens, i))
+ handlers[type + "_close"] = state => state.closeNode()
+ }
+ } else if (spec.node) {
+ let nodeType = schema.nodeType(spec.node)
+ handlers[type] = (state, tok, tokens, i) => state.addNode(nodeType, attrs(spec, tok, tokens, i))
+ } else if (spec.mark) {
+ let markType = schema.marks[spec.mark]
+ if (noCloseToken(spec, type)) {
+ handlers[type] = (state, tok, tokens, i) => {
+ state.openMark(markType.create(attrs(spec, tok, tokens, i)))
+ state.addText(withoutTrailingNewline(tok.content))
+ state.closeMark(markType)
+ }
+ } else {
+ handlers[type + "_open"] = (state, tok, tokens, i) => state.openMark(markType.create(attrs(spec, tok, tokens, i)))
+ handlers[type + "_close"] = state => state.closeMark(markType)
+ }
+ } else if (spec.ignore) {
+ if (noCloseToken(spec, type)) {
+ handlers[type] = noOp
+ } else {
+ handlers[type + "_open"] = noOp
+ handlers[type + "_close"] = noOp
+ }
+ } else {
+ throw new RangeError("Unrecognized parsing spec " + JSON.stringify(spec))
+ }
+ }
+
+ handlers.text = (state, tok) => state.addText(tok.content)
+ handlers.inline = (state, tok) => state.parseTokens(tok.children!)
+ handlers.softbreak = handlers.softbreak || (state => state.addText(" "))
+
+ return handlers
+}
+
+/// Object type used to specify how Markdown tokens should be parsed.
+export interface ParseSpec {
+ /// This token maps to a single node, whose type can be looked up
+ /// in the schema under the given name. Exactly one of `node`,
+ /// `block`, or `mark` must be set.
+ node?: string
+
+ /// This token (unless `noCloseToken` is true) comes in `_open`
+ /// and `_close` variants (which are appended to the base token
+ /// name provides a the object property), and wraps a block of
+ /// content. The block should be wrapped in a node of the type
+ /// named to by the property's value. If the token does not have
+ /// `_open` or `_close`, use the `noCloseToken` option.
+ block?: string
+
+ /// This token (again, unless `noCloseToken` is true) also comes
+ /// in `_open` and `_close` variants, but should add a mark
+ /// (named by the value) to its content, rather than wrapping it
+ /// in a node.
+ mark?: string
+
+ /// Attributes for the node or mark. When `getAttrs` is provided,
+ /// it takes precedence.
+ attrs?: Attrs | null
+
+ /// A function used to compute the attributes for the node or mark
+ /// that takes a [markdown-it
+ /// token](https://markdown-it.github.io/markdown-it/#Token) and
+ /// returns an attribute object.
+ getAttrs?: (token: Token, tokenStream: Token[], index: number) => Attrs | null
+
+ /// Indicates that the [markdown-it
+ /// token](https://markdown-it.github.io/markdown-it/#Token) has
+ /// no `_open` or `_close` for the nodes. This defaults to `true`
+ /// for `code_inline`, `code_block` and `fence`.
+ noCloseToken?: boolean
+
+ /// When true, ignore content for the matched token.
+ ignore?: boolean
+}
+
+/// A configuration of a Markdown parser. Such a parser uses
+/// [markdown-it](https://github.com/markdown-it/markdown-it) to
+/// tokenize a file, and then runs the custom rules it is given over
+/// the tokens to create a ProseMirror document tree.
+export class MarkdownParser {
+ /// @internal
+ tokenHandlers: {[token: string]: (stat: MarkdownParseState, token: Token, tokens: Token[], i: number) => void}
+
+ /// Create a parser with the given configuration. You can configure
+ /// the markdown-it parser to parse the dialect you want, and provide
+ /// a description of the ProseMirror entities those tokens map to in
+ /// the `tokens` object, which maps token names to descriptions of
+ /// what to do with them. Such a description is an object, and may
+ /// have the following properties:
+ constructor(
+ /// The parser's document schema.
+ readonly schema: Schema,
+ /// This parser's markdown-it tokenizer.
+ readonly tokenizer: MarkdownIt,
+ /// The value of the `tokens` object used to construct this
+ /// parser. Can be useful to copy and modify to base other parsers
+ /// on.
+ readonly tokens: {[name: string]: ParseSpec}
+ ) {
+ this.tokenHandlers = tokenHandlers(schema, tokens)
+ }
+
+ /// Parse a string as [CommonMark](http://commonmark.org/) markup,
+ /// and create a ProseMirror document as prescribed by this parser's
+ /// rules.
+ ///
+ /// The second argument, when given, is passed through to the
+ /// [Markdown
+ /// parser](https://markdown-it.github.io/markdown-it/#MarkdownIt.parse).
+ parse(text: string, markdownEnv: Object = {}) {
+ let state = new MarkdownParseState(this.schema, this.tokenHandlers), doc
+ state.parseTokens(this.tokenizer.parse(text, markdownEnv))
+ do { doc = state.closeNode() } while (state.stack.length)
+ return doc || this.schema.topNodeType.createAndFill()!
+ }
+}
+
+function listIsTight(tokens: readonly Token[], i: number) {
+ while (++i < tokens.length)
+ if (tokens[i].type != "list_item_open") return tokens[i].hidden
+ return false
+}
+
+/// A parser parsing unextended [CommonMark](http://commonmark.org/),
+/// without inline HTML, and producing a document in the basic schema.
+export const defaultMarkdownParser = new MarkdownParser(schema, MarkdownIt("commonmark", {html: false}), {
+ blockquote: {block: "blockquote"},
+ paragraph: {block: "paragraph"},
+ list_item: {block: "list_item"},
+ bullet_list: {block: "bullet_list", getAttrs: (_, tokens, i) => ({tight: listIsTight(tokens, i)})},
+ ordered_list: {block: "ordered_list", getAttrs: (tok, tokens, i) => ({
+ order: +tok.attrGet("start")! || 1,
+ tight: listIsTight(tokens, i)
+ })},
+ heading: {block: "heading", getAttrs: tok => ({level: +tok.tag.slice(1)})},
+ code_block: {block: "code_block", noCloseToken: true},
+ fence: {block: "code_block", getAttrs: tok => ({params: tok.info || ""}), noCloseToken: true},
+ hr: {node: "horizontal_rule"},
+ image: {node: "image", getAttrs: tok => ({
+ src: tok.attrGet("src"),
+ title: tok.attrGet("title") || null,
+ alt: tok.children![0] && tok.children![0].content || null
+ })},
+ hardbreak: {node: "hard_break"},
+
+ em: {mark: "em"},
+ strong: {mark: "strong"},
+ link: {mark: "link", getAttrs: tok => ({
+ href: tok.attrGet("href"),
+ title: tok.attrGet("title") || null
+ })},
+ code_inline: {mark: "code", noCloseToken: true}
+})
diff --git a/third_party/js/prosemirror/prosemirror-markdown/src/index.ts b/third_party/js/prosemirror/prosemirror-markdown/src/index.ts
@@ -0,0 +1,5 @@
+// Defines a parser and serializer for [CommonMark](http://commonmark.org/) text.
+
+export {schema} from "./schema"
+export {defaultMarkdownParser, MarkdownParser, ParseSpec} from "./from_markdown"
+export {MarkdownSerializer, defaultMarkdownSerializer, MarkdownSerializerState} from "./to_markdown"
diff --git a/third_party/js/prosemirror/prosemirror-markdown/src/schema.ts b/third_party/js/prosemirror/prosemirror-markdown/src/schema.ts
@@ -0,0 +1,156 @@
+import {Schema, MarkSpec} from "prosemirror-model"
+
+/// Document schema for the data model used by CommonMark.
+export const schema = new Schema({
+ nodes: {
+ doc: {
+ content: "block+"
+ },
+
+ paragraph: {
+ content: "inline*",
+ group: "block",
+ parseDOM: [{tag: "p"}],
+ toDOM() { return ["p", 0] }
+ },
+
+ blockquote: {
+ content: "block+",
+ group: "block",
+ parseDOM: [{tag: "blockquote"}],
+ toDOM() { return ["blockquote", 0] }
+ },
+
+ horizontal_rule: {
+ group: "block",
+ parseDOM: [{tag: "hr"}],
+ toDOM() { return ["div", ["hr"]] }
+ },
+
+ heading: {
+ attrs: {level: {default: 1}},
+ content: "(text | image)*",
+ group: "block",
+ defining: true,
+ parseDOM: [{tag: "h1", attrs: {level: 1}},
+ {tag: "h2", attrs: {level: 2}},
+ {tag: "h3", attrs: {level: 3}},
+ {tag: "h4", attrs: {level: 4}},
+ {tag: "h5", attrs: {level: 5}},
+ {tag: "h6", attrs: {level: 6}}],
+ toDOM(node) { return ["h" + node.attrs.level, 0] }
+ },
+
+ code_block: {
+ content: "text*",
+ group: "block",
+ code: true,
+ defining: true,
+ marks: "",
+ attrs: {params: {default: ""}},
+ parseDOM: [{tag: "pre", preserveWhitespace: "full", getAttrs: node => (
+ {params: (node as HTMLElement).getAttribute("data-params") || ""}
+ )}],
+ toDOM(node) { return ["pre", node.attrs.params ? {"data-params": node.attrs.params} : {}, ["code", 0]] }
+ },
+
+ ordered_list: {
+ content: "list_item+",
+ group: "block",
+ attrs: {order: {default: 1}, tight: {default: false}},
+ parseDOM: [{tag: "ol", getAttrs(dom) {
+ return {order: (dom as HTMLElement).hasAttribute("start") ? +(dom as HTMLElement).getAttribute("start")! : 1,
+ tight: (dom as HTMLElement).hasAttribute("data-tight")}
+ }}],
+ toDOM(node) {
+ return ["ol", {start: node.attrs.order == 1 ? null : node.attrs.order,
+ "data-tight": node.attrs.tight ? "true" : null}, 0]
+ }
+ },
+
+ bullet_list: {
+ content: "list_item+",
+ group: "block",
+ attrs: {tight: {default: false}},
+ parseDOM: [{tag: "ul", getAttrs: dom => ({tight: (dom as HTMLElement).hasAttribute("data-tight")})}],
+ toDOM(node) { return ["ul", {"data-tight": node.attrs.tight ? "true" : null}, 0] }
+ },
+
+ list_item: {
+ content: "block+",
+ defining: true,
+ parseDOM: [{tag: "li"}],
+ toDOM() { return ["li", 0] }
+ },
+
+ text: {
+ group: "inline"
+ },
+
+ image: {
+ inline: true,
+ attrs: {
+ src: {},
+ alt: {default: null},
+ title: {default: null}
+ },
+ group: "inline",
+ draggable: true,
+ parseDOM: [{tag: "img[src]", getAttrs(dom) {
+ return {
+ src: (dom as HTMLElement).getAttribute("src"),
+ title: (dom as HTMLElement).getAttribute("title"),
+ alt: (dom as HTMLElement).getAttribute("alt")
+ }
+ }}],
+ toDOM(node) { return ["img", node.attrs] }
+ },
+
+ hard_break: {
+ inline: true,
+ group: "inline",
+ selectable: false,
+ parseDOM: [{tag: "br"}],
+ toDOM() { return ["br"] }
+ }
+ },
+
+ marks: {
+ em: {
+ parseDOM: [
+ {tag: "i"}, {tag: "em"},
+ {style: "font-style=italic"},
+ {style: "font-style=normal", clearMark: m => m.type.name == "em"}
+ ],
+ toDOM() { return ["em"] }
+ },
+
+ strong: {
+ parseDOM: [
+ {tag: "strong"},
+ {tag: "b", getAttrs: node => node.style.fontWeight != "normal" && null},
+ {style: "font-weight=400", clearMark: m => m.type.name == "strong"},
+ {style: "font-weight", getAttrs: value => /^(bold(er)?|[5-9]\d{2,})$/.test(value) && null}
+ ],
+ toDOM() { return ["strong"] }
+ } as MarkSpec,
+
+ link: {
+ attrs: {
+ href: {},
+ title: {default: null}
+ },
+ inclusive: false,
+ parseDOM: [{tag: "a[href]", getAttrs(dom) {
+ return {href: (dom as HTMLElement).getAttribute("href"), title: dom.getAttribute("title")}
+ }}],
+ toDOM(node) { return ["a", node.attrs] }
+ },
+
+ code: {
+ code: true,
+ parseDOM: [{tag: "code"}],
+ toDOM() { return ["code"] }
+ }
+ }
+})
diff --git a/third_party/js/prosemirror/prosemirror-markdown/src/to_markdown.ts b/third_party/js/prosemirror/prosemirror-markdown/src/to_markdown.ts
@@ -0,0 +1,474 @@
+import {Node, Mark} from "prosemirror-model"
+
+type MarkSerializerSpec = {
+ /// The string that should appear before a piece of content marked
+ /// by this mark, either directly or as a function that returns an
+ /// appropriate string.
+ open: string | ((state: MarkdownSerializerState, mark: Mark, parent: Node, index: number) => string),
+ /// The string that should appear after a piece of content marked by
+ /// this mark.
+ close: string | ((state: MarkdownSerializerState, mark: Mark, parent: Node, index: number) => string),
+ /// When `true`, this indicates that the order in which the mark's
+ /// opening and closing syntax appears relative to other mixable
+ /// marks can be varied. (For example, you can say `**a *b***` and
+ /// `*a **b***`, but not `` `a *b*` ``.)
+ mixable?: boolean,
+ /// When enabled, causes the serializer to move enclosing whitespace
+ /// from inside the marks to outside the marks. This is necessary
+ /// for emphasis marks as CommonMark does not permit enclosing
+ /// whitespace inside emphasis marks, see:
+ /// http:///spec.commonmark.org/0.26/#example-330
+ expelEnclosingWhitespace?: boolean,
+ /// Can be set to `false` to disable character escaping in a mark. A
+ /// non-escaping mark has to have the highest precedence (must
+ /// always be the innermost mark).
+ escape?: boolean
+}
+
+const blankMark: MarkSerializerSpec = {open: "", close: "", mixable: true}
+
+/// A specification for serializing a ProseMirror document as
+/// Markdown/CommonMark text.
+export class MarkdownSerializer {
+ /// Construct a serializer with the given configuration. The `nodes`
+ /// object should map node names in a given schema to function that
+ /// take a serializer state and such a node, and serialize the node.
+ constructor(
+ /// The node serializer functions for this serializer.
+ readonly nodes: {[node: string]: (state: MarkdownSerializerState, node: Node, parent: Node, index: number) => void},
+ /// The mark serializer info.
+ readonly marks: {[mark: string]: MarkSerializerSpec},
+ readonly options: {
+ /// Extra characters can be added for escaping. This is passed
+ /// directly to String.replace(), and the matching characters are
+ /// preceded by a backslash.
+ escapeExtraCharacters?: RegExp,
+ /// Specify the node name of hard breaks.
+ /// Defaults to "hard_break"
+ hardBreakNodeName?: string,
+ /// By default, the serializer raises an error when it finds a
+ /// node or mark type for which no serializer is defined. Set
+ /// this to `false` to make it just ignore such elements,
+ /// rendering only their content.
+ strict?: boolean
+ } = {}
+ ) {}
+
+ /// Serialize the content of the given node to
+ /// [CommonMark](http://commonmark.org/).
+ serialize(content: Node, options: {
+ /// Whether to render lists in a tight style. This can be overridden
+ /// on a node level by specifying a tight attribute on the node.
+ /// Defaults to false.
+ tightLists?: boolean
+ } = {}) {
+ options = Object.assign({}, this.options, options)
+ let state = new MarkdownSerializerState(this.nodes, this.marks, options)
+ state.renderContent(content)
+ return state.out
+ }
+}
+
+/// A serializer for the [basic schema](#schema).
+export const defaultMarkdownSerializer = new MarkdownSerializer({
+ blockquote(state, node) {
+ state.wrapBlock("> ", null, node, () => state.renderContent(node))
+ },
+ code_block(state, node) {
+ // Make sure the front matter fences are longer than any dash sequence within it
+ const backticks = node.textContent.match(/`{3,}/gm)
+ const fence = backticks ? (backticks.sort().slice(-1)[0] + "`") : "```"
+
+ state.write(fence + (node.attrs.params || "") + "\n")
+ state.text(node.textContent, false)
+ // Add a newline to the current content before adding closing marker
+ state.write("\n")
+ state.write(fence)
+ state.closeBlock(node)
+ },
+ heading(state, node) {
+ state.write(state.repeat("#", node.attrs.level) + " ")
+ state.renderInline(node, false)
+ state.closeBlock(node)
+ },
+ horizontal_rule(state, node) {
+ state.write(node.attrs.markup || "---")
+ state.closeBlock(node)
+ },
+ bullet_list(state, node) {
+ state.renderList(node, " ", () => (node.attrs.bullet || "*") + " ")
+ },
+ ordered_list(state, node) {
+ let start = node.attrs.order || 1
+ let maxW = String(start + node.childCount - 1).length
+ let space = state.repeat(" ", maxW + 2)
+ state.renderList(node, space, i => {
+ let nStr = String(start + i)
+ return state.repeat(" ", maxW - nStr.length) + nStr + ". "
+ })
+ },
+ list_item(state, node) {
+ state.renderContent(node)
+ },
+ paragraph(state, node) {
+ state.renderInline(node)
+ state.closeBlock(node)
+ },
+
+ image(state, node) {
+ state.write("]/g, "\\$&") +
+ (node.attrs.title ? ' "' + node.attrs.title.replace(/"/g, '\\"') + '"' : "") + ")")
+ },
+ hard_break(state, node, parent, index) {
+ for (let i = index + 1; i < parent.childCount; i++)
+ if (parent.child(i).type != node.type) {
+ state.write("\\\n")
+ return
+ }
+ },
+ text(state, node) {
+ state.text(node.text!, !state.inAutolink)
+ }
+}, {
+ em: {open: "*", close: "*", mixable: true, expelEnclosingWhitespace: true},
+ strong: {open: "**", close: "**", mixable: true, expelEnclosingWhitespace: true},
+ link: {
+ open(state, mark, parent, index) {
+ state.inAutolink = isPlainURL(mark, parent, index)
+ return state.inAutolink ? "<" : "["
+ },
+ close(state, mark, parent, index) {
+ let {inAutolink} = state
+ state.inAutolink = undefined
+ return inAutolink ? ">"
+ : "](" + mark.attrs.href.replace(/[\(\)"]/g, "\\$&") + (mark.attrs.title ? ` "${mark.attrs.title.replace(/"/g, '\\"')}"` : "") + ")"
+ },
+ mixable: true
+ },
+ code: {open(_state, _mark, parent, index) { return backticksFor(parent.child(index), -1) },
+ close(_state, _mark, parent, index) { return backticksFor(parent.child(index - 1), 1) },
+ escape: false}
+})
+
+function backticksFor(node: Node, side: number) {
+ let ticks = /`+/g, m, len = 0
+ if (node.isText) while (m = ticks.exec(node.text!)) len = Math.max(len, m[0].length)
+ let result = len > 0 && side > 0 ? " `" : "`"
+ for (let i = 0; i < len; i++) result += "`"
+ if (len > 0 && side < 0) result += " "
+ return result
+}
+
+function isPlainURL(link: Mark, parent: Node, index: number) {
+ if (link.attrs.title || !/^\w+:/.test(link.attrs.href)) return false
+ let content = parent.child(index)
+ if (!content.isText || content.text != link.attrs.href || content.marks[content.marks.length - 1] != link) return false
+ return index == parent.childCount - 1 || !link.isInSet(parent.child(index + 1).marks)
+}
+
+/// This is an object used to track state and expose
+/// methods related to markdown serialization. Instances are passed to
+/// node and mark serialization methods (see `toMarkdown`).
+export class MarkdownSerializerState {
+ /// @internal
+ delim: string = ""
+ /// @internal
+ out: string = ""
+ /// @internal
+ closed: Node | null = null
+ /// @internal
+ inAutolink: boolean | undefined = undefined
+ /// @internal
+ atBlockStart: boolean = false
+ /// @internal
+ inTightList: boolean = false
+
+ /// @internal
+ constructor(
+ /// @internal
+ readonly nodes: {[node: string]: (state: MarkdownSerializerState, node: Node, parent: Node, index: number) => void},
+ /// @internal
+ readonly marks: {[mark: string]: MarkSerializerSpec},
+ /// The options passed to the serializer.
+ readonly options: {tightLists?: boolean, escapeExtraCharacters?: RegExp, hardBreakNodeName?: string, strict?: boolean}
+ ) {
+ if (typeof this.options.tightLists == "undefined")
+ this.options.tightLists = false
+ if (typeof this.options.hardBreakNodeName == "undefined")
+ this.options.hardBreakNodeName = "hard_break"
+ }
+
+ /// @internal
+ flushClose(size: number = 2) {
+ if (this.closed) {
+ if (!this.atBlank()) this.out += "\n"
+ if (size > 1) {
+ let delimMin = this.delim
+ let trim = /\s+$/.exec(delimMin)
+ if (trim) delimMin = delimMin.slice(0, delimMin.length - trim[0].length)
+ for (let i = 1; i < size; i++)
+ this.out += delimMin + "\n"
+ }
+ this.closed = null
+ }
+ }
+
+ /// @internal
+ getMark(name: string) {
+ let info = this.marks[name]
+ if (!info) {
+ if (this.options.strict !== false)
+ throw new Error(`Mark type \`${name}\` not supported by Markdown renderer`)
+ info = blankMark
+ }
+ return info
+ }
+
+ /// Render a block, prefixing each line with `delim`, and the first
+ /// line in `firstDelim`. `node` should be the node that is closed at
+ /// the end of the block, and `f` is a function that renders the
+ /// content of the block.
+ wrapBlock(delim: string, firstDelim: string | null, node: Node, f: () => void) {
+ let old = this.delim
+ this.write(firstDelim != null ? firstDelim : delim)
+ this.delim += delim
+ f()
+ this.delim = old
+ this.closeBlock(node)
+ }
+
+ /// @internal
+ atBlank() {
+ return /(^|\n)$/.test(this.out)
+ }
+
+ /// Ensure the current content ends with a newline.
+ ensureNewLine() {
+ if (!this.atBlank()) this.out += "\n"
+ }
+
+ /// Prepare the state for writing output (closing closed paragraphs,
+ /// adding delimiters, and so on), and then optionally add content
+ /// (unescaped) to the output.
+ write(content?: string) {
+ this.flushClose()
+ if (this.delim && this.atBlank())
+ this.out += this.delim
+ if (content) this.out += content
+ }
+
+ /// Close the block for the given node.
+ closeBlock(node: Node) {
+ this.closed = node
+ }
+
+ /// Add the given text to the document. When escape is not `false`,
+ /// it will be escaped.
+ text(text: string, escape = true) {
+ let lines = text.split("\n")
+ for (let i = 0; i < lines.length; i++) {
+ this.write()
+ // Escape exclamation marks in front of links
+ if (!escape && lines[i][0] == "[" && /(^|[^\\])\!$/.test(this.out))
+ this.out = this.out.slice(0, this.out.length - 1) + "\\!"
+ this.out += escape ? this.esc(lines[i], this.atBlockStart) : lines[i]
+ if (i != lines.length - 1) this.out += "\n"
+ }
+ }
+
+ /// Render the given node as a block.
+ render(node: Node, parent: Node, index: number) {
+ if (this.nodes[node.type.name]) {
+ this.nodes[node.type.name](this, node, parent, index)
+ } else {
+ if (this.options.strict !== false) {
+ throw new Error("Token type `" + node.type.name + "` not supported by Markdown renderer")
+ } else if (!node.type.isLeaf) {
+ if (node.type.inlineContent) this.renderInline(node)
+ else this.renderContent(node)
+ if (node.isBlock) this.closeBlock(node)
+ }
+ }
+ }
+
+ /// Render the contents of `parent` as block nodes.
+ renderContent(parent: Node) {
+ parent.forEach((node, _, i) => this.render(node, parent, i))
+ }
+
+ /// Render the contents of `parent` as inline content.
+ renderInline(parent: Node, fromBlockStart = true) {
+ this.atBlockStart = fromBlockStart
+ let active: Mark[] = [], trailing = ""
+ let progress = (node: Node | null, offset: number, index: number) => {
+ let marks = node ? node.marks : []
+
+ // Remove marks from `hard_break` that are the last node inside
+ // that mark to prevent parser edge cases with new lines just
+ // before closing marks.
+ if (node && node.type.name === this.options.hardBreakNodeName)
+ marks = marks.filter(m => {
+ if (index + 1 == parent.childCount) return false
+ let next = parent.child(index + 1)
+ return m.isInSet(next.marks) && (!next.isText || /\S/.test(next.text!))
+ })
+
+ let leading = trailing
+ trailing = ""
+ // If whitespace has to be expelled from the node, adjust
+ // leading and trailing accordingly.
+ if (node && node.isText && marks.some(mark => {
+ let info = this.getMark(mark.type.name)
+ return info && info.expelEnclosingWhitespace && !mark.isInSet(active)
+ })) {
+ let [_, lead, rest] = /^(\s*)(.*)$/m.exec(node.text!)!
+ if (lead) {
+ leading += lead
+ node = rest ? (node as any).withText(rest) : null
+ if (!node) marks = active
+ }
+ }
+ if (node && node.isText && marks.some(mark => {
+ let info = this.getMark(mark.type.name)
+ return info && info.expelEnclosingWhitespace &&
+ (index == parent.childCount - 1 || !mark.isInSet(parent.child(index + 1).marks))
+ })) {
+ let [_, rest, trail] = /^(.*?)(\s*)$/m.exec(node.text!)!
+ if (trail) {
+ trailing = trail
+ node = rest ? (node as any).withText(rest) : null
+ if (!node) marks = active
+ }
+ }
+ let inner = marks.length ? marks[marks.length - 1] : null
+ let noEsc = inner && this.getMark(inner.type.name).escape === false
+ let len = marks.length - (noEsc ? 1 : 0)
+
+ // Try to reorder 'mixable' marks, such as em and strong, which
+ // in Markdown may be opened and closed in different order, so
+ // that order of the marks for the token matches the order in
+ // active.
+ outer: for (let i = 0; i < len; i++) {
+ let mark = marks[i]
+ if (!this.getMark(mark.type.name).mixable) break
+ for (let j = 0; j < active.length; j++) {
+ let other = active[j]
+ if (!this.getMark(other.type.name).mixable) break
+ if (mark.eq(other)) {
+ if (i > j)
+ marks = marks.slice(0, j).concat(mark).concat(marks.slice(j, i)).concat(marks.slice(i + 1, len))
+ else if (j > i)
+ marks = marks.slice(0, i).concat(marks.slice(i + 1, j)).concat(mark).concat(marks.slice(j, len))
+ continue outer
+ }
+ }
+ }
+
+ // Find the prefix of the mark set that didn't change
+ let keep = 0
+ while (keep < Math.min(active.length, len) && marks[keep].eq(active[keep])) ++keep
+
+ // Close the marks that need to be closed
+ while (keep < active.length)
+ this.text(this.markString(active.pop()!, false, parent, index), false)
+
+ // Output any previously expelled trailing whitespace outside the marks
+ if (leading) this.text(leading)
+
+ // Open the marks that need to be opened
+ if (node) {
+ while (active.length < len) {
+ let add = marks[active.length]
+ active.push(add)
+ this.text(this.markString(add, true, parent, index), false)
+ this.atBlockStart = false
+ }
+
+ // Render the node. Special case code marks, since their content
+ // may not be escaped.
+ if (noEsc && node.isText)
+ this.text(this.markString(inner!, true, parent, index) + node.text +
+ this.markString(inner!, false, parent, index + 1), false)
+ else
+ this.render(node, parent, index)
+ this.atBlockStart = false
+ }
+
+ // After the first non-empty text node is rendered, the end of output
+ // is no longer at block start.
+ //
+ // FIXME: If a non-text node writes something to the output for this
+ // block, the end of output is also no longer at block start. But how
+ // can we detect that?
+ if (node?.isText && node.nodeSize > 0) {
+ this.atBlockStart = false
+ }
+ }
+ parent.forEach(progress)
+ progress(null, 0, parent.childCount)
+ this.atBlockStart = false
+ }
+
+ /// Render a node's content as a list. `delim` should be the extra
+ /// indentation added to all lines except the first in an item,
+ /// `firstDelim` is a function going from an item index to a
+ /// delimiter for the first line of the item.
+ renderList(node: Node, delim: string, firstDelim: (index: number) => string) {
+ if (this.closed && this.closed.type == node.type)
+ this.flushClose(3)
+ else if (this.inTightList)
+ this.flushClose(1)
+
+ let isTight = typeof node.attrs.tight != "undefined" ? node.attrs.tight : this.options.tightLists
+ let prevTight = this.inTightList
+ this.inTightList = isTight
+ node.forEach((child, _, i) => {
+ if (i && isTight) this.flushClose(1)
+ this.wrapBlock(delim, firstDelim(i), node, () => this.render(child, node, i))
+ })
+ this.inTightList = prevTight
+ }
+
+ /// Escape the given string so that it can safely appear in Markdown
+ /// content. If `startOfLine` is true, also escape characters that
+ /// have special meaning only at the start of the line.
+ esc(str: string, startOfLine = false) {
+ str = str.replace(
+ /[`*\\~\[\]_]/g,
+ (m, i) => m == "_" && i > 0 && i + 1 < str.length && str[i-1].match(/\w/) && str[i+1].match(/\w/) ? m : "\\" + m
+ )
+ if (startOfLine) str = str.replace(/^(\+[ ]|[\-*>])/, "\\$&").replace(/^(\s*)(#{1,6})(\s|$)/, '$1\\$2$3').replace(/^(\s*\d+)\.\s/, "$1\\. ")
+ if (this.options.escapeExtraCharacters) str = str.replace(this.options.escapeExtraCharacters, "\\$&")
+ return str
+ }
+
+ /// @internal
+ quote(str: string) {
+ let wrap = str.indexOf('"') == -1 ? '""' : str.indexOf("'") == -1 ? "''" : "()"
+ return wrap[0] + str + wrap[1]
+ }
+
+ /// Repeat the given string `n` times.
+ repeat(str: string, n: number) {
+ let out = ""
+ for (let i = 0; i < n; i++) out += str
+ return out
+ }
+
+ /// Get the markdown string for a given opening or closing mark.
+ markString(mark: Mark, open: boolean, parent: Node, index: number) {
+ let info = this.getMark(mark.type.name)
+ let value = open ? info.open : info.close
+ return typeof value == "string" ? value : value(this, mark, parent, index)
+ }
+
+ /// Get leading and trailing whitespace from a string. Values of
+ /// leading or trailing property of the return object will be undefined
+ /// if there is no match.
+ getEnclosingWhitespace(text: string): {leading?: string, trailing?: string} {
+ return {
+ leading: (text.match(/^(\s+)/) || [undefined])[0],
+ trailing: (text.match(/(\s+)$/) || [undefined])[0]
+ }
+ }
+}
diff --git a/third_party/js/prosemirror/prosemirror-model/LICENSE b/third_party/js/prosemirror/prosemirror-model/LICENSE
@@ -0,0 +1,19 @@
+Copyright (C) 2015-2017 by Marijn Haverbeke <marijn@haverbeke.berlin> and others
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
diff --git a/third_party/js/prosemirror/prosemirror-model/moz.yaml b/third_party/js/prosemirror/prosemirror-model/moz.yaml
@@ -0,0 +1,63 @@
+# Version of this schema
+schema: 1
+
+bugzilla:
+ # Bugzilla product and component for this directory and subdirectories
+ product: Firefox
+ component: General
+
+# Document the source of externally hosted code
+origin:
+
+ # Short name of the package/library
+ name: prosemirror-model
+
+ description: ProseMirror's document model
+
+ # Full URL for the package's homepage/etc
+ # Usually different from repository url
+ url: https://prosemirror.net/
+
+ # Human-readable identifier for this version/release
+ # Generally "version NNN", "tag SSS", "bookmark SSS"
+ release: 1.25.4 (2025-10-21T10:30:22+02:00).
+
+ # Revision to pull in
+ # Must be a long or short commit SHA (long preferred) or a tag
+ revision: 1.25.4
+
+ # The package's license, where possible using the mnemonic from
+ # https://spdx.org/licenses/
+ # Multiple licenses can be specified (as a YAML list)
+ # A "LICENSE" file must exist containing the full license text
+ license: MIT
+
+ # If the package's license is specified in a particular file,
+ # this is the name of the file.
+ # optional
+ license-file: LICENSE
+
+# Configuration for the automated vendoring system.
+# optional
+vendoring:
+
+ # Repository URL to vendor from
+ # eg. https://github.com/kinetiknz/nestegg
+ # Any repository host can be specified here, however initially we'll only
+ # support automated vendoring from selected sources.
+ url: https://github.com/ProseMirror/prosemirror-model
+
+ # Type of hosting for the upstream repository
+ # Valid values are 'gitlab', 'github', googlesource
+ source-hosting: github
+
+ # Whether to track by commit or tag
+ tracking: tag
+
+ exclude:
+ - "**"
+
+ include:
+ - LICENSE
+ - src/
+ - package.json
diff --git a/third_party/js/prosemirror/prosemirror-model/package.json b/third_party/js/prosemirror/prosemirror-model/package.json
@@ -0,0 +1,38 @@
+{
+ "name": "prosemirror-model",
+ "version": "1.25.4",
+ "description": "ProseMirror's document model",
+ "type": "module",
+ "main": "dist/index.cjs",
+ "module": "dist/index.js",
+ "types": "dist/index.d.ts",
+ "exports": {
+ "import": "./dist/index.js",
+ "require": "./dist/index.cjs"
+ },
+ "sideEffects": false,
+ "license": "MIT",
+ "maintainers": [
+ {
+ "name": "Marijn Haverbeke",
+ "email": "marijn@haverbeke.berlin",
+ "web": "http://marijnhaverbeke.nl"
+ }
+ ],
+ "repository": {
+ "type": "git",
+ "url": "git://github.com/prosemirror/prosemirror-model.git"
+ },
+ "dependencies": {
+ "orderedmap": "^2.0.0"
+ },
+ "devDependencies": {
+ "@prosemirror/buildhelper": "^0.1.5",
+ "jsdom": "^20.0.0",
+ "prosemirror-test-builder": "^1.0.0"
+ },
+ "scripts": {
+ "test": "pm-runtests",
+ "prepare": "pm-buildhelper src/index.ts"
+ }
+}
diff --git a/third_party/js/prosemirror/prosemirror-model/src/README.md b/third_party/js/prosemirror/prosemirror-model/src/README.md
@@ -0,0 +1,62 @@
+This module defines ProseMirror's content model, the data structures
+used to represent and work with documents.
+
+### Document Structure
+
+A ProseMirror document is a tree. At each level, a [node](#model.Node)
+describes the type of the content, and holds a
+[fragment](#model.Fragment) containing its children.
+
+@Node
+@Fragment
+@Mark
+@Slice
+@Attrs
+@ReplaceError
+
+### Resolved Positions
+
+Positions in a document can be represented as integer
+[offsets](/docs/guide/#doc.indexing). But you'll often want to use a
+more convenient representation.
+
+@ResolvedPos
+@NodeRange
+
+### Document Schema
+
+Every ProseMirror document conforms to a
+[schema](/docs/guide/#schema), which describes the set of nodes and
+marks that it is made out of, along with the relations between those,
+such as which node may occur as a child node of which other nodes.
+
+@Schema
+
+@SchemaSpec
+@NodeSpec
+@MarkSpec
+@AttributeSpec
+
+@NodeType
+@MarkType
+
+@ContentMatch
+
+### DOM Representation
+
+Because representing a document as a tree of DOM nodes is central to
+the way ProseMirror operates, DOM [parsing](#model.DOMParser) and
+[serializing](#model.DOMSerializer) is integrated with the model.
+
+(But note that you do _not_ need to have a DOM implementation loaded
+to use this module.)
+
+@DOMParser
+@ParseOptions
+@GenericParseRule
+@TagParseRule
+@StyleParseRule
+@ParseRule
+
+@DOMSerializer
+@DOMOutputSpec
diff --git a/third_party/js/prosemirror/prosemirror-model/src/comparedeep.ts b/third_party/js/prosemirror/prosemirror-model/src/comparedeep.ts
@@ -0,0 +1,15 @@
+export function compareDeep(a: any, b: any) {
+ if (a === b) return true
+ if (!(a && typeof a == "object") ||
+ !(b && typeof b == "object")) return false
+ let array = Array.isArray(a)
+ if (Array.isArray(b) != array) return false
+ if (array) {
+ if (a.length != b.length) return false
+ for (let i = 0; i < a.length; i++) if (!compareDeep(a[i], b[i])) return false
+ } else {
+ for (let p in a) if (!(p in b) || !compareDeep(a[p], b[p])) return false
+ for (let p in b) if (!(p in a)) return false
+ }
+ return true
+}
diff --git a/third_party/js/prosemirror/prosemirror-model/src/content.ts b/third_party/js/prosemirror/prosemirror-model/src/content.ts
@@ -0,0 +1,413 @@
+import {Fragment} from "./fragment"
+import {NodeType} from "./schema"
+
+type MatchEdge = {type: NodeType, next: ContentMatch}
+
+/// Instances of this class represent a match state of a node type's
+/// [content expression](#model.NodeSpec.content), and can be used to
+/// find out whether further content matches here, and whether a given
+/// position is a valid end of the node.
+export class ContentMatch {
+ /// @internal
+ readonly next: MatchEdge[] = []
+ /// @internal
+ readonly wrapCache: (NodeType | readonly NodeType[] | null)[] = []
+
+ /// @internal
+ constructor(
+ /// True when this match state represents a valid end of the node.
+ readonly validEnd: boolean
+ ) {}
+
+ /// @internal
+ static parse(string: string, nodeTypes: {readonly [name: string]: NodeType}): ContentMatch {
+ let stream = new TokenStream(string, nodeTypes)
+ if (stream.next == null) return ContentMatch.empty
+ let expr = parseExpr(stream)
+ if (stream.next) stream.err("Unexpected trailing text")
+ let match = dfa(nfa(expr))
+ checkForDeadEnds(match, stream)
+ return match
+ }
+
+ /// Match a node type, returning a match after that node if
+ /// successful.
+ matchType(type: NodeType): ContentMatch | null {
+ for (let i = 0; i < this.next.length; i++)
+ if (this.next[i].type == type) return this.next[i].next
+ return null
+ }
+
+ /// Try to match a fragment. Returns the resulting match when
+ /// successful.
+ matchFragment(frag: Fragment, start = 0, end = frag.childCount): ContentMatch | null {
+ let cur: ContentMatch | null = this
+ for (let i = start; cur && i < end; i++)
+ cur = cur.matchType(frag.child(i).type)
+ return cur
+ }
+
+ /// @internal
+ get inlineContent() {
+ return this.next.length != 0 && this.next[0].type.isInline
+ }
+
+ /// Get the first matching node type at this match position that can
+ /// be generated.
+ get defaultType(): NodeType | null {
+ for (let i = 0; i < this.next.length; i++) {
+ let {type} = this.next[i]
+ if (!(type.isText || type.hasRequiredAttrs())) return type
+ }
+ return null
+ }
+
+ /// @internal
+ compatible(other: ContentMatch) {
+ for (let i = 0; i < this.next.length; i++)
+ for (let j = 0; j < other.next.length; j++)
+ if (this.next[i].type == other.next[j].type) return true
+ return false
+ }
+
+ /// Try to match the given fragment, and if that fails, see if it can
+ /// be made to match by inserting nodes in front of it. When
+ /// successful, return a fragment of inserted nodes (which may be
+ /// empty if nothing had to be inserted). When `toEnd` is true, only
+ /// return a fragment if the resulting match goes to the end of the
+ /// content expression.
+ fillBefore(after: Fragment, toEnd = false, startIndex = 0): Fragment | null {
+ let seen: ContentMatch[] = [this]
+ function search(match: ContentMatch, types: readonly NodeType[]): Fragment | null {
+ let finished = match.matchFragment(after, startIndex)
+ if (finished && (!toEnd || finished.validEnd))
+ return Fragment.from(types.map(tp => tp.createAndFill()!))
+
+ for (let i = 0; i < match.next.length; i++) {
+ let {type, next} = match.next[i]
+ if (!(type.isText || type.hasRequiredAttrs()) && seen.indexOf(next) == -1) {
+ seen.push(next)
+ let found = search(next, types.concat(type))
+ if (found) return found
+ }
+ }
+ return null
+ }
+
+ return search(this, [])
+ }
+
+ /// Find a set of wrapping node types that would allow a node of the
+ /// given type to appear at this position. The result may be empty
+ /// (when it fits directly) and will be null when no such wrapping
+ /// exists.
+ findWrapping(target: NodeType): readonly NodeType[] | null {
+ for (let i = 0; i < this.wrapCache.length; i += 2)
+ if (this.wrapCache[i] == target) return this.wrapCache[i + 1] as (readonly NodeType[] | null)
+ let computed = this.computeWrapping(target)
+ this.wrapCache.push(target, computed)
+ return computed
+ }
+
+ /// @internal
+ computeWrapping(target: NodeType): readonly NodeType[] | null {
+ type Active = {match: ContentMatch, type: NodeType | null, via: Active | null}
+ let seen = Object.create(null), active: Active[] = [{match: this, type: null, via: null}]
+ while (active.length) {
+ let current = active.shift()!, match = current.match
+ if (match.matchType(target)) {
+ let result: NodeType[] = []
+ for (let obj: Active = current; obj.type; obj = obj.via!)
+ result.push(obj.type)
+ return result.reverse()
+ }
+ for (let i = 0; i < match.next.length; i++) {
+ let {type, next} = match.next[i]
+ if (!type.isLeaf && !type.hasRequiredAttrs() && !(type.name in seen) && (!current.type || next.validEnd)) {
+ active.push({match: type.contentMatch, type, via: current})
+ seen[type.name] = true
+ }
+ }
+ }
+ return null
+ }
+
+ /// The number of outgoing edges this node has in the finite
+ /// automaton that describes the content expression.
+ get edgeCount() {
+ return this.next.length
+ }
+
+ /// Get the _n_th outgoing edge from this node in the finite
+ /// automaton that describes the content expression.
+ edge(n: number): MatchEdge {
+ if (n >= this.next.length) throw new RangeError(`There's no ${n}th edge in this content match`)
+ return this.next[n]
+ }
+
+ /// @internal
+ toString() {
+ let seen: ContentMatch[] = []
+ function scan(m: ContentMatch) {
+ seen.push(m)
+ for (let i = 0; i < m.next.length; i++)
+ if (seen.indexOf(m.next[i].next) == -1) scan(m.next[i].next)
+ }
+ scan(this)
+ return seen.map((m, i) => {
+ let out = i + (m.validEnd ? "*" : " ") + " "
+ for (let i = 0; i < m.next.length; i++)
+ out += (i ? ", " : "") + m.next[i].type.name + "->" + seen.indexOf(m.next[i].next)
+ return out
+ }).join("\n")
+ }
+
+ /// @internal
+ static empty = new ContentMatch(true)
+}
+
+class TokenStream {
+ inline: boolean | null = null
+ pos = 0
+ tokens: string[]
+
+ constructor(
+ readonly string: string,
+ readonly nodeTypes: {readonly [name: string]: NodeType}
+ ) {
+ this.tokens = string.split(/\s*(?=\b|\W|$)/)
+ if (this.tokens[this.tokens.length - 1] == "") this.tokens.pop()
+ if (this.tokens[0] == "") this.tokens.shift()
+ }
+
+ get next() { return this.tokens[this.pos] }
+
+ eat(tok: string) { return this.next == tok && (this.pos++ || true) }
+
+ err(str: string): never { throw new SyntaxError(str + " (in content expression '" + this.string + "')") }
+}
+
+type Expr =
+ {type: "choice", exprs: Expr[]} |
+ {type: "seq", exprs: Expr[]} |
+ {type: "plus", expr: Expr} |
+ {type: "star", expr: Expr} |
+ {type: "opt", expr: Expr} |
+ {type: "range", min: number, max: number, expr: Expr} |
+ {type: "name", value: NodeType}
+
+function parseExpr(stream: TokenStream): Expr {
+ let exprs: Expr[] = []
+ do { exprs.push(parseExprSeq(stream)) }
+ while (stream.eat("|"))
+ return exprs.length == 1 ? exprs[0] : {type: "choice", exprs}
+}
+
+function parseExprSeq(stream: TokenStream): Expr {
+ let exprs: Expr[] = []
+ do { exprs.push(parseExprSubscript(stream)) }
+ while (stream.next && stream.next != ")" && stream.next != "|")
+ return exprs.length == 1 ? exprs[0] : {type: "seq", exprs}
+}
+
+function parseExprSubscript(stream: TokenStream): Expr {
+ let expr = parseExprAtom(stream)
+ for (;;) {
+ if (stream.eat("+"))
+ expr = {type: "plus", expr}
+ else if (stream.eat("*"))
+ expr = {type: "star", expr}
+ else if (stream.eat("?"))
+ expr = {type: "opt", expr}
+ else if (stream.eat("{"))
+ expr = parseExprRange(stream, expr)
+ else break
+ }
+ return expr
+}
+
+function parseNum(stream: TokenStream) {
+ if (/\D/.test(stream.next)) stream.err("Expected number, got '" + stream.next + "'")
+ let result = Number(stream.next)
+ stream.pos++
+ return result
+}
+
+function parseExprRange(stream: TokenStream, expr: Expr): Expr {
+ let min = parseNum(stream), max = min
+ if (stream.eat(",")) {
+ if (stream.next != "}") max = parseNum(stream)
+ else max = -1
+ }
+ if (!stream.eat("}")) stream.err("Unclosed braced range")
+ return {type: "range", min, max, expr}
+}
+
+function resolveName(stream: TokenStream, name: string): readonly NodeType[] {
+ let types = stream.nodeTypes, type = types[name]
+ if (type) return [type]
+ let result: NodeType[] = []
+ for (let typeName in types) {
+ let type = types[typeName]
+ if (type.isInGroup(name)) result.push(type)
+ }
+ if (result.length == 0) stream.err("No node type or group '" + name + "' found")
+ return result
+}
+
+function parseExprAtom(stream: TokenStream): Expr {
+ if (stream.eat("(")) {
+ let expr = parseExpr(stream)
+ if (!stream.eat(")")) stream.err("Missing closing paren")
+ return expr
+ } else if (!/\W/.test(stream.next)) {
+ let exprs = resolveName(stream, stream.next).map(type => {
+ if (stream.inline == null) stream.inline = type.isInline
+ else if (stream.inline != type.isInline) stream.err("Mixing inline and block content")
+ return {type: "name", value: type} as Expr
+ })
+ stream.pos++
+ return exprs.length == 1 ? exprs[0] : {type: "choice", exprs}
+ } else {
+ stream.err("Unexpected token '" + stream.next + "'")
+ }
+}
+
+// The code below helps compile a regular-expression-like language
+// into a deterministic finite automaton. For a good introduction to
+// these concepts, see https://swtch.com/~rsc/regexp/regexp1.html
+
+type Edge = {term: NodeType | undefined, to: number | undefined}
+
+// Construct an NFA from an expression as returned by the parser. The
+// NFA is represented as an array of states, which are themselves
+// arrays of edges, which are `{term, to}` objects. The first state is
+// the entry state and the last node is the success state.
+//
+// Note that unlike typical NFAs, the edge ordering in this one is
+// significant, in that it is used to contruct filler content when
+// necessary.
+function nfa(expr: Expr): Edge[][] {
+ let nfa: Edge[][] = [[]]
+ connect(compile(expr, 0), node())
+ return nfa
+
+ function node() { return nfa.push([]) - 1 }
+ function edge(from: number, to?: number, term?: NodeType) {
+ let edge = {term, to}
+ nfa[from].push(edge)
+ return edge
+ }
+ function connect(edges: Edge[], to: number) {
+ edges.forEach(edge => edge.to = to)
+ }
+
+ function compile(expr: Expr, from: number): Edge[] {
+ if (expr.type == "choice") {
+ return expr.exprs.reduce((out, expr) => out.concat(compile(expr, from)), [] as Edge[])
+ } else if (expr.type == "seq") {
+ for (let i = 0;; i++) {
+ let next = compile(expr.exprs[i], from)
+ if (i == expr.exprs.length - 1) return next
+ connect(next, from = node())
+ }
+ } else if (expr.type == "star") {
+ let loop = node()
+ edge(from, loop)
+ connect(compile(expr.expr, loop), loop)
+ return [edge(loop)]
+ } else if (expr.type == "plus") {
+ let loop = node()
+ connect(compile(expr.expr, from), loop)
+ connect(compile(expr.expr, loop), loop)
+ return [edge(loop)]
+ } else if (expr.type == "opt") {
+ return [edge(from)].concat(compile(expr.expr, from))
+ } else if (expr.type == "range") {
+ let cur = from
+ for (let i = 0; i < expr.min; i++) {
+ let next = node()
+ connect(compile(expr.expr, cur), next)
+ cur = next
+ }
+ if (expr.max == -1) {
+ connect(compile(expr.expr, cur), cur)
+ } else {
+ for (let i = expr.min; i < expr.max; i++) {
+ let next = node()
+ edge(cur, next)
+ connect(compile(expr.expr, cur), next)
+ cur = next
+ }
+ }
+ return [edge(cur)]
+ } else if (expr.type == "name") {
+ return [edge(from, undefined, expr.value)]
+ } else {
+ throw new Error("Unknown expr type")
+ }
+ }
+}
+
+function cmp(a: number, b: number) { return b - a }
+
+// Get the set of nodes reachable by null edges from `node`. Omit
+// nodes with only a single null-out-edge, since they may lead to
+// needless duplicated nodes.
+function nullFrom(nfa: Edge[][], node: number): readonly number[] {
+ let result: number[] = []
+ scan(node)
+ return result.sort(cmp)
+
+ function scan(node: number): void {
+ let edges = nfa[node]
+ if (edges.length == 1 && !edges[0].term) return scan(edges[0].to!)
+ result.push(node)
+ for (let i = 0; i < edges.length; i++) {
+ let {term, to} = edges[i]
+ if (!term && result.indexOf(to!) == -1) scan(to!)
+ }
+ }
+}
+
+// Compiles an NFA as produced by `nfa` into a DFA, modeled as a set
+// of state objects (`ContentMatch` instances) with transitions
+// between them.
+function dfa(nfa: Edge[][]): ContentMatch {
+ let labeled = Object.create(null)
+ return explore(nullFrom(nfa, 0))
+
+ function explore(states: readonly number[]) {
+ let out: [NodeType, number[]][] = []
+ states.forEach(node => {
+ nfa[node].forEach(({term, to}) => {
+ if (!term) return
+ let set: number[] | undefined
+ for (let i = 0; i < out.length; i++) if (out[i][0] == term) set = out[i][1]
+ nullFrom(nfa, to!).forEach(node => {
+ if (!set) out.push([term, set = []])
+ if (set.indexOf(node) == -1) set.push(node)
+ })
+ })
+ })
+ let state = labeled[states.join(",")] = new ContentMatch(states.indexOf(nfa.length - 1) > -1)
+ for (let i = 0; i < out.length; i++) {
+ let states = out[i][1].sort(cmp)
+ state.next.push({type: out[i][0], next: labeled[states.join(",")] || explore(states)})
+ }
+ return state
+ }
+}
+
+function checkForDeadEnds(match: ContentMatch, stream: TokenStream) {
+ for (let i = 0, work = [match]; i < work.length; i++) {
+ let state = work[i], dead = !state.validEnd, nodes: string[] = []
+ for (let j = 0; j < state.next.length; j++) {
+ let {type, next} = state.next[j]
+ nodes.push(type.name)
+ if (dead && !(type.isText || type.hasRequiredAttrs())) dead = false
+ if (work.indexOf(next) == -1) work.push(next)
+ }
+ if (dead) stream.err("Only non-generatable nodes (" + nodes.join(", ") + ") in a required position (see https://prosemirror.net/docs/guide/#generatable)")
+ }
+}
diff --git a/third_party/js/prosemirror/prosemirror-model/src/diff.ts b/third_party/js/prosemirror/prosemirror-model/src/diff.ts
@@ -0,0 +1,52 @@
+import {Fragment} from "./fragment"
+
+export function findDiffStart(a: Fragment, b: Fragment, pos: number): number | null {
+ for (let i = 0;; i++) {
+ if (i == a.childCount || i == b.childCount)
+ return a.childCount == b.childCount ? null : pos
+
+ let childA = a.child(i), childB = b.child(i)
+ if (childA == childB) { pos += childA.nodeSize; continue }
+
+ if (!childA.sameMarkup(childB)) return pos
+
+ if (childA.isText && childA.text != childB.text) {
+ for (let j = 0; childA.text![j] == childB.text![j]; j++)
+ pos++
+ return pos
+ }
+ if (childA.content.size || childB.content.size) {
+ let inner = findDiffStart(childA.content, childB.content, pos + 1)
+ if (inner != null) return inner
+ }
+ pos += childA.nodeSize
+ }
+}
+
+export function findDiffEnd(a: Fragment, b: Fragment, posA: number, posB: number): {a: number, b: number} | null {
+ for (let iA = a.childCount, iB = b.childCount;;) {
+ if (iA == 0 || iB == 0)
+ return iA == iB ? null : {a: posA, b: posB}
+
+ let childA = a.child(--iA), childB = b.child(--iB), size = childA.nodeSize
+ if (childA == childB) {
+ posA -= size; posB -= size
+ continue
+ }
+
+ if (!childA.sameMarkup(childB)) return {a: posA, b: posB}
+
+ if (childA.isText && childA.text != childB.text) {
+ let same = 0, minSize = Math.min(childA.text!.length, childB.text!.length)
+ while (same < minSize && childA.text![childA.text!.length - same - 1] == childB.text![childB.text!.length - same - 1]) {
+ same++; posA--; posB--
+ }
+ return {a: posA, b: posB}
+ }
+ if (childA.content.size || childB.content.size) {
+ let inner = findDiffEnd(childA.content, childB.content, posA - 1, posB - 1)
+ if (inner) return inner
+ }
+ posA -= size; posB -= size
+ }
+}
diff --git a/third_party/js/prosemirror/prosemirror-model/src/dom.ts b/third_party/js/prosemirror/prosemirror-model/src/dom.ts
@@ -0,0 +1 @@
+export type DOMNode = InstanceType<typeof window.Node>
diff --git a/third_party/js/prosemirror/prosemirror-model/src/fragment.ts b/third_party/js/prosemirror/prosemirror-model/src/fragment.ts
@@ -0,0 +1,268 @@
+import {findDiffStart, findDiffEnd} from "./diff"
+import {Node, TextNode} from "./node"
+import {Schema} from "./schema"
+
+/// A fragment represents a node's collection of child nodes.
+///
+/// Like nodes, fragments are persistent data structures, and you
+/// should not mutate them or their content. Rather, you create new
+/// instances whenever needed. The API tries to make this easy.
+export class Fragment {
+ /// The size of the fragment, which is the total of the size of
+ /// its content nodes.
+ readonly size: number
+
+ /// @internal
+ constructor(
+ /// The child nodes in this fragment.
+ readonly content: readonly Node[],
+ size?: number
+ ) {
+ this.size = size || 0
+ if (size == null) for (let i = 0; i < content.length; i++)
+ this.size += content[i].nodeSize
+ }
+
+ /// Invoke a callback for all descendant nodes between the given two
+ /// positions (relative to start of this fragment). Doesn't descend
+ /// into a node when the callback returns `false`.
+ nodesBetween(from: number, to: number,
+ f: (node: Node, start: number, parent: Node | null, index: number) => boolean | void,
+ nodeStart = 0,
+ parent?: Node) {
+ for (let i = 0, pos = 0; pos < to; i++) {
+ let child = this.content[i], end = pos + child.nodeSize
+ if (end > from && f(child, nodeStart + pos, parent || null, i) !== false && child.content.size) {
+ let start = pos + 1
+ child.nodesBetween(Math.max(0, from - start),
+ Math.min(child.content.size, to - start),
+ f, nodeStart + start)
+ }
+ pos = end
+ }
+ }
+
+ /// Call the given callback for every descendant node. `pos` will be
+ /// relative to the start of the fragment. The callback may return
+ /// `false` to prevent traversal of a given node's children.
+ descendants(f: (node: Node, pos: number, parent: Node | null, index: number) => boolean | void) {
+ this.nodesBetween(0, this.size, f)
+ }
+
+ /// Extract the text between `from` and `to`. See the same method on
+ /// [`Node`](#model.Node.textBetween).
+ textBetween(from: number, to: number, blockSeparator?: string | null, leafText?: string | null | ((leafNode: Node) => string)) {
+ let text = "", first = true
+ this.nodesBetween(from, to, (node, pos) => {
+ let nodeText = node.isText ? node.text!.slice(Math.max(from, pos) - pos, to - pos)
+ : !node.isLeaf ? ""
+ : leafText ? (typeof leafText === "function" ? leafText(node) : leafText)
+ : node.type.spec.leafText ? node.type.spec.leafText(node)
+ : ""
+ if (node.isBlock && (node.isLeaf && nodeText || node.isTextblock) && blockSeparator) {
+ if (first) first = false
+ else text += blockSeparator
+ }
+ text += nodeText
+ }, 0)
+ return text
+ }
+
+ /// Create a new fragment containing the combined content of this
+ /// fragment and the other.
+ append(other: Fragment) {
+ if (!other.size) return this
+ if (!this.size) return other
+ let last = this.lastChild!, first = other.firstChild!, content = this.content.slice(), i = 0
+ if (last.isText && last.sameMarkup(first)) {
+ content[content.length - 1] = (last as TextNode).withText(last.text! + first.text!)
+ i = 1
+ }
+ for (; i < other.content.length; i++) content.push(other.content[i])
+ return new Fragment(content, this.size + other.size)
+ }
+
+ /// Cut out the sub-fragment between the two given positions.
+ cut(from: number, to = this.size) {
+ if (from == 0 && to == this.size) return this
+ let result: Node[] = [], size = 0
+ if (to > from) for (let i = 0, pos = 0; pos < to; i++) {
+ let child = this.content[i], end = pos + child.nodeSize
+ if (end > from) {
+ if (pos < from || end > to) {
+ if (child.isText)
+ child = child.cut(Math.max(0, from - pos), Math.min(child.text!.length, to - pos))
+ else
+ child = child.cut(Math.max(0, from - pos - 1), Math.min(child.content.size, to - pos - 1))
+ }
+ result.push(child)
+ size += child.nodeSize
+ }
+ pos = end
+ }
+ return new Fragment(result, size)
+ }
+
+ /// @internal
+ cutByIndex(from: number, to: number) {
+ if (from == to) return Fragment.empty
+ if (from == 0 && to == this.content.length) return this
+ return new Fragment(this.content.slice(from, to))
+ }
+
+ /// Create a new fragment in which the node at the given index is
+ /// replaced by the given node.
+ replaceChild(index: number, node: Node) {
+ let current = this.content[index]
+ if (current == node) return this
+ let copy = this.content.slice()
+ let size = this.size + node.nodeSize - current.nodeSize
+ copy[index] = node
+ return new Fragment(copy, size)
+ }
+
+ /// Create a new fragment by prepending the given node to this
+ /// fragment.
+ addToStart(node: Node) {
+ return new Fragment([node].concat(this.content), this.size + node.nodeSize)
+ }
+
+ /// Create a new fragment by appending the given node to this
+ /// fragment.
+ addToEnd(node: Node) {
+ return new Fragment(this.content.concat(node), this.size + node.nodeSize)
+ }
+
+ /// Compare this fragment to another one.
+ eq(other: Fragment): boolean {
+ if (this.content.length != other.content.length) return false
+ for (let i = 0; i < this.content.length; i++)
+ if (!this.content[i].eq(other.content[i])) return false
+ return true
+ }
+
+ /// The first child of the fragment, or `null` if it is empty.
+ get firstChild(): Node | null { return this.content.length ? this.content[0] : null }
+
+ /// The last child of the fragment, or `null` if it is empty.
+ get lastChild(): Node | null { return this.content.length ? this.content[this.content.length - 1] : null }
+
+ /// The number of child nodes in this fragment.
+ get childCount() { return this.content.length }
+
+ /// Get the child node at the given index. Raise an error when the
+ /// index is out of range.
+ child(index: number) {
+ let found = this.content[index]
+ if (!found) throw new RangeError("Index " + index + " out of range for " + this)
+ return found
+ }
+
+ /// Get the child node at the given index, if it exists.
+ maybeChild(index: number): Node | null {
+ return this.content[index] || null
+ }
+
+ /// Call `f` for every child node, passing the node, its offset
+ /// into this parent node, and its index.
+ forEach(f: (node: Node, offset: number, index: number) => void) {
+ for (let i = 0, p = 0; i < this.content.length; i++) {
+ let child = this.content[i]
+ f(child, p, i)
+ p += child.nodeSize
+ }
+ }
+
+ /// Find the first position at which this fragment and another
+ /// fragment differ, or `null` if they are the same.
+ findDiffStart(other: Fragment, pos = 0) {
+ return findDiffStart(this, other, pos)
+ }
+
+ /// Find the first position, searching from the end, at which this
+ /// fragment and the given fragment differ, or `null` if they are
+ /// the same. Since this position will not be the same in both
+ /// nodes, an object with two separate positions is returned.
+ findDiffEnd(other: Fragment, pos = this.size, otherPos = other.size) {
+ return findDiffEnd(this, other, pos, otherPos)
+ }
+
+ /// Find the index and inner offset corresponding to a given relative
+ /// position in this fragment. The result object will be reused
+ /// (overwritten) the next time the function is called. @internal
+ findIndex(pos: number): {index: number, offset: number} {
+ if (pos == 0) return retIndex(0, pos)
+ if (pos == this.size) return retIndex(this.content.length, pos)
+ if (pos > this.size || pos < 0) throw new RangeError(`Position ${pos} outside of fragment (${this})`)
+ for (let i = 0, curPos = 0;; i++) {
+ let cur = this.child(i), end = curPos + cur.nodeSize
+ if (end >= pos) {
+ if (end == pos) return retIndex(i + 1, end)
+ return retIndex(i, curPos)
+ }
+ curPos = end
+ }
+ }
+
+ /// Return a debugging string that describes this fragment.
+ toString(): string { return "<" + this.toStringInner() + ">" }
+
+ /// @internal
+ toStringInner() { return this.content.join(", ") }
+
+ /// Create a JSON-serializeable representation of this fragment.
+ toJSON(): any {
+ return this.content.length ? this.content.map(n => n.toJSON()) : null
+ }
+
+ /// Deserialize a fragment from its JSON representation.
+ static fromJSON(schema: Schema, value: any) {
+ if (!value) return Fragment.empty
+ if (!Array.isArray(value)) throw new RangeError("Invalid input for Fragment.fromJSON")
+ return new Fragment(value.map(schema.nodeFromJSON))
+ }
+
+ /// Build a fragment from an array of nodes. Ensures that adjacent
+ /// text nodes with the same marks are joined together.
+ static fromArray(array: readonly Node[]) {
+ if (!array.length) return Fragment.empty
+ let joined: Node[] | undefined, size = 0
+ for (let i = 0; i < array.length; i++) {
+ let node = array[i]
+ size += node.nodeSize
+ if (i && node.isText && array[i - 1].sameMarkup(node)) {
+ if (!joined) joined = array.slice(0, i)
+ joined[joined.length - 1] = (node as TextNode)
+ .withText((joined[joined.length - 1] as TextNode).text + (node as TextNode).text)
+ } else if (joined) {
+ joined.push(node)
+ }
+ }
+ return new Fragment(joined || array, size)
+ }
+
+ /// Create a fragment from something that can be interpreted as a
+ /// set of nodes. For `null`, it returns the empty fragment. For a
+ /// fragment, the fragment itself. For a node or array of nodes, a
+ /// fragment containing those nodes.
+ static from(nodes?: Fragment | Node | readonly Node[] | null) {
+ if (!nodes) return Fragment.empty
+ if (nodes instanceof Fragment) return nodes
+ if (Array.isArray(nodes)) return this.fromArray(nodes)
+ if ((nodes as Node).attrs) return new Fragment([nodes as Node], (nodes as Node).nodeSize)
+ throw new RangeError("Can not convert " + nodes + " to a Fragment" +
+ ((nodes as any).nodesBetween ? " (looks like multiple versions of prosemirror-model were loaded)" : ""))
+ }
+
+ /// An empty fragment. Intended to be reused whenever a node doesn't
+ /// contain anything (rather than allocating a new empty fragment for
+ /// each leaf node).
+ static empty: Fragment = new Fragment([], 0)
+}
+
+const found = {index: 0, offset: 0}
+function retIndex(index: number, offset: number) {
+ found.index = index
+ found.offset = offset
+ return found
+}
diff --git a/third_party/js/prosemirror/prosemirror-model/src/from_dom.ts b/third_party/js/prosemirror/prosemirror-model/src/from_dom.ts
@@ -0,0 +1,848 @@
+import {Fragment} from "./fragment"
+import {Slice} from "./replace"
+import {Mark} from "./mark"
+import {Node, TextNode} from "./node"
+import {ContentMatch} from "./content"
+import {ResolvedPos} from "./resolvedpos"
+import {Schema, Attrs, NodeType, MarkType} from "./schema"
+import {DOMNode} from "./dom"
+
+/// These are the options recognized by the
+/// [`parse`](#model.DOMParser.parse) and
+/// [`parseSlice`](#model.DOMParser.parseSlice) methods.
+export interface ParseOptions {
+ /// By default, whitespace is collapsed as per HTML's rules. Pass
+ /// `true` to preserve whitespace, but normalize newlines to
+ /// spaces or, if available, [line break replacements](#model.NodeSpec.linebreakReplacement),
+ /// and `"full"` to preserve whitespace entirely.
+ preserveWhitespace?: boolean | "full"
+
+ /// When given, the parser will, beside parsing the content,
+ /// record the document positions of the given DOM positions. It
+ /// will do so by writing to the objects, adding a `pos` property
+ /// that holds the document position. DOM positions that are not
+ /// in the parsed content will not be written to.
+ findPositions?: {node: DOMNode, offset: number, pos?: number}[]
+
+ /// The child node index to start parsing from.
+ from?: number
+
+ /// The child node index to stop parsing at.
+ to?: number
+
+ /// By default, the content is parsed into the schema's default
+ /// [top node type](#model.Schema.topNodeType). You can pass this
+ /// option to use the type and attributes from a different node
+ /// as the top container.
+ topNode?: Node
+
+ /// Provide the starting content match that content parsed into the
+ /// top node is matched against.
+ topMatch?: ContentMatch
+
+ /// A set of additional nodes to count as
+ /// [context](#model.GenericParseRule.context) when parsing, above the
+ /// given [top node](#model.ParseOptions.topNode).
+ context?: ResolvedPos
+
+ /// @internal
+ ruleFromNode?: (node: DOMNode) => Omit<TagParseRule, "tag"> | null
+ /// @internal
+ topOpen?: boolean
+}
+
+/// Fields that may be present in both [tag](#model.TagParseRule) and
+/// [style](#model.StyleParseRule) parse rules.
+export interface GenericParseRule {
+ /// Can be used to change the order in which the parse rules in a
+ /// schema are tried. Those with higher priority come first. Rules
+ /// without a priority are counted as having priority 50. This
+ /// property is only meaningful in a schema—when directly
+ /// constructing a parser, the order of the rule array is used.
+ priority?: number
+
+ /// By default, when a rule matches an element or style, no further
+ /// rules get a chance to match it. By setting this to `false`, you
+ /// indicate that even when this rule matches, other rules that come
+ /// after it should also run.
+ consuming?: boolean
+
+ /// When given, restricts this rule to only match when the current
+ /// context—the parent nodes into which the content is being
+ /// parsed—matches this expression. Should contain one or more node
+ /// names or node group names followed by single or double slashes.
+ /// For example `"paragraph/"` means the rule only matches when the
+ /// parent node is a paragraph, `"blockquote/paragraph/"` restricts
+ /// it to be in a paragraph that is inside a blockquote, and
+ /// `"section//"` matches any position inside a section—a double
+ /// slash matches any sequence of ancestor nodes. To allow multiple
+ /// different contexts, they can be separated by a pipe (`|`)
+ /// character, as in `"blockquote/|list_item/"`.
+ context?: string
+
+ /// The name of the mark type to wrap the matched content in.
+ mark?: string
+
+ /// When true, ignore content that matches this rule.
+ ignore?: boolean
+
+ /// When true, finding an element that matches this rule will close
+ /// the current node.
+ closeParent?: boolean
+
+ /// When true, ignore the node that matches this rule, but do parse
+ /// its content.
+ skip?: boolean
+
+ /// Attributes for the node or mark created by this rule. When
+ /// `getAttrs` is provided, it takes precedence.
+ attrs?: Attrs
+}
+
+/// Parse rule targeting a DOM element.
+export interface TagParseRule extends GenericParseRule {
+ /// A CSS selector describing the kind of DOM elements to match.
+ tag: string
+
+ /// The namespace to match. Nodes are only matched when the
+ /// namespace matches or this property is null.
+ namespace?: string
+
+ /// The name of the node type to create when this rule matches. Each
+ /// rule should have either a `node`, `mark`, or `ignore` property
+ /// (except when it appears in a [node](#model.NodeSpec.parseDOM) or
+ /// [mark spec](#model.MarkSpec.parseDOM), in which case the `node`
+ /// or `mark` property will be derived from its position).
+ node?: string
+
+ /// A function used to compute the attributes for the node or mark
+ /// created by this rule. Can also be used to describe further
+ /// conditions the DOM element or style must match. When it returns
+ /// `false`, the rule won't match. When it returns null or undefined,
+ /// that is interpreted as an empty/default set of attributes.
+ getAttrs?: (node: HTMLElement) => Attrs | false | null
+
+ /// For rules that produce non-leaf nodes, by default the content of
+ /// the DOM element is parsed as content of the node. If the child
+ /// nodes are in a descendent node, this may be a CSS selector
+ /// string that the parser must use to find the actual content
+ /// element, or a function that returns the actual content element
+ /// to the parser.
+ contentElement?: string | HTMLElement | ((node: HTMLElement) => HTMLElement)
+
+ /// Can be used to override the content of a matched node. When
+ /// present, instead of parsing the node's child nodes, the result of
+ /// this function is used.
+ getContent?: (node: DOMNode, schema: Schema) => Fragment
+
+ /// Controls whether whitespace should be preserved when parsing the
+ /// content inside the matched element. `false` means whitespace may
+ /// be collapsed, `true` means that whitespace should be preserved
+ /// but newlines normalized to spaces, and `"full"` means that
+ /// newlines should also be preserved.
+ preserveWhitespace?: boolean | "full"
+}
+
+/// A parse rule targeting a style property.
+export interface StyleParseRule extends GenericParseRule {
+ /// A CSS property name to match. This rule will match inline styles
+ /// that list that property. May also have the form
+ /// `"property=value"`, in which case the rule only matches if the
+ /// property's value exactly matches the given value. (For more
+ /// complicated filters, use [`getAttrs`](#model.StyleParseRule.getAttrs)
+ /// and return false to indicate that the match failed.) Rules
+ /// matching styles may only produce [marks](#model.GenericParseRule.mark),
+ /// not nodes.
+ style: string
+
+ /// Given to make TS see ParseRule as a tagged union @hide
+ tag?: undefined
+
+ /// Style rules can remove marks from the set of active marks.
+ clearMark?: (mark: Mark) => boolean
+
+ /// A function used to compute the attributes for the node or mark
+ /// created by this rule. Called with the style's value.
+ getAttrs?: (node: string) => Attrs | false | null
+}
+
+/// A value that describes how to parse a given DOM node or inline
+/// style as a ProseMirror node or mark.
+export type ParseRule = TagParseRule | StyleParseRule
+
+function isTagRule(rule: ParseRule): rule is TagParseRule { return (rule as TagParseRule).tag != null }
+function isStyleRule(rule: ParseRule): rule is StyleParseRule { return (rule as StyleParseRule).style != null }
+
+/// A DOM parser represents a strategy for parsing DOM content into a
+/// ProseMirror document conforming to a given schema. Its behavior is
+/// defined by an array of [rules](#model.ParseRule).
+export class DOMParser {
+ /// @internal
+ tags: TagParseRule[] = []
+ /// @internal
+ styles: StyleParseRule[] = []
+ /// @internal
+ matchedStyles: readonly string[]
+ /// @internal
+ normalizeLists: boolean
+
+ /// Create a parser that targets the given schema, using the given
+ /// parsing rules.
+ constructor(
+ /// The schema into which the parser parses.
+ readonly schema: Schema,
+ /// The set of [parse rules](#model.ParseRule) that the parser
+ /// uses, in order of precedence.
+ readonly rules: readonly ParseRule[]
+ ) {
+ let matchedStyles: string[] = this.matchedStyles = []
+ rules.forEach(rule => {
+ if (isTagRule(rule)) {
+ this.tags.push(rule)
+ } else if (isStyleRule(rule)) {
+ let prop = /[^=]*/.exec(rule.style)![0]
+ if (matchedStyles.indexOf(prop) < 0) matchedStyles.push(prop)
+ this.styles.push(rule)
+ }
+ })
+
+ // Only normalize list elements when lists in the schema can't directly contain themselves
+ this.normalizeLists = !this.tags.some(r => {
+ if (!/^(ul|ol)\b/.test(r.tag!) || !r.node) return false
+ let node = schema.nodes[r.node]
+ return node.contentMatch.matchType(node)
+ })
+ }
+
+ /// Parse a document from the content of a DOM node.
+ parse(dom: DOMNode, options: ParseOptions = {}): Node {
+ let context = new ParseContext(this, options, false)
+ context.addAll(dom, Mark.none, options.from, options.to)
+ return context.finish() as Node
+ }
+
+ /// Parses the content of the given DOM node, like
+ /// [`parse`](#model.DOMParser.parse), and takes the same set of
+ /// options. But unlike that method, which produces a whole node,
+ /// this one returns a slice that is open at the sides, meaning that
+ /// the schema constraints aren't applied to the start of nodes to
+ /// the left of the input and the end of nodes at the end.
+ parseSlice(dom: DOMNode, options: ParseOptions = {}) {
+ let context = new ParseContext(this, options, true)
+ context.addAll(dom, Mark.none, options.from, options.to)
+ return Slice.maxOpen(context.finish() as Fragment)
+ }
+
+ /// @internal
+ matchTag(dom: DOMNode, context: ParseContext, after?: TagParseRule) {
+ for (let i = after ? this.tags.indexOf(after) + 1 : 0; i < this.tags.length; i++) {
+ let rule = this.tags[i]
+ if (matches(dom, rule.tag!) &&
+ (rule.namespace === undefined || (dom as HTMLElement).namespaceURI == rule.namespace) &&
+ (!rule.context || context.matchesContext(rule.context))) {
+ if (rule.getAttrs) {
+ let result = rule.getAttrs(dom as HTMLElement)
+ if (result === false) continue
+ rule.attrs = result || undefined
+ }
+ return rule
+ }
+ }
+ }
+
+ /// @internal
+ matchStyle(prop: string, value: string, context: ParseContext, after?: StyleParseRule) {
+ for (let i = after ? this.styles.indexOf(after) + 1 : 0; i < this.styles.length; i++) {
+ let rule = this.styles[i], style = rule.style!
+ if (style.indexOf(prop) != 0 ||
+ rule.context && !context.matchesContext(rule.context) ||
+ // Test that the style string either precisely matches the prop,
+ // or has an '=' sign after the prop, followed by the given
+ // value.
+ style.length > prop.length &&
+ (style.charCodeAt(prop.length) != 61 || style.slice(prop.length + 1) != value))
+ continue
+ if (rule.getAttrs) {
+ let result = rule.getAttrs(value)
+ if (result === false) continue
+ rule.attrs = result || undefined
+ }
+ return rule
+ }
+ }
+
+ /// @internal
+ static schemaRules(schema: Schema) {
+ let result: ParseRule[] = []
+ function insert(rule: ParseRule) {
+ let priority = rule.priority == null ? 50 : rule.priority, i = 0
+ for (; i < result.length; i++) {
+ let next = result[i], nextPriority = next.priority == null ? 50 : next.priority
+ if (nextPriority < priority) break
+ }
+ result.splice(i, 0, rule)
+ }
+
+ for (let name in schema.marks) {
+ let rules = schema.marks[name].spec.parseDOM
+ if (rules) rules.forEach(rule => {
+ insert(rule = copy(rule) as ParseRule)
+ if (!(rule.mark || rule.ignore || (rule as StyleParseRule).clearMark))
+ rule.mark = name
+ })
+ }
+ for (let name in schema.nodes) {
+ let rules = schema.nodes[name].spec.parseDOM
+ if (rules) rules.forEach(rule => {
+ insert(rule = copy(rule) as TagParseRule)
+ if (!((rule as TagParseRule).node || rule.ignore || rule.mark))
+ rule.node = name
+ })
+ }
+ return result
+ }
+
+ /// Construct a DOM parser using the parsing rules listed in a
+ /// schema's [node specs](#model.NodeSpec.parseDOM), reordered by
+ /// [priority](#model.GenericParseRule.priority).
+ static fromSchema(schema: Schema) {
+ return schema.cached.domParser as DOMParser ||
+ (schema.cached.domParser = new DOMParser(schema, DOMParser.schemaRules(schema)))
+ }
+}
+
+const blockTags: {[tagName: string]: boolean} = {
+ address: true, article: true, aside: true, blockquote: true, canvas: true,
+ dd: true, div: true, dl: true, fieldset: true, figcaption: true, figure: true,
+ footer: true, form: true, h1: true, h2: true, h3: true, h4: true, h5: true,
+ h6: true, header: true, hgroup: true, hr: true, li: true, noscript: true, ol: true,
+ output: true, p: true, pre: true, section: true, table: true, tfoot: true, ul: true
+}
+
+const ignoreTags: {[tagName: string]: boolean} = {
+ head: true, noscript: true, object: true, script: true, style: true, title: true
+}
+
+const listTags: {[tagName: string]: boolean} = {ol: true, ul: true}
+
+// Using a bitfield for node context options
+const OPT_PRESERVE_WS = 1, OPT_PRESERVE_WS_FULL = 2, OPT_OPEN_LEFT = 4
+
+function wsOptionsFor(type: NodeType | null, preserveWhitespace: boolean | "full" | undefined, base: number) {
+ if (preserveWhitespace != null) return (preserveWhitespace ? OPT_PRESERVE_WS : 0) |
+ (preserveWhitespace === "full" ? OPT_PRESERVE_WS_FULL : 0)
+ return type && type.whitespace == "pre" ? OPT_PRESERVE_WS | OPT_PRESERVE_WS_FULL : base & ~OPT_OPEN_LEFT
+}
+
+class NodeContext {
+ match: ContentMatch | null
+ content: Node[] = []
+
+ // Marks applied to the node's children
+ activeMarks: readonly Mark[] = Mark.none
+
+ constructor(
+ readonly type: NodeType | null,
+ readonly attrs: Attrs | null,
+ readonly marks: readonly Mark[],
+ readonly solid: boolean,
+ match: ContentMatch | null,
+ public options: number
+ ) {
+ this.match = match || (options & OPT_OPEN_LEFT ? null : type!.contentMatch)
+ }
+
+ findWrapping(node: Node) {
+ if (!this.match) {
+ if (!this.type) return []
+ let fill = this.type.contentMatch.fillBefore(Fragment.from(node))
+ if (fill) {
+ this.match = this.type.contentMatch.matchFragment(fill)!
+ } else {
+ let start = this.type.contentMatch, wrap
+ if (wrap = start.findWrapping(node.type)) {
+ this.match = start
+ return wrap
+ } else {
+ return null
+ }
+ }
+ }
+ return this.match.findWrapping(node.type)
+ }
+
+ finish(openEnd: boolean): Node | Fragment {
+ if (!(this.options & OPT_PRESERVE_WS)) { // Strip trailing whitespace
+ let last = this.content[this.content.length - 1], m
+ if (last && last.isText && (m = /[ \t\r\n\u000c]+$/.exec(last.text!))) {
+ let text = last as TextNode
+ if (last.text!.length == m[0].length) this.content.pop()
+ else this.content[this.content.length - 1] = text.withText(text.text.slice(0, text.text.length - m[0].length))
+ }
+ }
+ let content = Fragment.from(this.content)
+ if (!openEnd && this.match)
+ content = content.append(this.match.fillBefore(Fragment.empty, true)!)
+ return this.type ? this.type.create(this.attrs, content, this.marks) : content
+ }
+
+ inlineContext(node: DOMNode) {
+ if (this.type) return this.type.inlineContent
+ if (this.content.length) return this.content[0].isInline
+ return node.parentNode && !blockTags.hasOwnProperty(node.parentNode.nodeName.toLowerCase())
+ }
+}
+
+class ParseContext {
+ open: number = 0
+ find: {node: DOMNode, offset: number, pos?: number}[] | undefined
+ needsBlock: boolean
+ nodes: NodeContext[]
+ localPreserveWS = false
+
+ constructor(
+ // The parser we are using.
+ readonly parser: DOMParser,
+ // The options passed to this parse.
+ readonly options: ParseOptions,
+ readonly isOpen: boolean
+ ) {
+ let topNode = options.topNode, topContext: NodeContext
+ let topOptions = wsOptionsFor(null, options.preserveWhitespace, 0) | (isOpen ? OPT_OPEN_LEFT : 0)
+ if (topNode)
+ topContext = new NodeContext(topNode.type, topNode.attrs, Mark.none, true,
+ options.topMatch || topNode.type.contentMatch, topOptions)
+ else if (isOpen)
+ topContext = new NodeContext(null, null, Mark.none, true, null, topOptions)
+ else
+ topContext = new NodeContext(parser.schema.topNodeType, null, Mark.none, true, null, topOptions)
+ this.nodes = [topContext]
+ this.find = options.findPositions
+ this.needsBlock = false
+ }
+
+ get top() {
+ return this.nodes[this.open]
+ }
+
+ // Add a DOM node to the content. Text is inserted as text node,
+ // otherwise, the node is passed to `addElement` or, if it has a
+ // `style` attribute, `addElementWithStyles`.
+ addDOM(dom: DOMNode, marks: readonly Mark[]) {
+ if (dom.nodeType == 3) this.addTextNode(dom as Text, marks)
+ else if (dom.nodeType == 1) this.addElement(dom as HTMLElement, marks)
+ }
+
+ addTextNode(dom: Text, marks: readonly Mark[]) {
+ let value = dom.nodeValue!
+ let top = this.top, preserveWS = (top.options & OPT_PRESERVE_WS_FULL) ? "full"
+ : this.localPreserveWS || (top.options & OPT_PRESERVE_WS) > 0
+ let {schema} = this.parser
+ if (preserveWS === "full" ||
+ top.inlineContext(dom) ||
+ /[^ \t\r\n\u000c]/.test(value)) {
+ if (!preserveWS) {
+ value = value.replace(/[ \t\r\n\u000c]+/g, " ")
+ // If this starts with whitespace, and there is no node before it, or
+ // a hard break, or a text node that ends with whitespace, strip the
+ // leading space.
+ if (/^[ \t\r\n\u000c]/.test(value) && this.open == this.nodes.length - 1) {
+ let nodeBefore = top.content[top.content.length - 1]
+ let domNodeBefore = dom.previousSibling
+ if (!nodeBefore ||
+ (domNodeBefore && domNodeBefore.nodeName == 'BR') ||
+ (nodeBefore.isText && /[ \t\r\n\u000c]$/.test(nodeBefore.text!)))
+ value = value.slice(1)
+ }
+ } else if (preserveWS === "full") {
+ value = value.replace(/\r\n?/g, "\n")
+ } else if (schema.linebreakReplacement && /[\r\n]/.test(value) && this.top.findWrapping(schema.linebreakReplacement.create())) {
+ let lines = value.split(/\r?\n|\r/)
+ for (let i = 0; i < lines.length; i++) {
+ if (i) this.insertNode(schema.linebreakReplacement.create(), marks, true)
+ if (lines[i]) this.insertNode(schema.text(lines[i]), marks, !/\S/.test(lines[i]))
+ }
+ value = ""
+ } else {
+ value = value.replace(/\r?\n|\r/g, " ")
+ }
+ if (value) this.insertNode(schema.text(value), marks, !/\S/.test(value))
+ this.findInText(dom)
+ } else {
+ this.findInside(dom)
+ }
+ }
+
+ // Try to find a handler for the given tag and use that to parse. If
+ // none is found, the element's content nodes are added directly.
+ addElement(dom: HTMLElement, marks: readonly Mark[], matchAfter?: TagParseRule) {
+ let outerWS = this.localPreserveWS, top = this.top
+ if (dom.tagName == "PRE" || /pre/.test(dom.style && dom.style.whiteSpace))
+ this.localPreserveWS = true
+ let name = dom.nodeName.toLowerCase(), ruleID: TagParseRule | undefined
+ if (listTags.hasOwnProperty(name) && this.parser.normalizeLists) normalizeList(dom)
+ let rule = (this.options.ruleFromNode && this.options.ruleFromNode(dom)) ||
+ (ruleID = this.parser.matchTag(dom, this, matchAfter))
+ out:
+ if (rule ? rule.ignore : ignoreTags.hasOwnProperty(name)) {
+ this.findInside(dom)
+ this.ignoreFallback(dom, marks)
+ } else if (!rule || rule.skip || rule.closeParent) {
+ if (rule && rule.closeParent) this.open = Math.max(0, this.open - 1)
+ else if (rule && (rule.skip as any).nodeType) dom = rule.skip as any as HTMLElement
+ let sync, oldNeedsBlock = this.needsBlock
+ if (blockTags.hasOwnProperty(name)) {
+ if (top.content.length && top.content[0].isInline && this.open) {
+ this.open--
+ top = this.top
+ }
+ sync = true
+ if (!top.type) this.needsBlock = true
+ } else if (!dom.firstChild) {
+ this.leafFallback(dom, marks)
+ break out
+ }
+ let innerMarks = rule && rule.skip ? marks : this.readStyles(dom, marks)
+ if (innerMarks) this.addAll(dom, innerMarks)
+ if (sync) this.sync(top)
+ this.needsBlock = oldNeedsBlock
+ } else {
+ let innerMarks = this.readStyles(dom, marks)
+ if (innerMarks)
+ this.addElementByRule(dom, rule as TagParseRule, innerMarks, rule!.consuming === false ? ruleID : undefined)
+ }
+ this.localPreserveWS = outerWS
+ }
+
+ // Called for leaf DOM nodes that would otherwise be ignored
+ leafFallback(dom: DOMNode, marks: readonly Mark[]) {
+ if (dom.nodeName == "BR" && this.top.type && this.top.type.inlineContent)
+ this.addTextNode(dom.ownerDocument!.createTextNode("\n"), marks)
+ }
+
+ // Called for ignored nodes
+ ignoreFallback(dom: DOMNode, marks: readonly Mark[]) {
+ // Ignored BR nodes should at least create an inline context
+ if (dom.nodeName == "BR" && (!this.top.type || !this.top.type.inlineContent))
+ this.findPlace(this.parser.schema.text("-"), marks, true)
+ }
+
+ // Run any style parser associated with the node's styles. Either
+ // return an updated array of marks, or null to indicate some of the
+ // styles had a rule with `ignore` set.
+ readStyles(dom: HTMLElement, marks: readonly Mark[]) {
+ let styles = dom.style
+ // Because many properties will only show up in 'normalized' form
+ // in `style.item` (i.e. text-decoration becomes
+ // text-decoration-line, text-decoration-color, etc), we directly
+ // query the styles mentioned in our rules instead of iterating
+ // over the items.
+ if (styles && styles.length) for (let i = 0; i < this.parser.matchedStyles.length; i++) {
+ let name = this.parser.matchedStyles[i], value = styles.getPropertyValue(name)
+ if (value) for (let after: StyleParseRule | undefined = undefined;;) {
+ let rule = this.parser.matchStyle(name, value, this, after)
+ if (!rule) break
+ if (rule.ignore) return null
+ if (rule.clearMark)
+ marks = marks.filter(m => !rule!.clearMark!(m))
+ else
+ marks = marks.concat(this.parser.schema.marks[rule.mark!].create(rule.attrs))
+ if (rule.consuming === false) after = rule
+ else break
+ }
+ }
+ return marks
+ }
+
+ // Look up a handler for the given node. If none are found, return
+ // false. Otherwise, apply it, use its return value to drive the way
+ // the node's content is wrapped, and return true.
+ addElementByRule(dom: HTMLElement, rule: TagParseRule, marks: readonly Mark[], continueAfter?: TagParseRule) {
+ let sync, nodeType
+ if (rule.node) {
+ nodeType = this.parser.schema.nodes[rule.node]
+ if (!nodeType.isLeaf) {
+ let inner = this.enter(nodeType, rule.attrs || null, marks, rule.preserveWhitespace)
+ if (inner) {
+ sync = true
+ marks = inner
+ }
+ } else if (!this.insertNode(nodeType.create(rule.attrs), marks, dom.nodeName == "BR")) {
+ this.leafFallback(dom, marks)
+ }
+ } else {
+ let markType = this.parser.schema.marks[rule.mark!]
+ marks = marks.concat(markType.create(rule.attrs))
+ }
+ let startIn = this.top
+
+ if (nodeType && nodeType.isLeaf) {
+ this.findInside(dom)
+ } else if (continueAfter) {
+ this.addElement(dom, marks, continueAfter)
+ } else if (rule.getContent) {
+ this.findInside(dom)
+ rule.getContent(dom, this.parser.schema).forEach(node => this.insertNode(node, marks, false))
+ } else {
+ let contentDOM = dom
+ if (typeof rule.contentElement == "string") contentDOM = dom.querySelector(rule.contentElement)!
+ else if (typeof rule.contentElement == "function") contentDOM = rule.contentElement(dom)
+ else if (rule.contentElement) contentDOM = rule.contentElement
+ this.findAround(dom, contentDOM, true)
+ this.addAll(contentDOM, marks)
+ this.findAround(dom, contentDOM, false)
+ }
+ if (sync && this.sync(startIn)) this.open--
+ }
+
+ // Add all child nodes between `startIndex` and `endIndex` (or the
+ // whole node, if not given). If `sync` is passed, use it to
+ // synchronize after every block element.
+ addAll(parent: DOMNode, marks: readonly Mark[], startIndex?: number, endIndex?: number) {
+ let index = startIndex || 0
+ for (let dom = startIndex ? parent.childNodes[startIndex] : parent.firstChild,
+ end = endIndex == null ? null : parent.childNodes[endIndex];
+ dom != end; dom = dom!.nextSibling, ++index) {
+ this.findAtPoint(parent, index)
+ this.addDOM(dom!, marks)
+ }
+ this.findAtPoint(parent, index)
+ }
+
+ // Try to find a way to fit the given node type into the current
+ // context. May add intermediate wrappers and/or leave non-solid
+ // nodes that we're in.
+ findPlace(node: Node, marks: readonly Mark[], cautious: boolean) {
+ let route, sync: NodeContext | undefined
+ for (let depth = this.open, penalty = 0; depth >= 0; depth--) {
+ let cx = this.nodes[depth]
+ let found = cx.findWrapping(node)
+ if (found && (!route || route.length > found.length + penalty)) {
+ route = found
+ sync = cx
+ if (!found.length) break
+ }
+ if (cx.solid) {
+ if (cautious) break
+ penalty += 2
+ }
+ }
+ if (!route) return null
+ this.sync(sync!)
+ for (let i = 0; i < route.length; i++)
+ marks = this.enterInner(route[i], null, marks, false)
+ return marks
+ }
+
+ // Try to insert the given node, adjusting the context when needed.
+ insertNode(node: Node, marks: readonly Mark[], cautious: boolean) {
+ if (node.isInline && this.needsBlock && !this.top.type) {
+ let block = this.textblockFromContext()
+ if (block) marks = this.enterInner(block, null, marks)
+ }
+ let innerMarks = this.findPlace(node, marks, cautious)
+ if (innerMarks) {
+ this.closeExtra()
+ let top = this.top
+ if (top.match) top.match = top.match.matchType(node.type)
+ let nodeMarks = Mark.none
+ for (let m of innerMarks.concat(node.marks))
+ if (top.type ? top.type.allowsMarkType(m.type) : markMayApply(m.type, node.type))
+ nodeMarks = m.addToSet(nodeMarks)
+ top.content.push(node.mark(nodeMarks))
+ return true
+ }
+ return false
+ }
+
+ // Try to start a node of the given type, adjusting the context when
+ // necessary.
+ enter(type: NodeType, attrs: Attrs | null, marks: readonly Mark[], preserveWS?: boolean | "full") {
+ let innerMarks = this.findPlace(type.create(attrs), marks, false)
+ if (innerMarks) innerMarks = this.enterInner(type, attrs, marks, true, preserveWS)
+ return innerMarks
+ }
+
+ // Open a node of the given type
+ enterInner(type: NodeType, attrs: Attrs | null, marks: readonly Mark[],
+ solid: boolean = false, preserveWS?: boolean | "full") {
+ this.closeExtra()
+ let top = this.top
+ top.match = top.match && top.match.matchType(type)
+ let options = wsOptionsFor(type, preserveWS, top.options)
+ if ((top.options & OPT_OPEN_LEFT) && top.content.length == 0) options |= OPT_OPEN_LEFT
+ let applyMarks = Mark.none
+ marks = marks.filter(m => {
+ if (top.type ? top.type.allowsMarkType(m.type) : markMayApply(m.type, type)) {
+ applyMarks = m.addToSet(applyMarks)
+ return false
+ }
+ return true
+ })
+ this.nodes.push(new NodeContext(type, attrs, applyMarks, solid, null, options))
+ this.open++
+ return marks
+ }
+
+ // Make sure all nodes above this.open are finished and added to
+ // their parents
+ closeExtra(openEnd = false) {
+ let i = this.nodes.length - 1
+ if (i > this.open) {
+ for (; i > this.open; i--) this.nodes[i - 1].content.push(this.nodes[i].finish(openEnd) as Node)
+ this.nodes.length = this.open + 1
+ }
+ }
+
+ finish() {
+ this.open = 0
+ this.closeExtra(this.isOpen)
+ return this.nodes[0].finish(!!(this.isOpen || this.options.topOpen))
+ }
+
+ sync(to: NodeContext) {
+ for (let i = this.open; i >= 0; i--) {
+ if (this.nodes[i] == to) {
+ this.open = i
+ return true
+ } else if (this.localPreserveWS) {
+ this.nodes[i].options |= OPT_PRESERVE_WS
+ }
+ }
+ return false
+ }
+
+ get currentPos() {
+ this.closeExtra()
+ let pos = 0
+ for (let i = this.open; i >= 0; i--) {
+ let content = this.nodes[i].content
+ for (let j = content.length - 1; j >= 0; j--)
+ pos += content[j].nodeSize
+ if (i) pos++
+ }
+ return pos
+ }
+
+ findAtPoint(parent: DOMNode, offset: number) {
+ if (this.find) for (let i = 0; i < this.find.length; i++) {
+ if (this.find[i].node == parent && this.find[i].offset == offset)
+ this.find[i].pos = this.currentPos
+ }
+ }
+
+ findInside(parent: DOMNode) {
+ if (this.find) for (let i = 0; i < this.find.length; i++) {
+ if (this.find[i].pos == null && parent.nodeType == 1 && parent.contains(this.find[i].node))
+ this.find[i].pos = this.currentPos
+ }
+ }
+
+ findAround(parent: DOMNode, content: DOMNode, before: boolean) {
+ if (parent != content && this.find) for (let i = 0; i < this.find.length; i++) {
+ if (this.find[i].pos == null && parent.nodeType == 1 && parent.contains(this.find[i].node)) {
+ let pos = content.compareDocumentPosition(this.find[i].node)
+ if (pos & (before ? 2 : 4))
+ this.find[i].pos = this.currentPos
+ }
+ }
+ }
+
+ findInText(textNode: Text) {
+ if (this.find) for (let i = 0; i < this.find.length; i++) {
+ if (this.find[i].node == textNode)
+ this.find[i].pos = this.currentPos - (textNode.nodeValue!.length - this.find[i].offset)
+ }
+ }
+
+ // Determines whether the given context string matches this context.
+ matchesContext(context: string) {
+ if (context.indexOf("|") > -1)
+ return context.split(/\s*\|\s*/).some(this.matchesContext, this)
+
+ let parts = context.split("/")
+ let option = this.options.context
+ let useRoot = !this.isOpen && (!option || option.parent.type == this.nodes[0].type)
+ let minDepth = -(option ? option.depth + 1 : 0) + (useRoot ? 0 : 1)
+ let match = (i: number, depth: number) => {
+ for (; i >= 0; i--) {
+ let part = parts[i]
+ if (part == "") {
+ if (i == parts.length - 1 || i == 0) continue
+ for (; depth >= minDepth; depth--)
+ if (match(i - 1, depth)) return true
+ return false
+ } else {
+ let next = depth > 0 || (depth == 0 && useRoot) ? this.nodes[depth].type
+ : option && depth >= minDepth ? option.node(depth - minDepth).type
+ : null
+ if (!next || (next.name != part && !next.isInGroup(part)))
+ return false
+ depth--
+ }
+ }
+ return true
+ }
+ return match(parts.length - 1, this.open)
+ }
+
+ textblockFromContext() {
+ let $context = this.options.context
+ if ($context) for (let d = $context.depth; d >= 0; d--) {
+ let deflt = $context.node(d).contentMatchAt($context.indexAfter(d)).defaultType
+ if (deflt && deflt.isTextblock && deflt.defaultAttrs) return deflt
+ }
+ for (let name in this.parser.schema.nodes) {
+ let type = this.parser.schema.nodes[name]
+ if (type.isTextblock && type.defaultAttrs) return type
+ }
+ }
+}
+
+// Kludge to work around directly nested list nodes produced by some
+// tools and allowed by browsers to mean that the nested list is
+// actually part of the list item above it.
+function normalizeList(dom: DOMNode) {
+ for (let child = dom.firstChild, prevItem: ChildNode | null = null; child; child = child.nextSibling) {
+ let name = child.nodeType == 1 ? child.nodeName.toLowerCase() : null
+ if (name && listTags.hasOwnProperty(name) && prevItem) {
+ prevItem.appendChild(child)
+ child = prevItem
+ } else if (name == "li") {
+ prevItem = child
+ } else if (name) {
+ prevItem = null
+ }
+ }
+}
+
+// Apply a CSS selector.
+function matches(dom: any, selector: string): boolean {
+ return (dom.matches || dom.msMatchesSelector || dom.webkitMatchesSelector || dom.mozMatchesSelector).call(dom, selector)
+}
+
+function copy(obj: {[prop: string]: any}) {
+ let copy: {[prop: string]: any} = {}
+ for (let prop in obj) copy[prop] = obj[prop]
+ return copy
+}
+
+// Used when finding a mark at the top level of a fragment parse.
+// Checks whether it would be reasonable to apply a given mark type to
+// a given node, by looking at the way the mark occurs in the schema.
+function markMayApply(markType: MarkType, nodeType: NodeType) {
+ let nodes = nodeType.schema.nodes
+ for (let name in nodes) {
+ let parent = nodes[name]
+ if (!parent.allowsMarkType(markType)) continue
+ let seen: ContentMatch[] = [], scan = (match: ContentMatch) => {
+ seen.push(match)
+ for (let i = 0; i < match.edgeCount; i++) {
+ let {type, next} = match.edge(i)
+ if (type == nodeType) return true
+ if (seen.indexOf(next) < 0 && scan(next)) return true
+ }
+ }
+ if (scan(parent.contentMatch)) return true
+ }
+}
diff --git a/third_party/js/prosemirror/prosemirror-model/src/index.ts b/third_party/js/prosemirror/prosemirror-model/src/index.ts
@@ -0,0 +1,11 @@
+export {Node} from "./node"
+export {ResolvedPos, NodeRange} from "./resolvedpos"
+export {Fragment} from "./fragment"
+export {Slice, ReplaceError} from "./replace"
+export {Mark} from "./mark"
+
+export {Schema, NodeType, Attrs, MarkType, NodeSpec, MarkSpec, AttributeSpec, SchemaSpec} from "./schema"
+export {ContentMatch} from "./content"
+
+export {DOMParser, GenericParseRule, TagParseRule, StyleParseRule, ParseRule, ParseOptions} from "./from_dom"
+export {DOMSerializer, DOMOutputSpec} from "./to_dom"
diff --git a/third_party/js/prosemirror/prosemirror-model/src/mark.ts b/third_party/js/prosemirror/prosemirror-model/src/mark.ts
@@ -0,0 +1,111 @@
+import {compareDeep} from "./comparedeep"
+import {Attrs, MarkType, Schema} from "./schema"
+
+/// A mark is a piece of information that can be attached to a node,
+/// such as it being emphasized, in code font, or a link. It has a
+/// type and optionally a set of attributes that provide further
+/// information (such as the target of the link). Marks are created
+/// through a `Schema`, which controls which types exist and which
+/// attributes they have.
+export class Mark {
+ /// @internal
+ constructor(
+ /// The type of this mark.
+ readonly type: MarkType,
+ /// The attributes associated with this mark.
+ readonly attrs: Attrs
+ ) {}
+
+ /// Given a set of marks, create a new set which contains this one as
+ /// well, in the right position. If this mark is already in the set,
+ /// the set itself is returned. If any marks that are set to be
+ /// [exclusive](#model.MarkSpec.excludes) with this mark are present,
+ /// those are replaced by this one.
+ addToSet(set: readonly Mark[]): readonly Mark[] {
+ let copy, placed = false
+ for (let i = 0; i < set.length; i++) {
+ let other = set[i]
+ if (this.eq(other)) return set
+ if (this.type.excludes(other.type)) {
+ if (!copy) copy = set.slice(0, i)
+ } else if (other.type.excludes(this.type)) {
+ return set
+ } else {
+ if (!placed && other.type.rank > this.type.rank) {
+ if (!copy) copy = set.slice(0, i)
+ copy.push(this)
+ placed = true
+ }
+ if (copy) copy.push(other)
+ }
+ }
+ if (!copy) copy = set.slice()
+ if (!placed) copy.push(this)
+ return copy
+ }
+
+ /// Remove this mark from the given set, returning a new set. If this
+ /// mark is not in the set, the set itself is returned.
+ removeFromSet(set: readonly Mark[]): readonly Mark[] {
+ for (let i = 0; i < set.length; i++)
+ if (this.eq(set[i]))
+ return set.slice(0, i).concat(set.slice(i + 1))
+ return set
+ }
+
+ /// Test whether this mark is in the given set of marks.
+ isInSet(set: readonly Mark[]) {
+ for (let i = 0; i < set.length; i++)
+ if (this.eq(set[i])) return true
+ return false
+ }
+
+ /// Test whether this mark has the same type and attributes as
+ /// another mark.
+ eq(other: Mark) {
+ return this == other ||
+ (this.type == other.type && compareDeep(this.attrs, other.attrs))
+ }
+
+ /// Convert this mark to a JSON-serializeable representation.
+ toJSON(): any {
+ let obj: any = {type: this.type.name}
+ for (let _ in this.attrs) {
+ obj.attrs = this.attrs
+ break
+ }
+ return obj
+ }
+
+ /// Deserialize a mark from JSON.
+ static fromJSON(schema: Schema, json: any) {
+ if (!json) throw new RangeError("Invalid input for Mark.fromJSON")
+ let type = schema.marks[json.type]
+ if (!type) throw new RangeError(`There is no mark type ${json.type} in this schema`)
+ let mark = type.create(json.attrs)
+ type.checkAttrs(mark.attrs)
+ return mark
+ }
+
+ /// Test whether two sets of marks are identical.
+ static sameSet(a: readonly Mark[], b: readonly Mark[]) {
+ if (a == b) return true
+ if (a.length != b.length) return false
+ for (let i = 0; i < a.length; i++)
+ if (!a[i].eq(b[i])) return false
+ return true
+ }
+
+ /// Create a properly sorted mark set from null, a single mark, or an
+ /// unsorted array of marks.
+ static setFrom(marks?: Mark | readonly Mark[] | null): readonly Mark[] {
+ if (!marks || Array.isArray(marks) && marks.length == 0) return Mark.none
+ if (marks instanceof Mark) return [marks]
+ let copy = marks.slice()
+ copy.sort((a, b) => a.type.rank - b.type.rank)
+ return copy
+ }
+
+ /// The empty set of marks.
+ static none: readonly Mark[] = []
+}
diff --git a/third_party/js/prosemirror/prosemirror-model/src/node.ts b/third_party/js/prosemirror/prosemirror-model/src/node.ts
@@ -0,0 +1,402 @@
+import {Fragment} from "./fragment"
+import {Mark} from "./mark"
+import {Schema, NodeType, Attrs, MarkType} from "./schema"
+import {Slice, replace} from "./replace"
+import {ResolvedPos} from "./resolvedpos"
+import {compareDeep} from "./comparedeep"
+
+const emptyAttrs: Attrs = Object.create(null)
+
+/// This class represents a node in the tree that makes up a
+/// ProseMirror document. So a document is an instance of `Node`, with
+/// children that are also instances of `Node`.
+///
+/// Nodes are persistent data structures. Instead of changing them, you
+/// create new ones with the content you want. Old ones keep pointing
+/// at the old document shape. This is made cheaper by sharing
+/// structure between the old and new data as much as possible, which a
+/// tree shape like this (without back pointers) makes easy.
+///
+/// **Do not** directly mutate the properties of a `Node` object. See
+/// [the guide](/docs/guide/#doc) for more information.
+export class Node {
+ /// @internal
+ constructor(
+ /// The type of node that this is.
+ readonly type: NodeType,
+ /// An object mapping attribute names to values. The kind of
+ /// attributes allowed and required are
+ /// [determined](#model.NodeSpec.attrs) by the node type.
+ readonly attrs: Attrs,
+ // A fragment holding the node's children.
+ content?: Fragment | null,
+ /// The marks (things like whether it is emphasized or part of a
+ /// link) applied to this node.
+ readonly marks = Mark.none
+ ) {
+ this.content = content || Fragment.empty
+ }
+
+ /// A container holding the node's children.
+ readonly content: Fragment
+
+ /// The array of this node's child nodes.
+ get children() { return this.content.content }
+
+ /// For text nodes, this contains the node's text content.
+ readonly text: string | undefined
+
+ /// The size of this node, as defined by the integer-based [indexing
+ /// scheme](/docs/guide/#doc.indexing). For text nodes, this is the
+ /// amount of characters. For other leaf nodes, it is one. For
+ /// non-leaf nodes, it is the size of the content plus two (the
+ /// start and end token).
+ get nodeSize(): number { return this.isLeaf ? 1 : 2 + this.content.size }
+
+ /// The number of children that the node has.
+ get childCount() { return this.content.childCount }
+
+ /// Get the child node at the given index. Raises an error when the
+ /// index is out of range.
+ child(index: number) { return this.content.child(index) }
+
+ /// Get the child node at the given index, if it exists.
+ maybeChild(index: number) { return this.content.maybeChild(index) }
+
+ /// Call `f` for every child node, passing the node, its offset
+ /// into this parent node, and its index.
+ forEach(f: (node: Node, offset: number, index: number) => void) { this.content.forEach(f) }
+
+ /// Invoke a callback for all descendant nodes recursively between
+ /// the given two positions that are relative to start of this
+ /// node's content. The callback is invoked with the node, its
+ /// position relative to the original node (method receiver),
+ /// its parent node, and its child index. When the callback returns
+ /// false for a given node, that node's children will not be
+ /// recursed over. The last parameter can be used to specify a
+ /// starting position to count from.
+ nodesBetween(from: number, to: number,
+ f: (node: Node, pos: number, parent: Node | null, index: number) => void | boolean,
+ startPos = 0) {
+ this.content.nodesBetween(from, to, f, startPos, this)
+ }
+
+ /// Call the given callback for every descendant node. Doesn't
+ /// descend into a node when the callback returns `false`.
+ descendants(f: (node: Node, pos: number, parent: Node | null, index: number) => void | boolean) {
+ this.nodesBetween(0, this.content.size, f)
+ }
+
+ /// Concatenates all the text nodes found in this fragment and its
+ /// children.
+ get textContent() {
+ return (this.isLeaf && this.type.spec.leafText)
+ ? this.type.spec.leafText(this)
+ : this.textBetween(0, this.content.size, "")
+ }
+
+ /// Get all text between positions `from` and `to`. When
+ /// `blockSeparator` is given, it will be inserted to separate text
+ /// from different block nodes. If `leafText` is given, it'll be
+ /// inserted for every non-text leaf node encountered, otherwise
+ /// [`leafText`](#model.NodeSpec.leafText) will be used.
+ textBetween(from: number, to: number, blockSeparator?: string | null,
+ leafText?: null | string | ((leafNode: Node) => string)) {
+ return this.content.textBetween(from, to, blockSeparator, leafText)
+ }
+
+ /// Returns this node's first child, or `null` if there are no
+ /// children.
+ get firstChild(): Node | null { return this.content.firstChild }
+
+ /// Returns this node's last child, or `null` if there are no
+ /// children.
+ get lastChild(): Node | null { return this.content.lastChild }
+
+ /// Test whether two nodes represent the same piece of document.
+ eq(other: Node) {
+ return this == other || (this.sameMarkup(other) && this.content.eq(other.content))
+ }
+
+ /// Compare the markup (type, attributes, and marks) of this node to
+ /// those of another. Returns `true` if both have the same markup.
+ sameMarkup(other: Node) {
+ return this.hasMarkup(other.type, other.attrs, other.marks)
+ }
+
+ /// Check whether this node's markup correspond to the given type,
+ /// attributes, and marks.
+ hasMarkup(type: NodeType, attrs?: Attrs | null, marks?: readonly Mark[]): boolean {
+ return this.type == type &&
+ compareDeep(this.attrs, attrs || type.defaultAttrs || emptyAttrs) &&
+ Mark.sameSet(this.marks, marks || Mark.none)
+ }
+
+ /// Create a new node with the same markup as this node, containing
+ /// the given content (or empty, if no content is given).
+ copy(content: Fragment | null = null): Node {
+ if (content == this.content) return this
+ return new Node(this.type, this.attrs, content, this.marks)
+ }
+
+ /// Create a copy of this node, with the given set of marks instead
+ /// of the node's own marks.
+ mark(marks: readonly Mark[]): Node {
+ return marks == this.marks ? this : new Node(this.type, this.attrs, this.content, marks)
+ }
+
+ /// Create a copy of this node with only the content between the
+ /// given positions. If `to` is not given, it defaults to the end of
+ /// the node.
+ cut(from: number, to: number = this.content.size): Node {
+ if (from == 0 && to == this.content.size) return this
+ return this.copy(this.content.cut(from, to))
+ }
+
+ /// Cut out the part of the document between the given positions, and
+ /// return it as a `Slice` object.
+ slice(from: number, to: number = this.content.size, includeParents = false) {
+ if (from == to) return Slice.empty
+
+ let $from = this.resolve(from), $to = this.resolve(to)
+ let depth = includeParents ? 0 : $from.sharedDepth(to)
+ let start = $from.start(depth), node = $from.node(depth)
+ let content = node.content.cut($from.pos - start, $to.pos - start)
+ return new Slice(content, $from.depth - depth, $to.depth - depth)
+ }
+
+ /// Replace the part of the document between the given positions with
+ /// the given slice. The slice must 'fit', meaning its open sides
+ /// must be able to connect to the surrounding content, and its
+ /// content nodes must be valid children for the node they are placed
+ /// into. If any of this is violated, an error of type
+ /// [`ReplaceError`](#model.ReplaceError) is thrown.
+ replace(from: number, to: number, slice: Slice) {
+ return replace(this.resolve(from), this.resolve(to), slice)
+ }
+
+ /// Find the node directly after the given position.
+ nodeAt(pos: number): Node | null {
+ for (let node: Node | null = this;;) {
+ let {index, offset} = node.content.findIndex(pos)
+ node = node.maybeChild(index)
+ if (!node) return null
+ if (offset == pos || node.isText) return node
+ pos -= offset + 1
+ }
+ }
+
+ /// Find the (direct) child node after the given offset, if any,
+ /// and return it along with its index and offset relative to this
+ /// node.
+ childAfter(pos: number): {node: Node | null, index: number, offset: number} {
+ let {index, offset} = this.content.findIndex(pos)
+ return {node: this.content.maybeChild(index), index, offset}
+ }
+
+ /// Find the (direct) child node before the given offset, if any,
+ /// and return it along with its index and offset relative to this
+ /// node.
+ childBefore(pos: number): {node: Node | null, index: number, offset: number} {
+ if (pos == 0) return {node: null, index: 0, offset: 0}
+ let {index, offset} = this.content.findIndex(pos)
+ if (offset < pos) return {node: this.content.child(index), index, offset}
+ let node = this.content.child(index - 1)
+ return {node, index: index - 1, offset: offset - node.nodeSize}
+ }
+
+ /// Resolve the given position in the document, returning an
+ /// [object](#model.ResolvedPos) with information about its context.
+ resolve(pos: number) { return ResolvedPos.resolveCached(this, pos) }
+
+ /// @internal
+ resolveNoCache(pos: number) { return ResolvedPos.resolve(this, pos) }
+
+ /// Test whether a given mark or mark type occurs in this document
+ /// between the two given positions.
+ rangeHasMark(from: number, to: number, type: Mark | MarkType): boolean {
+ let found = false
+ if (to > from) this.nodesBetween(from, to, node => {
+ if (type.isInSet(node.marks)) found = true
+ return !found
+ })
+ return found
+ }
+
+ /// True when this is a block (non-inline node)
+ get isBlock() { return this.type.isBlock }
+
+ /// True when this is a textblock node, a block node with inline
+ /// content.
+ get isTextblock() { return this.type.isTextblock }
+
+ /// True when this node allows inline content.
+ get inlineContent() { return this.type.inlineContent }
+
+ /// True when this is an inline node (a text node or a node that can
+ /// appear among text).
+ get isInline() { return this.type.isInline }
+
+ /// True when this is a text node.
+ get isText() { return this.type.isText }
+
+ /// True when this is a leaf node.
+ get isLeaf() { return this.type.isLeaf }
+
+ /// True when this is an atom, i.e. when it does not have directly
+ /// editable content. This is usually the same as `isLeaf`, but can
+ /// be configured with the [`atom` property](#model.NodeSpec.atom)
+ /// on a node's spec (typically used when the node is displayed as
+ /// an uneditable [node view](#view.NodeView)).
+ get isAtom() { return this.type.isAtom }
+
+ /// Return a string representation of this node for debugging
+ /// purposes.
+ toString(): string {
+ if (this.type.spec.toDebugString) return this.type.spec.toDebugString(this)
+ let name = this.type.name
+ if (this.content.size)
+ name += "(" + this.content.toStringInner() + ")"
+ return wrapMarks(this.marks, name)
+ }
+
+ /// Get the content match in this node at the given index.
+ contentMatchAt(index: number) {
+ let match = this.type.contentMatch.matchFragment(this.content, 0, index)
+ if (!match) throw new Error("Called contentMatchAt on a node with invalid content")
+ return match
+ }
+
+ /// Test whether replacing the range between `from` and `to` (by
+ /// child index) with the given replacement fragment (which defaults
+ /// to the empty fragment) would leave the node's content valid. You
+ /// can optionally pass `start` and `end` indices into the
+ /// replacement fragment.
+ canReplace(from: number, to: number, replacement = Fragment.empty, start = 0, end = replacement.childCount) {
+ let one = this.contentMatchAt(from).matchFragment(replacement, start, end)
+ let two = one && one.matchFragment(this.content, to)
+ if (!two || !two.validEnd) return false
+ for (let i = start; i < end; i++) if (!this.type.allowsMarks(replacement.child(i).marks)) return false
+ return true
+ }
+
+ /// Test whether replacing the range `from` to `to` (by index) with
+ /// a node of the given type would leave the node's content valid.
+ canReplaceWith(from: number, to: number, type: NodeType, marks?: readonly Mark[]) {
+ if (marks && !this.type.allowsMarks(marks)) return false
+ let start = this.contentMatchAt(from).matchType(type)
+ let end = start && start.matchFragment(this.content, to)
+ return end ? end.validEnd : false
+ }
+
+ /// Test whether the given node's content could be appended to this
+ /// node. If that node is empty, this will only return true if there
+ /// is at least one node type that can appear in both nodes (to avoid
+ /// merging completely incompatible nodes).
+ canAppend(other: Node) {
+ if (other.content.size) return this.canReplace(this.childCount, this.childCount, other.content)
+ else return this.type.compatibleContent(other.type)
+ }
+
+ /// Check whether this node and its descendants conform to the
+ /// schema, and raise an exception when they do not.
+ check() {
+ this.type.checkContent(this.content)
+ this.type.checkAttrs(this.attrs)
+ let copy = Mark.none
+ for (let i = 0; i < this.marks.length; i++) {
+ let mark = this.marks[i]
+ mark.type.checkAttrs(mark.attrs)
+ copy = mark.addToSet(copy)
+ }
+ if (!Mark.sameSet(copy, this.marks))
+ throw new RangeError(`Invalid collection of marks for node ${this.type.name}: ${this.marks.map(m => m.type.name)}`)
+ this.content.forEach(node => node.check())
+ }
+
+ /// Return a JSON-serializeable representation of this node.
+ toJSON(): any {
+ let obj: any = {type: this.type.name}
+ for (let _ in this.attrs) {
+ obj.attrs = this.attrs
+ break
+ }
+ if (this.content.size)
+ obj.content = this.content.toJSON()
+ if (this.marks.length)
+ obj.marks = this.marks.map(n => n.toJSON())
+ return obj
+ }
+
+ /// Deserialize a node from its JSON representation.
+ static fromJSON(schema: Schema, json: any): Node {
+ if (!json) throw new RangeError("Invalid input for Node.fromJSON")
+ let marks: Mark[] | undefined = undefined
+ if (json.marks) {
+ if (!Array.isArray(json.marks)) throw new RangeError("Invalid mark data for Node.fromJSON")
+ marks = json.marks.map(schema.markFromJSON)
+ }
+ if (json.type == "text") {
+ if (typeof json.text != "string") throw new RangeError("Invalid text node in JSON")
+ return schema.text(json.text, marks)
+ }
+ let content = Fragment.fromJSON(schema, json.content)
+ let node = schema.nodeType(json.type).create(json.attrs, content, marks)
+ node.type.checkAttrs(node.attrs)
+ return node
+ }
+}
+
+;(Node.prototype as any).text = undefined
+
+export class TextNode extends Node {
+ readonly text: string
+
+ /// @internal
+ constructor(type: NodeType, attrs: Attrs, content: string, marks?: readonly Mark[]) {
+ super(type, attrs, null, marks)
+ if (!content) throw new RangeError("Empty text nodes are not allowed")
+ this.text = content
+ }
+
+ toString() {
+ if (this.type.spec.toDebugString) return this.type.spec.toDebugString(this)
+ return wrapMarks(this.marks, JSON.stringify(this.text))
+ }
+
+ get textContent() { return this.text }
+
+ textBetween(from: number, to: number) { return this.text.slice(from, to) }
+
+ get nodeSize() { return this.text.length }
+
+ mark(marks: readonly Mark[]) {
+ return marks == this.marks ? this : new TextNode(this.type, this.attrs, this.text, marks)
+ }
+
+ withText(text: string) {
+ if (text == this.text) return this
+ return new TextNode(this.type, this.attrs, text, this.marks)
+ }
+
+ cut(from = 0, to = this.text.length) {
+ if (from == 0 && to == this.text.length) return this
+ return this.withText(this.text.slice(from, to))
+ }
+
+ eq(other: Node) {
+ return this.sameMarkup(other) && this.text == other.text
+ }
+
+ toJSON() {
+ let base = super.toJSON()
+ base.text = this.text
+ return base
+ }
+}
+
+function wrapMarks(marks: readonly Mark[], str: string) {
+ for (let i = marks.length - 1; i >= 0; i--)
+ str = marks[i].type.name + "(" + str + ")"
+ return str
+}
diff --git a/third_party/js/prosemirror/prosemirror-model/src/replace.ts b/third_party/js/prosemirror/prosemirror-model/src/replace.ts
@@ -0,0 +1,225 @@
+import {Fragment} from "./fragment"
+import {Schema} from "./schema"
+import {Node, TextNode} from "./node"
+import {ResolvedPos} from "./resolvedpos"
+
+/// Error type raised by [`Node.replace`](#model.Node.replace) when
+/// given an invalid replacement.
+export class ReplaceError extends Error {}
+/*
+ReplaceError = function(this: any, message: string) {
+ let err = Error.call(this, message)
+ ;(err as any).__proto__ = ReplaceError.prototype
+ return err
+} as any
+
+ReplaceError.prototype = Object.create(Error.prototype)
+ReplaceError.prototype.constructor = ReplaceError
+ReplaceError.prototype.name = "ReplaceError"
+*/
+
+/// A slice represents a piece cut out of a larger document. It
+/// stores not only a fragment, but also the depth up to which nodes on
+/// both side are ‘open’ (cut through).
+export class Slice {
+ /// Create a slice. When specifying a non-zero open depth, you must
+ /// make sure that there are nodes of at least that depth at the
+ /// appropriate side of the fragment—i.e. if the fragment is an
+ /// empty paragraph node, `openStart` and `openEnd` can't be greater
+ /// than 1.
+ ///
+ /// It is not necessary for the content of open nodes to conform to
+ /// the schema's content constraints, though it should be a valid
+ /// start/end/middle for such a node, depending on which sides are
+ /// open.
+ constructor(
+ /// The slice's content.
+ readonly content: Fragment,
+ /// The open depth at the start of the fragment.
+ readonly openStart: number,
+ /// The open depth at the end.
+ readonly openEnd: number
+ ) {}
+
+ /// The size this slice would add when inserted into a document.
+ get size(): number {
+ return this.content.size - this.openStart - this.openEnd
+ }
+
+ /// @internal
+ insertAt(pos: number, fragment: Fragment) {
+ let content = insertInto(this.content, pos + this.openStart, fragment)
+ return content && new Slice(content, this.openStart, this.openEnd)
+ }
+
+ /// @internal
+ removeBetween(from: number, to: number) {
+ return new Slice(removeRange(this.content, from + this.openStart, to + this.openStart), this.openStart, this.openEnd)
+ }
+
+ /// Tests whether this slice is equal to another slice.
+ eq(other: Slice): boolean {
+ return this.content.eq(other.content) && this.openStart == other.openStart && this.openEnd == other.openEnd
+ }
+
+ /// @internal
+ toString() {
+ return this.content + "(" + this.openStart + "," + this.openEnd + ")"
+ }
+
+ /// Convert a slice to a JSON-serializable representation.
+ toJSON(): any {
+ if (!this.content.size) return null
+ let json: any = {content: this.content.toJSON()}
+ if (this.openStart > 0) json.openStart = this.openStart
+ if (this.openEnd > 0) json.openEnd = this.openEnd
+ return json
+ }
+
+ /// Deserialize a slice from its JSON representation.
+ static fromJSON(schema: Schema, json: any): Slice {
+ if (!json) return Slice.empty
+ let openStart = json.openStart || 0, openEnd = json.openEnd || 0
+ if (typeof openStart != "number" || typeof openEnd != "number")
+ throw new RangeError("Invalid input for Slice.fromJSON")
+ return new Slice(Fragment.fromJSON(schema, json.content), openStart, openEnd)
+ }
+
+ /// Create a slice from a fragment by taking the maximum possible
+ /// open value on both side of the fragment.
+ static maxOpen(fragment: Fragment, openIsolating = true) {
+ let openStart = 0, openEnd = 0
+ for (let n = fragment.firstChild; n && !n.isLeaf && (openIsolating || !n.type.spec.isolating); n = n.firstChild) openStart++
+ for (let n = fragment.lastChild; n && !n.isLeaf && (openIsolating || !n.type.spec.isolating); n = n.lastChild) openEnd++
+ return new Slice(fragment, openStart, openEnd)
+ }
+
+ /// The empty slice.
+ static empty = new Slice(Fragment.empty, 0, 0)
+}
+
+function removeRange(content: Fragment, from: number, to: number): Fragment {
+ let {index, offset} = content.findIndex(from), child = content.maybeChild(index)
+ let {index: indexTo, offset: offsetTo} = content.findIndex(to)
+ if (offset == from || child!.isText) {
+ if (offsetTo != to && !content.child(indexTo).isText) throw new RangeError("Removing non-flat range")
+ return content.cut(0, from).append(content.cut(to))
+ }
+ if (index != indexTo) throw new RangeError("Removing non-flat range")
+ return content.replaceChild(index, child!.copy(removeRange(child!.content, from - offset - 1, to - offset - 1)))
+}
+
+function insertInto(content: Fragment, dist: number, insert: Fragment, parent?: Node | null): Fragment | null {
+ let {index, offset} = content.findIndex(dist), child = content.maybeChild(index)
+ if (offset == dist || child!.isText) {
+ if (parent && !parent.canReplace(index, index, insert)) return null
+ return content.cut(0, dist).append(insert).append(content.cut(dist))
+ }
+ let inner = insertInto(child!.content, dist - offset - 1, insert, child)
+ return inner && content.replaceChild(index, child!.copy(inner))
+}
+
+export function replace($from: ResolvedPos, $to: ResolvedPos, slice: Slice) {
+ if (slice.openStart > $from.depth)
+ throw new ReplaceError("Inserted content deeper than insertion position")
+ if ($from.depth - slice.openStart != $to.depth - slice.openEnd)
+ throw new ReplaceError("Inconsistent open depths")
+ return replaceOuter($from, $to, slice, 0)
+}
+
+function replaceOuter($from: ResolvedPos, $to: ResolvedPos, slice: Slice, depth: number): Node {
+ let index = $from.index(depth), node = $from.node(depth)
+ if (index == $to.index(depth) && depth < $from.depth - slice.openStart) {
+ let inner = replaceOuter($from, $to, slice, depth + 1)
+ return node.copy(node.content.replaceChild(index, inner))
+ } else if (!slice.content.size) {
+ return close(node, replaceTwoWay($from, $to, depth))
+ } else if (!slice.openStart && !slice.openEnd && $from.depth == depth && $to.depth == depth) { // Simple, flat case
+ let parent = $from.parent, content = parent.content
+ return close(parent, content.cut(0, $from.parentOffset).append(slice.content).append(content.cut($to.parentOffset)))
+ } else {
+ let {start, end} = prepareSliceForReplace(slice, $from)
+ return close(node, replaceThreeWay($from, start, end, $to, depth))
+ }
+}
+
+function checkJoin(main: Node, sub: Node) {
+ if (!sub.type.compatibleContent(main.type))
+ throw new ReplaceError("Cannot join " + sub.type.name + " onto " + main.type.name)
+}
+
+function joinable($before: ResolvedPos, $after: ResolvedPos, depth: number) {
+ let node = $before.node(depth)
+ checkJoin(node, $after.node(depth))
+ return node
+}
+
+function addNode(child: Node, target: Node[]) {
+ let last = target.length - 1
+ if (last >= 0 && child.isText && child.sameMarkup(target[last]))
+ target[last] = (child as TextNode).withText(target[last].text! + child.text!)
+ else
+ target.push(child)
+}
+
+function addRange($start: ResolvedPos | null, $end: ResolvedPos | null, depth: number, target: Node[]) {
+ let node = ($end || $start)!.node(depth)
+ let startIndex = 0, endIndex = $end ? $end.index(depth) : node.childCount
+ if ($start) {
+ startIndex = $start.index(depth)
+ if ($start.depth > depth) {
+ startIndex++
+ } else if ($start.textOffset) {
+ addNode($start.nodeAfter!, target)
+ startIndex++
+ }
+ }
+ for (let i = startIndex; i < endIndex; i++) addNode(node.child(i), target)
+ if ($end && $end.depth == depth && $end.textOffset)
+ addNode($end.nodeBefore!, target)
+}
+
+function close(node: Node, content: Fragment) {
+ node.type.checkContent(content)
+ return node.copy(content)
+}
+
+function replaceThreeWay($from: ResolvedPos, $start: ResolvedPos, $end: ResolvedPos, $to: ResolvedPos, depth: number) {
+ let openStart = $from.depth > depth && joinable($from, $start, depth + 1)
+ let openEnd = $to.depth > depth && joinable($end, $to, depth + 1)
+
+ let content: Node[] = []
+ addRange(null, $from, depth, content)
+ if (openStart && openEnd && $start.index(depth) == $end.index(depth)) {
+ checkJoin(openStart, openEnd)
+ addNode(close(openStart, replaceThreeWay($from, $start, $end, $to, depth + 1)), content)
+ } else {
+ if (openStart)
+ addNode(close(openStart, replaceTwoWay($from, $start, depth + 1)), content)
+ addRange($start, $end, depth, content)
+ if (openEnd)
+ addNode(close(openEnd, replaceTwoWay($end, $to, depth + 1)), content)
+ }
+ addRange($to, null, depth, content)
+ return new Fragment(content)
+}
+
+function replaceTwoWay($from: ResolvedPos, $to: ResolvedPos, depth: number) {
+ let content: Node[] = []
+ addRange(null, $from, depth, content)
+ if ($from.depth > depth) {
+ let type = joinable($from, $to, depth + 1)
+ addNode(close(type, replaceTwoWay($from, $to, depth + 1)), content)
+ }
+ addRange($to, null, depth, content)
+ return new Fragment(content)
+}
+
+function prepareSliceForReplace(slice: Slice, $along: ResolvedPos) {
+ let extra = $along.depth - slice.openStart, parent = $along.node(extra)
+ let node = parent.copy(slice.content)
+ for (let i = extra - 1; i >= 0; i--)
+ node = $along.node(i).copy(Fragment.from(node))
+ return {start: node.resolveNoCache(slice.openStart + extra),
+ end: node.resolveNoCache(node.content.size - slice.openEnd - extra)}
+}
diff --git a/third_party/js/prosemirror/prosemirror-model/src/resolvedpos.ts b/third_party/js/prosemirror/prosemirror-model/src/resolvedpos.ts
@@ -0,0 +1,289 @@
+import {Mark} from "./mark"
+import {Node} from "./node"
+
+/// You can [_resolve_](#model.Node.resolve) a position to get more
+/// information about it. Objects of this class represent such a
+/// resolved position, providing various pieces of context
+/// information, and some helper methods.
+///
+/// Throughout this interface, methods that take an optional `depth`
+/// parameter will interpret undefined as `this.depth` and negative
+/// numbers as `this.depth + value`.
+export class ResolvedPos {
+ /// The number of levels the parent node is from the root. If this
+ /// position points directly into the root node, it is 0. If it
+ /// points into a top-level paragraph, 1, and so on.
+ depth: number
+
+ /// @internal
+ constructor(
+ /// The position that was resolved.
+ readonly pos: number,
+ /// @internal
+ readonly path: any[],
+ /// The offset this position has into its parent node.
+ readonly parentOffset: number
+ ) {
+ this.depth = path.length / 3 - 1
+ }
+
+ /// @internal
+ resolveDepth(val: number | undefined | null) {
+ if (val == null) return this.depth
+ if (val < 0) return this.depth + val
+ return val
+ }
+
+ /// The parent node that the position points into. Note that even if
+ /// a position points into a text node, that node is not considered
+ /// the parent—text nodes are ‘flat’ in this model, and have no content.
+ get parent() { return this.node(this.depth) }
+
+ /// The root node in which the position was resolved.
+ get doc() { return this.node(0) }
+
+ /// The ancestor node at the given level. `p.node(p.depth)` is the
+ /// same as `p.parent`.
+ node(depth?: number | null): Node { return this.path[this.resolveDepth(depth) * 3] }
+
+ /// The index into the ancestor at the given level. If this points
+ /// at the 3rd node in the 2nd paragraph on the top level, for
+ /// example, `p.index(0)` is 1 and `p.index(1)` is 2.
+ index(depth?: number | null): number { return this.path[this.resolveDepth(depth) * 3 + 1] }
+
+ /// The index pointing after this position into the ancestor at the
+ /// given level.
+ indexAfter(depth?: number | null): number {
+ depth = this.resolveDepth(depth)
+ return this.index(depth) + (depth == this.depth && !this.textOffset ? 0 : 1)
+ }
+
+ /// The (absolute) position at the start of the node at the given
+ /// level.
+ start(depth?: number | null): number {
+ depth = this.resolveDepth(depth)
+ return depth == 0 ? 0 : this.path[depth * 3 - 1] + 1
+ }
+
+ /// The (absolute) position at the end of the node at the given
+ /// level.
+ end(depth?: number | null): number {
+ depth = this.resolveDepth(depth)
+ return this.start(depth) + this.node(depth).content.size
+ }
+
+ /// The (absolute) position directly before the wrapping node at the
+ /// given level, or, when `depth` is `this.depth + 1`, the original
+ /// position.
+ before(depth?: number | null): number {
+ depth = this.resolveDepth(depth)
+ if (!depth) throw new RangeError("There is no position before the top-level node")
+ return depth == this.depth + 1 ? this.pos : this.path[depth * 3 - 1]
+ }
+
+ /// The (absolute) position directly after the wrapping node at the
+ /// given level, or the original position when `depth` is `this.depth + 1`.
+ after(depth?: number | null): number {
+ depth = this.resolveDepth(depth)
+ if (!depth) throw new RangeError("There is no position after the top-level node")
+ return depth == this.depth + 1 ? this.pos : this.path[depth * 3 - 1] + this.path[depth * 3].nodeSize
+ }
+
+ /// When this position points into a text node, this returns the
+ /// distance between the position and the start of the text node.
+ /// Will be zero for positions that point between nodes.
+ get textOffset(): number { return this.pos - this.path[this.path.length - 1] }
+
+ /// Get the node directly after the position, if any. If the position
+ /// points into a text node, only the part of that node after the
+ /// position is returned.
+ get nodeAfter(): Node | null {
+ let parent = this.parent, index = this.index(this.depth)
+ if (index == parent.childCount) return null
+ let dOff = this.pos - this.path[this.path.length - 1], child = parent.child(index)
+ return dOff ? parent.child(index).cut(dOff) : child
+ }
+
+ /// Get the node directly before the position, if any. If the
+ /// position points into a text node, only the part of that node
+ /// before the position is returned.
+ get nodeBefore(): Node | null {
+ let index = this.index(this.depth)
+ let dOff = this.pos - this.path[this.path.length - 1]
+ if (dOff) return this.parent.child(index).cut(0, dOff)
+ return index == 0 ? null : this.parent.child(index - 1)
+ }
+
+ /// Get the position at the given index in the parent node at the
+ /// given depth (which defaults to `this.depth`).
+ posAtIndex(index: number, depth?: number | null): number {
+ depth = this.resolveDepth(depth)
+ let node = this.path[depth * 3], pos = depth == 0 ? 0 : this.path[depth * 3 - 1] + 1
+ for (let i = 0; i < index; i++) pos += node.child(i).nodeSize
+ return pos
+ }
+
+ /// Get the marks at this position, factoring in the surrounding
+ /// marks' [`inclusive`](#model.MarkSpec.inclusive) property. If the
+ /// position is at the start of a non-empty node, the marks of the
+ /// node after it (if any) are returned.
+ marks(): readonly Mark[] {
+ let parent = this.parent, index = this.index()
+
+ // In an empty parent, return the empty array
+ if (parent.content.size == 0) return Mark.none
+
+ // When inside a text node, just return the text node's marks
+ if (this.textOffset) return parent.child(index).marks
+
+ let main = parent.maybeChild(index - 1), other = parent.maybeChild(index)
+ // If the `after` flag is true of there is no node before, make
+ // the node after this position the main reference.
+ if (!main) { let tmp = main; main = other; other = tmp }
+
+ // Use all marks in the main node, except those that have
+ // `inclusive` set to false and are not present in the other node.
+ let marks = main!.marks
+ for (var i = 0; i < marks.length; i++)
+ if (marks[i].type.spec.inclusive === false && (!other || !marks[i].isInSet(other.marks)))
+ marks = marks[i--].removeFromSet(marks)
+
+ return marks
+ }
+
+ /// Get the marks after the current position, if any, except those
+ /// that are non-inclusive and not present at position `$end`. This
+ /// is mostly useful for getting the set of marks to preserve after a
+ /// deletion. Will return `null` if this position is at the end of
+ /// its parent node or its parent node isn't a textblock (in which
+ /// case no marks should be preserved).
+ marksAcross($end: ResolvedPos): readonly Mark[] | null {
+ let after = this.parent.maybeChild(this.index())
+ if (!after || !after.isInline) return null
+
+ let marks = after.marks, next = $end.parent.maybeChild($end.index())
+ for (var i = 0; i < marks.length; i++)
+ if (marks[i].type.spec.inclusive === false && (!next || !marks[i].isInSet(next.marks)))
+ marks = marks[i--].removeFromSet(marks)
+ return marks
+ }
+
+ /// The depth up to which this position and the given (non-resolved)
+ /// position share the same parent nodes.
+ sharedDepth(pos: number): number {
+ for (let depth = this.depth; depth > 0; depth--)
+ if (this.start(depth) <= pos && this.end(depth) >= pos) return depth
+ return 0
+ }
+
+ /// Returns a range based on the place where this position and the
+ /// given position diverge around block content. If both point into
+ /// the same textblock, for example, a range around that textblock
+ /// will be returned. If they point into different blocks, the range
+ /// around those blocks in their shared ancestor is returned. You can
+ /// pass in an optional predicate that will be called with a parent
+ /// node to see if a range into that parent is acceptable.
+ blockRange(other: ResolvedPos = this, pred?: (node: Node) => boolean): NodeRange | null {
+ if (other.pos < this.pos) return other.blockRange(this)
+ for (let d = this.depth - (this.parent.inlineContent || this.pos == other.pos ? 1 : 0); d >= 0; d--)
+ if (other.pos <= this.end(d) && (!pred || pred(this.node(d))))
+ return new NodeRange(this, other, d)
+ return null
+ }
+
+ /// Query whether the given position shares the same parent node.
+ sameParent(other: ResolvedPos): boolean {
+ return this.pos - this.parentOffset == other.pos - other.parentOffset
+ }
+
+ /// Return the greater of this and the given position.
+ max(other: ResolvedPos): ResolvedPos {
+ return other.pos > this.pos ? other : this
+ }
+
+ /// Return the smaller of this and the given position.
+ min(other: ResolvedPos): ResolvedPos {
+ return other.pos < this.pos ? other : this
+ }
+
+ /// @internal
+ toString() {
+ let str = ""
+ for (let i = 1; i <= this.depth; i++)
+ str += (str ? "/" : "") + this.node(i).type.name + "_" + this.index(i - 1)
+ return str + ":" + this.parentOffset
+ }
+
+ /// @internal
+ static resolve(doc: Node, pos: number): ResolvedPos {
+ if (!(pos >= 0 && pos <= doc.content.size)) throw new RangeError("Position " + pos + " out of range")
+ let path: Array<Node | number> = []
+ let start = 0, parentOffset = pos
+ for (let node = doc;;) {
+ let {index, offset} = node.content.findIndex(parentOffset)
+ let rem = parentOffset - offset
+ path.push(node, index, start + offset)
+ if (!rem) break
+ node = node.child(index)
+ if (node.isText) break
+ parentOffset = rem - 1
+ start += offset + 1
+ }
+ return new ResolvedPos(pos, path, parentOffset)
+ }
+
+ /// @internal
+ static resolveCached(doc: Node, pos: number): ResolvedPos {
+ let cache = resolveCache.get(doc)
+ if (cache) {
+ for (let i = 0; i < cache.elts.length; i++) {
+ let elt = cache.elts[i]
+ if (elt.pos == pos) return elt
+ }
+ } else {
+ resolveCache.set(doc, cache = new ResolveCache)
+ }
+ let result = cache.elts[cache.i] = ResolvedPos.resolve(doc, pos)
+ cache.i = (cache.i + 1) % resolveCacheSize
+ return result
+ }
+}
+
+class ResolveCache {
+ elts: ResolvedPos[] = []
+ i = 0
+}
+
+const resolveCacheSize = 12, resolveCache = new WeakMap<Node, ResolveCache>()
+
+/// Represents a flat range of content, i.e. one that starts and
+/// ends in the same node.
+export class NodeRange {
+ /// Construct a node range. `$from` and `$to` should point into the
+ /// same node until at least the given `depth`, since a node range
+ /// denotes an adjacent set of nodes in a single parent node.
+ constructor(
+ /// A resolved position along the start of the content. May have a
+ /// `depth` greater than this object's `depth` property, since
+ /// these are the positions that were used to compute the range,
+ /// not re-resolved positions directly at its boundaries.
+ readonly $from: ResolvedPos,
+ /// A position along the end of the content. See
+ /// caveat for [`$from`](#model.NodeRange.$from).
+ readonly $to: ResolvedPos,
+ /// The depth of the node that this range points into.
+ readonly depth: number
+ ) {}
+
+ /// The position at the start of the range.
+ get start() { return this.$from.before(this.depth + 1) }
+ /// The position at the end of the range.
+ get end() { return this.$to.after(this.depth + 1) }
+
+ /// The parent node that the range points into.
+ get parent() { return this.$from.node(this.depth) }
+ /// The start index of the range in the parent node.
+ get startIndex() { return this.$from.index(this.depth) }
+ /// The end index of the range in the parent node.
+ get endIndex() { return this.$to.indexAfter(this.depth) }
+}
diff --git a/third_party/js/prosemirror/prosemirror-model/src/schema.ts b/third_party/js/prosemirror/prosemirror-model/src/schema.ts
@@ -0,0 +1,705 @@
+import OrderedMap from "orderedmap"
+
+import {Node, TextNode} from "./node"
+import {Fragment} from "./fragment"
+import {Mark} from "./mark"
+import {ContentMatch} from "./content"
+import {DOMOutputSpec} from "./to_dom"
+import {ParseRule, TagParseRule} from "./from_dom"
+
+/// An object holding the attributes of a node.
+export type Attrs = {readonly [attr: string]: any}
+
+// For node types where all attrs have a default value (or which don't
+// have any attributes), build up a single reusable default attribute
+// object, and use it for all nodes that don't specify specific
+// attributes.
+function defaultAttrs(attrs: {[name: string]: Attribute}) {
+ let defaults = Object.create(null)
+ for (let attrName in attrs) {
+ let attr = attrs[attrName]
+ if (!attr.hasDefault) return null
+ defaults[attrName] = attr.default
+ }
+ return defaults
+}
+
+function computeAttrs(attrs: {[name: string]: Attribute}, value: Attrs | null) {
+ let built = Object.create(null)
+ for (let name in attrs) {
+ let given = value && value[name]
+ if (given === undefined) {
+ let attr = attrs[name]
+ if (attr.hasDefault) given = attr.default
+ else throw new RangeError("No value supplied for attribute " + name)
+ }
+ built[name] = given
+ }
+ return built
+}
+
+export function checkAttrs(attrs: {[name: string]: Attribute}, values: Attrs, type: string, name: string) {
+ for (let name in values)
+ if (!(name in attrs)) throw new RangeError(`Unsupported attribute ${name} for ${type} of type ${name}`)
+ for (let name in attrs) {
+ let attr = attrs[name]
+ if (attr.validate) attr.validate(values[name])
+ }
+}
+
+function initAttrs(typeName: string, attrs?: {[name: string]: AttributeSpec}) {
+ let result: {[name: string]: Attribute} = Object.create(null)
+ if (attrs) for (let name in attrs) result[name] = new Attribute(typeName, name, attrs[name])
+ return result
+}
+
+/// Node types are objects allocated once per `Schema` and used to
+/// [tag](#model.Node.type) `Node` instances. They contain information
+/// about the node type, such as its name and what kind of node it
+/// represents.
+export class NodeType {
+ /// @internal
+ groups: readonly string[]
+ /// @internal
+ attrs: {[name: string]: Attribute}
+ /// @internal
+ defaultAttrs: Attrs
+
+ /// @internal
+ constructor(
+ /// The name the node type has in this schema.
+ readonly name: string,
+ /// A link back to the `Schema` the node type belongs to.
+ readonly schema: Schema,
+ /// The spec that this type is based on
+ readonly spec: NodeSpec
+ ) {
+ this.groups = spec.group ? spec.group.split(" ") : []
+ this.attrs = initAttrs(name, spec.attrs)
+ this.defaultAttrs = defaultAttrs(this.attrs)
+
+ // Filled in later
+ ;(this as any).contentMatch = null
+ ;(this as any).inlineContent = null
+
+ this.isBlock = !(spec.inline || name == "text")
+ this.isText = name == "text"
+ }
+
+ /// True if this node type has inline content.
+ declare inlineContent: boolean
+ /// True if this is a block type
+ isBlock: boolean
+ /// True if this is the text node type.
+ isText: boolean
+
+ /// True if this is an inline type.
+ get isInline() { return !this.isBlock }
+
+ /// True if this is a textblock type, a block that contains inline
+ /// content.
+ get isTextblock() { return this.isBlock && this.inlineContent }
+
+ /// True for node types that allow no content.
+ get isLeaf() { return this.contentMatch == ContentMatch.empty }
+
+ /// True when this node is an atom, i.e. when it does not have
+ /// directly editable content.
+ get isAtom() { return this.isLeaf || !!this.spec.atom }
+
+ /// Return true when this node type is part of the given
+ /// [group](#model.NodeSpec.group).
+ isInGroup(group: string) {
+ return this.groups.indexOf(group) > -1
+ }
+
+ /// The starting match of the node type's content expression.
+ declare contentMatch: ContentMatch
+
+ /// The set of marks allowed in this node. `null` means all marks
+ /// are allowed.
+ markSet: readonly MarkType[] | null = null
+
+ /// The node type's [whitespace](#model.NodeSpec.whitespace) option.
+ get whitespace(): "pre" | "normal" {
+ return this.spec.whitespace || (this.spec.code ? "pre" : "normal")
+ }
+
+ /// Tells you whether this node type has any required attributes.
+ hasRequiredAttrs() {
+ for (let n in this.attrs) if (this.attrs[n].isRequired) return true
+ return false
+ }
+
+ /// Indicates whether this node allows some of the same content as
+ /// the given node type.
+ compatibleContent(other: NodeType) {
+ return this == other || this.contentMatch.compatible(other.contentMatch)
+ }
+
+ /// @internal
+ computeAttrs(attrs: Attrs | null): Attrs {
+ if (!attrs && this.defaultAttrs) return this.defaultAttrs
+ else return computeAttrs(this.attrs, attrs)
+ }
+
+ /// Create a `Node` of this type. The given attributes are
+ /// checked and defaulted (you can pass `null` to use the type's
+ /// defaults entirely, if no required attributes exist). `content`
+ /// may be a `Fragment`, a node, an array of nodes, or
+ /// `null`. Similarly `marks` may be `null` to default to the empty
+ /// set of marks.
+ create(attrs: Attrs | null = null, content?: Fragment | Node | readonly Node[] | null, marks?: readonly Mark[]) {
+ if (this.isText) throw new Error("NodeType.create can't construct text nodes")
+ return new Node(this, this.computeAttrs(attrs), Fragment.from(content), Mark.setFrom(marks))
+ }
+
+ /// Like [`create`](#model.NodeType.create), but check the given content
+ /// against the node type's content restrictions, and throw an error
+ /// if it doesn't match.
+ createChecked(attrs: Attrs | null = null, content?: Fragment | Node | readonly Node[] | null, marks?: readonly Mark[]) {
+ content = Fragment.from(content)
+ this.checkContent(content)
+ return new Node(this, this.computeAttrs(attrs), content, Mark.setFrom(marks))
+ }
+
+ /// Like [`create`](#model.NodeType.create), but see if it is
+ /// necessary to add nodes to the start or end of the given fragment
+ /// to make it fit the node. If no fitting wrapping can be found,
+ /// return null. Note that, due to the fact that required nodes can
+ /// always be created, this will always succeed if you pass null or
+ /// `Fragment.empty` as content.
+ createAndFill(attrs: Attrs | null = null, content?: Fragment | Node | readonly Node[] | null, marks?: readonly Mark[]) {
+ attrs = this.computeAttrs(attrs)
+ content = Fragment.from(content)
+ if (content.size) {
+ let before = this.contentMatch.fillBefore(content)
+ if (!before) return null
+ content = before.append(content)
+ }
+ let matched = this.contentMatch.matchFragment(content)
+ let after = matched && matched.fillBefore(Fragment.empty, true)
+ if (!after) return null
+ return new Node(this, attrs, (content as Fragment).append(after), Mark.setFrom(marks))
+ }
+
+ /// Returns true if the given fragment is valid content for this node
+ /// type.
+ validContent(content: Fragment) {
+ let result = this.contentMatch.matchFragment(content)
+ if (!result || !result.validEnd) return false
+ for (let i = 0; i < content.childCount; i++)
+ if (!this.allowsMarks(content.child(i).marks)) return false
+ return true
+ }
+
+ /// Throws a RangeError if the given fragment is not valid content for this
+ /// node type.
+ /// @internal
+ checkContent(content: Fragment) {
+ if (!this.validContent(content))
+ throw new RangeError(`Invalid content for node ${this.name}: ${content.toString().slice(0, 50)}`)
+ }
+
+ /// @internal
+ checkAttrs(attrs: Attrs) {
+ checkAttrs(this.attrs, attrs, "node", this.name)
+ }
+
+ /// Check whether the given mark type is allowed in this node.
+ allowsMarkType(markType: MarkType) {
+ return this.markSet == null || this.markSet.indexOf(markType) > -1
+ }
+
+ /// Test whether the given set of marks are allowed in this node.
+ allowsMarks(marks: readonly Mark[]) {
+ if (this.markSet == null) return true
+ for (let i = 0; i < marks.length; i++) if (!this.allowsMarkType(marks[i].type)) return false
+ return true
+ }
+
+ /// Removes the marks that are not allowed in this node from the given set.
+ allowedMarks(marks: readonly Mark[]): readonly Mark[] {
+ if (this.markSet == null) return marks
+ let copy
+ for (let i = 0; i < marks.length; i++) {
+ if (!this.allowsMarkType(marks[i].type)) {
+ if (!copy) copy = marks.slice(0, i)
+ } else if (copy) {
+ copy.push(marks[i])
+ }
+ }
+ return !copy ? marks : copy.length ? copy : Mark.none
+ }
+
+ /// @internal
+ static compile<Nodes extends string>(nodes: OrderedMap<NodeSpec>, schema: Schema<Nodes>): {readonly [name in Nodes]: NodeType} {
+ let result = Object.create(null)
+ nodes.forEach((name, spec) => result[name] = new NodeType(name, schema, spec))
+
+ let topType = schema.spec.topNode || "doc"
+ if (!result[topType]) throw new RangeError("Schema is missing its top node type ('" + topType + "')")
+ if (!result.text) throw new RangeError("Every schema needs a 'text' type")
+ for (let _ in result.text.attrs) throw new RangeError("The text node type should not have attributes")
+
+ return result
+ }
+}
+
+function validateType(typeName: string, attrName: string, type: string) {
+ let types = type.split("|")
+ return (value: any) => {
+ let name = value === null ? "null" : typeof value
+ if (types.indexOf(name) < 0) throw new RangeError(`Expected value of type ${types} for attribute ${attrName} on type ${typeName}, got ${name}`)
+ }
+}
+
+// Attribute descriptors
+
+class Attribute {
+ hasDefault: boolean
+ default: any
+ validate: undefined | ((value: any) => void)
+
+ constructor(typeName: string, attrName: string, options: AttributeSpec) {
+ this.hasDefault = Object.prototype.hasOwnProperty.call(options, "default")
+ this.default = options.default
+ this.validate = typeof options.validate == "string" ? validateType(typeName, attrName, options.validate) : options.validate
+ }
+
+ get isRequired() {
+ return !this.hasDefault
+ }
+}
+
+// Marks
+
+/// Like nodes, marks (which are associated with nodes to signify
+/// things like emphasis or being part of a link) are
+/// [tagged](#model.Mark.type) with type objects, which are
+/// instantiated once per `Schema`.
+export class MarkType {
+ /// @internal
+ attrs: {[name: string]: Attribute}
+ /// @internal
+ declare excluded: readonly MarkType[]
+ /// @internal
+ instance: Mark | null
+
+ /// @internal
+ constructor(
+ /// The name of the mark type.
+ readonly name: string,
+ /// @internal
+ readonly rank: number,
+ /// The schema that this mark type instance is part of.
+ readonly schema: Schema,
+ /// The spec on which the type is based.
+ readonly spec: MarkSpec
+ ) {
+ this.attrs = initAttrs(name, spec.attrs)
+ ;(this as any).excluded = null
+ let defaults = defaultAttrs(this.attrs)
+ this.instance = defaults ? new Mark(this, defaults) : null
+ }
+
+ /// Create a mark of this type. `attrs` may be `null` or an object
+ /// containing only some of the mark's attributes. The others, if
+ /// they have defaults, will be added.
+ create(attrs: Attrs | null = null) {
+ if (!attrs && this.instance) return this.instance
+ return new Mark(this, computeAttrs(this.attrs, attrs))
+ }
+
+ /// @internal
+ static compile(marks: OrderedMap<MarkSpec>, schema: Schema) {
+ let result = Object.create(null), rank = 0
+ marks.forEach((name, spec) => result[name] = new MarkType(name, rank++, schema, spec))
+ return result
+ }
+
+ /// When there is a mark of this type in the given set, a new set
+ /// without it is returned. Otherwise, the input set is returned.
+ removeFromSet(set: readonly Mark[]): readonly Mark[] {
+ for (var i = 0; i < set.length; i++) if (set[i].type == this) {
+ set = set.slice(0, i).concat(set.slice(i + 1))
+ i--
+ }
+ return set
+ }
+
+ /// Tests whether there is a mark of this type in the given set.
+ isInSet(set: readonly Mark[]): Mark | undefined {
+ for (let i = 0; i < set.length; i++)
+ if (set[i].type == this) return set[i]
+ }
+
+ /// @internal
+ checkAttrs(attrs: Attrs) {
+ checkAttrs(this.attrs, attrs, "mark", this.name)
+ }
+
+ /// Queries whether a given mark type is
+ /// [excluded](#model.MarkSpec.excludes) by this one.
+ excludes(other: MarkType) {
+ return this.excluded.indexOf(other) > -1
+ }
+}
+
+/// An object describing a schema, as passed to the [`Schema`](#model.Schema)
+/// constructor.
+export interface SchemaSpec<Nodes extends string = any, Marks extends string = any> {
+ /// The node types in this schema. Maps names to
+ /// [`NodeSpec`](#model.NodeSpec) objects that describe the node type
+ /// associated with that name. Their order is significant—it
+ /// determines which [parse rules](#model.NodeSpec.parseDOM) take
+ /// precedence by default, and which nodes come first in a given
+ /// [group](#model.NodeSpec.group).
+ nodes: {[name in Nodes]: NodeSpec} | OrderedMap<NodeSpec>,
+
+ /// The mark types that exist in this schema. The order in which they
+ /// are provided determines the order in which [mark
+ /// sets](#model.Mark.addToSet) are sorted and in which [parse
+ /// rules](#model.MarkSpec.parseDOM) are tried.
+ marks?: {[name in Marks]: MarkSpec} | OrderedMap<MarkSpec>
+
+ /// The name of the default top-level node for the schema. Defaults
+ /// to `"doc"`.
+ topNode?: string
+}
+
+/// A description of a node type, used when defining a schema.
+export interface NodeSpec {
+ /// The content expression for this node, as described in the [schema
+ /// guide](/docs/guide/#schema.content_expressions). When not given,
+ /// the node does not allow any content.
+ content?: string
+
+ /// The marks that are allowed inside of this node. May be a
+ /// space-separated string referring to mark names or groups, `"_"`
+ /// to explicitly allow all marks, or `""` to disallow marks. When
+ /// not given, nodes with inline content default to allowing all
+ /// marks, other nodes default to not allowing marks.
+ marks?: string
+
+ /// The group or space-separated groups to which this node belongs,
+ /// which can be referred to in the content expressions for the
+ /// schema.
+ group?: string
+
+ /// Should be set to true for inline nodes. (Implied for text nodes.)
+ inline?: boolean
+
+ /// Can be set to true to indicate that, though this isn't a [leaf
+ /// node](#model.NodeType.isLeaf), it doesn't have directly editable
+ /// content and should be treated as a single unit in the view.
+ atom?: boolean
+
+ /// The attributes that nodes of this type get.
+ attrs?: {[name: string]: AttributeSpec}
+
+ /// Controls whether nodes of this type can be selected as a [node
+ /// selection](#state.NodeSelection). Defaults to true for non-text
+ /// nodes.
+ selectable?: boolean
+
+ /// Determines whether nodes of this type can be dragged without
+ /// being selected. Defaults to false.
+ draggable?: boolean
+
+ /// Can be used to indicate that this node contains code, which
+ /// causes some commands to behave differently.
+ code?: boolean
+
+ /// Controls way whitespace in this a node is parsed. The default is
+ /// `"normal"`, which causes the [DOM parser](#model.DOMParser) to
+ /// collapse whitespace in normal mode, and normalize it (replacing
+ /// newlines and such with spaces) otherwise. `"pre"` causes the
+ /// parser to preserve spaces inside the node. When this option isn't
+ /// given, but [`code`](#model.NodeSpec.code) is true, `whitespace`
+ /// will default to `"pre"`. Note that this option doesn't influence
+ /// the way the node is rendered—that should be handled by `toDOM`
+ /// and/or styling.
+ whitespace?: "pre" | "normal"
+
+ /// Determines whether this node is considered an important parent
+ /// node during replace operations (such as paste). Non-defining (the
+ /// default) nodes get dropped when their entire content is replaced,
+ /// whereas defining nodes persist and wrap the inserted content.
+ definingAsContext?: boolean
+
+ /// In inserted content the defining parents of the content are
+ /// preserved when possible. Typically, non-default-paragraph
+ /// textblock types, and possibly list items, are marked as defining.
+ definingForContent?: boolean
+
+ /// When enabled, enables both
+ /// [`definingAsContext`](#model.NodeSpec.definingAsContext) and
+ /// [`definingForContent`](#model.NodeSpec.definingForContent).
+ defining?: boolean
+
+ /// When enabled (default is false), the sides of nodes of this type
+ /// count as boundaries that regular editing operations, like
+ /// backspacing or lifting, won't cross. An example of a node that
+ /// should probably have this enabled is a table cell.
+ isolating?: boolean
+
+ /// Defines the default way a node of this type should be serialized
+ /// to DOM/HTML (as used by
+ /// [`DOMSerializer.fromSchema`](#model.DOMSerializer^fromSchema)).
+ /// Should return a DOM node or an [array
+ /// structure](#model.DOMOutputSpec) that describes one, with an
+ /// optional number zero (“hole”) in it to indicate where the node's
+ /// content should be inserted.
+ ///
+ /// For text nodes, the default is to create a text DOM node. Though
+ /// it is possible to create a serializer where text is rendered
+ /// differently, this is not supported inside the editor, so you
+ /// shouldn't override that in your text node spec.
+ toDOM?: (node: Node) => DOMOutputSpec
+
+ /// Associates DOM parser information with this node, which can be
+ /// used by [`DOMParser.fromSchema`](#model.DOMParser^fromSchema) to
+ /// automatically derive a parser. The `node` field in the rules is
+ /// implied (the name of this node will be filled in automatically).
+ /// If you supply your own parser, you do not need to also specify
+ /// parsing rules in your schema.
+ parseDOM?: readonly TagParseRule[]
+
+ /// Defines the default way a node of this type should be serialized
+ /// to a string representation for debugging (e.g. in error messages).
+ toDebugString?: (node: Node) => string
+
+ /// Defines the default way a [leaf node](#model.NodeType.isLeaf) of
+ /// this type should be serialized to a string (as used by
+ /// [`Node.textBetween`](#model.Node.textBetween) and
+ /// [`Node.textContent`](#model.Node.textContent)).
+ leafText?: (node: Node) => string
+
+ /// A single inline node in a schema can be set to be a linebreak
+ /// equivalent. When converting between block types that support the
+ /// node and block types that don't but have
+ /// [`whitespace`](#model.NodeSpec.whitespace) set to `"pre"`,
+ /// [`setBlockType`](#transform.Transform.setBlockType) will convert
+ /// between newline characters to or from linebreak nodes as
+ /// appropriate.
+ linebreakReplacement?: boolean
+
+ /// Node specs may include arbitrary properties that can be read by
+ /// other code via [`NodeType.spec`](#model.NodeType.spec).
+ [key: string]: any
+}
+
+/// Used to define marks when creating a schema.
+export interface MarkSpec {
+ /// The attributes that marks of this type get.
+ attrs?: {[name: string]: AttributeSpec}
+
+ /// Whether this mark should be active when the cursor is positioned
+ /// at its end (or at its start when that is also the start of the
+ /// parent node). Defaults to true.
+ inclusive?: boolean
+
+ /// Determines which other marks this mark can coexist with. Should
+ /// be a space-separated strings naming other marks or groups of marks.
+ /// When a mark is [added](#model.Mark.addToSet) to a set, all marks
+ /// that it excludes are removed in the process. If the set contains
+ /// any mark that excludes the new mark but is not, itself, excluded
+ /// by the new mark, the mark can not be added an the set. You can
+ /// use the value `"_"` to indicate that the mark excludes all
+ /// marks in the schema.
+ ///
+ /// Defaults to only being exclusive with marks of the same type. You
+ /// can set it to an empty string (or any string not containing the
+ /// mark's own name) to allow multiple marks of a given type to
+ /// coexist (as long as they have different attributes).
+ excludes?: string
+
+ /// The group or space-separated groups to which this mark belongs.
+ group?: string
+
+ /// Determines whether marks of this type can span multiple adjacent
+ /// nodes when serialized to DOM/HTML. Defaults to true.
+ spanning?: boolean
+
+ /// Marks the content of this span as being code, which causes some
+ /// commands and extensions to treat it differently.
+ code?: boolean
+
+ /// Defines the default way marks of this type should be serialized
+ /// to DOM/HTML. When the resulting spec contains a hole, that is
+ /// where the marked content is placed. Otherwise, it is appended to
+ /// the top node.
+ toDOM?: (mark: Mark, inline: boolean) => DOMOutputSpec
+
+ /// Associates DOM parser information with this mark (see the
+ /// corresponding [node spec field](#model.NodeSpec.parseDOM)). The
+ /// `mark` field in the rules is implied.
+ parseDOM?: readonly ParseRule[]
+
+ /// Mark specs can include additional properties that can be
+ /// inspected through [`MarkType.spec`](#model.MarkType.spec) when
+ /// working with the mark.
+ [key: string]: any
+}
+
+/// Used to [define](#model.NodeSpec.attrs) attributes on nodes or
+/// marks.
+export interface AttributeSpec {
+ /// The default value for this attribute, to use when no explicit
+ /// value is provided. Attributes that have no default must be
+ /// provided whenever a node or mark of a type that has them is
+ /// created.
+ default?: any
+ /// A function or type name used to validate values of this
+ /// attribute. This will be used when deserializing the attribute
+ /// from JSON, and when running [`Node.check`](#model.Node.check).
+ /// When a function, it should raise an exception if the value isn't
+ /// of the expected type or shape. When a string, it should be a
+ /// `|`-separated string of primitive types (`"number"`, `"string"`,
+ /// `"boolean"`, `"null"`, and `"undefined"`), and the library will
+ /// raise an error when the value is not one of those types.
+ validate?: string | ((value: any) => void)
+}
+
+/// A document schema. Holds [node](#model.NodeType) and [mark
+/// type](#model.MarkType) objects for the nodes and marks that may
+/// occur in conforming documents, and provides functionality for
+/// creating and deserializing such documents.
+///
+/// When given, the type parameters provide the names of the nodes and
+/// marks in this schema.
+export class Schema<Nodes extends string = any, Marks extends string = any> {
+ /// The [spec](#model.SchemaSpec) on which the schema is based,
+ /// with the added guarantee that its `nodes` and `marks`
+ /// properties are
+ /// [`OrderedMap`](https://github.com/marijnh/orderedmap) instances
+ /// (not raw objects).
+ spec: {
+ nodes: OrderedMap<NodeSpec>,
+ marks: OrderedMap<MarkSpec>,
+ topNode?: string
+ }
+
+ /// An object mapping the schema's node names to node type objects.
+ nodes: {readonly [name in Nodes]: NodeType} & {readonly [key: string]: NodeType}
+
+ /// A map from mark names to mark type objects.
+ marks: {readonly [name in Marks]: MarkType} & {readonly [key: string]: MarkType}
+
+ /// The [linebreak
+ /// replacement](#model.NodeSpec.linebreakReplacement) node defined
+ /// in this schema, if any.
+ linebreakReplacement: NodeType | null = null
+
+ /// Construct a schema from a schema [specification](#model.SchemaSpec).
+ constructor(spec: SchemaSpec<Nodes, Marks>) {
+ let instanceSpec = this.spec = {} as any
+ for (let prop in spec) instanceSpec[prop] = (spec as any)[prop]
+ instanceSpec.nodes = OrderedMap.from(spec.nodes),
+ instanceSpec.marks = OrderedMap.from(spec.marks || {}),
+
+ this.nodes = NodeType.compile(this.spec.nodes, this)
+ this.marks = MarkType.compile(this.spec.marks, this)
+
+ let contentExprCache = Object.create(null)
+ for (let prop in this.nodes) {
+ if (prop in this.marks)
+ throw new RangeError(prop + " can not be both a node and a mark")
+ let type = this.nodes[prop], contentExpr = type.spec.content || "", markExpr = type.spec.marks
+ type.contentMatch = contentExprCache[contentExpr] ||
+ (contentExprCache[contentExpr] = ContentMatch.parse(contentExpr, this.nodes))
+ ;(type as any).inlineContent = type.contentMatch.inlineContent
+ if (type.spec.linebreakReplacement) {
+ if (this.linebreakReplacement) throw new RangeError("Multiple linebreak nodes defined")
+ if (!type.isInline || !type.isLeaf) throw new RangeError("Linebreak replacement nodes must be inline leaf nodes")
+ this.linebreakReplacement = type
+ }
+ type.markSet = markExpr == "_" ? null :
+ markExpr ? gatherMarks(this, markExpr.split(" ")) :
+ markExpr == "" || !type.inlineContent ? [] : null
+ }
+ for (let prop in this.marks) {
+ let type = this.marks[prop], excl = type.spec.excludes
+ type.excluded = excl == null ? [type] : excl == "" ? [] : gatherMarks(this, excl.split(" "))
+ }
+
+ this.nodeFromJSON = json => Node.fromJSON(this, json)
+ this.markFromJSON = json => Mark.fromJSON(this, json)
+ this.topNodeType = this.nodes[this.spec.topNode || "doc"]
+ this.cached.wrappings = Object.create(null)
+ }
+
+ /// The type of the [default top node](#model.SchemaSpec.topNode)
+ /// for this schema.
+ topNodeType: NodeType
+
+ /// An object for storing whatever values modules may want to
+ /// compute and cache per schema. (If you want to store something
+ /// in it, try to use property names unlikely to clash.)
+ cached: {[key: string]: any} = Object.create(null)
+
+ /// Create a node in this schema. The `type` may be a string or a
+ /// `NodeType` instance. Attributes will be extended with defaults,
+ /// `content` may be a `Fragment`, `null`, a `Node`, or an array of
+ /// nodes.
+ node(type: string | NodeType,
+ attrs: Attrs | null = null,
+ content?: Fragment | Node | readonly Node[],
+ marks?: readonly Mark[]) {
+ if (typeof type == "string")
+ type = this.nodeType(type)
+ else if (!(type instanceof NodeType))
+ throw new RangeError("Invalid node type: " + type)
+ else if (type.schema != this)
+ throw new RangeError("Node type from different schema used (" + type.name + ")")
+
+ return type.createChecked(attrs, content, marks)
+ }
+
+ /// Create a text node in the schema. Empty text nodes are not
+ /// allowed.
+ text(text: string, marks?: readonly Mark[] | null): Node {
+ let type = this.nodes.text
+ return new TextNode(type, type.defaultAttrs, text, Mark.setFrom(marks))
+ }
+
+ /// Create a mark with the given type and attributes.
+ mark(type: string | MarkType, attrs?: Attrs | null) {
+ if (typeof type == "string") type = this.marks[type]
+ return type.create(attrs)
+ }
+
+ /// Deserialize a node from its JSON representation. This method is
+ /// bound.
+ nodeFromJSON: (json: any) => Node
+
+ /// Deserialize a mark from its JSON representation. This method is
+ /// bound.
+ markFromJSON: (json: any) => Mark
+
+ /// @internal
+ nodeType(name: string) {
+ let found = this.nodes[name]
+ if (!found) throw new RangeError("Unknown node type: " + name)
+ return found
+ }
+}
+
+function gatherMarks(schema: Schema, marks: readonly string[]) {
+ let found: MarkType[] = []
+ for (let i = 0; i < marks.length; i++) {
+ let name = marks[i], mark = schema.marks[name], ok = mark
+ if (mark) {
+ found.push(mark)
+ } else {
+ for (let prop in schema.marks) {
+ let mark = schema.marks[prop]
+ if (name == "_" || (mark.spec.group && mark.spec.group.split(" ").indexOf(name) > -1))
+ found.push(ok = mark)
+ }
+ }
+ if (!ok) throw new SyntaxError("Unknown mark type: '" + marks[i] + "'")
+ }
+ return found
+}
diff --git a/third_party/js/prosemirror/prosemirror-model/src/to_dom.ts b/third_party/js/prosemirror/prosemirror-model/src/to_dom.ts
@@ -0,0 +1,238 @@
+import {Fragment} from "./fragment"
+import {Node} from "./node"
+import {Schema, NodeType, MarkType} from "./schema"
+import {Mark} from "./mark"
+import {DOMNode} from "./dom"
+
+/// A description of a DOM structure. Can be either a string, which is
+/// interpreted as a text node, a DOM node, which is interpreted as
+/// itself, a `{dom, contentDOM}` object, or an array.
+///
+/// An array describes a DOM element. The first value in the array
+/// should be a string—the name of the DOM element, optionally prefixed
+/// by a namespace URL and a space. If the second element is plain
+/// object, it is interpreted as a set of attributes for the element.
+/// Any elements after that (including the 2nd if it's not an attribute
+/// object) are interpreted as children of the DOM elements, and must
+/// either be valid `DOMOutputSpec` values, or the number zero.
+///
+/// The number zero (pronounced “hole”) is used to indicate the place
+/// where a node's child nodes should be inserted. If it occurs in an
+/// output spec, it should be the only child element in its parent
+/// node.
+export type DOMOutputSpec = string | DOMNode | {dom: DOMNode, contentDOM?: HTMLElement} | readonly [string, ...any[]]
+
+/// A DOM serializer knows how to convert ProseMirror nodes and
+/// marks of various types to DOM nodes.
+export class DOMSerializer {
+ /// Create a serializer. `nodes` should map node names to functions
+ /// that take a node and return a description of the corresponding
+ /// DOM. `marks` does the same for mark names, but also gets an
+ /// argument that tells it whether the mark's content is block or
+ /// inline content (for typical use, it'll always be inline). A mark
+ /// serializer may be `null` to indicate that marks of that type
+ /// should not be serialized.
+ constructor(
+ /// The node serialization functions.
+ readonly nodes: {[node: string]: (node: Node) => DOMOutputSpec},
+ /// The mark serialization functions.
+ readonly marks: {[mark: string]: (mark: Mark, inline: boolean) => DOMOutputSpec}
+ ) {}
+
+ /// Serialize the content of this fragment to a DOM fragment. When
+ /// not in the browser, the `document` option, containing a DOM
+ /// document, should be passed so that the serializer can create
+ /// nodes.
+ serializeFragment(fragment: Fragment, options: {document?: Document} = {}, target?: HTMLElement | DocumentFragment) {
+ if (!target) target = doc(options).createDocumentFragment()
+
+ let top = target!, active: [Mark, HTMLElement | DocumentFragment][] = []
+ fragment.forEach(node => {
+ if (active.length || node.marks.length) {
+ let keep = 0, rendered = 0
+ while (keep < active.length && rendered < node.marks.length) {
+ let next = node.marks[rendered]
+ if (!this.marks[next.type.name]) { rendered++; continue }
+ if (!next.eq(active[keep][0]) || next.type.spec.spanning === false) break
+ keep++; rendered++
+ }
+ while (keep < active.length) top = active.pop()![1]
+ while (rendered < node.marks.length) {
+ let add = node.marks[rendered++]
+ let markDOM = this.serializeMark(add, node.isInline, options)
+ if (markDOM) {
+ active.push([add, top])
+ top.appendChild(markDOM.dom)
+ top = markDOM.contentDOM || markDOM.dom as HTMLElement
+ }
+ }
+ }
+ top.appendChild(this.serializeNodeInner(node, options))
+ })
+
+ return target
+ }
+
+ /// @internal
+ serializeNodeInner(node: Node, options: {document?: Document}) {
+ let {dom, contentDOM} =
+ renderSpec(doc(options), this.nodes[node.type.name](node), null, node.attrs)
+ if (contentDOM) {
+ if (node.isLeaf)
+ throw new RangeError("Content hole not allowed in a leaf node spec")
+ this.serializeFragment(node.content, options, contentDOM)
+ }
+ return dom
+ }
+
+ /// Serialize this node to a DOM node. This can be useful when you
+ /// need to serialize a part of a document, as opposed to the whole
+ /// document. To serialize a whole document, use
+ /// [`serializeFragment`](#model.DOMSerializer.serializeFragment) on
+ /// its [content](#model.Node.content).
+ serializeNode(node: Node, options: {document?: Document} = {}) {
+ let dom = this.serializeNodeInner(node, options)
+ for (let i = node.marks.length - 1; i >= 0; i--) {
+ let wrap = this.serializeMark(node.marks[i], node.isInline, options)
+ if (wrap) {
+ ;(wrap.contentDOM || wrap.dom).appendChild(dom)
+ dom = wrap.dom
+ }
+ }
+ return dom
+ }
+
+ /// @internal
+ serializeMark(mark: Mark, inline: boolean, options: {document?: Document} = {}) {
+ let toDOM = this.marks[mark.type.name]
+ return toDOM && renderSpec(doc(options), toDOM(mark, inline), null, mark.attrs)
+ }
+
+ /// Render an [output spec](#model.DOMOutputSpec) to a DOM node. If
+ /// the spec has a hole (zero) in it, `contentDOM` will point at the
+ /// node with the hole.
+ static renderSpec(doc: Document, structure: DOMOutputSpec, xmlNS?: string | null): {
+ dom: DOMNode,
+ contentDOM?: HTMLElement
+ }
+ static renderSpec(doc: Document, structure: DOMOutputSpec, xmlNS: string | null = null,
+ blockArraysIn?: {[name: string]: any}): {
+ dom: DOMNode,
+ contentDOM?: HTMLElement
+ } {
+ return renderSpec(doc, structure, xmlNS, blockArraysIn)
+ }
+
+ /// Build a serializer using the [`toDOM`](#model.NodeSpec.toDOM)
+ /// properties in a schema's node and mark specs.
+ static fromSchema(schema: Schema): DOMSerializer {
+ return schema.cached.domSerializer as DOMSerializer ||
+ (schema.cached.domSerializer = new DOMSerializer(this.nodesFromSchema(schema), this.marksFromSchema(schema)))
+ }
+
+ /// Gather the serializers in a schema's node specs into an object.
+ /// This can be useful as a base to build a custom serializer from.
+ static nodesFromSchema(schema: Schema) {
+ let result = gatherToDOM(schema.nodes)
+ if (!result.text) result.text = node => node.text
+ return result as {[node: string]: (node: Node) => DOMOutputSpec}
+ }
+
+ /// Gather the serializers in a schema's mark specs into an object.
+ static marksFromSchema(schema: Schema) {
+ return gatherToDOM(schema.marks) as {[mark: string]: (mark: Mark, inline: boolean) => DOMOutputSpec}
+ }
+}
+
+function gatherToDOM(obj: {[node: string]: NodeType | MarkType}) {
+ let result: {[node: string]: (value: any, inline: boolean) => DOMOutputSpec} = {}
+ for (let name in obj) {
+ let toDOM = obj[name].spec.toDOM
+ if (toDOM) result[name] = toDOM
+ }
+ return result
+}
+
+function doc(options: {document?: Document}) {
+ return options.document || window.document
+}
+
+const suspiciousAttributeCache = new WeakMap<any, readonly any[] | null>()
+
+function suspiciousAttributes(attrs: {[name: string]: any}): readonly any[] | null {
+ let value = suspiciousAttributeCache.get(attrs)
+ if (value === undefined)
+ suspiciousAttributeCache.set(attrs, value = suspiciousAttributesInner(attrs))
+ return value
+}
+
+function suspiciousAttributesInner(attrs: {[name: string]: any}): readonly any[] | null {
+ let result: any[] | null = null
+ function scan(value: any) {
+ if (value && typeof value == "object") {
+ if (Array.isArray(value)) {
+ if (typeof value[0] == "string") {
+ if (!result) result = []
+ result.push(value)
+ } else {
+ for (let i = 0; i < value.length; i++) scan(value[i])
+ }
+ } else {
+ for (let prop in value) scan(value[prop])
+ }
+ }
+ }
+ scan(attrs)
+ return result
+}
+
+function renderSpec(doc: Document, structure: DOMOutputSpec, xmlNS: string | null,
+ blockArraysIn?: {[name: string]: any}): {
+ dom: DOMNode,
+ contentDOM?: HTMLElement
+} {
+ if (typeof structure == "string")
+ return {dom: doc.createTextNode(structure)}
+ if ((structure as DOMNode).nodeType != null)
+ return {dom: structure as DOMNode}
+ if ((structure as any).dom && (structure as any).dom.nodeType != null)
+ return structure as {dom: DOMNode, contentDOM?: HTMLElement}
+ let tagName = (structure as [string])[0], suspicious
+ if (typeof tagName != "string") throw new RangeError("Invalid array passed to renderSpec")
+ if (blockArraysIn && (suspicious = suspiciousAttributes(blockArraysIn)) &&
+ suspicious.indexOf(structure) > -1)
+ throw new RangeError("Using an array from an attribute object as a DOM spec. This may be an attempted cross site scripting attack.")
+ let space = tagName.indexOf(" ")
+ if (space > 0) {
+ xmlNS = tagName.slice(0, space)
+ tagName = tagName.slice(space + 1)
+ }
+ let contentDOM: HTMLElement | undefined
+ let dom = (xmlNS ? doc.createElementNS(xmlNS, tagName) : doc.createElement(tagName)) as HTMLElement
+ let attrs = (structure as any)[1], start = 1
+ if (attrs && typeof attrs == "object" && attrs.nodeType == null && !Array.isArray(attrs)) {
+ start = 2
+ for (let name in attrs) if (attrs[name] != null) {
+ let space = name.indexOf(" ")
+ if (space > 0) dom.setAttributeNS(name.slice(0, space), name.slice(space + 1), attrs[name])
+ else if (name == "style" && dom.style) dom.style.cssText = attrs[name]
+ else dom.setAttribute(name, attrs[name])
+ }
+ }
+ for (let i = start; i < (structure as readonly any[]).length; i++) {
+ let child = (structure as any)[i] as DOMOutputSpec | 0
+ if (child === 0) {
+ if (i < (structure as readonly any[]).length - 1 || i > start)
+ throw new RangeError("Content hole must be the only child of its parent node")
+ return {dom, contentDOM: dom}
+ } else {
+ let {dom: inner, contentDOM: innerContent} = renderSpec(doc, child, xmlNS, blockArraysIn)
+ dom.appendChild(inner)
+ if (innerContent) {
+ if (contentDOM) throw new RangeError("Multiple content holes")
+ contentDOM = innerContent as HTMLElement
+ }
+ }
+ }
+ return {dom, contentDOM}
+}
diff --git a/third_party/js/prosemirror/prosemirror-schema-basic/LICENSE b/third_party/js/prosemirror/prosemirror-schema-basic/LICENSE
@@ -0,0 +1,19 @@
+Copyright (C) 2015-2017 by Marijn Haverbeke <marijn@haverbeke.berlin> and others
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
diff --git a/third_party/js/prosemirror/prosemirror-schema-basic/moz.yaml b/third_party/js/prosemirror/prosemirror-schema-basic/moz.yaml
@@ -0,0 +1,63 @@
+# Version of this schema
+schema: 1
+
+bugzilla:
+ # Bugzilla product and component for this directory and subdirectories
+ product: Firefox
+ component: General
+
+# Document the source of externally hosted code
+origin:
+
+ # Short name of the package/library
+ name: prosemirror-schema-basic
+
+ description: Basic schema elements for ProseMirror
+
+ # Full URL for the package's homepage/etc
+ # Usually different from repository url
+ url: https://prosemirror.net/
+
+ # Human-readable identifier for this version/release
+ # Generally "version NNN", "tag SSS", "bookmark SSS"
+ release: 1.2.4 (2025-03-18T09:14:56+01:00).
+
+ # Revision to pull in
+ # Must be a long or short commit SHA (long preferred) or a tag
+ revision: 1.2.4
+
+ # The package's license, where possible using the mnemonic from
+ # https://spdx.org/licenses/
+ # Multiple licenses can be specified (as a YAML list)
+ # A "LICENSE" file must exist containing the full license text
+ license: MIT
+
+ # If the package's license is specified in a particular file,
+ # this is the name of the file.
+ # optional
+ license-file: LICENSE
+
+# Configuration for the automated vendoring system.
+# optional
+vendoring:
+
+ # Repository URL to vendor from
+ # eg. https://github.com/kinetiknz/nestegg
+ # Any repository host can be specified here, however initially we'll only
+ # support automated vendoring from selected sources.
+ url: https://github.com/ProseMirror/prosemirror-schema-basic
+
+ # Type of hosting for the upstream repository
+ # Valid values are 'gitlab', 'github', googlesource
+ source-hosting: github
+
+ # Whether to track by commit or tag
+ tracking: tag
+
+ exclude:
+ - "**"
+
+ include:
+ - LICENSE
+ - src/
+ - package.json
diff --git a/third_party/js/prosemirror/prosemirror-schema-basic/package.json b/third_party/js/prosemirror/prosemirror-schema-basic/package.json
@@ -0,0 +1,37 @@
+{
+ "name": "prosemirror-schema-basic",
+ "version": "1.2.4",
+ "description": "Basic schema elements for ProseMirror",
+ "type": "module",
+ "main": "dist/index.cjs",
+ "module": "dist/index.js",
+ "types": "dist/index.d.ts",
+ "exports": {
+ "import": "./dist/index.js",
+ "require": "./dist/index.cjs"
+ },
+ "sideEffects": false,
+ "license": "MIT",
+ "maintainers": [
+ {
+ "name": "Marijn Haverbeke",
+ "email": "marijn@haverbeke.berlin",
+ "web": "http://marijnhaverbeke.nl"
+ }
+ ],
+ "repository": {
+ "type": "git",
+ "url": "git://github.com/prosemirror/prosemirror-schema-basic.git"
+ },
+ "dependencies": {
+ "prosemirror-model": "^1.25.0"
+ },
+ "devDependencies": {
+ "@prosemirror/buildhelper": "^0.1.5",
+ "prosemirror-test-builder": "^1.0.0"
+ },
+ "scripts": {
+ "test": "pm-runtests",
+ "prepare": "pm-buildhelper src/schema-basic.ts"
+ }
+}
diff --git a/third_party/js/prosemirror/prosemirror-schema-basic/src/README.md b/third_party/js/prosemirror/prosemirror-schema-basic/src/README.md
@@ -0,0 +1,8 @@
+This module defines a simple schema. You can use it directly, extend
+it, or just pick out a few node and mark specs to use in a new schema.
+
+@schema
+
+@nodes
+
+@marks
diff --git a/third_party/js/prosemirror/prosemirror-schema-basic/src/schema-basic.ts b/third_party/js/prosemirror/prosemirror-schema-basic/src/schema-basic.ts
@@ -0,0 +1,166 @@
+import {Schema, NodeSpec, MarkSpec, DOMOutputSpec} from "prosemirror-model"
+
+const pDOM: DOMOutputSpec = ["p", 0], blockquoteDOM: DOMOutputSpec = ["blockquote", 0],
+ hrDOM: DOMOutputSpec = ["hr"], preDOM: DOMOutputSpec = ["pre", ["code", 0]],
+ brDOM: DOMOutputSpec = ["br"]
+
+/// [Specs](#model.NodeSpec) for the nodes defined in this schema.
+export const nodes = {
+ /// NodeSpec The top level document node.
+ doc: {
+ content: "block+"
+ } as NodeSpec,
+
+ /// A plain paragraph textblock. Represented in the DOM
+ /// as a `<p>` element.
+ paragraph: {
+ content: "inline*",
+ group: "block",
+ parseDOM: [{tag: "p"}],
+ toDOM() { return pDOM }
+ } as NodeSpec,
+
+ /// A blockquote (`<blockquote>`) wrapping one or more blocks.
+ blockquote: {
+ content: "block+",
+ group: "block",
+ defining: true,
+ parseDOM: [{tag: "blockquote"}],
+ toDOM() { return blockquoteDOM }
+ } as NodeSpec,
+
+ /// A horizontal rule (`<hr>`).
+ horizontal_rule: {
+ group: "block",
+ parseDOM: [{tag: "hr"}],
+ toDOM() { return hrDOM }
+ } as NodeSpec,
+
+ /// A heading textblock, with a `level` attribute that
+ /// should hold the number 1 to 6. Parsed and serialized as `<h1>` to
+ /// `<h6>` elements.
+ heading: {
+ attrs: {level: {default: 1, validate: "number"}},
+ content: "inline*",
+ group: "block",
+ defining: true,
+ parseDOM: [{tag: "h1", attrs: {level: 1}},
+ {tag: "h2", attrs: {level: 2}},
+ {tag: "h3", attrs: {level: 3}},
+ {tag: "h4", attrs: {level: 4}},
+ {tag: "h5", attrs: {level: 5}},
+ {tag: "h6", attrs: {level: 6}}],
+ toDOM(node) { return ["h" + node.attrs.level, 0] }
+ } as NodeSpec,
+
+ /// A code listing. Disallows marks or non-text inline
+ /// nodes by default. Represented as a `<pre>` element with a
+ /// `<code>` element inside of it.
+ code_block: {
+ content: "text*",
+ marks: "",
+ group: "block",
+ code: true,
+ defining: true,
+ parseDOM: [{tag: "pre", preserveWhitespace: "full"}],
+ toDOM() { return preDOM }
+ } as NodeSpec,
+
+ /// The text node.
+ text: {
+ group: "inline"
+ } as NodeSpec,
+
+ /// An inline image (`<img>`) node. Supports `src`,
+ /// `alt`, and `href` attributes. The latter two default to the empty
+ /// string.
+ image: {
+ inline: true,
+ attrs: {
+ src: {validate: "string"},
+ alt: {default: null, validate: "string|null"},
+ title: {default: null, validate: "string|null"}
+ },
+ group: "inline",
+ draggable: true,
+ parseDOM: [{tag: "img[src]", getAttrs(dom: HTMLElement) {
+ return {
+ src: dom.getAttribute("src"),
+ title: dom.getAttribute("title"),
+ alt: dom.getAttribute("alt")
+ }
+ }}],
+ toDOM(node) { let {src, alt, title} = node.attrs; return ["img", {src, alt, title}] }
+ } as NodeSpec,
+
+ /// A hard line break, represented in the DOM as `<br>`.
+ hard_break: {
+ inline: true,
+ group: "inline",
+ selectable: false,
+ parseDOM: [{tag: "br"}],
+ toDOM() { return brDOM }
+ } as NodeSpec
+}
+
+const emDOM: DOMOutputSpec = ["em", 0], strongDOM: DOMOutputSpec = ["strong", 0], codeDOM: DOMOutputSpec = ["code", 0]
+
+/// [Specs](#model.MarkSpec) for the marks in the schema.
+export const marks = {
+ /// A link. Has `href` and `title` attributes. `title`
+ /// defaults to the empty string. Rendered and parsed as an `<a>`
+ /// element.
+ link: {
+ attrs: {
+ href: {validate: "string"},
+ title: {default: null, validate: "string|null"}
+ },
+ inclusive: false,
+ parseDOM: [{tag: "a[href]", getAttrs(dom: HTMLElement) {
+ return {href: dom.getAttribute("href"), title: dom.getAttribute("title")}
+ }}],
+ toDOM(node) { let {href, title} = node.attrs; return ["a", {href, title}, 0] }
+ } as MarkSpec,
+
+ /// An emphasis mark. Rendered as an `<em>` element. Has parse rules
+ /// that also match `<i>` and `font-style: italic`.
+ em: {
+ parseDOM: [
+ {tag: "i"}, {tag: "em"},
+ {style: "font-style=italic"},
+ {style: "font-style=normal", clearMark: m => m.type.name == "em"}
+ ],
+ toDOM() { return emDOM }
+ } as MarkSpec,
+
+ /// A strong mark. Rendered as `<strong>`, parse rules also match
+ /// `<b>` and `font-weight: bold`.
+ strong: {
+ parseDOM: [
+ {tag: "strong"},
+ // This works around a Google Docs misbehavior where
+ // pasted content will be inexplicably wrapped in `<b>`
+ // tags with a font-weight normal.
+ {tag: "b", getAttrs: (node: HTMLElement) => node.style.fontWeight != "normal" && null},
+ {style: "font-weight=400", clearMark: m => m.type.name == "strong"},
+ {style: "font-weight", getAttrs: (value: string) => /^(bold(er)?|[5-9]\d{2,})$/.test(value) && null},
+ ],
+ toDOM() { return strongDOM }
+ } as MarkSpec,
+
+ /// Code font mark. Represented as a `<code>` element.
+ code: {
+ code: true,
+ parseDOM: [{tag: "code"}],
+ toDOM() { return codeDOM }
+ } as MarkSpec
+}
+
+/// This schema roughly corresponds to the document schema used by
+/// [CommonMark](http://commonmark.org/), minus the list elements,
+/// which are defined in the [`prosemirror-schema-list`](#schema-list)
+/// module.
+///
+/// To reuse elements from this schema, extend or read from its
+/// `spec.nodes` and `spec.marks` [properties](#model.Schema.spec).
+export const schema = new Schema({nodes, marks})
diff --git a/third_party/js/prosemirror/prosemirror-state/LICENSE b/third_party/js/prosemirror/prosemirror-state/LICENSE
@@ -0,0 +1,19 @@
+Copyright (C) 2015-2017 by Marijn Haverbeke <marijn@haverbeke.berlin> and others
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
diff --git a/third_party/js/prosemirror/prosemirror-state/moz.yaml b/third_party/js/prosemirror/prosemirror-state/moz.yaml
@@ -0,0 +1,63 @@
+# Version of this schema
+schema: 1
+
+bugzilla:
+ # Bugzilla product and component for this directory and subdirectories
+ product: Firefox
+ component: General
+
+# Document the source of externally hosted code
+origin:
+
+ # Short name of the package/library
+ name: prosemirror-state
+
+ description: ProseMirror editor state
+
+ # Full URL for the package's homepage/etc
+ # Usually different from repository url
+ url: https://prosemirror.net/
+
+ # Human-readable identifier for this version/release
+ # Generally "version NNN", "tag SSS", "bookmark SSS"
+ release: 1.4.4 (2025-10-23T13:57:31+02:00).
+
+ # Revision to pull in
+ # Must be a long or short commit SHA (long preferred) or a tag
+ revision: 1.4.4
+
+ # The package's license, where possible using the mnemonic from
+ # https://spdx.org/licenses/
+ # Multiple licenses can be specified (as a YAML list)
+ # A "LICENSE" file must exist containing the full license text
+ license: MIT
+
+ # If the package's license is specified in a particular file,
+ # this is the name of the file.
+ # optional
+ license-file: LICENSE
+
+# Configuration for the automated vendoring system.
+# optional
+vendoring:
+
+ # Repository URL to vendor from
+ # eg. https://github.com/kinetiknz/nestegg
+ # Any repository host can be specified here, however initially we'll only
+ # support automated vendoring from selected sources.
+ url: https://github.com/ProseMirror/prosemirror-state
+
+ # Type of hosting for the upstream repository
+ # Valid values are 'gitlab', 'github', googlesource
+ source-hosting: github
+
+ # Whether to track by commit or tag
+ tracking: tag
+
+ exclude:
+ - "**"
+
+ include:
+ - LICENSE
+ - src/
+ - package.json
diff --git a/third_party/js/prosemirror/prosemirror-state/package.json b/third_party/js/prosemirror/prosemirror-state/package.json
@@ -0,0 +1,39 @@
+{
+ "name": "prosemirror-state",
+ "version": "1.4.4",
+ "description": "ProseMirror editor state",
+ "type": "module",
+ "main": "dist/index.cjs",
+ "module": "dist/index.js",
+ "types": "dist/index.d.ts",
+ "exports": {
+ "import": "./dist/index.js",
+ "require": "./dist/index.cjs"
+ },
+ "sideEffects": false,
+ "license": "MIT",
+ "maintainers": [
+ {
+ "name": "Marijn Haverbeke",
+ "email": "marijn@haverbeke.berlin",
+ "web": "http://marijnhaverbeke.nl"
+ }
+ ],
+ "repository": {
+ "type": "git",
+ "url": "git://github.com/prosemirror/prosemirror-state.git"
+ },
+ "dependencies": {
+ "prosemirror-model": "^1.0.0",
+ "prosemirror-transform": "^1.0.0",
+ "prosemirror-view": "^1.27.0"
+ },
+ "devDependencies": {
+ "@prosemirror/buildhelper": "^0.1.5",
+ "prosemirror-test-builder": "^1.0.0"
+ },
+ "scripts": {
+ "test": "pm-runtests",
+ "prepare": "pm-buildhelper src/index.ts"
+ }
+}
diff --git a/third_party/js/prosemirror/prosemirror-state/src/README.md b/third_party/js/prosemirror/prosemirror-state/src/README.md
@@ -0,0 +1,42 @@
+This module implements the state object of a ProseMirror editor, along
+with the representation of the selection and the plugin abstraction.
+
+### Editor State
+
+ProseMirror keeps all editor state (the things, basically, that would
+be required to create an editor just like the current one) in a single
+[object](#state.EditorState). That object is updated (creating a new
+state) by applying [transactions](#state.Transaction) to it.
+
+@EditorState
+@EditorStateConfig
+@Transaction
+@Command
+
+### Selection
+
+A ProseMirror selection can be one of several types. This module
+defines types for classical [text selections](#state.TextSelection)
+(of which cursors are a special case) and [_node_
+selections](#state.NodeSelection), where a specific document node is
+selected. It is possible to extend the editor with custom selection
+types.
+
+@Selection
+@TextSelection
+@NodeSelection
+@AllSelection
+
+@SelectionRange
+@SelectionBookmark
+
+### Plugin System
+
+To make it easy to package and enable extra editor functionality,
+ProseMirror has a plugin system.
+
+@PluginSpec
+@StateField
+@PluginView
+@Plugin
+@PluginKey
diff --git a/third_party/js/prosemirror/prosemirror-state/src/index.ts b/third_party/js/prosemirror/prosemirror-state/src/index.ts
@@ -0,0 +1,7 @@
+export {Selection, SelectionRange, TextSelection, NodeSelection, AllSelection, SelectionBookmark} from "./selection"
+
+export {Transaction, Command} from "./transaction"
+
+export {EditorState, EditorStateConfig} from "./state"
+
+export {Plugin, PluginKey, PluginSpec, StateField, PluginView} from "./plugin"
diff --git a/third_party/js/prosemirror/prosemirror-state/src/plugin.ts b/third_party/js/prosemirror/prosemirror-state/src/plugin.ts
@@ -0,0 +1,142 @@
+import {type EditorView, type EditorProps} from "prosemirror-view"
+import {EditorState, EditorStateConfig} from "./state"
+import {Transaction} from "./transaction"
+
+/// This is the type passed to the [`Plugin`](#state.Plugin)
+/// constructor. It provides a definition for a plugin.
+export interface PluginSpec<PluginState> {
+ /// The [view props](#view.EditorProps) added by this plugin. Props
+ /// that are functions will be bound to have the plugin instance as
+ /// their `this` binding.
+ props?: EditorProps<Plugin<PluginState>>
+
+ /// Allows a plugin to define a [state field](#state.StateField), an
+ /// extra slot in the state object in which it can keep its own data.
+ state?: StateField<PluginState>
+
+ /// Can be used to make this a keyed plugin. You can have only one
+ /// plugin with a given key in a given state, but it is possible to
+ /// access the plugin's configuration and state through the key,
+ /// without having access to the plugin instance object.
+ key?: PluginKey
+
+ /// When the plugin needs to interact with the editor view, or
+ /// set something up in the DOM, use this field. The function
+ /// will be called when the plugin's state is associated with an
+ /// editor view.
+ view?: (view: EditorView) => PluginView
+
+ /// When present, this will be called before a transaction is
+ /// applied by the state, allowing the plugin to cancel it (by
+ /// returning false).
+ filterTransaction?: (tr: Transaction, state: EditorState) => boolean
+
+ /// Allows the plugin to append another transaction to be applied
+ /// after the given array of transactions. When another plugin
+ /// appends a transaction after this was called, it is called again
+ /// with the new state and new transactions—but only the new
+ /// transactions, i.e. it won't be passed transactions that it
+ /// already saw.
+ appendTransaction?: (transactions: readonly Transaction[], oldState: EditorState, newState: EditorState) => Transaction | null | undefined
+
+ /// Additional properties are allowed on plugin specs, which can be
+ /// read via [`Plugin.spec`](#state.Plugin.spec).
+ [key: string]: any
+}
+
+/// A stateful object that can be installed in an editor by a
+/// [plugin](#state.PluginSpec.view).
+export type PluginView = {
+ /// Called whenever the view's state is updated.
+ update?: (view: EditorView, prevState: EditorState) => void
+
+ /// Called when the view is destroyed or receives a state
+ /// with different plugins.
+ destroy?: () => void
+}
+
+function bindProps(obj: {[prop: string]: any}, self: any, target: {[prop: string]: any}) {
+ 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](#state.EditorState) and
+/// may influence that state and the view that contains it.
+export class Plugin<PluginState = any> {
+ /// Create a plugin.
+ constructor(
+ /// The plugin's [spec object](#state.PluginSpec).
+ readonly spec: PluginSpec<PluginState>
+ ) {
+ if (spec.props) bindProps(spec.props, this, this.props)
+ this.key = spec.key ? spec.key.key : createKey("plugin")
+ }
+
+ /// The [props](#view.EditorProps) exported by this plugin.
+ readonly props: EditorProps<Plugin<PluginState>> = {}
+
+ /// @internal
+ key: string
+
+ /// Extract the plugin's state field from an editor state.
+ getState(state: EditorState): PluginState | undefined { return (state as any)[this.key] }
+}
+
+/// A plugin spec may provide a state field (under its
+/// [`state`](#state.PluginSpec.state) property) of this type, which
+/// describes the state it wants to keep. Functions provided here are
+/// always called with the plugin instance as their `this` binding.
+export interface StateField<T> {
+ /// Initialize the value of the field. `config` will be the object
+ /// passed to [`EditorState.create`](#state.EditorState^create). Note
+ /// that `instance` is a half-initialized state instance, and will
+ /// not have values for plugin fields initialized after this one.
+ init: (config: EditorStateConfig, instance: EditorState) => T
+
+ /// Apply the given transaction to this state field, producing a new
+ /// field value. Note that the `newState` argument is again a partially
+ /// constructed state does not yet contain the state from plugins
+ /// coming after this one.
+ apply: (tr: Transaction, value: T, oldState: EditorState, newState: EditorState) => T
+
+ /// Convert this field to JSON. Optional, can be left off to disable
+ /// JSON serialization for the field.
+ toJSON?: (value: T) => any
+
+ /// Deserialize the JSON representation of this field. Note that the
+ /// `state` argument is again a half-initialized state.
+ fromJSON?: (config: EditorStateConfig, value: any, state: EditorState) => T
+}
+
+const keys = Object.create(null)
+
+function createKey(name: string) {
+ if (name in keys) return name + "$" + ++keys[name]
+ keys[name] = 0
+ return name + "$"
+}
+
+/// A key is used to [tag](#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.
+export class PluginKey<PluginState = any> {
+ /// @internal
+ key: string
+
+ /// 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: EditorState): Plugin<PluginState> | undefined { return state.config.pluginsByKey[this.key] }
+
+ /// Get the plugin's state from an editor state.
+ getState(state: EditorState): PluginState | undefined { return (state as any)[this.key] }
+}
diff --git a/third_party/js/prosemirror/prosemirror-state/src/selection.ts b/third_party/js/prosemirror/prosemirror-state/src/selection.ts
@@ -0,0 +1,462 @@
+import {Slice, Fragment, ResolvedPos, Node} from "prosemirror-model"
+import {ReplaceStep, ReplaceAroundStep, Mappable} from "prosemirror-transform"
+import {Transaction} from "./transaction"
+
+const classesById = Object.create(null)
+
+/// Superclass for editor selections. Every selection type should
+/// extend this. Should not be instantiated directly.
+export abstract 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).
+ readonly $anchor: ResolvedPos,
+ /// The resolved head of the selection (the side that moves when
+ /// the selection is modified).
+ readonly $head: ResolvedPos,
+ ranges?: readonly SelectionRange[]
+ ) {
+ this.ranges = ranges || [new SelectionRange($anchor.min($head), $anchor.max($head))]
+ }
+
+ /// The ranges covered by the selection.
+ ranges: readonly SelectionRange[]
+
+ /// 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(): boolean {
+ 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
+ }
+
+ /// Test whether the selection is the same as another selection.
+ abstract eq(selection: Selection): boolean
+
+ /// Map this selection through a [mappable](#transform.Mappable)
+ /// thing. `doc` should be the new document to which we are mapping.
+ abstract map(doc: Node, mapping: Mappable): Selection
+
+ /// 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: Transaction, 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: Transaction, node: 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)
+ }
+ }
+ }
+
+ /// Convert the selection to a JSON representation. When implementing
+ /// this for a custom selection class, make sure to give the object a
+ /// `type` property whose value matches the ID under which you
+ /// [registered](#state.Selection^jsonID) your class.
+ abstract toJSON(): any
+
+ /// 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: ResolvedPos, dir: number, textOnly: boolean = false): Selection | null {
+ 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: ResolvedPos, bias = 1): Selection {
+ 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`](#state.AllSelection) if no valid position
+ /// exists.
+ static atStart(doc: Node): Selection {
+ 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: Node): Selection {
+ 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: Node, json: any): Selection {
+ 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: string, selectionClass: {fromJSON: (doc: Node, json: any) => Selection}) {
+ if (id in classesById) throw new RangeError("Duplicate use of selection JSON ID " + id)
+ classesById[id] = selectionClass
+ ;(selectionClass as any).prototype.jsonID = id
+ return selectionClass
+ }
+
+ /// Get a [bookmark](#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(): SelectionBookmark {
+ return TextSelection.between(this.$anchor, this.$head).getBookmark()
+ }
+
+ /// Controls whether, when a selection of this type is active in the
+ /// browser, the selected range should be visible to the user.
+ /// Defaults to `true`.
+ declare visible: boolean
+}
+
+Selection.prototype.visible = true
+
+/// A lightweight, document-independent representation of a selection.
+/// You can define a custom bookmark type for a custom selection class
+/// to make the history handle it well.
+export interface SelectionBookmark {
+ /// Map the bookmark through a set of changes.
+ map: (mapping: Mappable) => SelectionBookmark
+
+ /// Resolve the bookmark to a real selection again. This may need to
+ /// do some error checking and may fall back to a default (usually
+ /// [`TextSelection.between`](#state.TextSelection^between)) if
+ /// mapping made the bookmark invalid.
+ resolve: (doc: Node) => Selection
+}
+
+/// Represents a selected range in a document.
+export class SelectionRange {
+ /// Create a range.
+ constructor(
+ /// The lower bound of the range.
+ readonly $from: ResolvedPos,
+ /// The upper bound of the range.
+ readonly $to: ResolvedPos
+ ) {}
+}
+
+let warnedAboutTextSelection = false
+function checkTextSelection($pos: ResolvedPos) {
+ 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).
+export class TextSelection extends Selection {
+ /// Construct a text selection between the given points.
+ constructor($anchor: ResolvedPos, $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: Node, mapping: Mappable): Selection {
+ 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: Transaction, 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: Selection): boolean {
+ return other instanceof TextSelection && other.anchor == this.anchor && other.head == this.head
+ }
+
+ getBookmark() {
+ return new TextBookmark(this.anchor, this.head)
+ }
+
+ toJSON(): any {
+ return {type: "text", anchor: this.anchor, head: this.head}
+ }
+
+ /// @internal
+ static fromJSON(doc: Node, json: any) {
+ 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: Node, anchor: number, 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`](#state.Selection^near) when the document
+ /// doesn't contain a valid text position.
+ static between($anchor: ResolvedPos, $head: ResolvedPos, bias?: number): Selection {
+ 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(readonly anchor: number, readonly head: number) {}
+
+ map(mapping: Mappable) {
+ return new TextBookmark(mapping.map(this.anchor), mapping.map(this.head))
+ }
+ resolve(doc: Node) {
+ 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](#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`..
+export class NodeSelection extends Selection {
+ /// Create a node selection. Does not verify the validity of its
+ /// argument.
+ constructor($pos: ResolvedPos) {
+ let node = $pos.nodeAfter!
+ let $end = $pos.node(0).resolve($pos.pos + node.nodeSize)
+ super($pos, $end)
+ this.node = node
+ }
+
+ /// The selected node.
+ node: Node
+
+ map(doc: Node, mapping: Mappable): Selection {
+ let {deleted, pos} = mapping.mapResult(this.anchor)
+ let $pos = doc.resolve(pos)
+ if (deleted) return Selection.near($pos)
+ return new NodeSelection($pos)
+ }
+
+ content() {
+ return new Slice(Fragment.from(this.node), 0, 0)
+ }
+
+ eq(other: Selection): boolean {
+ return other instanceof NodeSelection && other.anchor == this.anchor
+ }
+
+ toJSON(): any {
+ return {type: "node", anchor: this.anchor}
+ }
+
+ getBookmark() { return new NodeBookmark(this.anchor) }
+
+ /// @internal
+ static fromJSON(doc: Node, json: any) {
+ if (typeof json.anchor != "number")
+ throw new RangeError("Invalid input for NodeSelection.fromJSON")
+ return new NodeSelection(doc.resolve(json.anchor))
+ }
+
+ /// Create a node selection from non-resolved positions.
+ static create(doc: Node, from: number) {
+ return new NodeSelection(doc.resolve(from))
+ }
+
+ /// Determines whether the given node may be selected as a node
+ /// selection.
+ static isSelectable(node: Node) {
+ return !node.isText && node.type.spec.selectable !== false
+ }
+}
+
+NodeSelection.prototype.visible = false
+
+Selection.jsonID("node", NodeSelection)
+
+class NodeBookmark {
+ constructor(readonly anchor: number) {}
+ map(mapping: Mappable) {
+ let {deleted, pos} = mapping.mapResult(this.anchor)
+ return deleted ? new TextBookmark(pos, pos) : new NodeBookmark(pos)
+ }
+ resolve(doc: Node) {
+ let $pos = doc.resolve(this.anchor), node = $pos.nodeAfter
+ if (node && NodeSelection.isSelectable(node)) return new 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).
+export class AllSelection extends Selection {
+ /// Create an all-selection over the given document.
+ constructor(doc: Node) {
+ super(doc.resolve(0), doc.resolve(doc.content.size))
+ }
+
+ replace(tr: Transaction, 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(): any { return {type: "all"} }
+
+ /// @internal
+ static fromJSON(doc: Node) { return new AllSelection(doc) }
+
+ map(doc: Node) { return new AllSelection(doc) }
+
+ eq(other: Selection) { return other instanceof AllSelection }
+
+ getBookmark() { return AllBookmark }
+}
+
+Selection.jsonID("all", AllSelection)
+
+const AllBookmark = {
+ map() { return this },
+ resolve(doc: Node) { 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, node: Node, pos: number, index: number, dir: number, text = false): Selection | null {
+ 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 && NodeSelection.isSelectable(child)) {
+ return NodeSelection.create(doc, pos - (dir < 0 ? child.nodeSize : 0))
+ }
+ pos += child.nodeSize * dir
+ }
+ return null
+}
+
+function selectionToInsertionEnd(tr: Transaction, startLen: number, bias: number) {
+ 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: number | undefined
+ map.forEach((_from, _to, _newFrom, newTo) => { if (end == null) end = newTo })
+ tr.setSelection(Selection.near(tr.doc.resolve(end!), bias))
+}
diff --git a/third_party/js/prosemirror/prosemirror-state/src/state.ts b/third_party/js/prosemirror/prosemirror-state/src/state.ts
@@ -0,0 +1,266 @@
+import {Node, Mark, Schema} from "prosemirror-model"
+
+import {Selection, TextSelection} from "./selection"
+import {Transaction} from "./transaction"
+import {Plugin, StateField} from "./plugin"
+
+function bind<T extends Function>(f: T, self: any): T {
+ return !self || !f ? f : f.bind(self)
+}
+
+class FieldDesc<T> {
+ init: (config: EditorStateConfig, instance: EditorState) => T
+ apply: (tr: Transaction, value: T, oldState: EditorState, newState: EditorState) => T
+
+ constructor(readonly name: string, desc: StateField<any>, self?: any) {
+ this.init = bind(desc.init, self)
+ this.apply = bind(desc.apply, self)
+ }
+}
+
+const baseFields = [
+ new FieldDesc<Node>("doc", {
+ init(config) { return config.doc || config.schema!.topNodeType.createAndFill() },
+ apply(tr) { return tr.doc }
+ }),
+
+ new FieldDesc<Selection>("selection", {
+ init(config, instance) { return config.selection || Selection.atStart(instance.doc) },
+ apply(tr) { return tr.selection }
+ }),
+
+ new FieldDesc<readonly Mark[] | null>("storedMarks", {
+ init(config) { return config.storedMarks || null },
+ apply(tr, _marks, _old, state) { return (state.selection as TextSelection).$cursor ? tr.storedMarks : null }
+ }),
+
+ new FieldDesc<number>("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 {
+ fields: FieldDesc<any>[]
+ plugins: Plugin[] = []
+ pluginsByKey: {[key: string]: Plugin} = Object.create(null)
+
+ constructor(readonly schema: Schema, plugins?: readonly Plugin[]) {
+ 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<any>(plugin.key, plugin.spec.state, plugin))
+ })
+ }
+}
+
+/// The type of object passed to
+/// [`EditorState.create`](#state.EditorState^create).
+export interface EditorStateConfig {
+ /// The schema to use (only relevant if no `doc` is specified).
+ schema?: Schema
+
+ /// The starting document. Either this or `schema` _must_ be
+ /// provided.
+ doc?: Node
+
+ /// A valid selection in the document.
+ selection?: Selection
+
+ /// The initial set of [stored marks](#state.EditorState.storedMarks).
+ storedMarks?: readonly Mark[] | null
+
+ /// The plugins that should be active in this state.
+ plugins?: readonly 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`](#state.EditorState.apply) method.
+///
+/// A state holds a number of built-in fields, and plugins can
+/// [define](#state.PluginSpec.state) additional fields.
+export class EditorState {
+ /// @internal
+ constructor(
+ /// @internal
+ readonly config: Configuration
+ ) {}
+
+ /// The current document.
+ declare doc: Node
+
+ /// The selection.
+ declare selection: Selection
+
+ /// A set of marks to apply to the next input. Will be null when
+ /// no explicit marks have been set.
+ declare storedMarks: readonly Mark[] | null
+
+ /// The schema of the state's document.
+ get schema(): Schema {
+ return this.config.schema
+ }
+
+ /// The plugins that are active in this state.
+ get plugins(): readonly Plugin[] {
+ return this.config.plugins
+ }
+
+ /// Apply the given transaction to produce a new state.
+ apply(tr: Transaction): EditorState {
+ return this.applyTransaction(tr).state
+ }
+
+ /// @internal
+ filterTransaction(tr: Transaction, 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`](#state.EditorState.apply) that
+ /// returns the precise transactions that were applied (which might
+ /// be influenced by the [transaction
+ /// hooks](#state.PluginSpec.filterTransaction) of
+ /// plugins) along with the new state.
+ applyTransaction(rootTr: Transaction): {state: EditorState, transactions: readonly Transaction[]} {
+ 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: Transaction) {
+ 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 as any)[field.name] = field.apply(tr, (this as any)[field.name], this, newInstance)
+ }
+ return newInstance
+ }
+
+ /// Accessor that constructs and returns a new [transaction](#state.Transaction) from this state.
+ get tr(): Transaction { return new Transaction(this) }
+
+ /// Create a new state.
+ static create(config: EditorStateConfig) {
+ 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 as any)[$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`](#state.StateField.init) method, passing in the new
+ /// configuration object..
+ reconfigure(config: {
+ /// New set of active plugins.
+ plugins?: readonly Plugin[]
+ }) {
+ 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 as any)[name] = this.hasOwnProperty(name) ? (this as any)[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?: {[propName: string]: Plugin}): any {
+ let result: any = {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 as any)[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: {
+ /// The schema to use.
+ schema: Schema
+ /// The set of active plugins.
+ plugins?: readonly Plugin[]
+ }, json: any, pluginFields?: {[propName: string]: Plugin}) {
+ 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 = 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)) {
+ // This field belongs to a plugin mapped to a JSON field, read it from there.
+ ;(instance as any)[field.name] = state.fromJSON.call(plugin, config, json[prop], instance)
+ return
+ }
+ }
+ ;(instance as any)[field.name] = field.init(config, instance)
+ }
+ })
+ return instance
+ }
+}
diff --git a/third_party/js/prosemirror/prosemirror-state/src/transaction.ts b/third_party/js/prosemirror/prosemirror-state/src/transaction.ts
@@ -0,0 +1,215 @@
+import {Transform, Step} from "prosemirror-transform"
+import {Mark, MarkType, Node, Slice} from "prosemirror-model"
+import {type EditorView} from "prosemirror-view"
+import {Selection} from "./selection"
+import {Plugin, PluginKey} from "./plugin"
+import {EditorState} from "./state"
+
+/// Commands are functions that take a state and a an optional
+/// transaction dispatch function and...
+///
+/// - determine whether they apply to this state
+/// - if not, return false
+/// - if `dispatch` was passed, perform their effect, possibly by
+/// passing a transaction to `dispatch`
+/// - return true
+///
+/// In some cases, the editor view is passed as a third argument.
+export type Command = (state: EditorState, dispatch?: (tr: Transaction) => void, view?: EditorView) => boolean
+
+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`](#state.EditorState.tr) to create an instance.
+///
+/// Transactions track changes to the document (they are a subclass of
+/// [`Transform`](#transform.Transform)), but also other state changes,
+/// like selection updates and adjustments of the set of [stored
+/// marks](#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](#state.StateField) accordingly.
+///
+/// The [editor view](#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, a `"composition"` property holding an ID identifying the
+/// composition that caused it to transactions caused by composed DOM
+/// input, and a `"uiEvent"` property of that may be `"paste"`,
+/// `"cut"`, or `"drop"`.
+export class Transaction extends Transform {
+ /// The timestamp associated with this transaction, in the same
+ /// format as `Date.now()`.
+ time: number
+
+ private curSelection: Selection
+ // The step count for which the current selection is valid.
+ private curSelectionFor = 0
+ // Bitfield to track which aspects of the state were updated by
+ // this transaction.
+ private updated = 0
+ // Object used to store metadata properties for the transaction.
+ private meta: {[name: string]: any} = Object.create(null)
+
+ /// The stored marks set by this transaction, if any.
+ storedMarks: readonly Mark[] | null
+
+ /// @internal
+ constructor(state: EditorState) {
+ super(state.doc)
+ this.time = Date.now()
+ this.curSelection = state.selection
+ this.storedMarks = state.storedMarks
+ }
+
+ /// The transaction's current selection. This defaults to the editor
+ /// selection [mapped](#state.Selection.map) through the steps in the
+ /// transaction, but can be overwritten with
+ /// [`setSelection`](#state.Transaction.setSelection).
+ get selection(): 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: Selection): this {
+ 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: readonly Mark[] | null): this {
+ 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: readonly Mark[]): this {
+ 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: Mark): this {
+ 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: Mark | MarkType): this {
+ 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: Step, doc: Node) {
+ super.addStep(step, doc)
+ this.updated = this.updated & ~UPDATED_MARKS
+ this.storedMarks = null
+ }
+
+ /// Update the timestamp for the transaction.
+ setTime(time: number): this {
+ this.time = time
+ return this
+ }
+
+ /// Replace the current selection with the given slice.
+ replaceSelection(slice: Slice): this {
+ 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: Node, inheritMarks = true): this {
+ 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 {
+ 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: string, from?: number, to?: number): this {
+ 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
+ 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.selection.to == from + text.length)
+ 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: string | Plugin | PluginKey, value: any): this {
+ this.meta[typeof key == "string" ? key : key.key] = value
+ return this
+ }
+
+ /// Retrieve a metadata property for a given name or plugin.
+ getMeta(key: string | Plugin | PluginKey) {
+ 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 {
+ this.updated |= UPDATED_SCROLL
+ return this
+ }
+
+ /// True when this transaction has had `scrollIntoView` called on it.
+ get scrolledIntoView() {
+ return (this.updated & UPDATED_SCROLL) > 0
+ }
+}
diff --git a/third_party/js/prosemirror/prosemirror-suggestions/.babelrc b/third_party/js/prosemirror/prosemirror-suggestions/.babelrc
@@ -0,0 +1,13 @@
+{
+ "presets": [
+ ["es2015", { "modules": false }]
+ ],
+ "plugins": [
+ "transform-object-rest-spread"
+ ],
+ "env": {
+ "test": {
+ "presets": ["es2015"]
+ }
+ }
+}
+\ No newline at end of file
diff --git a/third_party/js/prosemirror/prosemirror-suggestions/LICENSE b/third_party/js/prosemirror/prosemirror-suggestions/LICENSE
@@ -0,0 +1,14 @@
+Copyright 2017 Quartzy, Inc.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+
diff --git a/third_party/js/prosemirror/prosemirror-suggestions/moz.yaml b/third_party/js/prosemirror/prosemirror-suggestions/moz.yaml
@@ -0,0 +1,65 @@
+# Version of this schema
+schema: 1
+
+bugzilla:
+ # Bugzilla product and component for this directory and subdirectories
+ product: Firefox
+ component: General
+
+# Document the source of externally hosted code
+origin:
+
+ # Short name of the package/library
+ name: prosemirror-suggestions
+
+ description: A plugin for ProseMirror that helps you add suggestions to your editor.
+
+ # Full URL for the package's homepage/etc
+ # Usually different from repository url
+ url: https://prosemirror.net/
+
+ # Human-readable identifier for this version/release
+ # Generally "version NNN", "tag SSS", "bookmark SSS"
+ release: v0.1.1 (2017-10-05T23:32:20-07:00).
+
+ # Revision to pull in
+ # Must be a long or short commit SHA (long preferred) or a tag
+ revision: v0.1.1
+
+ # The package's license, where possible using the mnemonic from
+ # https://spdx.org/licenses/
+ # Multiple licenses can be specified (as a YAML list)
+ # A "LICENSE" file must exist containing the full license text
+ license: Apache-2.0
+
+ # If the package's license is specified in a particular file,
+ # this is the name of the file.
+ # optional
+ license-file: LICENSE
+
+# Configuration for the automated vendoring system.
+# optional
+vendoring:
+
+ # Repository URL to vendor from
+ # eg. https://github.com/kinetiknz/nestegg
+ # Any repository host can be specified here, however initially we'll only
+ # support automated vendoring from selected sources.
+ url: https://github.com/quartzy/prosemirror-suggestions
+
+ # Type of hosting for the upstream repository
+ # Valid values are 'gitlab', 'github', googlesource
+ source-hosting: github
+
+ # Whether to track by commit or tag
+ tracking: tag
+
+ exclude:
+ - "**"
+
+ include:
+ - LICENSE
+ - .babelrc
+ - src/
+ - rollup.config.js
+ - package.json
diff --git a/third_party/js/prosemirror/prosemirror-suggestions/package.json b/third_party/js/prosemirror/prosemirror-suggestions/package.json
@@ -0,0 +1,36 @@
+{
+ "name": "@quartzy/prosemirror-suggestions",
+ "version": "0.1.1",
+ "description": "ProseMirror plugin for suggestions (i.e. mentions, tags)",
+ "main": "dist/index.js",
+ "author": "Tristan Pemble",
+ "license": "Apache-2.0",
+ "peerDependencies": {
+ "prosemirror-markdown": "^0.24.0",
+ "prosemirror-state": "^0.24.0",
+ "prosemirror-view": "^0.24.0"
+ },
+ "devDependencies": {
+ "@types/jest": "^21.1.2",
+ "babel": "^6.23.0",
+ "babel-core": "^6.26.0",
+ "babel-jest": "^21.2.0",
+ "babel-plugin-external-helpers": "^6.22.0",
+ "babel-plugin-transform-object-rest-spread": "^6.26.0",
+ "babel-preset-es2015": "^6.24.1",
+ "jest": "^21.2.1",
+ "prosemirror-markdown": "^0.24.0",
+ "prosemirror-schema-basic": "^0.24.0",
+ "prosemirror-state": "^0.24.0",
+ "prosemirror-view": "^0.24.0",
+ "regenerator-runtime": "^0.11.0",
+ "rollup": "^0.50.0",
+ "rollup-plugin-babel": "^3.0.2"
+ },
+ "scripts": {
+ "build": "rollup -c",
+ "watch": "rollup -c -w",
+ "prepare": "npm run build",
+ "test": "jest"
+ }
+}
diff --git a/third_party/js/prosemirror/prosemirror-suggestions/rollup.config.js b/third_party/js/prosemirror/prosemirror-suggestions/rollup.config.js
@@ -0,0 +1,16 @@
+import babel from 'rollup-plugin-babel';
+
+export default {
+ input: './src/index.js',
+ output: {
+ format: 'cjs',
+ file: 'dist/index.js'
+ },
+ sourcemap: true,
+ plugins: [
+ babel()
+ ],
+ external(id) {
+ return !/^[\.\/]/.test(id)
+ },
+};
diff --git a/third_party/js/prosemirror/prosemirror-suggestions/src/index.js b/third_party/js/prosemirror/prosemirror-suggestions/src/index.js
@@ -0,0 +1,10 @@
+export { suggestionsPlugin, triggerCharacter } from './suggestions';
+export { addTagNodes, tagNodeSpec } from './tags';
+export {
+ addMentionNodes,
+ mentionNodeSpec,
+ markdownParser,
+ markdownSerializer,
+ addMentionsToMarkdownParser,
+ addMentionsToMarkdownSerializer,
+} from './mentions';
diff --git a/third_party/js/prosemirror/prosemirror-suggestions/src/mentions.js b/third_party/js/prosemirror/prosemirror-suggestions/src/mentions.js
@@ -0,0 +1,79 @@
+import { MarkdownSerializer, MarkdownParser } from 'prosemirror-markdown';
+
+/**
+ * @type {NodeSpec}
+ */
+export const mentionNodeSpec = {
+ attrs: {
+ type: {},
+ id: {},
+ label: {},
+ },
+
+ group: 'inline',
+ inline: true,
+ selectable: false,
+ atom: true,
+
+ toDOM: node => ['span', {
+ 'class': 'mention',
+ 'data-mention-type': node.attrs.type,
+ 'data-mention-id': node.attrs.id,
+ }, `@${node.attrs.label}`],
+
+ parseDOM: [{
+ tag: 'span[data-mention-type][data-mention-id]',
+
+ /**
+ * @param {Element} dom
+ * @returns {{type: string, id: string, label: string}}
+ */
+ getAttrs: dom => {
+ const type = dom.getAttribute('data-mention-type');
+ const id = dom.getAttribute('data-mention-id');
+ const label = dom.innerText;
+
+ return { type, id, label };
+ },
+ }],
+};
+
+/**
+ * @param {OrderedMap} nodes
+ * @returns {OrderedMap}
+ */
+export function addMentionNodes(nodes) {
+ return nodes.append({
+ mention: mentionNodeSpec,
+ });
+}
+
+export function markdownSerializer() {
+ return (state, node) => {
+ const label = state.esc(node.attrs.label || '');
+ const uri = state.esc(`mention://${node.attrs.type}/${node.attrs.id}`);
+
+ state.write(`@[${label}](${uri})`)
+ };
+}
+
+export function addMentionsToMarkdownSerializer(serializer) {
+ return new MarkdownSerializer({
+ ...serializer.nodes,
+ mention: markdownSerializer(),
+ }, serializer.marks);
+}
+
+export function markdownParser() {
+ return {
+ node: 'mention',
+ getAttrs: ({ mention: { type, id, label } }) => ({ type, id, label }),
+ };
+}
+
+export function addMentionsToMarkdownParser(parser) {
+ return new MarkdownParser(parser.schema, parser.tokenizer, {
+ ...parser.tokens,
+ mention: markdownParser(),
+ });
+}
diff --git a/third_party/js/prosemirror/prosemirror-suggestions/src/suggestions.js b/third_party/js/prosemirror/prosemirror-suggestions/src/suggestions.js
@@ -0,0 +1,187 @@
+import { Plugin, PluginKey } from 'prosemirror-state';
+import { Decoration, DecorationSet } from 'prosemirror-view';
+
+/**
+ * Create a matcher that matches when a specific character is typed. Useful for @mentions and #tags.
+ *
+ * @param {String} char
+ * @param {Boolean} allowSpaces
+ * @returns {function(*)}
+ */
+export function triggerCharacter(char, { allowSpaces = false }) {
+ /**
+ * @param {ResolvedPos} $position
+ */
+ return $position => {
+ // Matching expressions used for later
+ const suffix = new RegExp(`\\s${char}$`);
+ const regexp = allowSpaces
+ ? new RegExp(`${char}.*?(?=\\s${char}|$)`, 'g')
+ : new RegExp(`(?:^)?${char}[^\\s${char}]*`, 'g');
+
+ // Lookup the boundaries of the current node
+ const textFrom = $position.before();
+ const textTo = $position.end();
+
+ const text = $position.doc.textBetween(textFrom, textTo, '\0', '\0');
+
+ let match;
+
+ while (match = regexp.exec(text)) {
+ // Javascript doesn't have lookbehinds; this hacks a check that first character is " " or the line beginning
+ const prefix = match.input.slice(Math.max(0, match.index - 1), match.index);
+ if (!/^[\s\0]?$/.test(prefix)) {
+ continue;
+ }
+
+ // The absolute position of the match in the document
+ const from = match.index + $position.start();
+ let to = from + match[0].length;
+
+ // Edge case handling; if spaces are allowed and we're directly in between two triggers
+ if (allowSpaces && suffix.test(text.slice(to - 1, to + 1))) {
+ match[0] += ' ';
+ to++;
+ }
+
+ // If the $position is located within the matched substring, return that range
+ if (from < $position.pos && to >= $position.pos) {
+ return { range: { from, to }, text: match[0] };
+ }
+ }
+ }
+}
+
+/**
+ * @returns {Plugin}
+ */
+export function suggestionsPlugin({
+ matcher = triggerCharacter('#'),
+ suggestionClass = 'ProseMirror-suggestion',
+ onEnter = () => false,
+ onChange = () => false,
+ onExit = () => false,
+ onKeyDown = () => false,
+ debug = false,
+}) {
+ return new Plugin({
+ key: new PluginKey('suggestions'),
+
+ view() {
+ return {
+ update: (view, prevState) => {
+ const prev = this.key.getState(prevState);
+ const next = this.key.getState(view.state);
+
+ // See how the state changed
+ const moved = prev.active && next.active && prev.range.from !== next.range.from;
+ const started = !prev.active && next.active;
+ const stopped = prev.active && !next.active;
+ const changed = !started && !stopped && prev.text !== next.text;
+
+ // Trigger the hooks when necessary
+ if (stopped || moved) onExit({ view, range: prev.range, text: prev.text });
+ if (changed && !moved) onChange({ view, range: next.range, text: next.text });
+ if (started || moved) onEnter({ view, range: next.range, text: next.text });
+ },
+ };
+ },
+
+ state: {
+ /**
+ * Initialize the plugin's internal state.
+ *
+ * @returns {Object}
+ */
+ init() {
+ return {
+ active: false,
+ range: {},
+ text: null,
+ };
+ },
+
+ /**
+ * Apply changes to the plugin state from a view transaction.
+ *
+ * @param {Transaction} tr
+ * @param {Object} prev
+ *
+ * @returns {Object}
+ */
+ apply(tr, prev) {
+ const { selection } = tr;
+ const next = { ...prev };
+
+ // We can only be suggesting if there is no selection
+ if (selection.from === selection.to) {
+ // Reset active state if we just left the previous suggestion range
+ if (selection.from < prev.range.from || selection.from > prev.range.to) {
+ next.active = false;
+ }
+
+ // Try to match against where our cursor currently is
+ const $position = selection.$from;
+ const match = matcher($position);
+
+ // If we found a match, update the current state to show it
+ if (match) {
+ next.active = true;
+ next.range = match.range;
+ next.text = match.text;
+ } else {
+ next.active = false;
+ }
+ } else {
+ next.active = false;
+ }
+
+ // Make sure to empty the range if suggestion is inactive
+ if (!next.active) {
+ next.range = {};
+ next.text = null;
+ }
+
+ return next;
+ },
+ },
+
+ props: {
+ /**
+ * Call the keydown hook if suggestion is active.
+ *
+ * @param view
+ * @param event
+ * @returns {boolean}
+ */
+ handleKeyDown(view, event) {
+ const { active } = this.getState(view.state);
+
+ if (!active) return false;
+
+ return onKeyDown({ view, event });
+ },
+
+ /**
+ * Setup decorator on the currently active suggestion.
+ *
+ * @param {EditorState} editorState
+ *
+ * @returns {?DecorationSet}
+ */
+ decorations(editorState) {
+ const { active, range } = this.getState(editorState);
+
+ if (!active) return null;
+
+ return DecorationSet.create(editorState.doc, [
+ Decoration.inline(range.from, range.to, {
+ nodeName: 'span',
+ class: suggestionClass,
+ style: debug ? 'background: rgba(0, 0, 255, 0.05); color: blue; border: 2px solid blue;' : null,
+ }),
+ ]);
+ },
+ },
+ });
+}
diff --git a/third_party/js/prosemirror/prosemirror-suggestions/src/suggestions.test.js b/third_party/js/prosemirror/prosemirror-suggestions/src/suggestions.test.js
@@ -0,0 +1,240 @@
+import { schema } from 'prosemirror-schema-basic';
+import { triggerCharacter } from './suggestions';
+
+const CUR = '\u2038';
+
+function createPosition(text) {
+ const position = text.indexOf(CUR) + 1;
+ const stripped = text.replace(CUR, '');
+
+ expect(position).toBeGreaterThan(0);
+ expect(position).toBeLessThanOrEqual(stripped.length + 1);
+
+ const doc = schema.node('doc', null, [
+ schema.node('paragraph', null, [
+ schema.text(stripped),
+ ]),
+ ]);
+
+ return doc.resolve(position);
+}
+
+function mention(text) {
+ const $position = createPosition(text);
+
+ return triggerCharacter('@', { allowSpaces: true })($position);
+}
+
+function tag(text) {
+ const $position = createPosition(text);
+
+ return triggerCharacter('#', { allowSpaces: false })($position);
+}
+
+describe('the triggerCharacter matcher', () => {
+ it('will match when cursor is immediately after a node', () => {
+ const doc = schema.node('doc', null, [
+ schema.node('paragraph', null, [
+ schema.node('hard_break'),
+ schema.text('@mention'),
+ ]),
+ ]);
+
+ const $position = doc.resolve(3);
+ const result = triggerCharacter('@', { allowSpaces: true })($position);
+
+ expect(result).toMatchObject({
+ text: '@mention',
+ });
+ });
+
+ describe(`for tags`, () => {
+ it(`won't match "${CUR}#"`, () => {
+ const text = `${CUR}#`;
+
+ expect(tag(text)).toBeUndefined();
+ });
+
+ it(`will match "#${CUR}"`, () => {
+ const text = `#${CUR}`;
+
+ expect(tag(text)).toMatchObject({
+ text: '#',
+ });
+ });
+
+ it(`won't match "${CUR}#tag"`, () => {
+ const text = `${CUR}#tag`;
+
+ expect(tag(text)).toBeUndefined();
+ });
+
+ it(`will match "#${CUR}tag"`, () => {
+ const text = `#${CUR}tag`;
+
+ expect(tag(text)).toMatchObject({
+ text: '#tag',
+ range: { from: 1, to: 5 },
+ });
+ });
+
+ it(`will match "#tag-with-dashes${CUR}"`, () => {
+ const text = `#tag-with-dashes${CUR}`;
+
+ expect(tag(text)).toMatchObject({
+ text: '#tag-with-dashes',
+ });
+ });
+
+ it(`won't match "#tag with spaces${CUR}"`, () => {
+ const text = `#tag with spaces${CUR}`;
+
+ expect(tag(text)).toBeUndefined();
+ });
+
+ it(`won't match "#tag#with#hashes${CUR}"`, () => {
+ const text = `#tag#with#hashes${CUR}`;
+
+ expect(tag(text)).toBeUndefined();
+ });
+
+ it(`will match "#multiple${CUR} #tags #separately"`, () => {
+ const text = `#multiple${CUR} #tags #separately`;
+
+ expect(tag(text)).toMatchObject({
+ text: '#multiple',
+ });
+ });
+
+ it(`will match "#multiple #tags${CUR} #separately"`, () => {
+ const text = `#multiple #tags${CUR} #separately`;
+
+ expect(tag(text)).toMatchObject({
+ text: '#tags',
+ });
+ });
+
+ it(`will match "#multiple #tags #separately${CUR}"`, () => {
+ const text = `#multiple #tags #separately${CUR}`;
+
+ expect(tag(text)).toMatchObject({
+ text: '#separately',
+ });
+ });
+
+ it(`won't match "#multiple ${CUR}#tags" when cursor is outside`, () => {
+ const text = `#multiple ${CUR}#tags`;
+
+ expect(tag(text)).toBeUndefined();
+ });
+ });
+
+ describe('for mentions', () => {
+ it(`won't match "${CUR}@"`, () => {
+ const text = `${CUR}@`;
+
+ expect(mention(text)).toBeUndefined();
+ });
+
+ it(`will match "@${CUR}"`, () => {
+ const text = `@${CUR}`;
+
+ expect(mention(text)).toMatchObject({
+ text: '@',
+ });
+ });
+
+ it(`won't match "${CUR}@mention"`, () => {
+ const text = `${CUR}@mention`;
+
+ expect(mention(text)).toBeUndefined();
+ });
+
+ it(`will match "@${CUR}mention"`, () => {
+ const text = `@${CUR}mention`;
+
+ expect(mention(text)).toMatchObject({
+ text: '@mention',
+ range: { from: 1, to: 9 },
+ });
+ });
+
+ it(`will match "@m${CUR}ention"`, () => {
+ const text = `@m${CUR}ention`;
+
+ expect(mention(text)).toMatchObject({
+ text: '@mention',
+ });
+ });
+
+ it(`will match "@mention${CUR}"`, () => {
+ const text = `@mention${CUR}`;
+
+ expect(mention(text)).toMatchObject({
+ text: '@mention',
+ });
+ });
+
+ it(`will match "@mention ${CUR}"`, () => {
+ const text = `@mention ${CUR}`;
+
+ expect(mention(text)).toMatchObject({
+ text: '@mention ',
+ });
+ });
+
+ it(`will match "@mentions with${CUR} spaces"`, () => {
+ const text = `@mentions with${CUR} spaces`;
+
+ expect(mention(text)).toMatchObject({
+ text: '@mentions with spaces',
+ });
+ });
+
+ it(`will match "@${CUR}mentions with spaces"`, () => {
+ const text = `@${CUR}mentions with spaces`;
+
+ expect(mention(text)).toMatchObject({
+ text: '@mentions with spaces',
+ });
+ });
+
+ it(`will match "@multiple ${CUR}@mentions"`, () => {
+ const text = `@multiple ${CUR}@mentions`;
+
+ expect(mention(text)).toMatchObject({
+ text: '@multiple ',
+ });
+ });
+
+ it(`will match "@multiple @${CUR}mentions"`, () => {
+ const text = `@multiple @${CUR}mentions`;
+
+ expect(mention(text)).toMatchObject({
+ text: '@mentions',
+ });
+ });
+
+ it(`will match "mentions with text @${CUR}before them"`, () => {
+ const text = `mentions with text @${CUR}before them`;
+
+ expect(mention(text)).toMatchObject({
+ text: '@before them',
+ });
+ });
+
+ it(`will match "@${CUR}mentioned@email.address"`, () => {
+ const text = `@${CUR}mentioned@email.address`;
+
+ expect(mention(text)).toMatchObject({
+ text: '@mentioned@email.address',
+ });
+ });
+
+ it(`won't match "normal@${CUR}email.address"`, () => {
+ const text = `normal@${CUR}email.address`;
+
+ expect(mention(text)).toBeUndefined();
+ });
+ });
+});
diff --git a/third_party/js/prosemirror/prosemirror-suggestions/src/tags.js b/third_party/js/prosemirror/prosemirror-suggestions/src/tags.js
@@ -0,0 +1,45 @@
+/**
+ * @type {NodeSpec}
+ */
+export const tagNodeSpec = {
+ attrs: {
+ id: {},
+ },
+
+ group: 'inline',
+ inline: true,
+ selectable: false,
+ atom: true,
+
+ /**
+ * @param {Node} node
+ */
+ toDOM: node => ['span', {
+ 'class': 'tag',
+ 'data-tag-id': node.attrs.id,
+ }, node.attrs.id],
+
+ parseDOM: [{
+ tag: 'span[data-tag-id]',
+
+ /**
+ * @param {Element} dom
+ * @returns {{id: string}}
+ */
+ getAttrs: dom => {
+ const id = dom.getAttribute('data-tag-id');
+
+ return { id };
+ },
+ }],
+};
+
+/**
+ * @param {OrderedMap} nodes
+ * @returns {OrderedMap}
+ */
+export function addTagNodes(nodes) {
+ return nodes.append({
+ tag: tagNodeSpec,
+ });
+}
diff --git a/third_party/js/prosemirror/prosemirror-transform/LICENSE b/third_party/js/prosemirror/prosemirror-transform/LICENSE
@@ -0,0 +1,19 @@
+Copyright (C) 2015-2017 by Marijn Haverbeke <marijn@haverbeke.berlin> and others
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
diff --git a/third_party/js/prosemirror/prosemirror-transform/moz.yaml b/third_party/js/prosemirror/prosemirror-transform/moz.yaml
@@ -0,0 +1,64 @@
+# Version of this schema
+schema: 1
+
+bugzilla:
+ # Bugzilla product and component for this directory and subdirectories
+ product: Firefox
+ component: General
+
+# Document the source of externally hosted code
+origin:
+
+ # Short name of the package/library
+ name: prosemirror-transform
+
+ description: ProseMirror document transformations
+
+ # Full URL for the package's homepage/etc
+ # Usually different from repository url
+ url: https://prosemirror.net/
+
+ # Human-readable identifier for this version/release
+ # Generally "version NNN", "tag SSS", "bookmark SSS"
+ release: 1.10.5 (2025-11-11T09:10:08+01:00).
+
+ # Revision to pull in
+ # Must be a long or short commit SHA (long preferred) or a tag
+ revision: 1.10.5
+
+ # The package's license, where possible using the mnemonic from
+ # https://spdx.org/licenses/
+ # Multiple licenses can be specified (as a YAML list)
+ # A "LICENSE" file must exist containing the full license text
+ license: MIT
+
+ # If the package's license is specified in a particular file,
+ # this is the name of the file.
+ # optional
+ license-file: LICENSE
+
+# Configuration for the automated vendoring system.
+# optional
+vendoring:
+
+ # Repository URL to vendor from
+ # eg. https://github.com/kinetiknz/nestegg
+ # Any repository host can be specified here, however initially we'll only
+ # support automated vendoring from selected sources.
+ url: https://github.com/ProseMirror/prosemirror-transform
+
+ # Type of hosting for the upstream repository
+ # Valid values are 'gitlab', 'github', googlesource
+ source-hosting: github
+
+ # Whether to track by commit or tag
+ tracking: tag
+
+ exclude:
+ - "**"
+
+ include:
+ - LICENSE
+ - src/
+ - style/
+ - package.json
diff --git a/third_party/js/prosemirror/prosemirror-transform/package.json b/third_party/js/prosemirror/prosemirror-transform/package.json
@@ -0,0 +1,37 @@
+{
+ "name": "prosemirror-transform",
+ "version": "1.10.5",
+ "description": "ProseMirror document transformations",
+ "type": "module",
+ "main": "dist/index.cjs",
+ "module": "dist/index.js",
+ "types": "dist/index.d.ts",
+ "exports": {
+ "import": "./dist/index.js",
+ "require": "./dist/index.cjs"
+ },
+ "sideEffects": false,
+ "license": "MIT",
+ "maintainers": [
+ {
+ "name": "Marijn Haverbeke",
+ "email": "marijn@haverbeke.berlin",
+ "web": "http://marijnhaverbeke.nl"
+ }
+ ],
+ "repository": {
+ "type": "git",
+ "url": "git://github.com/prosemirror/prosemirror-transform.git"
+ },
+ "dependencies": {
+ "prosemirror-model": "^1.21.0"
+ },
+ "devDependencies": {
+ "@prosemirror/buildhelper": "^0.1.5",
+ "prosemirror-test-builder": "^1.0.0"
+ },
+ "scripts": {
+ "test": "pm-runtests",
+ "prepare": "pm-buildhelper src/index.ts"
+ }
+}
diff --git a/third_party/js/prosemirror/prosemirror-transform/src/README.md b/third_party/js/prosemirror/prosemirror-transform/src/README.md
@@ -0,0 +1,59 @@
+This module defines a way of modifying documents that allows changes
+to be recorded, replayed, and reordered. You can read more about
+transformations in [the guide](/docs/guide/#transform).
+
+### Steps
+
+Transforming happens in `Step`s, which are atomic, well-defined
+modifications to a document. [Applying](#transform.Step.apply) a step
+produces a new document.
+
+Each step provides a [change map](#transform.StepMap) that maps
+positions in the old document to position in the transformed document.
+Steps can be [inverted](#transform.Step.invert) to create a step that
+undoes their effect, and chained together in a convenience object
+called a [`Transform`](#transform.Transform).
+
+@Step
+@StepResult
+@ReplaceStep
+@ReplaceAroundStep
+@AddMarkStep
+@RemoveMarkStep
+@AddNodeMarkStep
+@RemoveNodeMarkStep
+@AttrStep
+@DocAttrStep
+
+### Position Mapping
+
+Mapping positions from one document to another by running through the
+[step maps](#transform.StepMap) produced by steps is an important
+operation in ProseMirror. It is used, for example, for updating the
+selection when the document changes.
+
+@Mappable
+@MapResult
+@StepMap
+@Mapping
+
+### Document transforms
+
+Because you often need to collect a number of steps together to effect
+a composite change, ProseMirror provides an abstraction to make this
+easy. [State transactions](#state.Transaction) are a subclass of
+transforms.
+
+@Transform
+
+The following helper functions can be useful when creating
+transformations or determining whether they are even possible.
+
+@replaceStep
+@liftTarget
+@findWrapping
+@canSplit
+@canJoin
+@joinPoint
+@insertPoint
+@dropPoint
diff --git a/third_party/js/prosemirror/prosemirror-transform/src/attr_step.ts b/third_party/js/prosemirror/prosemirror-transform/src/attr_step.ts
@@ -0,0 +1,98 @@
+import {Fragment, Slice, Node, Schema} from "prosemirror-model"
+import {Step, StepResult} from "./step"
+import {StepMap, Mappable} from "./map"
+
+/// Update an attribute in a specific node.
+export class AttrStep extends Step {
+ /// Construct an attribute step.
+ constructor(
+ /// The position of the target node.
+ readonly pos: number,
+ /// The attribute to set.
+ readonly attr: string,
+ // The attribute's new value.
+ readonly value: any
+ ) {
+ super()
+ }
+
+ apply(doc: Node) {
+ let node = doc.nodeAt(this.pos)
+ if (!node) return StepResult.fail("No node at attribute step's position")
+ let attrs = Object.create(null)
+ for (let name in node.attrs) attrs[name] = node.attrs[name]
+ attrs[this.attr] = this.value
+ let updated = node.type.create(attrs, null, node.marks)
+ return StepResult.fromReplace(doc, this.pos, this.pos + 1, new Slice(Fragment.from(updated), 0, node.isLeaf ? 0 : 1))
+ }
+
+ getMap() {
+ return StepMap.empty
+ }
+
+ invert(doc: Node) {
+ return new AttrStep(this.pos, this.attr, doc.nodeAt(this.pos)!.attrs[this.attr])
+ }
+
+ map(mapping: Mappable) {
+ let pos = mapping.mapResult(this.pos, 1)
+ return pos.deletedAfter ? null : new AttrStep(pos.pos, this.attr, this.value)
+ }
+
+ toJSON(): any {
+ return {stepType: "attr", pos: this.pos, attr: this.attr, value: this.value}
+ }
+
+ static fromJSON(schema: Schema, json: any) {
+ if (typeof json.pos != "number" || typeof json.attr != "string")
+ throw new RangeError("Invalid input for AttrStep.fromJSON")
+ return new AttrStep(json.pos, json.attr, json.value)
+ }
+}
+
+Step.jsonID("attr", AttrStep)
+
+/// Update an attribute in the doc node.
+export class DocAttrStep extends Step {
+ /// Construct an attribute step.
+ constructor(
+ /// The attribute to set.
+ readonly attr: string,
+ // The attribute's new value.
+ readonly value: any
+ ) {
+ super()
+ }
+
+ apply(doc: Node) {
+ let attrs = Object.create(null)
+ for (let name in doc.attrs) attrs[name] = doc.attrs[name]
+ attrs[this.attr] = this.value
+ let updated = doc.type.create(attrs, doc.content, doc.marks)
+ return StepResult.ok(updated)
+ }
+
+ getMap() {
+ return StepMap.empty
+ }
+
+ invert(doc: Node) {
+ return new DocAttrStep(this.attr, doc.attrs[this.attr])
+ }
+
+ map(mapping: Mappable) {
+ return this
+ }
+
+ toJSON(): any {
+ return {stepType: "docAttr", attr: this.attr, value: this.value}
+ }
+
+ static fromJSON(schema: Schema, json: any) {
+ if (typeof json.attr != "string")
+ throw new RangeError("Invalid input for DocAttrStep.fromJSON")
+ return new DocAttrStep(json.attr, json.value)
+ }
+}
+
+Step.jsonID("docAttr", DocAttrStep)
+\ No newline at end of file
diff --git a/third_party/js/prosemirror/prosemirror-transform/src/index.ts b/third_party/js/prosemirror/prosemirror-transform/src/index.ts
@@ -0,0 +1,11 @@
+export {Transform} from "./transform"
+/// @internal
+export {TransformError} from "./transform"
+export {Step, StepResult} from "./step"
+export {joinPoint, canJoin, canSplit, insertPoint, dropPoint, liftTarget, findWrapping} from "./structure"
+export {StepMap, MapResult, Mapping, Mappable} from "./map"
+export {AddMarkStep, RemoveMarkStep, AddNodeMarkStep, RemoveNodeMarkStep} from "./mark_step"
+export {ReplaceStep, ReplaceAroundStep} from "./replace_step"
+export {AttrStep, DocAttrStep} from "./attr_step"
+import "./mark"
+export {replaceStep} from "./replace"
diff --git a/third_party/js/prosemirror/prosemirror-transform/src/map.ts b/third_party/js/prosemirror/prosemirror-transform/src/map.ts
@@ -0,0 +1,284 @@
+/// There are several things that positions can be mapped through.
+/// Such objects conform to this interface.
+export interface Mappable {
+ /// Map a position through this object. When given, `assoc` (should
+ /// be -1 or 1, defaults to 1) determines with which side the
+ /// position is associated, which determines in which direction to
+ /// move when a chunk of content is inserted at the mapped position.
+ map: (pos: number, assoc?: number) => number
+
+ /// Map a position, and return an object containing additional
+ /// information about the mapping. The result's `deleted` field tells
+ /// you whether the position was deleted (completely enclosed in a
+ /// replaced range) during the mapping. When content on only one side
+ /// is deleted, the position itself is only considered deleted when
+ /// `assoc` points in the direction of the deleted content.
+ mapResult: (pos: number, assoc?: number) => MapResult
+}
+
+// Recovery values encode a range index and an offset. They are
+// represented as numbers, because tons of them will be created when
+// mapping, for example, a large number of decorations. The number's
+// lower 16 bits provide the index, the remaining bits the offset.
+//
+// Note: We intentionally don't use bit shift operators to en- and
+// decode these, since those clip to 32 bits, which we might in rare
+// cases want to overflow. A 64-bit float can represent 48-bit
+// integers precisely.
+
+const lower16 = 0xffff
+const factor16 = Math.pow(2, 16)
+
+function makeRecover(index: number, offset: number) { return index + offset * factor16 }
+function recoverIndex(value: number) { return value & lower16 }
+function recoverOffset(value: number) { return (value - (value & lower16)) / factor16 }
+
+const DEL_BEFORE = 1, DEL_AFTER = 2, DEL_ACROSS = 4, DEL_SIDE = 8
+
+/// An object representing a mapped position with extra
+/// information.
+export class MapResult {
+ /// @internal
+ constructor(
+ /// The mapped version of the position.
+ readonly pos: number,
+ /// @internal
+ readonly delInfo: number,
+ /// @internal
+ readonly recover: number | null
+ ) {}
+
+ /// Tells you whether the position was deleted, that is, whether the
+ /// step removed the token on the side queried (via the `assoc`)
+ /// argument from the document.
+ get deleted() { return (this.delInfo & DEL_SIDE) > 0 }
+
+ /// Tells you whether the token before the mapped position was deleted.
+ get deletedBefore() { return (this.delInfo & (DEL_BEFORE | DEL_ACROSS)) > 0 }
+
+ /// True when the token after the mapped position was deleted.
+ get deletedAfter() { return (this.delInfo & (DEL_AFTER | DEL_ACROSS)) > 0 }
+
+ /// Tells whether any of the steps mapped through deletes across the
+ /// position (including both the token before and after the
+ /// position).
+ get deletedAcross() { return (this.delInfo & DEL_ACROSS) > 0 }
+}
+
+/// A map describing the deletions and insertions made by a step, which
+/// can be used to find the correspondence between positions in the
+/// pre-step version of a document and the same position in the
+/// post-step version.
+export class StepMap implements Mappable {
+ /// Create a position map. The modifications to the document are
+ /// represented as an array of numbers, in which each group of three
+ /// represents a modified chunk as `[start, oldSize, newSize]`.
+ constructor(
+ /// @internal
+ readonly ranges: readonly number[],
+ /// @internal
+ readonly inverted = false
+ ) {
+ if (!ranges.length && StepMap.empty) return StepMap.empty
+ }
+
+ /// @internal
+ recover(value: number) {
+ let diff = 0, index = recoverIndex(value)
+ if (!this.inverted) for (let i = 0; i < index; i++)
+ diff += this.ranges[i * 3 + 2] - this.ranges[i * 3 + 1]
+ return this.ranges[index * 3] + diff + recoverOffset(value)
+ }
+
+ mapResult(pos: number, assoc = 1): MapResult { return this._map(pos, assoc, false) as MapResult }
+
+ map(pos: number, assoc = 1): number { return this._map(pos, assoc, true) as number }
+
+ /// @internal
+ _map(pos: number, assoc: number, simple: boolean) {
+ let diff = 0, oldIndex = this.inverted ? 2 : 1, newIndex = this.inverted ? 1 : 2
+ for (let i = 0; i < this.ranges.length; i += 3) {
+ let start = this.ranges[i] - (this.inverted ? diff : 0)
+ if (start > pos) break
+ let oldSize = this.ranges[i + oldIndex], newSize = this.ranges[i + newIndex], end = start + oldSize
+ if (pos <= end) {
+ let side = !oldSize ? assoc : pos == start ? -1 : pos == end ? 1 : assoc
+ let result = start + diff + (side < 0 ? 0 : newSize)
+ if (simple) return result
+ let recover = pos == (assoc < 0 ? start : end) ? null : makeRecover(i / 3, pos - start)
+ let del = pos == start ? DEL_AFTER : pos == end ? DEL_BEFORE : DEL_ACROSS
+ if (assoc < 0 ? pos != start : pos != end) del |= DEL_SIDE
+ return new MapResult(result, del, recover)
+ }
+ diff += newSize - oldSize
+ }
+ return simple ? pos + diff : new MapResult(pos + diff, 0, null)
+ }
+
+ /// @internal
+ touches(pos: number, recover: number) {
+ let diff = 0, index = recoverIndex(recover)
+ let oldIndex = this.inverted ? 2 : 1, newIndex = this.inverted ? 1 : 2
+ for (let i = 0; i < this.ranges.length; i += 3) {
+ let start = this.ranges[i] - (this.inverted ? diff : 0)
+ if (start > pos) break
+ let oldSize = this.ranges[i + oldIndex], end = start + oldSize
+ if (pos <= end && i == index * 3) return true
+ diff += this.ranges[i + newIndex] - oldSize
+ }
+ return false
+ }
+
+ /// Calls the given function on each of the changed ranges included in
+ /// this map.
+ forEach(f: (oldStart: number, oldEnd: number, newStart: number, newEnd: number) => void) {
+ let oldIndex = this.inverted ? 2 : 1, newIndex = this.inverted ? 1 : 2
+ for (let i = 0, diff = 0; i < this.ranges.length; i += 3) {
+ let start = this.ranges[i], oldStart = start - (this.inverted ? diff : 0), newStart = start + (this.inverted ? 0 : diff)
+ let oldSize = this.ranges[i + oldIndex], newSize = this.ranges[i + newIndex]
+ f(oldStart, oldStart + oldSize, newStart, newStart + newSize)
+ diff += newSize - oldSize
+ }
+ }
+
+ /// Create an inverted version of this map. The result can be used to
+ /// map positions in the post-step document to the pre-step document.
+ invert() {
+ return new StepMap(this.ranges, !this.inverted)
+ }
+
+ /// @internal
+ toString() {
+ return (this.inverted ? "-" : "") + JSON.stringify(this.ranges)
+ }
+
+ /// Create a map that moves all positions by offset `n` (which may be
+ /// negative). This can be useful when applying steps meant for a
+ /// sub-document to a larger document, or vice-versa.
+ static offset(n: number) {
+ return n == 0 ? StepMap.empty : new StepMap(n < 0 ? [0, -n, 0] : [0, 0, n])
+ }
+
+ /// A StepMap that contains no changed ranges.
+ static empty = new StepMap([])
+}
+
+/// A mapping represents a pipeline of zero or more [step
+/// maps](#transform.StepMap). It has special provisions for losslessly
+/// handling mapping positions through a series of steps in which some
+/// steps are inverted versions of earlier steps. (This comes up when
+/// ‘[rebasing](/docs/guide/#transform.rebasing)’ steps for
+/// collaboration or history management.)
+export class Mapping implements Mappable {
+ /// Create a new mapping with the given position maps.
+ constructor(
+ maps?: readonly StepMap[],
+ /// @internal
+ public mirror?: number[],
+ /// The starting position in the `maps` array, used when `map` or
+ /// `mapResult` is called.
+ public from = 0,
+ /// The end position in the `maps` array.
+ public to = maps ? maps.length : 0
+ ) {
+ this._maps = (maps as StepMap[]) || []
+ this.ownData = !(maps || mirror)
+ }
+
+ /// The step maps in this mapping.
+ get maps(): readonly StepMap[] { return this._maps }
+
+ private _maps: StepMap[]
+ // False if maps/mirror are shared arrays that we shouldn't mutate
+ private ownData: boolean
+
+ /// Create a mapping that maps only through a part of this one.
+ slice(from = 0, to = this.maps.length) {
+ return new Mapping(this._maps, this.mirror, from, to)
+ }
+
+ /// Add a step map to the end of this mapping. If `mirrors` is
+ /// given, it should be the index of the step map that is the mirror
+ /// image of this one.
+ appendMap(map: StepMap, mirrors?: number) {
+ if (!this.ownData) {
+ this._maps = this._maps.slice()
+ this.mirror = this.mirror && this.mirror.slice()
+ this.ownData = true
+ }
+ this.to = this._maps.push(map)
+ if (mirrors != null) this.setMirror(this._maps.length - 1, mirrors)
+ }
+
+ /// Add all the step maps in a given mapping to this one (preserving
+ /// mirroring information).
+ appendMapping(mapping: Mapping) {
+ for (let i = 0, startSize = this._maps.length; i < mapping._maps.length; i++) {
+ let mirr = mapping.getMirror(i)
+ this.appendMap(mapping._maps[i], mirr != null && mirr < i ? startSize + mirr : undefined)
+ }
+ }
+
+ /// Finds the offset of the step map that mirrors the map at the
+ /// given offset, in this mapping (as per the second argument to
+ /// `appendMap`).
+ getMirror(n: number): number | undefined {
+ if (this.mirror) for (let i = 0; i < this.mirror.length; i++)
+ if (this.mirror[i] == n) return this.mirror[i + (i % 2 ? -1 : 1)]
+ }
+
+ /// @internal
+ setMirror(n: number, m: number) {
+ if (!this.mirror) this.mirror = []
+ this.mirror.push(n, m)
+ }
+
+ /// Append the inverse of the given mapping to this one.
+ appendMappingInverted(mapping: Mapping) {
+ for (let i = mapping.maps.length - 1, totalSize = this._maps.length + mapping._maps.length; i >= 0; i--) {
+ let mirr = mapping.getMirror(i)
+ this.appendMap(mapping._maps[i].invert(), mirr != null && mirr > i ? totalSize - mirr - 1 : undefined)
+ }
+ }
+
+ /// Create an inverted version of this mapping.
+ invert() {
+ let inverse = new Mapping
+ inverse.appendMappingInverted(this)
+ return inverse
+ }
+
+ /// Map a position through this mapping.
+ map(pos: number, assoc = 1) {
+ if (this.mirror) return this._map(pos, assoc, true) as number
+ for (let i = this.from; i < this.to; i++)
+ pos = this._maps[i].map(pos, assoc)
+ return pos
+ }
+
+ /// Map a position through this mapping, returning a mapping
+ /// result.
+ mapResult(pos: number, assoc = 1) { return this._map(pos, assoc, false) as MapResult }
+
+ /// @internal
+ _map(pos: number, assoc: number, simple: boolean) {
+ let delInfo = 0
+
+ for (let i = this.from; i < this.to; i++) {
+ let map = this._maps[i], result = map.mapResult(pos, assoc)
+ if (result.recover != null) {
+ let corr = this.getMirror(i)
+ if (corr != null && corr > i && corr < this.to) {
+ i = corr
+ pos = this._maps[corr].recover(result.recover)
+ continue
+ }
+ }
+
+ delInfo |= result.delInfo
+ pos = result.pos
+ }
+
+ return simple ? pos : new MapResult(pos, delInfo, null)
+ }
+}
diff --git a/third_party/js/prosemirror/prosemirror-transform/src/mark.ts b/third_party/js/prosemirror/prosemirror-transform/src/mark.ts
@@ -0,0 +1,106 @@
+import {Mark, MarkType, Slice, Fragment, NodeType} from "prosemirror-model"
+
+import {Step} from "./step"
+import {Transform} from "./transform"
+import {AddMarkStep, RemoveMarkStep} from "./mark_step"
+import {ReplaceStep} from "./replace_step"
+
+export function addMark(tr: Transform, from: number, to: number, mark: Mark) {
+ let removed: Step[] = [], added: Step[] = []
+ let removing: RemoveMarkStep | undefined, adding: AddMarkStep | undefined
+ tr.doc.nodesBetween(from, to, (node, pos, parent) => {
+ if (!node.isInline) return
+ let marks = node.marks
+ if (!mark.isInSet(marks) && parent!.type.allowsMarkType(mark.type)) {
+ let start = Math.max(pos, from), end = Math.min(pos + node.nodeSize, to)
+ let newSet = mark.addToSet(marks)
+
+ for (let i = 0; i < marks.length; i++) {
+ if (!marks[i].isInSet(newSet)) {
+ if (removing && removing.to == start && removing.mark.eq(marks[i]))
+ (removing as any).to = end
+ else
+ removed.push(removing = new RemoveMarkStep(start, end, marks[i]))
+ }
+ }
+
+ if (adding && adding.to == start)
+ (adding as any).to = end
+ else
+ added.push(adding = new AddMarkStep(start, end, mark))
+ }
+ })
+
+ removed.forEach(s => tr.step(s))
+ added.forEach(s => tr.step(s))
+}
+
+export function removeMark(tr: Transform, from: number, to: number, mark?: Mark | MarkType | null) {
+ let matched: {style: Mark, from: number, to: number, step: number}[] = [], step = 0
+ tr.doc.nodesBetween(from, to, (node, pos) => {
+ if (!node.isInline) return
+ step++
+ let toRemove = null
+ if (mark instanceof MarkType) {
+ let set = node.marks, found
+ while (found = mark.isInSet(set)) {
+ ;(toRemove || (toRemove = [])).push(found)
+ set = found.removeFromSet(set)
+ }
+ } else if (mark) {
+ if (mark.isInSet(node.marks)) toRemove = [mark]
+ } else {
+ toRemove = node.marks
+ }
+ if (toRemove && toRemove.length) {
+ let end = Math.min(pos + node.nodeSize, to)
+ for (let i = 0; i < toRemove.length; i++) {
+ let style = toRemove[i], found
+ for (let j = 0; j < matched.length; j++) {
+ let m = matched[j]
+ if (m.step == step - 1 && style.eq(matched[j].style)) found = m
+ }
+ if (found) {
+ found.to = end
+ found.step = step
+ } else {
+ matched.push({style, from: Math.max(pos, from), to: end, step})
+ }
+ }
+ }
+ })
+ matched.forEach(m => tr.step(new RemoveMarkStep(m.from, m.to, m.style)))
+}
+
+export function clearIncompatible(tr: Transform, pos: number, parentType: NodeType,
+ match = parentType.contentMatch,
+ clearNewlines = true) {
+ let node = tr.doc.nodeAt(pos)!
+ let replSteps: Step[] = [], cur = pos + 1
+ for (let i = 0; i < node.childCount; i++) {
+ let child = node.child(i), end = cur + child.nodeSize
+ let allowed = match.matchType(child.type)
+ if (!allowed) {
+ replSteps.push(new ReplaceStep(cur, end, Slice.empty))
+ } else {
+ match = allowed
+ for (let j = 0; j < child.marks.length; j++) if (!parentType.allowsMarkType(child.marks[j].type))
+ tr.step(new RemoveMarkStep(cur, end, child.marks[j]))
+
+ if (clearNewlines && child.isText && parentType.whitespace != "pre") {
+ let m, newline = /\r?\n|\r/g, slice
+ while (m = newline.exec(child.text!)) {
+ if (!slice) slice = new Slice(Fragment.from(parentType.schema.text(" ", parentType.allowedMarks(child.marks))),
+ 0, 0)
+ replSteps.push(new ReplaceStep(cur + m.index, cur + m.index + m[0].length, slice))
+ }
+ }
+ }
+ cur = end
+ }
+ if (!match.validEnd) {
+ let fill = match.fillBefore(Fragment.empty, true)
+ tr.replace(cur, cur, new Slice(fill!, 0, 0))
+ }
+ for (let i = replSteps.length - 1; i >= 0; i--) tr.step(replSteps[i])
+}
diff --git a/third_party/js/prosemirror/prosemirror-transform/src/mark_step.ts b/third_party/js/prosemirror/prosemirror-transform/src/mark_step.ts
@@ -0,0 +1,224 @@
+import {Fragment, Slice, Node, Mark, Schema} from "prosemirror-model"
+import {Step, StepResult} from "./step"
+import {Mappable} from "./map"
+
+function mapFragment(fragment: Fragment, f: (child: Node, parent: Node, i: number) => Node, parent: Node): Fragment {
+ let mapped = []
+ for (let i = 0; i < fragment.childCount; i++) {
+ let child = fragment.child(i)
+ if (child.content.size) child = child.copy(mapFragment(child.content, f, child))
+ if (child.isInline) child = f(child, parent, i)
+ mapped.push(child)
+ }
+ return Fragment.fromArray(mapped)
+}
+
+/// Add a mark to all inline content between two positions.
+export class AddMarkStep extends Step {
+ /// Create a mark step.
+ constructor(
+ /// The start of the marked range.
+ readonly from: number,
+ /// The end of the marked range.
+ readonly to: number,
+ /// The mark to add.
+ readonly mark: Mark
+ ) {
+ super()
+ }
+
+ apply(doc: Node) {
+ let oldSlice = doc.slice(this.from, this.to), $from = doc.resolve(this.from)
+ let parent = $from.node($from.sharedDepth(this.to))
+ let slice = new Slice(mapFragment(oldSlice.content, (node, parent) => {
+ if (!node.isAtom || !parent.type.allowsMarkType(this.mark.type)) return node
+ return node.mark(this.mark.addToSet(node.marks))
+ }, parent), oldSlice.openStart, oldSlice.openEnd)
+ return StepResult.fromReplace(doc, this.from, this.to, slice)
+ }
+
+ invert(): Step {
+ return new RemoveMarkStep(this.from, this.to, this.mark)
+ }
+
+ map(mapping: Mappable): Step | null {
+ let from = mapping.mapResult(this.from, 1), to = mapping.mapResult(this.to, -1)
+ if (from.deleted && to.deleted || from.pos >= to.pos) return null
+ return new AddMarkStep(from.pos, to.pos, this.mark)
+ }
+
+ merge(other: Step): Step | null {
+ if (other instanceof AddMarkStep &&
+ other.mark.eq(this.mark) &&
+ this.from <= other.to && this.to >= other.from)
+ return new AddMarkStep(Math.min(this.from, other.from),
+ Math.max(this.to, other.to), this.mark)
+ return null
+ }
+
+ toJSON(): any {
+ return {stepType: "addMark", mark: this.mark.toJSON(),
+ from: this.from, to: this.to}
+ }
+
+ /// @internal
+ static fromJSON(schema: Schema, json: any) {
+ if (typeof json.from != "number" || typeof json.to != "number")
+ throw new RangeError("Invalid input for AddMarkStep.fromJSON")
+ return new AddMarkStep(json.from, json.to, schema.markFromJSON(json.mark))
+ }
+}
+
+Step.jsonID("addMark", AddMarkStep)
+
+/// Remove a mark from all inline content between two positions.
+export class RemoveMarkStep extends Step {
+ /// Create a mark-removing step.
+ constructor(
+ /// The start of the unmarked range.
+ readonly from: number,
+ /// The end of the unmarked range.
+ readonly to: number,
+ /// The mark to remove.
+ readonly mark: Mark
+ ) {
+ super()
+ }
+
+ apply(doc: Node) {
+ let oldSlice = doc.slice(this.from, this.to)
+ let slice = new Slice(mapFragment(oldSlice.content, node => {
+ return node.mark(this.mark.removeFromSet(node.marks))
+ }, doc), oldSlice.openStart, oldSlice.openEnd)
+ return StepResult.fromReplace(doc, this.from, this.to, slice)
+ }
+
+ invert(): Step {
+ return new AddMarkStep(this.from, this.to, this.mark)
+ }
+
+ map(mapping: Mappable): Step | null {
+ let from = mapping.mapResult(this.from, 1), to = mapping.mapResult(this.to, -1)
+ if (from.deleted && to.deleted || from.pos >= to.pos) return null
+ return new RemoveMarkStep(from.pos, to.pos, this.mark)
+ }
+
+ merge(other: Step): Step | null {
+ if (other instanceof RemoveMarkStep &&
+ other.mark.eq(this.mark) &&
+ this.from <= other.to && this.to >= other.from)
+ return new RemoveMarkStep(Math.min(this.from, other.from),
+ Math.max(this.to, other.to), this.mark)
+ return null
+ }
+
+ toJSON(): any {
+ return {stepType: "removeMark", mark: this.mark.toJSON(),
+ from: this.from, to: this.to}
+ }
+
+ /// @internal
+ static fromJSON(schema: Schema, json: any) {
+ if (typeof json.from != "number" || typeof json.to != "number")
+ throw new RangeError("Invalid input for RemoveMarkStep.fromJSON")
+ return new RemoveMarkStep(json.from, json.to, schema.markFromJSON(json.mark))
+ }
+}
+
+Step.jsonID("removeMark", RemoveMarkStep)
+
+/// Add a mark to a specific node.
+export class AddNodeMarkStep extends Step {
+ /// Create a node mark step.
+ constructor(
+ /// The position of the target node.
+ readonly pos: number,
+ /// The mark to add.
+ readonly mark: Mark
+ ) {
+ super()
+ }
+
+ apply(doc: Node) {
+ let node = doc.nodeAt(this.pos)
+ if (!node) return StepResult.fail("No node at mark step's position")
+ let updated = node.type.create(node.attrs, null, this.mark.addToSet(node.marks))
+ return StepResult.fromReplace(doc, this.pos, this.pos + 1, new Slice(Fragment.from(updated), 0, node.isLeaf ? 0 : 1))
+ }
+
+ invert(doc: Node): Step {
+ let node = doc.nodeAt(this.pos)
+ if (node) {
+ let newSet = this.mark.addToSet(node.marks)
+ if (newSet.length == node.marks.length) {
+ for (let i = 0; i < node.marks.length; i++)
+ if (!node.marks[i].isInSet(newSet))
+ return new AddNodeMarkStep(this.pos, node.marks[i])
+ return new AddNodeMarkStep(this.pos, this.mark)
+ }
+ }
+ return new RemoveNodeMarkStep(this.pos, this.mark)
+ }
+
+ map(mapping: Mappable): Step | null {
+ let pos = mapping.mapResult(this.pos, 1)
+ return pos.deletedAfter ? null : new AddNodeMarkStep(pos.pos, this.mark)
+ }
+
+ toJSON(): any {
+ return {stepType: "addNodeMark", pos: this.pos, mark: this.mark.toJSON()}
+ }
+
+ /// @internal
+ static fromJSON(schema: Schema, json: any) {
+ if (typeof json.pos != "number")
+ throw new RangeError("Invalid input for AddNodeMarkStep.fromJSON")
+ return new AddNodeMarkStep(json.pos, schema.markFromJSON(json.mark))
+ }
+}
+
+Step.jsonID("addNodeMark", AddNodeMarkStep)
+
+/// Remove a mark from a specific node.
+export class RemoveNodeMarkStep extends Step {
+ /// Create a mark-removing step.
+ constructor(
+ /// The position of the target node.
+ readonly pos: number,
+ /// The mark to remove.
+ readonly mark: Mark
+ ) {
+ super()
+ }
+
+ apply(doc: Node) {
+ let node = doc.nodeAt(this.pos)
+ if (!node) return StepResult.fail("No node at mark step's position")
+ let updated = node.type.create(node.attrs, null, this.mark.removeFromSet(node.marks))
+ return StepResult.fromReplace(doc, this.pos, this.pos + 1, new Slice(Fragment.from(updated), 0, node.isLeaf ? 0 : 1))
+ }
+
+ invert(doc: Node): Step {
+ let node = doc.nodeAt(this.pos)
+ if (!node || !this.mark.isInSet(node.marks)) return this
+ return new AddNodeMarkStep(this.pos, this.mark)
+ }
+
+ map(mapping: Mappable): Step | null {
+ let pos = mapping.mapResult(this.pos, 1)
+ return pos.deletedAfter ? null : new RemoveNodeMarkStep(pos.pos, this.mark)
+ }
+
+ toJSON(): any {
+ return {stepType: "removeNodeMark", pos: this.pos, mark: this.mark.toJSON()}
+ }
+
+ /// @internal
+ static fromJSON(schema: Schema, json: any) {
+ if (typeof json.pos != "number")
+ throw new RangeError("Invalid input for RemoveNodeMarkStep.fromJSON")
+ return new RemoveNodeMarkStep(json.pos, schema.markFromJSON(json.mark))
+ }
+}
+
+Step.jsonID("removeNodeMark", RemoveNodeMarkStep)
diff --git a/third_party/js/prosemirror/prosemirror-transform/src/replace.ts b/third_party/js/prosemirror/prosemirror-transform/src/replace.ts
@@ -0,0 +1,460 @@
+import {Fragment, Slice, Node, ResolvedPos, NodeType, ContentMatch, Attrs} from "prosemirror-model"
+
+import {Step} from "./step"
+import {ReplaceStep, ReplaceAroundStep} from "./replace_step"
+import {Transform} from "./transform"
+import {insertPoint} from "./structure"
+
+/// ‘Fit’ a slice into a given position in the document, producing a
+/// [step](#transform.Step) that inserts it. Will return null if
+/// there's no meaningful way to insert the slice here, or inserting it
+/// would be a no-op (an empty slice over an empty range).
+export function replaceStep(doc: Node, from: number, to = from, slice = Slice.empty): Step | null {
+ if (from == to && !slice.size) return null
+
+ let $from = doc.resolve(from), $to = doc.resolve(to)
+ // Optimization -- avoid work if it's obvious that it's not needed.
+ if (fitsTrivially($from, $to, slice)) return new ReplaceStep(from, to, slice)
+ return new Fitter($from, $to, slice).fit()
+}
+
+function fitsTrivially($from: ResolvedPos, $to: ResolvedPos, slice: Slice) {
+ return !slice.openStart && !slice.openEnd && $from.start() == $to.start() &&
+ $from.parent.canReplace($from.index(), $to.index(), slice.content)
+}
+
+interface Fittable {
+ sliceDepth: number
+ frontierDepth: number
+ parent: Node | null
+ inject?: Fragment | null
+ wrap?: readonly NodeType[]
+}
+
+// Algorithm for 'placing' the elements of a slice into a gap:
+//
+// We consider the content of each node that is open to the left to be
+// independently placeable. I.e. in <p("foo"), p("bar")>, 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 {
+ frontier: {type: NodeType, match: ContentMatch}[] = []
+ placed: Fragment = Fragment.empty
+
+ constructor(
+ readonly $from: ResolvedPos,
+ readonly $to: ResolvedPos,
+ public unplaced: Slice
+ ) {
+ 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(): Fittable | undefined {
+ let startDepth = this.unplaced.openStart
+ for (let cur = this.unplaced.content, d = 0, openEnd = this.unplaced.openEnd; d < startDepth; d++) {
+ let node = cur.firstChild!
+ if (cur.childCount > 1) openEnd = 0
+ if (node.type.spec.isolating && openEnd <= d) {
+ startDepth = d
+ break
+ }
+ cur = node.content
+ }
+
+ // Only try wrapping nodes (pass 2) after finding a place without
+ // wrapping failed.
+ for (let pass = 1; pass <= 2; pass++) {
+ for (let sliceDepth = pass == 1 ? startDepth : 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: Fragment | null = 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}: Fittable) {
+ 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: ResolvedPos) {
+ 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: ResolvedPos) {
+ 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: NodeType, attrs: Attrs | null = null, content?: Fragment) {
+ 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: Fragment, depth: number, count: number): Fragment {
+ 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: Fragment, depth: number, content: Fragment): Fragment {
+ 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: Fragment, depth: number) {
+ for (let i = 0; i < depth; i++) fragment = fragment.firstChild!.content
+ return fragment
+}
+
+function closeNodeStart(node: Node, openStart: number, openEnd: number) {
+ 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: ResolvedPos, depth: number, type: NodeType, match: ContentMatch, open: boolean) {
+ 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: NodeType, fragment: Fragment, start: number) {
+ for (let i = start; i < fragment.childCount; i++)
+ if (!type.allowsMarks(fragment.child(i).marks)) return true
+ return false
+}
+
+function definesContent(type: NodeType) {
+ return type.spec.defining || type.spec.definingForContent
+}
+
+export function replaceRange(tr: Transform, from: number, to: number, slice: 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, $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: Node[] = [], 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 leftNode = leftNodes[d], def = definesContent(leftNode.type)
+ if (def && !leftNode.sameMarkup($from.node(Math.abs(preferredTarget) - 1))) preferredDepth = d
+ else if (def || !leftNode.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: Fragment, depth: number, oldOpen: number, newOpen: number, parent?: Node) {
+ 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
+}
+
+export function replaceRangeWith(tr: Transform, from: number, to: number, node: 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))
+}
+
+export function deleteRange(tr: Transform, from: number, to: number) {
+ 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 &&
+ $from.start(d - 1) == $to.start(d - 1) && $from.node(d - 1).canReplace($from.index(d - 1), $to.index(d - 1)))
+ 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: ResolvedPos, $to: ResolvedPos) {
+ let result: number[] = [], 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
+}
diff --git a/third_party/js/prosemirror/prosemirror-transform/src/replace_step.ts b/third_party/js/prosemirror/prosemirror-transform/src/replace_step.ts
@@ -0,0 +1,178 @@
+import {Slice, Node, Schema} from "prosemirror-model"
+
+import {Step, StepResult} from "./step"
+import {StepMap, Mappable} from "./map"
+
+/// Replace a part of the document with a slice of new content.
+export class ReplaceStep extends Step {
+ /// The given `slice` should fit the 'gap' between `from` and
+ /// `to`—the depths must line up, and the surrounding nodes must be
+ /// able to be joined with the open sides of the slice. When
+ /// `structure` is true, the step will fail if the content between
+ /// from and to is not just a sequence of closing and then opening
+ /// tokens (this is to guard against rebased replace steps
+ /// overwriting something they weren't supposed to).
+ constructor(
+ /// The start position of the replaced range.
+ readonly from: number,
+ /// The end position of the replaced range.
+ readonly to: number,
+ /// The slice to insert.
+ readonly slice: Slice,
+ /// @internal
+ readonly structure = false
+ ) {
+ super()
+ }
+
+ apply(doc: Node) {
+ if (this.structure && contentBetween(doc, this.from, this.to))
+ return StepResult.fail("Structure replace would overwrite content")
+ return StepResult.fromReplace(doc, this.from, this.to, this.slice)
+ }
+
+ getMap() {
+ return new StepMap([this.from, this.to - this.from, this.slice.size])
+ }
+
+ invert(doc: Node) {
+ return new ReplaceStep(this.from, this.from + this.slice.size, doc.slice(this.from, this.to))
+ }
+
+ map(mapping: Mappable) {
+ let from = mapping.mapResult(this.from, 1), to = mapping.mapResult(this.to, -1)
+ if (from.deletedAcross && to.deletedAcross) return null
+ return new ReplaceStep(from.pos, Math.max(from.pos, to.pos), this.slice, this.structure)
+ }
+
+ merge(other: Step) {
+ if (!(other instanceof ReplaceStep) || other.structure || this.structure) return null
+
+ if (this.from + this.slice.size == other.from && !this.slice.openEnd && !other.slice.openStart) {
+ let slice = this.slice.size + other.slice.size == 0 ? Slice.empty
+ : new Slice(this.slice.content.append(other.slice.content), this.slice.openStart, other.slice.openEnd)
+ return new ReplaceStep(this.from, this.to + (other.to - other.from), slice, this.structure)
+ } else if (other.to == this.from && !this.slice.openStart && !other.slice.openEnd) {
+ let slice = this.slice.size + other.slice.size == 0 ? Slice.empty
+ : new Slice(other.slice.content.append(this.slice.content), other.slice.openStart, this.slice.openEnd)
+ return new ReplaceStep(other.from, this.to, slice, this.structure)
+ } else {
+ return null
+ }
+ }
+
+ toJSON(): any {
+ let json: any = {stepType: "replace", from: this.from, to: this.to}
+ if (this.slice.size) json.slice = this.slice.toJSON()
+ if (this.structure) json.structure = true
+ return json
+ }
+
+ /// @internal
+ static fromJSON(schema: Schema, json: any) {
+ if (typeof json.from != "number" || typeof json.to != "number")
+ throw new RangeError("Invalid input for ReplaceStep.fromJSON")
+ return new ReplaceStep(json.from, json.to, Slice.fromJSON(schema, json.slice), !!json.structure)
+ }
+}
+
+Step.jsonID("replace", ReplaceStep)
+
+/// Replace a part of the document with a slice of content, but
+/// preserve a range of the replaced content by moving it into the
+/// slice.
+export class ReplaceAroundStep extends Step {
+ /// Create a replace-around step with the given range and gap.
+ /// `insert` should be the point in the slice into which the content
+ /// of the gap should be moved. `structure` has the same meaning as
+ /// it has in the [`ReplaceStep`](#transform.ReplaceStep) class.
+ constructor(
+ /// The start position of the replaced range.
+ readonly from: number,
+ /// The end position of the replaced range.
+ readonly to: number,
+ /// The start of preserved range.
+ readonly gapFrom: number,
+ /// The end of preserved range.
+ readonly gapTo: number,
+ /// The slice to insert.
+ readonly slice: Slice,
+ /// The position in the slice where the preserved range should be
+ /// inserted.
+ readonly insert: number,
+ /// @internal
+ readonly structure = false
+ ) {
+ super()
+ }
+
+ apply(doc: Node) {
+ if (this.structure && (contentBetween(doc, this.from, this.gapFrom) ||
+ contentBetween(doc, this.gapTo, this.to)))
+ return StepResult.fail("Structure gap-replace would overwrite content")
+
+ let gap = doc.slice(this.gapFrom, this.gapTo)
+ if (gap.openStart || gap.openEnd)
+ return StepResult.fail("Gap is not a flat range")
+ let inserted = this.slice.insertAt(this.insert, gap.content)
+ if (!inserted) return StepResult.fail("Content does not fit in gap")
+ return StepResult.fromReplace(doc, this.from, this.to, inserted)
+ }
+
+ getMap() {
+ return new StepMap([this.from, this.gapFrom - this.from, this.insert,
+ this.gapTo, this.to - this.gapTo, this.slice.size - this.insert])
+ }
+
+ invert(doc: Node) {
+ let gap = this.gapTo - this.gapFrom
+ return new ReplaceAroundStep(this.from, this.from + this.slice.size + gap,
+ this.from + this.insert, this.from + this.insert + gap,
+ doc.slice(this.from, this.to).removeBetween(this.gapFrom - this.from, this.gapTo - this.from),
+ this.gapFrom - this.from, this.structure)
+ }
+
+ map(mapping: Mappable) {
+ let from = mapping.mapResult(this.from, 1), to = mapping.mapResult(this.to, -1)
+ let gapFrom = this.from == this.gapFrom ? from.pos : mapping.map(this.gapFrom, -1)
+ let gapTo = this.to == this.gapTo ? to.pos : mapping.map(this.gapTo, 1)
+ if ((from.deletedAcross && to.deletedAcross) || gapFrom < from.pos || gapTo > to.pos) return null
+ return new ReplaceAroundStep(from.pos, to.pos, gapFrom, gapTo, this.slice, this.insert, this.structure)
+ }
+
+ toJSON(): any {
+ let json: any = {stepType: "replaceAround", from: this.from, to: this.to,
+ gapFrom: this.gapFrom, gapTo: this.gapTo, insert: this.insert}
+ if (this.slice.size) json.slice = this.slice.toJSON()
+ if (this.structure) json.structure = true
+ return json
+ }
+
+ /// @internal
+ static fromJSON(schema: Schema, json: any) {
+ if (typeof json.from != "number" || typeof json.to != "number" ||
+ typeof json.gapFrom != "number" || typeof json.gapTo != "number" || typeof json.insert != "number")
+ throw new RangeError("Invalid input for ReplaceAroundStep.fromJSON")
+ return new ReplaceAroundStep(json.from, json.to, json.gapFrom, json.gapTo,
+ Slice.fromJSON(schema, json.slice), json.insert, !!json.structure)
+ }
+}
+
+Step.jsonID("replaceAround", ReplaceAroundStep)
+
+function contentBetween(doc: Node, from: number, to: number) {
+ let $from = doc.resolve(from), dist = to - from, depth = $from.depth
+ while (dist > 0 && depth > 0 && $from.indexAfter(depth) == $from.node(depth).childCount) {
+ depth--
+ dist--
+ }
+ if (dist > 0) {
+ let next = $from.node(depth).maybeChild($from.indexAfter(depth))
+ while (dist > 0) {
+ if (!next || next.isLeaf) return true
+ next = next.firstChild
+ dist--
+ }
+ }
+ return false
+}
diff --git a/third_party/js/prosemirror/prosemirror-transform/src/step.ts b/third_party/js/prosemirror/prosemirror-transform/src/step.ts
@@ -0,0 +1,97 @@
+import {ReplaceError, Schema, Slice, Node} from "prosemirror-model"
+
+import {StepMap, Mappable} from "./map"
+
+const stepsByID: {[id: string]: {fromJSON(schema: Schema, json: any): Step}} = Object.create(null)
+
+/// A step object represents an atomic change. It generally applies
+/// only to the document it was created for, since the positions
+/// stored in it will only make sense for that document.
+///
+/// New steps are defined by creating classes that extend `Step`,
+/// overriding the `apply`, `invert`, `map`, `getMap` and `fromJSON`
+/// methods, and registering your class with a unique
+/// JSON-serialization identifier using
+/// [`Step.jsonID`](#transform.Step^jsonID).
+export abstract class Step {
+ /// Applies this step to the given document, returning a result
+ /// object that either indicates failure, if the step can not be
+ /// applied to this document, or indicates success by containing a
+ /// transformed document.
+ abstract apply(doc: Node): StepResult
+
+ /// Get the step map that represents the changes made by this step,
+ /// and which can be used to transform between positions in the old
+ /// and the new document.
+ getMap(): StepMap { return StepMap.empty }
+
+ /// Create an inverted version of this step. Needs the document as it
+ /// was before the step as argument.
+ abstract invert(doc: Node): Step
+
+ /// Map this step through a mappable thing, returning either a
+ /// version of that step with its positions adjusted, or `null` if
+ /// the step was entirely deleted by the mapping.
+ abstract map(mapping: Mappable): Step | null
+
+ /// Try to merge this step with another one, to be applied directly
+ /// after it. Returns the merged step when possible, null if the
+ /// steps can't be merged.
+ merge(other: Step): Step | null { return null }
+
+ /// Create a JSON-serializeable representation of this step. When
+ /// defining this for a custom subclass, make sure the result object
+ /// includes the step type's [JSON id](#transform.Step^jsonID) under
+ /// the `stepType` property.
+ abstract toJSON(): any
+
+ /// Deserialize a step from its JSON representation. Will call
+ /// through to the step class' own implementation of this method.
+ static fromJSON(schema: Schema, json: any): Step {
+ if (!json || !json.stepType) throw new RangeError("Invalid input for Step.fromJSON")
+ let type = stepsByID[json.stepType]
+ if (!type) throw new RangeError(`No step type ${json.stepType} defined`)
+ return type.fromJSON(schema, json)
+ }
+
+ /// To be able to serialize steps to JSON, each step needs a string
+ /// ID to attach to its JSON representation. Use this method to
+ /// register an ID for your step classes. Try to pick something
+ /// that's unlikely to clash with steps from other modules.
+ static jsonID(id: string, stepClass: {fromJSON(schema: Schema, json: any): Step}) {
+ if (id in stepsByID) throw new RangeError("Duplicate use of step JSON ID " + id)
+ stepsByID[id] = stepClass
+ ;(stepClass as any).prototype.jsonID = id
+ return stepClass
+ }
+}
+
+/// The result of [applying](#transform.Step.apply) a step. Contains either a
+/// new document or a failure value.
+export class StepResult {
+ /// @internal
+ constructor(
+ /// The transformed document, if successful.
+ readonly doc: Node | null,
+ /// The failure message, if unsuccessful.
+ readonly failed: string | null
+ ) {}
+
+ /// Create a successful step result.
+ static ok(doc: Node) { return new StepResult(doc, null) }
+
+ /// Create a failed step result.
+ static fail(message: string) { return new StepResult(null, message) }
+
+ /// Call [`Node.replace`](#model.Node.replace) with the given
+ /// arguments. Create a successful result if it succeeds, and a
+ /// failed one if it throws a `ReplaceError`.
+ static fromReplace(doc: Node, from: number, to: number, slice: Slice) {
+ try {
+ return StepResult.ok(doc.replace(from, to, slice))
+ } catch (e) {
+ if (e instanceof ReplaceError) return StepResult.fail(e.message)
+ throw e
+ }
+ }
+}
diff --git a/third_party/js/prosemirror/prosemirror-transform/src/structure.ts b/third_party/js/prosemirror/prosemirror-transform/src/structure.ts
@@ -0,0 +1,349 @@
+import {Slice, Fragment, NodeRange, NodeType, Node, Mark, Attrs, ContentMatch} from "prosemirror-model"
+
+import {Transform} from "./transform"
+import {ReplaceStep, ReplaceAroundStep} from "./replace_step"
+import {clearIncompatible} from "./mark"
+
+function canCut(node: Node, start: number, end: number) {
+ return (start == 0 || node.canReplace(start, node.childCount)) &&
+ (end == node.childCount || node.canReplace(0, end))
+}
+
+/// Try to find a target depth to which the content in the given range
+/// can be lifted. Will not go across
+/// [isolating](#model.NodeSpec.isolating) parent nodes.
+export function liftTarget(range: NodeRange): number | null {
+ let parent = range.parent
+ let content = parent.content.cutByIndex(range.startIndex, range.endIndex)
+ for (let depth = range.depth, contentBefore = 0, contentAfter = 0;; --depth) {
+ let node = range.$from.node(depth)
+ let index = range.$from.index(depth) + contentBefore, endIndex = range.$to.indexAfter(depth) - contentAfter
+ if (depth < range.depth && node.canReplace(index, endIndex, content))
+ return depth
+ if (depth == 0 || node.type.spec.isolating || !canCut(node, index, endIndex)) break
+ if (index) contentBefore = 1
+ if (endIndex < node.childCount) contentAfter = 1
+ }
+ return null
+}
+
+export function lift(tr: Transform, range: NodeRange, target: number) {
+ let {$from, $to, depth} = range
+
+ let gapStart = $from.before(depth + 1), gapEnd = $to.after(depth + 1)
+ let start = gapStart, end = gapEnd
+
+ let before = Fragment.empty, openStart = 0
+ for (let d = depth, splitting = false; d > target; d--)
+ if (splitting || $from.index(d) > 0) {
+ splitting = true
+ before = Fragment.from($from.node(d).copy(before))
+ openStart++
+ } else {
+ start--
+ }
+ let after = Fragment.empty, openEnd = 0
+ for (let d = depth, splitting = false; d > target; d--)
+ if (splitting || $to.after(d + 1) < $to.end(d)) {
+ splitting = true
+ after = Fragment.from($to.node(d).copy(after))
+ openEnd++
+ } else {
+ end++
+ }
+
+ tr.step(new ReplaceAroundStep(start, end, gapStart, gapEnd,
+ new Slice(before.append(after), openStart, openEnd),
+ before.size - openStart, true))
+}
+
+/// Try to find a valid way to wrap the content in the given range in a
+/// node of the given type. May introduce extra nodes around and inside
+/// the wrapper node, if necessary. Returns null if no valid wrapping
+/// could be found. When `innerRange` is given, that range's content is
+/// used as the content to fit into the wrapping, instead of the
+/// content of `range`.
+export function findWrapping(
+ range: NodeRange,
+ nodeType: NodeType,
+ attrs: Attrs | null = null,
+ innerRange = range
+): {type: NodeType, attrs: Attrs | null}[] | null {
+ let around = findWrappingOutside(range, nodeType)
+ let inner = around && findWrappingInside(innerRange, nodeType)
+ if (!inner) return null
+ return (around!.map(withAttrs) as {type: NodeType, attrs: Attrs | null}[])
+ .concat({type: nodeType, attrs}).concat(inner.map(withAttrs))
+}
+
+function withAttrs(type: NodeType) { return {type, attrs: null} }
+
+function findWrappingOutside(range: NodeRange, type: NodeType) {
+ let {parent, startIndex, endIndex} = range
+ let around = parent.contentMatchAt(startIndex).findWrapping(type)
+ if (!around) return null
+ let outer = around.length ? around[0] : type
+ return parent.canReplaceWith(startIndex, endIndex, outer) ? around : null
+}
+
+function findWrappingInside(range: NodeRange, type: NodeType) {
+ let {parent, startIndex, endIndex} = range
+ let inner = parent.child(startIndex)
+ let inside = type.contentMatch.findWrapping(inner.type)
+ if (!inside) return null
+ let lastType = inside.length ? inside[inside.length - 1] : type
+ let innerMatch: ContentMatch | null = lastType.contentMatch
+ for (let i = startIndex; innerMatch && i < endIndex; i++)
+ innerMatch = innerMatch.matchType(parent.child(i).type)
+ if (!innerMatch || !innerMatch.validEnd) return null
+ return inside
+}
+
+export function wrap(tr: Transform, range: NodeRange, wrappers: readonly {type: NodeType, attrs?: Attrs | null}[]) {
+ let content = Fragment.empty
+ for (let i = wrappers.length - 1; i >= 0; i--) {
+ if (content.size) {
+ let match = wrappers[i].type.contentMatch.matchFragment(content)
+ if (!match || !match.validEnd)
+ throw new RangeError("Wrapper type given to Transform.wrap does not form valid content of its parent wrapper")
+ }
+ content = Fragment.from(wrappers[i].type.create(wrappers[i].attrs, content))
+ }
+
+ let start = range.start, end = range.end
+ tr.step(new ReplaceAroundStep(start, end, start, end, new Slice(content, 0, 0), wrappers.length, true))
+}
+
+export function setBlockType(tr: Transform, from: number, to: number,
+ type: NodeType, attrs: Attrs | null | ((oldNode: Node) => Attrs)) {
+ if (!type.isTextblock) throw new RangeError("Type given to setBlockType should be a textblock")
+ let mapFrom = tr.steps.length
+ tr.doc.nodesBetween(from, to, (node, pos) => {
+ let attrsHere = typeof attrs == "function" ? attrs(node) : attrs
+ if (node.isTextblock && !node.hasMarkup(type, attrsHere) &&
+ canChangeType(tr.doc, tr.mapping.slice(mapFrom).map(pos), type)) {
+ let convertNewlines = null
+ if (type.schema.linebreakReplacement) {
+ let pre = type.whitespace == "pre", supportLinebreak = !!type.contentMatch.matchType(type.schema.linebreakReplacement)
+ if (pre && !supportLinebreak) convertNewlines = false
+ else if (!pre && supportLinebreak) convertNewlines = true
+ }
+ // Ensure all markup that isn't allowed in the new node type is cleared
+ if (convertNewlines === false) replaceLinebreaks(tr, node, pos, mapFrom)
+ clearIncompatible(tr, tr.mapping.slice(mapFrom).map(pos, 1), type, undefined, convertNewlines === null)
+ let mapping = tr.mapping.slice(mapFrom)
+ let startM = mapping.map(pos, 1), endM = mapping.map(pos + node.nodeSize, 1)
+ tr.step(new ReplaceAroundStep(startM, endM, startM + 1, endM - 1,
+ new Slice(Fragment.from(type.create(attrsHere, null, node.marks)), 0, 0), 1, true))
+ if (convertNewlines === true) replaceNewlines(tr, node, pos, mapFrom)
+ return false
+ }
+ })
+}
+
+function replaceNewlines(tr: Transform, node: Node, pos: number, mapFrom: number) {
+ node.forEach((child, offset) => {
+ if (child.isText) {
+ let m, newline = /\r?\n|\r/g
+ while (m = newline.exec(child.text!)) {
+ let start = tr.mapping.slice(mapFrom).map(pos + 1 + offset + m.index)
+ tr.replaceWith(start, start + 1, node.type.schema.linebreakReplacement!.create())
+ }
+ }
+ })
+}
+
+function replaceLinebreaks(tr: Transform, node: Node, pos: number, mapFrom: number) {
+ node.forEach((child, offset) => {
+ if (child.type == child.type.schema.linebreakReplacement) {
+ let start = tr.mapping.slice(mapFrom).map(pos + 1 + offset)
+ tr.replaceWith(start, start + 1, node.type.schema.text("\n"))
+ }
+ })
+}
+
+function canChangeType(doc: Node, pos: number, type: NodeType) {
+ let $pos = doc.resolve(pos), index = $pos.index()
+ return $pos.parent.canReplaceWith(index, index + 1, type)
+}
+
+/// Change the type, attributes, and/or marks of the node at `pos`.
+/// When `type` isn't given, the existing node type is preserved,
+export function setNodeMarkup(tr: Transform, pos: number, type: NodeType | undefined | null,
+ attrs: Attrs | null, marks: readonly Mark[] | undefined) {
+ let node = tr.doc.nodeAt(pos)
+ if (!node) throw new RangeError("No node at given position")
+ if (!type) type = node.type
+ let newNode = type.create(attrs, null, marks || node.marks)
+ if (node.isLeaf)
+ return tr.replaceWith(pos, pos + node.nodeSize, newNode)
+
+ if (!type.validContent(node.content))
+ throw new RangeError("Invalid content for node type " + type.name)
+
+ tr.step(new ReplaceAroundStep(pos, pos + node.nodeSize, pos + 1, pos + node.nodeSize - 1,
+ new Slice(Fragment.from(newNode), 0, 0), 1, true))
+}
+
+/// Check whether splitting at the given position is allowed.
+export function canSplit(doc: Node, pos: number, depth = 1,
+ typesAfter?: (null | {type: NodeType, attrs?: Attrs | null})[]): boolean {
+ let $pos = doc.resolve(pos), base = $pos.depth - depth
+ let innerType = (typesAfter && typesAfter[typesAfter.length - 1]) || $pos.parent
+ if (base < 0 || $pos.parent.type.spec.isolating ||
+ !$pos.parent.canReplace($pos.index(), $pos.parent.childCount) ||
+ !innerType.type.validContent($pos.parent.content.cutByIndex($pos.index(), $pos.parent.childCount)))
+ return false
+ for (let d = $pos.depth - 1, i = depth - 2; d > base; d--, i--) {
+ let node = $pos.node(d), index = $pos.index(d)
+ if (node.type.spec.isolating) return false
+ let rest = node.content.cutByIndex(index, node.childCount)
+ let overrideChild = typesAfter && typesAfter[i + 1]
+ if (overrideChild)
+ rest = rest.replaceChild(0, overrideChild.type.create(overrideChild.attrs))
+ let after = (typesAfter && typesAfter[i]) || node
+ if (!node.canReplace(index + 1, node.childCount) || !after.type.validContent(rest))
+ return false
+ }
+ let index = $pos.indexAfter(base)
+ let baseType = typesAfter && typesAfter[0]
+ return $pos.node(base).canReplaceWith(index, index, baseType ? baseType.type : $pos.node(base + 1).type)
+}
+
+export function split(tr: Transform, pos: number, depth = 1, typesAfter?: (null | {type: NodeType, attrs?: Attrs | null})[]) {
+ let $pos = tr.doc.resolve(pos), before = Fragment.empty, after = Fragment.empty
+ for (let d = $pos.depth, e = $pos.depth - depth, i = depth - 1; d > e; d--, i--) {
+ before = Fragment.from($pos.node(d).copy(before))
+ let typeAfter = typesAfter && typesAfter[i]
+ after = Fragment.from(typeAfter ? typeAfter.type.create(typeAfter.attrs, after) : $pos.node(d).copy(after))
+ }
+ tr.step(new ReplaceStep(pos, pos, new Slice(before.append(after), depth, depth), true))
+}
+
+/// Test whether the blocks before and after a given position can be
+/// joined.
+export function canJoin(doc: Node, pos: number): boolean {
+ let $pos = doc.resolve(pos), index = $pos.index()
+ return joinable($pos.nodeBefore, $pos.nodeAfter) &&
+ $pos.parent.canReplace(index, index + 1)
+}
+
+function canAppendWithSubstitutedLinebreaks(a: Node, b: Node) {
+ if (!b.content.size) a.type.compatibleContent(b.type)
+ let match: ContentMatch | null = a.contentMatchAt(a.childCount)
+ let {linebreakReplacement} = a.type.schema
+ for (let i = 0; i < b.childCount; i++) {
+ let child = b.child(i)
+ let type = child.type == linebreakReplacement ? a.type.schema.nodes.text : child.type
+ match = match.matchType(type)
+ if (!match) return false
+ if (!a.type.allowsMarks(child.marks)) return false
+ }
+ return match.validEnd
+}
+
+function joinable(a: Node | null, b: Node | null) {
+ return !!(a && b && !a.isLeaf && canAppendWithSubstitutedLinebreaks(a, b))
+}
+
+/// Find an ancestor of the given position that can be joined to the
+/// block before (or after if `dir` is positive). Returns the joinable
+/// point, if any.
+export function joinPoint(doc: Node, pos: number, dir = -1) {
+ let $pos = doc.resolve(pos)
+ for (let d = $pos.depth;; d--) {
+ let before, after, index = $pos.index(d)
+ if (d == $pos.depth) {
+ before = $pos.nodeBefore
+ after = $pos.nodeAfter
+ } else if (dir > 0) {
+ before = $pos.node(d + 1)
+ index++
+ after = $pos.node(d).maybeChild(index)
+ } else {
+ before = $pos.node(d).maybeChild(index - 1)
+ after = $pos.node(d + 1)
+ }
+ if (before && !before.isTextblock && joinable(before, after) &&
+ $pos.node(d).canReplace(index, index + 1)) return pos
+ if (d == 0) break
+ pos = dir < 0 ? $pos.before(d) : $pos.after(d)
+ }
+}
+
+export function join(tr: Transform, pos: number, depth: number) {
+ let convertNewlines = null
+ let {linebreakReplacement} = tr.doc.type.schema
+ let $before = tr.doc.resolve(pos - depth), beforeType = $before.node().type
+ if (linebreakReplacement && beforeType.inlineContent) {
+ let pre = beforeType.whitespace == "pre"
+ let supportLinebreak = !!beforeType.contentMatch.matchType(linebreakReplacement)
+ if (pre && !supportLinebreak) convertNewlines = false
+ else if (!pre && supportLinebreak) convertNewlines = true
+ }
+ let mapFrom = tr.steps.length
+ if (convertNewlines === false) {
+ let $after = tr.doc.resolve(pos + depth)
+ replaceLinebreaks(tr, $after.node(), $after.before(), mapFrom)
+ }
+ if (beforeType.inlineContent)
+ clearIncompatible(tr, pos + depth - 1, beforeType,
+ $before.node().contentMatchAt($before.index()), convertNewlines == null)
+ let mapping = tr.mapping.slice(mapFrom), start = mapping.map(pos - depth)
+ tr.step(new ReplaceStep(start, mapping.map(pos + depth, - 1), Slice.empty, true))
+ if (convertNewlines === true) {
+ let $full = tr.doc.resolve(start)
+ replaceNewlines(tr, $full.node(), $full.before(), tr.steps.length)
+ }
+ return tr
+}
+
+/// Try to find a point where a node of the given type can be inserted
+/// near `pos`, by searching up the node hierarchy when `pos` itself
+/// isn't a valid place but is at the start or end of a node. Return
+/// null if no position was found.
+export function insertPoint(doc: Node, pos: number, nodeType: NodeType): number | null {
+ let $pos = doc.resolve(pos)
+ if ($pos.parent.canReplaceWith($pos.index(), $pos.index(), nodeType)) return pos
+
+ if ($pos.parentOffset == 0)
+ for (let d = $pos.depth - 1; d >= 0; d--) {
+ let index = $pos.index(d)
+ if ($pos.node(d).canReplaceWith(index, index, nodeType)) return $pos.before(d + 1)
+ if (index > 0) return null
+ }
+ if ($pos.parentOffset == $pos.parent.content.size)
+ for (let d = $pos.depth - 1; d >= 0; d--) {
+ let index = $pos.indexAfter(d)
+ if ($pos.node(d).canReplaceWith(index, index, nodeType)) return $pos.after(d + 1)
+ if (index < $pos.node(d).childCount) return null
+ }
+ return null
+}
+
+/// Finds a position at or around the given position where the given
+/// slice can be inserted. Will look at parent nodes' nearest boundary
+/// and try there, even if the original position wasn't directly at the
+/// start or end of that node. Returns null when no position was found.
+export function dropPoint(doc: Node, pos: number, slice: Slice): number | null {
+ let $pos = doc.resolve(pos)
+ if (!slice.content.size) return pos
+ let content = slice.content
+ for (let i = 0; i < slice.openStart; i++) content = content.firstChild!.content
+ for (let pass = 1; pass <= (slice.openStart == 0 && slice.size ? 2 : 1); pass++) {
+ for (let d = $pos.depth; d >= 0; d--) {
+ let bias = d == $pos.depth ? 0 : $pos.pos <= ($pos.start(d + 1) + $pos.end(d + 1)) / 2 ? -1 : 1
+ let insertPos = $pos.index(d) + (bias > 0 ? 1 : 0)
+ let parent = $pos.node(d), fits: boolean | null = false
+ if (pass == 1) {
+ fits = parent.canReplace(insertPos, insertPos, content)
+ } else {
+ let wrapping = parent.contentMatchAt(insertPos).findWrapping(content.firstChild!.type)
+ fits = wrapping && parent.canReplaceWith(insertPos, insertPos, wrapping[0])
+ }
+ if (fits)
+ return bias == 0 ? $pos.pos : bias < 0 ? $pos.before(d + 1) : $pos.after(d + 1)
+ }
+ }
+ return null
+}
diff --git a/third_party/js/prosemirror/prosemirror-transform/src/transform.ts b/third_party/js/prosemirror/prosemirror-transform/src/transform.ts
@@ -0,0 +1,252 @@
+import {Node, NodeType, Mark, MarkType, ContentMatch, Slice, Fragment, NodeRange, Attrs} from "prosemirror-model"
+
+import {Mapping} from "./map"
+import {Step} from "./step"
+import {addMark, removeMark, clearIncompatible} from "./mark"
+import {replaceStep, replaceRange, replaceRangeWith, deleteRange} from "./replace"
+import {lift, wrap, setBlockType, setNodeMarkup, split, join} from "./structure"
+import {AttrStep, DocAttrStep} from "./attr_step"
+import {AddNodeMarkStep, RemoveNodeMarkStep} from "./mark_step"
+
+/// @internal
+export let TransformError = class extends Error {}
+
+TransformError = function TransformError(this: any, message: string) {
+ let err = Error.call(this, message)
+ ;(err as any).__proto__ = TransformError.prototype
+ return err
+} as any
+
+TransformError.prototype = Object.create(Error.prototype)
+TransformError.prototype.constructor = TransformError
+TransformError.prototype.name = "TransformError"
+
+/// Abstraction to build up and track an array of
+/// [steps](#transform.Step) representing a document transformation.
+///
+/// Most transforming methods return the `Transform` object itself, so
+/// that they can be chained.
+export class Transform {
+ /// The steps in this transform.
+ readonly steps: Step[] = []
+ /// The documents before each of the steps.
+ readonly docs: Node[] = []
+ /// A mapping with the maps for each of the steps in this transform.
+ readonly mapping: Mapping = new Mapping
+
+ /// Create a transform that starts with the given document.
+ constructor(
+ /// The current document (the result of applying the steps in the
+ /// transform).
+ public doc: Node
+ ) {}
+
+ /// 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: 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: 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: Step, doc: Node) {
+ 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: number, to = from, slice = Slice.empty): this {
+ 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: number, to: number, content: Fragment | Node | readonly Node[]): this {
+ return this.replace(from, to, new Slice(Fragment.from(content), 0, 0))
+ }
+
+ /// Delete the content between the given positions.
+ delete(from: number, to: number): this {
+ return this.replace(from, to, Slice.empty)
+ }
+
+ /// Insert the given content at the given position.
+ insert(pos: number, content: Fragment | Node | readonly Node[]): this {
+ return this.replaceWith(pos, pos, content)
+ }
+
+ /// Replace a range of the document with a given slice, using
+ /// `from`, `to`, and the slice's
+ /// [`openStart`](#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](#model.NodeSpec.definingAsContext), or including an
+ /// open parent node from the slice that _is_ marked as [defining
+ /// its content](#model.NodeSpec.definingForContent).
+ ///
+ /// This is the method, for example, to handle paste. The similar
+ /// [`replace`](#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: number, to: number, slice: Slice): this {
+ 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: number, to: number, node: Node): this {
+ 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: number, to: number): this {
+ 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`](#transform.liftTarget) to compute `target`, to make
+ /// sure the lift is valid.
+ lift(range: NodeRange, target: number): this {
+ 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: number, depth: number = 1): this {
+ join(this, pos, depth)
+ return this
+ }
+
+ /// Wrap the given [range](#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`](#transform.findWrapping).
+ wrap(range: NodeRange, wrappers: readonly {type: NodeType, attrs?: Attrs | null}[]): this {
+ 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: number, to = from, type: NodeType, attrs: Attrs | null | ((oldNode: Node) => Attrs) = null): this {
+ 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: number, type?: NodeType | null, attrs: Attrs | null = null, marks?: readonly Mark[]): this {
+ setNodeMarkup(this, pos, type, attrs, marks)
+ return this
+ }
+
+ /// Set a single attribute on a given node to a new value.
+ /// The `pos` addresses the document content. Use `setDocAttribute`
+ /// to set attributes on the document itself.
+ setNodeAttribute(pos: number, attr: string, value: any): this {
+ this.step(new AttrStep(pos, attr, value))
+ return this
+ }
+
+ /// Set a single attribute on the document to a new value.
+ setDocAttribute(attr: string, value: any): this {
+ this.step(new DocAttrStep(attr, value))
+ return this
+ }
+
+ /// Add a mark to the node at position `pos`.
+ addNodeMark(pos: number, mark: Mark): this {
+ this.step(new AddNodeMarkStep(pos, mark))
+ return this
+ }
+
+ /// Remove a mark (or all marks of the given type) from the node at
+ /// position `pos`.
+ removeNodeMark(pos: number, mark: Mark | MarkType): this {
+ let node = this.doc.nodeAt(pos)
+ if (!node) throw new RangeError("No node at position " + pos)
+ if (mark instanceof Mark) {
+ if (mark.isInSet(node.marks)) this.step(new RemoveNodeMarkStep(pos, mark))
+ } else {
+ let set = node.marks, found, steps: Step[] = []
+ while (found = mark.isInSet(set)) {
+ steps.push(new RemoveNodeMarkStep(pos, found))
+ set = found.removeFromSet(set)
+ }
+ for (let i = steps.length - 1; i >= 0; i--) this.step(steps[i])
+ }
+ 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 (with the outermost nodes coming first).
+ split(pos: number, depth = 1, typesAfter?: (null | {type: NodeType, attrs?: Attrs | null})[]) {
+ split(this, pos, depth, typesAfter)
+ return this
+ }
+
+ /// Add the given mark to the inline content between `from` and `to`.
+ addMark(from: number, to: number, mark: Mark): this {
+ 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: number, to: number, mark?: Mark | MarkType | null) {
+ 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](#model.ContentMatch) as
+ /// third argument.
+ clearIncompatible(pos: number, parentType: NodeType, match?: ContentMatch) {
+ clearIncompatible(this, pos, parentType, match)
+ return this
+ }
+}
diff --git a/third_party/js/prosemirror/prosemirror-view/LICENSE b/third_party/js/prosemirror/prosemirror-view/LICENSE
@@ -0,0 +1,19 @@
+Copyright (C) 2015-2017 by Marijn Haverbeke <marijn@haverbeke.berlin> and others
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
diff --git a/third_party/js/prosemirror/prosemirror-view/moz.yaml b/third_party/js/prosemirror/prosemirror-view/moz.yaml
@@ -0,0 +1,64 @@
+# Version of this schema
+schema: 1
+
+bugzilla:
+ # Bugzilla product and component for this directory and subdirectories
+ product: Firefox
+ component: General
+
+# Document the source of externally hosted code
+origin:
+
+ # Short name of the package/library
+ name: prosemirror-view
+
+ description: ProseMirror's view component
+
+ # Full URL for the package's homepage/etc
+ # Usually different from repository url
+ url: https://prosemirror.net/
+
+ # Human-readable identifier for this version/release
+ # Generally "version NNN", "tag SSS", "bookmark SSS"
+ release: 1.41.4 (2025-12-01T17:21:00+01:00).
+
+ # Revision to pull in
+ # Must be a long or short commit SHA (long preferred) or a tag
+ revision: 1.41.4
+
+ # The package's license, where possible using the mnemonic from
+ # https://spdx.org/licenses/
+ # Multiple licenses can be specified (as a YAML list)
+ # A "LICENSE" file must exist containing the full license text
+ license: MIT
+
+ # If the package's license is specified in a particular file,
+ # this is the name of the file.
+ # optional
+ license-file: LICENSE
+
+# Configuration for the automated vendoring system.
+# optional
+vendoring:
+
+ # Repository URL to vendor from
+ # eg. https://github.com/kinetiknz/nestegg
+ # Any repository host can be specified here, however initially we'll only
+ # support automated vendoring from selected sources.
+ url: https://github.com/ProseMirror/prosemirror-view
+
+ # Type of hosting for the upstream repository
+ # Valid values are 'gitlab', 'github', googlesource
+ source-hosting: github
+
+ # Whether to track by commit or tag
+ tracking: tag
+
+ exclude:
+ - "**"
+
+ include:
+ - LICENSE
+ - src/
+ - style/
+ - package.json
diff --git a/third_party/js/prosemirror/prosemirror-view/package.json b/third_party/js/prosemirror/prosemirror-view/package.json
@@ -0,0 +1,43 @@
+{
+ "name": "prosemirror-view",
+ "version": "1.41.4",
+ "description": "ProseMirror's view component",
+ "type": "module",
+ "main": "dist/index.cjs",
+ "module": "dist/index.js",
+ "types": "dist/index.d.ts",
+ "exports": {
+ ".": {
+ "import": "./dist/index.js",
+ "require": "./dist/index.cjs"
+ },
+ "./style/prosemirror.css": "./style/prosemirror.css"
+ },
+ "sideEffects": ["./style/prosemirror.css"],
+ "style": "style/prosemirror.css",
+ "license": "MIT",
+ "maintainers": [
+ {
+ "name": "Marijn Haverbeke",
+ "email": "marijn@haverbeke.berlin",
+ "web": "http://marijnhaverbeke.nl"
+ }
+ ],
+ "repository": {
+ "type": "git",
+ "url": "git://github.com/prosemirror/prosemirror-view.git"
+ },
+ "dependencies": {
+ "prosemirror-model": "^1.20.0",
+ "prosemirror-state": "^1.0.0",
+ "prosemirror-transform": "^1.1.0"
+ },
+ "devDependencies": {
+ "@prosemirror/buildhelper": "^0.1.5",
+ "prosemirror-test-builder": "^1.0.0"
+ },
+ "scripts": {
+ "test": "pm-runtests",
+ "prepare": "pm-buildhelper src/index.ts"
+ }
+}
diff --git a/third_party/js/prosemirror/prosemirror-view/src/README.md b/third_party/js/prosemirror/prosemirror-view/src/README.md
@@ -0,0 +1,38 @@
+ProseMirror's view module displays a given [editor
+state](#state.EditorState) in the DOM, and handles user events.
+
+Make sure you load `style/prosemirror.css` as a stylesheet when using
+this module.
+
+@EditorView
+
+### Props
+
+@EditorProps
+
+@NodeViewConstructor
+
+@MarkViewConstructor
+
+@DirectEditorProps
+
+@NodeView
+
+@MarkView
+
+@ViewMutationRecord
+
+@DOMEventMap
+
+### Decorations
+
+Decorations make it possible to influence the way the document is
+drawn, without actually changing the document.
+
+@Decoration
+
+@DecorationAttrs
+
+@DecorationSet
+
+@DecorationSource
diff --git a/third_party/js/prosemirror/prosemirror-view/src/browser.ts b/third_party/js/prosemirror/prosemirror-view/src/browser.ts
@@ -0,0 +1,24 @@
+const nav = typeof navigator != "undefined" ? navigator : null
+const 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)
+
+export const ie = !!(ie_upto10 || ie_11up || ie_edge)
+export const ie_version = ie_upto10 ? (document as any).documentMode : ie_11up ? +ie_11up[1] : ie_edge ? +ie_edge[1] : 0
+export const gecko = !ie && /gecko\/(\d+)/i.test(agent)
+export const gecko_version = gecko && +(/Firefox\/(\d+)/.exec(agent) || [0, 0])[1]
+
+const _chrome = !ie && /Chrome\/(\d+)/.exec(agent)
+export const chrome = !!_chrome
+export const chrome_version = _chrome ? +_chrome[1] : 0
+export const safari = !ie && !!nav && /Apple Computer/.test(nav.vendor)
+// Is true for both iOS and iPadOS for convenience
+export const ios = safari && (/Mobile\/\w+/.test(agent) || !!nav && nav.maxTouchPoints > 2)
+export const mac = ios || (nav ? /Mac/.test(nav.platform) : false)
+export const windows = nav ? /Win/.test(nav.platform) : false
+export const android = /Android \d/.test(agent)
+export const webkit = !!doc && "webkitFontSmoothing" in doc.documentElement.style
+export const webkit_version = webkit ? +(/\bAppleWebKit\/(\d+)/.exec(navigator.userAgent) || [0, 0])[1] : 0
diff --git a/third_party/js/prosemirror/prosemirror-view/src/capturekeys.ts b/third_party/js/prosemirror/prosemirror-view/src/capturekeys.ts
@@ -0,0 +1,345 @@
+import {Selection, NodeSelection, TextSelection, AllSelection, EditorState} from "prosemirror-state"
+import {EditorView} from "./index"
+import * as browser from "./browser"
+import {domIndex, selectionCollapsed, hasBlockDesc} from "./dom"
+import {selectionToDOM} from "./selection"
+
+function moveSelectionBlock(state: EditorState, dir: number) {
+ 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: EditorView, sel: Selection) {
+ view.dispatch(view.state.tr.setSelection(sel).scrollIntoView())
+ return true
+}
+
+function selectHorizontally(view: EditorView, dir: number, mods: string) {
+ let sel = view.state.selection
+ if (sel instanceof TextSelection) {
+ if (mods.indexOf("s") > -1) {
+ let {$head} = sel, node = $head.textOffset ? null : dir < 0 ? $head.nodeBefore : $head.nodeAfter
+ if (!node || node.isText || !node.isLeaf) return false
+ let $newHead = view.state.doc.resolve($head.pos + node.nodeSize * (dir < 0 ? -1 : 1))
+ return apply(view, new TextSelection(sel.$anchor, $newHead))
+ } else if (!sel.empty) {
+ return false
+ } else if (view.endOfTextblock(dir > 0 ? "forward" : "backward")) {
+ let next = moveSelectionBlock(view.state, dir)
+ if (next && (next instanceof NodeSelection)) return apply(view, next)
+ return false
+ } else if (!(browser.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 (NodeSelection.isSelectable(node)) {
+ return apply(view, new NodeSelection(dir < 0 ? view.state.doc.resolve($head.pos - node.nodeSize) : $head))
+ } else if (browser.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 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: Node) {
+ return node.nodeType == 3 ? node.nodeValue!.length : node.childNodes.length
+}
+
+function isIgnorable(dom: Node, dir: number) {
+ let desc = dom.pmViewDesc
+ return desc && desc.size == 0 && (dir < 0 || dom.nextSibling || dom.nodeName != "BR")
+}
+
+function skipIgnoredNodes(view: EditorView, dir: number) {
+ return dir < 0 ? skipIgnoredNodesBefore(view) : skipIgnoredNodesAfter(view)
+}
+
+// Make sure the cursor isn't directly after one or more ignored
+// nodes, which will confuse the browser's cursor motion logic.
+function skipIgnoredNodesBefore(view: EditorView) {
+ let sel = view.domSelectionRange()
+ let node = sel.focusNode!, offset = sel.focusOffset
+ if (!node) return
+ let moveNode, moveOffset: number | undefined, 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 (browser.gecko && node.nodeType == 1 && offset < nodeLen(node) && isIgnorable(node.childNodes[offset], -1)) force = true
+ for (;;) {
+ if (offset > 0) {
+ if (node.nodeType != 1) {
+ break
+ } else {
+ let before = node.childNodes[offset - 1]
+ if (isIgnorable(before, -1)) {
+ 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, -1)) {
+ 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, node, offset)
+ else if (moveNode) setSelFocus(view, moveNode, moveOffset!)
+}
+
+// Make sure the cursor isn't directly before one or more ignored
+// nodes.
+function skipIgnoredNodesAfter(view: EditorView) {
+ let sel = view.domSelectionRange()
+ let node = sel.focusNode!, offset = sel.focusOffset
+ if (!node) return
+ let len = nodeLen(node)
+ let moveNode, moveOffset: number | undefined
+ for (;;) {
+ if (offset < len) {
+ if (node.nodeType != 1) break
+ let after = node.childNodes[offset]
+ if (isIgnorable(after, 1)) {
+ moveNode = node
+ moveOffset = ++offset
+ }
+ else break
+ } else if (isBlockNode(node)) {
+ break
+ } else {
+ let next = node.nextSibling
+ while (next && isIgnorable(next, 1)) {
+ 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, moveNode, moveOffset!)
+}
+
+function isBlockNode(dom: Node) {
+ let desc = dom.pmViewDesc
+ return desc && desc.node && desc.node.isBlock
+}
+
+function textNodeAfter(node: Node | null, offset: number): Text | undefined {
+ while (node && offset == node.childNodes.length && !hasBlockDesc(node)) {
+ offset = domIndex(node) + 1
+ node = node.parentNode
+ }
+ while (node && offset < node.childNodes.length) {
+ let next = node.childNodes[offset]
+ if (next.nodeType == 3) return next as Text
+ if (next.nodeType == 1 && (next as HTMLElement).contentEditable == "false") break
+ node = next
+ offset = 0
+ }
+}
+
+function textNodeBefore(node: Node | null, offset: number): Text | undefined {
+ while (node && !offset && !hasBlockDesc(node)) {
+ offset = domIndex(node)
+ node = node.parentNode
+ }
+ while (node && offset) {
+ let next = node.childNodes[offset - 1]
+ if (next.nodeType == 3) return next as Text
+ if (next.nodeType == 1 && (next as HTMLElement).contentEditable == "false") break
+ node = next
+ offset = node.childNodes.length
+ }
+}
+
+function setSelFocus(view: EditorView, node: Node, offset: number) {
+ if (node.nodeType != 3) {
+ let before, after
+ if (after = textNodeAfter(node, offset)) {
+ node = after
+ offset = 0
+ } else if (before = textNodeBefore(node, offset)) {
+ node = before
+ offset = before.nodeValue!.length
+ }
+ }
+
+ let sel = view.domSelection()
+ if (!sel) return
+ 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)
+}
+
+function findDirection(view: EditorView, pos: number): "rtl" | "ltr" {
+ let $pos = view.state.doc.resolve(pos)
+ if (!(browser.chrome || browser.windows) && $pos.parent.inlineContent) {
+ let coords = view.coordsAtPos(pos)
+ if (pos > $pos.start()) {
+ let before = view.coordsAtPos(pos - 1)
+ let mid = (before.top + before.bottom) / 2
+ if (mid > coords.top && mid < coords.bottom && Math.abs(before.left - coords.left) > 1)
+ return before.left < coords.left ? "ltr" : "rtl"
+ }
+ if (pos < $pos.end()) {
+ let after = view.coordsAtPos(pos + 1)
+ let mid = (after.top + after.bottom) / 2
+ if (mid > coords.top && mid < coords.bottom && Math.abs(after.left - coords.left) > 1)
+ return after.left > coords.left ? "ltr" : "rtl"
+ }
+ }
+ let computed = getComputedStyle(view.dom).direction
+ return computed == "rtl" ? "rtl" : "ltr"
+}
+
+// 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: EditorView, dir: number, mods: string) {
+ let sel = view.state.selection
+ if (sel instanceof TextSelection && !sel.empty || mods.indexOf("s") > -1) return false
+ if (browser.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 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: EditorView, dir: number) {
+ 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: EditorView, node: HTMLElement, state: string) {
+ 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: EditorView) {
+ if (!browser.safari || view.state.selection.$head.parentOffset > 0) return false
+ let {focusNode, focusOffset} = view.domSelectionRange()
+ if (focusNode && focusNode.nodeType == 1 && focusOffset == 0 &&
+ focusNode.firstChild && (focusNode.firstChild as HTMLElement).contentEditable == "false") {
+ let child = focusNode.firstChild as HTMLElement
+ 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: KeyboardEvent) {
+ let result = ""
+ if (event.ctrlKey) result += "c"
+ if (event.metaKey) result += "m"
+ if (event.altKey) result += "a"
+ if (event.shiftKey) result += "s"
+ return result
+}
+
+export function captureKeyDown(view: EditorView, event: KeyboardEvent) {
+ let code = event.keyCode, mods = getMods(event)
+ if (code == 8 || (browser.mac && code == 72 && mods == "c")) { // Backspace, Ctrl-h on Mac
+ return stopNativeHorizontalDelete(view, -1) || skipIgnoredNodes(view, -1)
+ } else if ((code == 46 && !event.shiftKey) || (browser.mac && code == 68 && mods == "c")) { // Delete, Ctrl-d on Mac
+ return stopNativeHorizontalDelete(view, 1) || skipIgnoredNodes(view, 1)
+ } else if (code == 13 || code == 27) { // Enter, Esc
+ return true
+ } else if (code == 37 || (browser.mac && code == 66 && mods == "c")) { // Left arrow, Ctrl-b on Mac
+ let dir = code == 37 ? (findDirection(view, view.state.selection.from) == "ltr" ? -1 : 1) : -1
+ return selectHorizontally(view, dir, mods) || skipIgnoredNodes(view, dir)
+ } else if (code == 39 || (browser.mac && code == 70 && mods == "c")) { // Right arrow, Ctrl-f on Mac
+ let dir = code == 39 ? (findDirection(view, view.state.selection.from) == "ltr" ? 1 : -1) : 1
+ return selectHorizontally(view, dir, mods) || skipIgnoredNodes(view, dir)
+ } else if (code == 38 || (browser.mac && code == 80 && mods == "c")) { // Up arrow, Ctrl-p on Mac
+ return selectVertically(view, -1, mods) || skipIgnoredNodes(view, -1)
+ } else if (code == 40 || (browser.mac && code == 78 && mods == "c")) { // Down arrow, Ctrl-n on Mac
+ return safariDownArrowBug(view) || selectVertically(view, 1, mods) || skipIgnoredNodes(view, 1)
+ } else if (mods == (browser.mac ? "m" : "c") &&
+ (code == 66 || code == 73 || code == 89 || code == 90)) { // Mod-[biyz]
+ return true
+ }
+ return false
+}
diff --git a/third_party/js/prosemirror/prosemirror-view/src/clipboard.ts b/third_party/js/prosemirror/prosemirror-view/src/clipboard.ts
@@ -0,0 +1,263 @@
+import {Slice, Fragment, DOMParser, DOMSerializer, ResolvedPos, NodeType, Node} from "prosemirror-model"
+import * as browser from "./browser"
+import {EditorView} from "./index"
+
+export function serializeForClipboard(view: EditorView, slice: Slice) {
+ view.someProp("transformCopied", f => { slice = f(slice!, view) })
+
+ 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 as HTMLElement).setAttribute(
+ "data-pm-slice", `${openStart} ${openEnd}${wrappers ? ` -${wrappers}` : ""} ${JSON.stringify(context)}`)
+
+ let text = view.someProp("clipboardTextSerializer", f => f(slice, view)) ||
+ slice.content.textBetween(0, slice.content.size, "\n\n")
+
+ return {dom: wrap, text, slice}
+}
+
+// Read a slice of content from the clipboard (or drop data).
+export function parseFromClipboard(view: EditorView, text: string, html: string | null, plainText: boolean, $context: ResolvedPos) {
+ let inCode = $context.parent.type.spec.code
+ let dom: HTMLElement | undefined, slice: Slice | undefined
+ if (!html && !text) return null
+ let asText = !!text && (plainText || inCode || !html)
+ if (asText) {
+ view.someProp("transformPastedText", f => { text = f(text, inCode || plainText, view) })
+ if (inCode) {
+ slice = new Slice(Fragment.from(view.state.schema.text(text.replace(/\r\n?/g, "\n"))), 0, 0)
+ view.someProp("transformPasted", f => { slice = f(slice!, view, true) })
+ return slice
+ }
+ let parsed = view.someProp("clipboardTextParser", f => f(text, $context, plainText, view))
+ 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!, view) })
+ dom = readHTML(html!)
+ if (browser.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; i--) {
+ let child = dom!.firstChild
+ while (child && child.nodeType != 1) child = child.nextSibling
+ if (!child) break
+ dom = child as HTMLElement
+ }
+
+ 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!, view, asText) })
+ 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: Fragment, $context: ResolvedPos) {
+ 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: readonly NodeType[] | undefined, result: Node[] | null = []
+ 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: Node, wrap: readonly NodeType[], 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: readonly NodeType[], lastWrap: readonly NodeType[],
+ node: Node, sibling: Node, depth: number): Node | undefined {
+ 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: Node, depth: number) {
+ 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: Fragment, side: number, from: number, to: number, depth: number, openEnd: number) {
+ let node = side < 0 ? fragment.firstChild! : fragment.lastChild!, inner = node.content
+ if (fragment.childCount > 1) openEnd = 0
+ if (depth < to - 1) inner = closeRange(inner, side, from, to, depth + 1, openEnd)
+ if (depth >= from)
+ inner = side < 0 ? node.contentMatchAt(0)!.fillBefore(inner, 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: Slice, openStart: number, openEnd: number) {
+ 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 =
+// "<td>..</td>"` the table cells are ignored.
+const wrapMap: {[node: string]: string[]} = {
+ thead: ["table"],
+ tbody: ["table"],
+ tfoot: ["table"],
+ caption: ["table"],
+ colgroup: ["table"],
+ col: ["table", "colgroup"],
+ tr: ["table", "tbody"],
+ td: ["table", "tbody", "tr"],
+ th: ["table", "tbody", "tr"]
+}
+
+let _detachedDoc: Document | null = null
+function detachedDoc() {
+ return _detachedDoc || (_detachedDoc = document.implementation.createHTMLDocument("title"))
+}
+
+let _policy: any = null
+
+function maybeWrapTrusted(html: string): string {
+ let trustedTypes = (window as any).trustedTypes
+ if (!trustedTypes) return html
+ // With the require-trusted-types-for CSP, Chrome will block
+ // innerHTML, even on a detached document. This wraps the string in
+ // a way that makes the browser allow us to use its parser again.
+ if (!_policy)
+ _policy = trustedTypes.defaultPolicy || trustedTypes.createPolicy("ProseMirrorClipboard", {createHTML: (s: string) => s})
+ return _policy.createHTML(html)
+}
+
+function readHTML(html: string) {
+ let metas = /^(\s*<meta [^>]*>)*/.exec(html)
+ if (metas) html = html.slice(metas[0].length)
+ let elt = detachedDoc().createElement("div")
+ let firstTag = /<([a-z][^>\s]+)/i.exec(html), wrap
+ if (wrap = firstTag && wrapMap[firstTag[1].toLowerCase()])
+ html = wrap.map(n => "<" + n + ">").join("") + html + wrap.map(n => "</" + n + ">").reverse().join("")
+ elt.innerHTML = maybeWrapTrusted(html)
+ if (wrap) for (let i = 0; i < wrap.length; i++) elt = elt.querySelector(wrap[i]) || elt
+ return elt
+}
+
+// Webkit browsers do some hard-to-predict replacement of regular
+// spaces with non-breaking spaces when putting content on the
+// clipboard. This tries to convert such non-breaking spaces (which
+// will be wrapped in a plain span on Chrome, a span with class
+// Apple-converted-space on Safari) back to regular spaces.
+function restoreReplacedSpaces(dom: HTMLElement) {
+ let nodes = dom.querySelectorAll(browser.chrome ? "span:not([class]):not([style])" : "span.Apple-converted-space")
+ for (let i = 0; i < nodes.length; i++) {
+ let node = nodes[i]
+ if (node.childNodes.length == 1 && node.textContent == "\u00a0" && node.parentNode)
+ node.parentNode.replaceChild(dom.ownerDocument.createTextNode(" "), node)
+ }
+}
+
+function addContext(slice: Slice, context: string) {
+ if (!slice.size) return slice
+ let schema = slice.content.firstChild!.type.schema, array
+ try { array = JSON.parse(context) }
+ catch(e) { return slice }
+ let {content, openStart, openEnd} = slice
+ for (let i = array.length - 2; i >= 0; i -= 2) {
+ let type = schema.nodes[array[i]]
+ if (!type || type.hasRequiredAttrs()) break
+ content = Fragment.from(type.create(array[i + 1], content))
+ openStart++; openEnd++
+ }
+ return new Slice(content, openStart, openEnd)
+}
diff --git a/third_party/js/prosemirror/prosemirror-view/src/decoration.ts b/third_party/js/prosemirror/prosemirror-view/src/decoration.ts
@@ -0,0 +1,793 @@
+import {Node, Mark} from "prosemirror-model"
+import {Mappable, Mapping} from "prosemirror-transform"
+import {EditorView} from "./index"
+import {DOMNode} from "./dom"
+
+function compareObjs(a: {[prop: string]: any}, b: {[prop: string]: any}) {
+ if (a == b) return true
+ for (let p in a) if (a[p] !== b[p]) return false
+ for (let p in b) if (!(p in a)) return false
+ return true
+}
+
+export interface DecorationType {
+ spec: any
+ map(mapping: Mappable, span: Decoration, offset: number, oldOffset: number): Decoration | null
+ valid(node: Node, span: Decoration): boolean
+ eq(other: DecorationType): boolean
+ destroy(dom: DOMNode): void
+}
+
+export type WidgetConstructor = ((view: EditorView, getPos: () => number | undefined) => DOMNode) | DOMNode
+
+export class WidgetType implements DecorationType {
+ spec: any
+ side: number
+
+ constructor(readonly toDOM: WidgetConstructor, spec: any) {
+ this.spec = spec || noSpec
+ this.side = this.spec.side || 0
+ }
+
+ map(mapping: Mappable, span: Decoration, offset: number, oldOffset: number): Decoration | null {
+ let {pos, deleted} = mapping.mapResult(span.from + oldOffset, this.side < 0 ? -1 : 1)
+ return deleted ? null : new Decoration(pos - offset, pos - offset, this)
+ }
+
+ valid() { return true }
+
+ eq(other: WidgetType) {
+ return this == other ||
+ (other instanceof WidgetType &&
+ (this.spec.key && this.spec.key == other.spec.key ||
+ this.toDOM == other.toDOM && compareObjs(this.spec, other.spec)))
+ }
+
+ destroy(node: DOMNode) {
+ if (this.spec.destroy) this.spec.destroy(node)
+ }
+}
+
+export class InlineType implements DecorationType {
+ spec: any
+
+ constructor(readonly attrs: DecorationAttrs, spec: any) {
+ this.spec = spec || noSpec
+ }
+
+ map(mapping: Mappable, span: Decoration, offset: number, oldOffset: number): Decoration | null {
+ let from = mapping.map(span.from + oldOffset, this.spec.inclusiveStart ? -1 : 1) - offset
+ let to = mapping.map(span.to + oldOffset, this.spec.inclusiveEnd ? 1 : -1) - offset
+ return from >= to ? null : new Decoration(from, to, this)
+ }
+
+ valid(_: Node, span: Decoration) { return span.from < span.to }
+
+ eq(other: DecorationType): boolean {
+ return this == other ||
+ (other instanceof InlineType && compareObjs(this.attrs, other.attrs) &&
+ compareObjs(this.spec, other.spec))
+ }
+
+ static is(span: Decoration) { return span.type instanceof InlineType }
+
+ destroy() {}
+}
+
+export class NodeType implements DecorationType {
+ spec: any
+ constructor(readonly attrs: DecorationAttrs, spec: any) {
+ this.spec = spec || noSpec
+ }
+
+ map(mapping: Mappable, span: Decoration, offset: number, oldOffset: number): Decoration | null {
+ let from = mapping.mapResult(span.from + oldOffset, 1)
+ if (from.deleted) return null
+ let to = mapping.mapResult(span.to + oldOffset, -1)
+ if (to.deleted || to.pos <= from.pos) return null
+ return new Decoration(from.pos - offset, to.pos - offset, this)
+ }
+
+ valid(node: Node, span: Decoration): boolean {
+ let {index, offset} = node.content.findIndex(span.from), child
+ return offset == span.from && !(child = node.child(index)).isText && offset + child.nodeSize == span.to
+ }
+
+ eq(other: DecorationType): boolean {
+ return this == other ||
+ (other instanceof NodeType && compareObjs(this.attrs, other.attrs) &&
+ compareObjs(this.spec, other.spec))
+ }
+
+ destroy() {}
+}
+
+/// Decoration objects can be provided to the view through the
+/// [`decorations` prop](#view.EditorProps.decorations). They come in
+/// several variants—see the static members of this class for details.
+export class Decoration {
+ /// @internal
+ constructor(
+ /// The start position of the decoration.
+ readonly from: number,
+ /// The end position. Will be the same as `from` for [widget
+ /// decorations](#view.Decoration^widget).
+ readonly to: number,
+ /// @internal
+ readonly type: DecorationType
+ ) {}
+
+ /// @internal
+ copy(from: number, to: number) {
+ return new Decoration(from, to, this.type)
+ }
+
+ /// @internal
+ eq(other: Decoration, offset = 0) {
+ return this.type.eq(other.type) && this.from + offset == other.from && this.to + offset == other.to
+ }
+
+ /// @internal
+ map(mapping: Mappable, offset: number, oldOffset: number) {
+ return this.type.map(mapping, this, offset, oldOffset)
+ }
+
+ /// Creates a widget decoration, which is a DOM node that's shown in
+ /// the document at the given position. It is recommended that you
+ /// delay rendering the widget by passing a function that will be
+ /// called when the widget is actually drawn in a view, but you can
+ /// also directly pass a DOM node. `getPos` can be used to find the
+ /// widget's current document position.
+ static widget(pos: number, toDOM: WidgetConstructor, spec?: {
+ /// Controls which side of the document position this widget is
+ /// associated with. When negative, it is drawn before a cursor
+ /// at its position, and content inserted at that position ends
+ /// up after the widget. When zero (the default) or positive, the
+ /// widget is drawn after the cursor and content inserted there
+ /// ends up before the widget.
+ ///
+ /// When there are multiple widgets at a given position, their
+ /// `side` values determine the order in which they appear. Those
+ /// with lower values appear first. The ordering of widgets with
+ /// the same `side` value is unspecified.
+ ///
+ /// When `marks` is null, `side` also determines the marks that
+ /// the widget is wrapped in—those of the node before when
+ /// negative, those of the node after when positive.
+ side?: number
+
+ /// By default, the cursor, when at the position of the widget,
+ /// will be strictly kept on the side indicated by
+ /// [`side`](#view.Decoration^widget^spec.side). Set this to true
+ /// to allow the DOM selection to stay on the other side if the
+ /// client sets it there.
+ ///
+ /// **Note**: Mapping of this decoration, which decides on which
+ /// side insertions at its position appear, will still happen
+ /// according to `side`, and keyboard cursor motion will not,
+ /// without further custom handling, visit both sides of the
+ /// widget.
+ relaxedSide?: boolean
+
+ /// The precise set of marks to draw around the widget.
+ marks?: readonly Mark[]
+
+ /// Can be used to control which DOM events, when they bubble out
+ /// of this widget, the editor view should ignore.
+ stopEvent?: (event: Event) => boolean
+
+ /// When set (defaults to false), selection changes inside the
+ /// widget are ignored, and don't cause ProseMirror to try and
+ /// re-sync the selection with its selection state.
+ ignoreSelection?: boolean
+
+ /// When comparing decorations of this type (in order to decide
+ /// whether it needs to be redrawn), ProseMirror will by default
+ /// compare the widget DOM node by identity. If you pass a key,
+ /// that key will be compared instead, which can be useful when
+ /// you generate decorations on the fly and don't want to store
+ /// and reuse DOM nodes. Make sure that any widgets with the same
+ /// key are interchangeable—if widgets differ in, for example,
+ /// the behavior of some event handler, they should get
+ /// different keys.
+ key?: string
+
+ /// Called when the widget decoration is removed or the editor is
+ /// destroyed.
+ destroy?: (node: DOMNode) => void
+
+ /// Specs allow arbitrary additional properties.
+ [key: string]: any
+ }): Decoration {
+ return new Decoration(pos, pos, new WidgetType(toDOM, spec))
+ }
+
+ /// Creates an inline decoration, which adds the given attributes to
+ /// each inline node between `from` and `to`.
+ static inline(from: number, to: number, attrs: DecorationAttrs, spec?: {
+ /// Determines how the left side of the decoration is
+ /// [mapped](#transform.Position_Mapping) when content is
+ /// inserted directly at that position. By default, the decoration
+ /// won't include the new content, but you can set this to `true`
+ /// to make it inclusive.
+ inclusiveStart?: boolean
+
+ /// Determines how the right side of the decoration is mapped.
+ /// See
+ /// [`inclusiveStart`](#view.Decoration^inline^spec.inclusiveStart).
+ inclusiveEnd?: boolean
+
+ /// Specs may have arbitrary additional properties.
+ [key: string]: any
+ }) {
+ return new Decoration(from, to, new InlineType(attrs, spec))
+ }
+
+ /// Creates a node decoration. `from` and `to` should point precisely
+ /// before and after a node in the document. That node, and only that
+ /// node, will receive the given attributes.
+ static node(from: number, to: number, attrs: DecorationAttrs, spec?: any) {
+ return new Decoration(from, to, new NodeType(attrs, spec))
+ }
+
+ /// The spec provided when creating this decoration. Can be useful
+ /// if you've stored extra information in that object.
+ get spec() { return this.type.spec }
+
+ /// @internal
+ get inline() { return this.type instanceof InlineType }
+
+ /// @internal
+ get widget() { return this.type instanceof WidgetType }
+}
+
+/// A set of attributes to add to a decorated node. Most properties
+/// simply directly correspond to DOM attributes of the same name,
+/// which will be set to the property's value. These are exceptions:
+export type DecorationAttrs = {
+ /// When non-null, the target node is wrapped in a DOM element of
+ /// this type (and the other attributes are applied to this element).
+ nodeName?: string
+
+ /// A CSS class name or a space-separated set of class names to be
+ /// _added_ to the classes that the node already had.
+ class?: string
+
+ /// A string of CSS to be _added_ to the node's existing `style` property.
+ style?: string
+
+ /// Any other properties are treated as regular DOM attributes.
+ [attribute: string]: string | undefined
+}
+
+const none: readonly any[] = [], noSpec = {}
+
+/// An object that can [provide](#view.EditorProps.decorations)
+/// decorations. Implemented by [`DecorationSet`](#view.DecorationSet),
+/// and passed to [node views](#view.EditorProps.nodeViews).
+export interface DecorationSource {
+ /// Map the set of decorations in response to a change in the
+ /// document.
+ map: (mapping: Mapping, node: Node) => DecorationSource
+ /// @internal
+ locals(node: Node): readonly Decoration[]
+ /// Extract a DecorationSource containing decorations for the given child node at the given offset.
+ forChild(offset: number, child: Node): DecorationSource
+ /// @internal
+ eq(other: DecorationSource): boolean
+ /// Call the given function for each decoration set in the group.
+ forEachSet(f: (set: DecorationSet) => void): void
+}
+
+/// A collection of [decorations](#view.Decoration), organized in such
+/// a way that the drawing algorithm can efficiently use and compare
+/// them. This is a persistent data structure—it is not modified,
+/// updates create a new value.
+export class DecorationSet implements DecorationSource {
+ /// @internal
+ local: readonly Decoration[]
+ /// @internal
+ children: readonly (number | DecorationSet)[]
+
+ /// @internal
+ constructor(local: readonly Decoration[], children: readonly (number | DecorationSet)[]) {
+ this.local = local.length ? local : none
+ this.children = children.length ? children : none
+ }
+
+ /// Create a set of decorations, using the structure of the given
+ /// document. This will consume (modify) the `decorations` array, so
+ /// you must make a copy if you want need to preserve that.
+ static create(doc: Node, decorations: Decoration[]) {
+ return decorations.length ? buildTree(decorations, doc, 0, noSpec) : empty
+ }
+
+ /// Find all decorations in this set which touch the given range
+ /// (including decorations that start or end directly at the
+ /// boundaries) and match the given predicate on their spec. When
+ /// `start` and `end` are omitted, all decorations in the set are
+ /// considered. When `predicate` isn't given, all decorations are
+ /// assumed to match.
+ find(start?: number, end?: number, predicate?: (spec: any) => boolean): Decoration[] {
+ let result: Decoration[] = []
+ this.findInner(start == null ? 0 : start, end == null ? 1e9 : end, result, 0, predicate)
+ return result
+ }
+
+ private findInner(start: number, end: number, result: Decoration[], offset: number, predicate?: (spec: any) => boolean) {
+ for (let i = 0; i < this.local.length; i++) {
+ let span = this.local[i]
+ if (span.from <= end && span.to >= start && (!predicate || predicate(span.spec)))
+ result.push(span.copy(span.from + offset, span.to + offset))
+ }
+ for (let i = 0; i < this.children.length; i += 3) {
+ if ((this.children[i] as number) < end && (this.children[i + 1] as number) > start) {
+ let childOff = (this.children[i] as number) + 1
+ ;(this.children[i + 2] as DecorationSet).findInner(start - childOff, end - childOff,
+ result, offset + childOff, predicate)
+ }
+ }
+ }
+
+ /// Map the set of decorations in response to a change in the
+ /// document.
+ map(mapping: Mapping, doc: Node, options?: {
+ /// When given, this function will be called for each decoration
+ /// that gets dropped as a result of the mapping, passing the
+ /// spec of that decoration.
+ onRemove?: (decorationSpec: any) => void
+ }) {
+ if (this == empty || mapping.maps.length == 0) return this
+ return this.mapInner(mapping, doc, 0, 0, options || noSpec)
+ }
+
+ /// @internal
+ mapInner(mapping: Mapping, node: Node, offset: number, oldOffset: number, options: {
+ onRemove?: (decorationSpec: any) => void
+ }) {
+ let newLocal: Decoration[] | undefined
+ for (let i = 0; i < this.local.length; i++) {
+ let mapped = this.local[i].map(mapping, offset, oldOffset)
+ if (mapped && mapped.type.valid(node, mapped)) (newLocal || (newLocal = [])).push(mapped)
+ else if (options.onRemove) options.onRemove(this.local[i].spec)
+ }
+
+ if (this.children.length)
+ return mapChildren(this.children, newLocal || [], mapping, node, offset, oldOffset, options)
+ else
+ return newLocal ? new DecorationSet(newLocal.sort(byPos), none) : empty
+ }
+
+ /// Add the given array of decorations to the ones in the set,
+ /// producing a new set. Consumes the `decorations` array. Needs
+ /// access to the current document to create the appropriate tree
+ /// structure.
+ add(doc: Node, decorations: Decoration[]) {
+ if (!decorations.length) return this
+ if (this == empty) return DecorationSet.create(doc, decorations)
+ return this.addInner(doc, decorations, 0)
+ }
+
+ private addInner(doc: Node, decorations: Decoration[], offset: number) {
+ let children: (number | DecorationSet)[] | undefined, childIndex = 0
+ doc.forEach((childNode, childOffset) => {
+ let baseOffset = childOffset + offset, found
+ if (!(found = takeSpansForNode(decorations, childNode, baseOffset))) return
+
+ if (!children) children = this.children.slice()
+ while (childIndex < children.length && (children[childIndex] as number) < childOffset) childIndex += 3
+ if (children[childIndex] == childOffset)
+ children[childIndex + 2] = (children[childIndex + 2] as DecorationSet).addInner(childNode, found, baseOffset + 1)
+ else
+ children.splice(childIndex, 0, childOffset, childOffset + childNode.nodeSize, buildTree(found, childNode, baseOffset + 1, noSpec))
+ childIndex += 3
+ })
+
+ let local = moveSpans(childIndex ? withoutNulls(decorations) : decorations, -offset)
+ for (let i = 0; i < local.length; i++) if (!local[i].type.valid(doc, local[i])) local.splice(i--, 1)
+
+ return new DecorationSet(local.length ? this.local.concat(local).sort(byPos) : this.local,
+ children || this.children)
+ }
+
+ /// Create a new set that contains the decorations in this set, minus
+ /// the ones in the given array.
+ remove(decorations: Decoration[]) {
+ if (decorations.length == 0 || this == empty) return this
+ return this.removeInner(decorations, 0)
+ }
+
+ private removeInner(decorations: (Decoration | null)[], offset: number) {
+ let children = this.children as (number | DecorationSet)[], local = this.local as Decoration[]
+ for (let i = 0; i < children.length; i += 3) {
+ let found: Decoration[] | undefined
+ let from = (children[i] as number) + offset, to = (children[i + 1] as number) + offset
+ for (let j = 0, span; j < decorations.length; j++) if (span = decorations[j]) {
+ if (span.from > from && span.to < to) {
+ decorations[j] = null
+ ;(found || (found = [])).push(span)
+ }
+ }
+ if (!found) continue
+ if (children == this.children) children = this.children.slice()
+ let removed = (children[i + 2] as DecorationSet).removeInner(found, from + 1)
+ if (removed != empty) {
+ children[i + 2] = removed
+ } else {
+ children.splice(i, 3)
+ i -= 3
+ }
+ }
+ if (local.length) for (let i = 0, span; i < decorations.length; i++) if (span = decorations[i]) {
+ for (let j = 0; j < local.length; j++) if (local[j].eq(span, offset)) {
+ if (local == this.local) local = this.local.slice()
+ local.splice(j--, 1)
+ }
+ }
+ if (children == this.children && local == this.local) return this
+ return local.length || children.length ? new DecorationSet(local, children) : empty
+ }
+
+ forChild(offset: number, node: Node): DecorationSet | DecorationGroup {
+ if (this == empty) return this
+ if (node.isLeaf) return DecorationSet.empty
+
+ let child, local: Decoration[] | undefined
+ for (let i = 0; i < this.children.length; i += 3) if ((this.children[i] as number) >= offset) {
+ if (this.children[i] == offset) child = this.children[i + 2] as DecorationSet
+ break
+ }
+ let start = offset + 1, end = start + node.content.size
+ for (let i = 0; i < this.local.length; i++) {
+ let dec = this.local[i]
+ if (dec.from < end && dec.to > start && (dec.type instanceof InlineType)) {
+ let from = Math.max(start, dec.from) - start, to = Math.min(end, dec.to) - start
+ if (from < to) (local || (local = [])).push(dec.copy(from, to))
+ }
+ }
+ if (local) {
+ let localSet = new DecorationSet(local.sort(byPos), none)
+ return child ? new DecorationGroup([localSet, child]) : localSet
+ }
+ return child || empty
+ }
+
+ /// @internal
+ eq(other: DecorationSet) {
+ if (this == other) return true
+ if (!(other instanceof DecorationSet) ||
+ this.local.length != other.local.length ||
+ this.children.length != other.children.length) return false
+ for (let i = 0; i < this.local.length; i++)
+ if (!this.local[i].eq(other.local[i])) return false
+ for (let i = 0; i < this.children.length; i += 3)
+ if (this.children[i] != other.children[i] ||
+ this.children[i + 1] != other.children[i + 1] ||
+ !(this.children[i + 2] as DecorationSet).eq(other.children[i + 2] as DecorationSet))
+ return false
+ return true
+ }
+
+ /// @internal
+ locals(node: Node) {
+ return removeOverlap(this.localsInner(node))
+ }
+
+ /// @internal
+ localsInner(node: Node): readonly Decoration[] {
+ if (this == empty) return none
+ if (node.inlineContent || !this.local.some(InlineType.is)) return this.local
+ let result = []
+ for (let i = 0; i < this.local.length; i++) {
+ if (!(this.local[i].type instanceof InlineType))
+ result.push(this.local[i])
+ }
+ return result
+ }
+
+ /// The empty set of decorations.
+ static empty: DecorationSet = new DecorationSet([], [])
+
+ /// @internal
+ static removeOverlap = removeOverlap
+
+ forEachSet(f: (set: DecorationSet) => void) { f(this) }
+}
+
+const empty = DecorationSet.empty
+
+// An abstraction that allows the code dealing with decorations to
+// treat multiple DecorationSet objects as if it were a single object
+// with (a subset of) the same interface.
+class DecorationGroup implements DecorationSource {
+ constructor(readonly members: readonly DecorationSet[]) {}
+
+ map(mapping: Mapping, doc: Node) {
+ const mappedDecos = this.members.map(
+ member => member.map(mapping, doc, noSpec)
+ )
+ return DecorationGroup.from(mappedDecos)
+ }
+
+ forChild(offset: number, child: Node) {
+ if (child.isLeaf) return DecorationSet.empty
+ let found: DecorationSet[] = []
+ for (let i = 0; i < this.members.length; i++) {
+ let result = this.members[i].forChild(offset, child)
+ if (result == empty) continue
+ if (result instanceof DecorationGroup) found = found.concat(result.members)
+ else found.push(result)
+ }
+ return DecorationGroup.from(found)
+ }
+
+ eq(other: DecorationGroup) {
+ if (!(other instanceof DecorationGroup) ||
+ other.members.length != this.members.length) return false
+ for (let i = 0; i < this.members.length; i++)
+ if (!this.members[i].eq(other.members[i])) return false
+ return true
+ }
+
+ locals(node: Node) {
+ let result: Decoration[] | undefined, sorted = true
+ for (let i = 0; i < this.members.length; i++) {
+ let locals = this.members[i].localsInner(node)
+ if (!locals.length) continue
+ if (!result) {
+ result = locals as Decoration[]
+ } else {
+ if (sorted) {
+ result = result.slice()
+ sorted = false
+ }
+ for (let j = 0; j < locals.length; j++) result.push(locals[j])
+ }
+ }
+ return result ? removeOverlap(sorted ? result : result.sort(byPos)) : none
+ }
+
+ // Create a group for the given array of decoration sets, or return
+ // a single set when possible.
+ static from(members: readonly DecorationSource[]): DecorationSource {
+ switch (members.length) {
+ case 0: return empty
+ case 1: return members[0]
+ default: return new DecorationGroup(
+ members.every(m => m instanceof DecorationSet) ? members as DecorationSet[] :
+ members.reduce((r, m) => r.concat(m instanceof DecorationSet ? m : (m as DecorationGroup).members),
+ [] as DecorationSet[]))
+ }
+ }
+
+ forEachSet(f: (set: DecorationSet) => void) {
+ for (let i = 0; i < this.members.length; i++) this.members[i].forEachSet(f)
+ }
+}
+
+function mapChildren(
+ oldChildren: readonly (number | DecorationSet)[],
+ newLocal: Decoration[],
+ mapping: Mapping,
+ node: Node,
+ offset: number,
+ oldOffset: number,
+ options: {onRemove?: (decorationSpec: any) => void}
+) {
+ let children = oldChildren.slice() as (number | DecorationSet)[]
+
+ // Mark the children that are directly touched by changes, and
+ // move those that are after the changes.
+ for (let i = 0, baseOffset = oldOffset; i < mapping.maps.length; i++) {
+ let moved = 0
+ mapping.maps[i].forEach((oldStart: number, oldEnd: number, newStart: number, newEnd: number) => {
+ let dSize = (newEnd - newStart) - (oldEnd - oldStart)
+ for (let i = 0; i < children.length; i += 3) {
+ let end = children[i + 1] as number
+ if (end < 0 || oldStart > end + baseOffset - moved) continue
+ let start = (children[i] as number) + baseOffset - moved
+ if (oldEnd >= start) {
+ children[i + 1] = oldStart <= start ? -2 : -1
+ } else if (oldStart >= baseOffset && dSize) {
+ ;(children[i] as number) += dSize
+ ;(children[i + 1] as number) += dSize
+ }
+ }
+ moved += dSize
+ })
+ baseOffset = mapping.maps[i].map(baseOffset, -1)
+ }
+
+ // Find the child nodes that still correspond to a single node,
+ // recursively call mapInner on them and update their positions.
+ let mustRebuild = false
+ for (let i = 0; i < children.length; i += 3) if ((children[i + 1] as number) < 0) { // Touched nodes
+ if (children[i + 1] == -2) {
+ mustRebuild = true
+ children[i + 1] = -1
+ continue
+ }
+ let from = mapping.map((oldChildren[i] as number) + oldOffset), fromLocal = from - offset
+ if (fromLocal < 0 || fromLocal >= node.content.size) {
+ mustRebuild = true
+ continue
+ }
+ // Must read oldChildren because children was tagged with -1
+ let to = mapping.map((oldChildren[i + 1] as number) + oldOffset, -1), toLocal = to - offset
+ let {index, offset: childOffset} = node.content.findIndex(fromLocal)
+ let childNode = node.maybeChild(index)
+ if (childNode && childOffset == fromLocal && childOffset + childNode.nodeSize == toLocal) {
+ let mapped = (children[i + 2] as DecorationSet)
+ .mapInner(mapping, childNode, from + 1, (oldChildren[i] as number) + oldOffset + 1, options)
+ if (mapped != empty) {
+ children[i] = fromLocal
+ children[i + 1] = toLocal
+ children[i + 2] = mapped
+ } else {
+ children[i + 1] = -2
+ mustRebuild = true
+ }
+ } else {
+ mustRebuild = true
+ }
+ }
+
+ // Remaining children must be collected and rebuilt into the appropriate structure
+ if (mustRebuild) {
+ let decorations = mapAndGatherRemainingDecorations(children, oldChildren, newLocal, mapping,
+ offset, oldOffset, options)
+ let built = buildTree(decorations, node, 0, options)
+ newLocal = built.local as Decoration[]
+ for (let i = 0; i < children.length; i += 3) if ((children[i + 1] as number) < 0) {
+ children.splice(i, 3)
+ i -= 3
+ }
+ for (let i = 0, j = 0; i < built.children.length; i += 3) {
+ let from = built.children[i]
+ while (j < children.length && children[j] < from) j += 3
+ children.splice(j, 0, built.children[i], built.children[i + 1], built.children[i + 2])
+ }
+ }
+
+ return new DecorationSet(newLocal.sort(byPos), children)
+}
+
+function moveSpans(spans: Decoration[], offset: number) {
+ if (!offset || !spans.length) return spans
+ let result = []
+ for (let i = 0; i < spans.length; i++) {
+ let span = spans[i]
+ result.push(new Decoration(span.from + offset, span.to + offset, span.type))
+ }
+ return result
+}
+
+function mapAndGatherRemainingDecorations(
+ children: (number | DecorationSet)[],
+ oldChildren: readonly (number | DecorationSet)[],
+ decorations: Decoration[],
+ mapping: Mapping,
+ offset: number,
+ oldOffset: number,
+ options: {onRemove?: (decorationSpec: any) => void}
+) {
+ // Gather all decorations from the remaining marked children
+ function gather(set: DecorationSet, oldOffset: number) {
+ for (let i = 0; i < set.local.length; i++) {
+ let mapped = set.local[i].map(mapping, offset, oldOffset)
+ if (mapped) decorations.push(mapped)
+ else if (options.onRemove) options.onRemove(set.local[i].spec)
+ }
+ for (let i = 0; i < set.children.length; i += 3)
+ gather(set.children[i + 2] as DecorationSet, set.children[i] as number + oldOffset + 1)
+ }
+ for (let i = 0; i < children.length; i += 3) if (children[i + 1] == -1)
+ gather(children[i + 2] as DecorationSet, oldChildren[i] as number + oldOffset + 1)
+
+ return decorations
+}
+
+function takeSpansForNode(spans: (Decoration | null)[], node: Node, offset: number): Decoration[] | null {
+ if (node.isLeaf) return null
+ let end = offset + node.nodeSize, found = null
+ for (let i = 0, span; i < spans.length; i++) {
+ if ((span = spans[i]) && span.from > offset && span.to < end) {
+ ;(found || (found = [])).push(span)
+ spans[i] = null
+ }
+ }
+ return found
+}
+
+function withoutNulls<T>(array: readonly (T | null)[]): T[] {
+ let result: T[] = []
+ for (let i = 0; i < array.length; i++)
+ if (array[i] != null) result.push(array[i]!)
+ return result
+}
+
+// Build up a tree that corresponds to a set of decorations. `offset`
+// is a base offset that should be subtracted from the `from` and `to`
+// positions in the spans (so that we don't have to allocate new spans
+// for recursive calls).
+function buildTree(
+ spans: Decoration[],
+ node: Node,
+ offset: number,
+ options: {onRemove?: (decorationSpec: any) => void}
+) {
+ let children: (DecorationSet | number)[] = [], hasNulls = false
+ node.forEach((childNode, localStart) => {
+ let found = takeSpansForNode(spans, childNode, localStart + offset)
+ if (found) {
+ hasNulls = true
+ let subtree = buildTree(found, childNode, offset + localStart + 1, options)
+ if (subtree != empty)
+ children.push(localStart, localStart + childNode.nodeSize, subtree)
+ }
+ })
+ let locals = moveSpans(hasNulls ? withoutNulls(spans) : spans, -offset).sort(byPos)
+ for (let i = 0; i < locals.length; i++) if (!locals[i].type.valid(node, locals[i])) {
+ if (options.onRemove) options.onRemove(locals[i].spec)
+ locals.splice(i--, 1)
+ }
+ return locals.length || children.length ? new DecorationSet(locals, children) : empty
+}
+
+// Used to sort decorations so that ones with a low start position
+// come first, and within a set with the same start position, those
+// with an smaller end position come first.
+function byPos(a: Decoration, b: Decoration) {
+ return a.from - b.from || a.to - b.to
+}
+
+// Scan a sorted array of decorations for partially overlapping spans,
+// and split those so that only fully overlapping spans are left (to
+// make subsequent rendering easier). Will return the input array if
+// no partially overlapping spans are found (the common case).
+function removeOverlap(spans: readonly Decoration[]): Decoration[] {
+ let working: Decoration[] = spans as Decoration[]
+ for (let i = 0; i < working.length - 1; i++) {
+ let span = working[i]
+ if (span.from != span.to) for (let j = i + 1; j < working.length; j++) {
+ let next = working[j]
+ if (next.from == span.from) {
+ if (next.to != span.to) {
+ if (working == spans) working = spans.slice()
+ // Followed by a partially overlapping larger span. Split that
+ // span.
+ working[j] = next.copy(next.from, span.to)
+ insertAhead(working, j + 1, next.copy(span.to, next.to))
+ }
+ continue
+ } else {
+ if (next.from < span.to) {
+ if (working == spans) working = spans.slice()
+ // The end of this one overlaps with a subsequent span. Split
+ // this one.
+ working[i] = span.copy(span.from, next.from)
+ insertAhead(working, j, span.copy(next.from, span.to))
+ }
+ break
+ }
+ }
+ }
+ return working
+}
+
+function insertAhead(array: Decoration[], i: number, deco: Decoration) {
+ while (i < array.length && byPos(deco, array[i]) > 0) i++
+ array.splice(i, 0, deco)
+}
+
+// Get the decorations associated with the current props of a view.
+export function viewDecorations(view: EditorView): DecorationSource {
+ let found: DecorationSource[] = []
+ view.someProp("decorations", f => {
+ let result = f(view.state)
+ if (result && result != empty) found.push(result)
+ })
+ if (view.cursorWrapper)
+ found.push(DecorationSet.create(view.state.doc, [view.cursorWrapper.deco]))
+ return DecorationGroup.from(found)
+}
diff --git a/third_party/js/prosemirror/prosemirror-view/src/dom.ts b/third_party/js/prosemirror/prosemirror-view/src/dom.ts
@@ -0,0 +1,159 @@
+export type DOMNode = InstanceType<typeof window.Node>
+export type DOMSelection = InstanceType<typeof window.Selection>
+export type DOMSelectionRange = {
+ focusNode: DOMNode | null, focusOffset: number,
+ anchorNode: DOMNode | null, anchorOffset: number
+}
+
+export const domIndex = function(node: Node) {
+ for (var index = 0;; index++) {
+ node = node.previousSibling!
+ if (!node) return index
+ }
+}
+
+export const parentNode = function(node: Node): Node | null {
+ let parent = (node as HTMLSlotElement).assignedSlot || node.parentNode
+ return parent && parent.nodeType == 11 ? (parent as ShadowRoot).host : parent
+}
+
+let reusedRange: Range | null = 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.
+export const textRange = function(node: Text, from?: number, to?: number) {
+ let range = reusedRange || (reusedRange = document.createRange())
+ range.setEnd(node, to == null ? node.nodeValue!.length : to)
+ range.setStart(node, from || 0)
+ return range
+}
+
+export const clearReusedRange = function() {
+ reusedRange = null
+}
+
+// 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)
+export const isEquivalentPosition = function(node: Node, off: number, targetNode: Node, targetOff: number) {
+ 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: Node, off: number, targetNode: Node, targetOff: number, dir: number) {
+ 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 as HTMLElement).contentEditable == "false")
+ return false
+ off = domIndex(node) + (dir < 0 ? 0 : 1)
+ node = parent
+ } else if (node.nodeType == 1) {
+ let child = node.childNodes[off + (dir < 0 ? -1 : 0)]
+ if (child.nodeType == 1 && (child as HTMLElement).contentEditable == "false") {
+ if (child.pmViewDesc?.ignoreForSelection) off += dir
+ else return false
+ } else {
+ node = child
+ off = dir < 0 ? nodeSize(node) : 0
+ }
+ } else {
+ return false
+ }
+ }
+}
+
+export function nodeSize(node: Node) {
+ return node.nodeType == 3 ? node.nodeValue!.length : node.childNodes.length
+}
+
+export function textNodeBefore(node: Node, offset: number) {
+ for (;;) {
+ if (node.nodeType == 3 && offset) return node as Text
+ if (node.nodeType == 1 && offset > 0) {
+ if ((node as HTMLElement).contentEditable == "false") return null
+ node = node.childNodes[offset - 1]
+ offset = nodeSize(node)
+ } else if (node.parentNode && !hasBlockDesc(node)) {
+ offset = domIndex(node)
+ node = node.parentNode
+ } else {
+ return null
+ }
+ }
+}
+
+export function textNodeAfter(node: Node, offset: number) {
+ for (;;) {
+ if (node.nodeType == 3 && offset < node.nodeValue!.length) return node as Text
+ if (node.nodeType == 1 && offset < node.childNodes.length) {
+ if ((node as HTMLElement).contentEditable == "false") return null
+ node = node.childNodes[offset]
+ offset = 0
+ } else if (node.parentNode && !hasBlockDesc(node)) {
+ offset = domIndex(node) + 1
+ node = node.parentNode
+ } else {
+ return null
+ }
+ }
+}
+
+export function isOnEdge(node: Node, offset: number, parent: Node) {
+ 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)
+ }
+}
+
+export function hasBlockDesc(dom: Node) {
+ let desc
+ for (let cur: Node | null = 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)
+export const selectionCollapsed = function(domSel: DOMSelectionRange) {
+ return domSel.focusNode && isEquivalentPosition(domSel.focusNode, domSel.focusOffset,
+ domSel.anchorNode!, domSel.anchorOffset)
+}
+
+export function keyEvent(keyCode: number, key: string) {
+ let event = document.createEvent("Event") as KeyboardEvent
+ event.initEvent("keydown", true, true)
+ ;(event as any).keyCode = keyCode
+ ;(event as any).key = (event as any).code = key
+ return event
+}
+
+export function deepActiveElement(doc: Document) {
+ let elt = doc.activeElement
+ while (elt && elt.shadowRoot) elt = elt.shadowRoot.activeElement
+ return elt
+}
+
+export function caretFromPoint(doc: Document, x: number, y: number): {node: Node, offset: number} | undefined {
+ if ((doc as any).caretPositionFromPoint) {
+ try { // Firefox throws for this call in hard-to-predict circumstances (#994)
+ let pos = (doc as any).caretPositionFromPoint(x, y)
+ // Clip the offset, because Chrome will return a text offset
+ // into <input> nodes, which can't be treated as a regular DOM
+ // offset
+ if (pos) return {node: pos.offsetNode, offset: Math.min(nodeSize(pos.offsetNode), pos.offset)}
+ } catch (_) {}
+ }
+ if (doc.caretRangeFromPoint) {
+ let range = doc.caretRangeFromPoint(x, y)
+ if (range) return {node: range.startContainer, offset: Math.min(nodeSize(range.startContainer), range.startOffset)}
+ }
+}
diff --git a/third_party/js/prosemirror/prosemirror-view/src/domchange.ts b/third_party/js/prosemirror/prosemirror-view/src/domchange.ts
@@ -0,0 +1,383 @@
+import {Fragment, DOMParser, TagParseRule, Node, Mark, ResolvedPos} from "prosemirror-model"
+import {TextSelection, Transaction} from "prosemirror-state"
+
+import {selectionBetween, selectionFromDOM, selectionToDOM} from "./selection"
+import {selectionCollapsed, keyEvent, DOMNode} from "./dom"
+import * as browser from "./browser"
+import {EditorView} from "./index"
+
+// Note that all referencing and parsing is done with the
+// start-of-operation selection and document, since that's the one
+// that the DOM represents. If any changes came in in the meantime,
+// the modification is mapped over those before it is applied, in
+// readDOMChange.
+
+function parseBetween(view: EditorView, from_: number, to_: number) {
+ let {node: parent, fromOffset, toOffset, from, to} = view.docView.parseRange(from_, to_)
+
+ let domSel = view.domSelectionRange()
+ let find: {node: DOMNode, offset: number, pos?: number}[] | undefined
+ let anchor = domSel.anchorNode
+ if (anchor && view.dom.contains(anchor.nodeType == 1 ? anchor : anchor.parentNode)) {
+ find = [{node: anchor, offset: domSel.anchorOffset}]
+ if (!selectionCollapsed(domSel))
+ find.push({node: domSel.focusNode!, offset: domSel.focusOffset})
+ }
+ // Work around issue in Chrome where backspacing sometimes replaces
+ // the deleted content with a random BR node (issues #799, #831)
+ if (browser.chrome && view.input.lastKeyCode === 8) {
+ for (let off = toOffset; off > fromOffset; off--) {
+ let node = parent.childNodes[off - 1], desc = node.pmViewDesc
+ if (node.nodeName == "BR" && !desc) { toOffset = off; break }
+ if (!desc || desc.size) break
+ }
+ }
+ let startDoc = view.state.doc
+ let parser = view.someProp("domParser") || DOMParser.fromSchema(view.state.schema)
+ let $from = startDoc.resolve(from)
+
+ let sel = null, doc = parser.parse(parent, {
+ topNode: $from.parent,
+ topMatch: $from.parent.contentMatchAt($from.index()),
+ topOpen: true,
+ from: fromOffset,
+ to: toOffset,
+ preserveWhitespace: $from.parent.type.whitespace == "pre" ? "full" : true,
+ findPositions: find,
+ ruleFromNode,
+ context: $from
+ })
+ if (find && find[0].pos != null) {
+ let anchor = find[0].pos, head = find[1] && find[1].pos
+ if (head == null) head = anchor
+ sel = {anchor: anchor + from, head: head + from}
+ }
+ return {doc, sel, from, to}
+}
+
+function ruleFromNode(dom: DOMNode): Omit<TagParseRule, "tag"> | null {
+ let desc = dom.pmViewDesc
+ if (desc) {
+ return desc.parseRule()
+ } else if (dom.nodeName == "BR" && dom.parentNode) {
+ // Safari replaces the list item or table cell with a BR
+ // directly in the list node (?!) if you delete the last
+ // character in a list item or table cell (#708, #862)
+ if (browser.safari && /^(ul|ol)$/i.test(dom.parentNode.nodeName)) {
+ let skip = document.createElement("div")
+ skip.appendChild(document.createElement("li"))
+ return {skip} as any
+ } else if (dom.parentNode.lastChild == dom || browser.safari && /^(tr|table)$/i.test(dom.parentNode.nodeName)) {
+ return {ignore: true}
+ }
+ } else if (dom.nodeName == "IMG" && (dom as HTMLElement).getAttribute("mark-placeholder")) {
+ return {ignore: true}
+ }
+ return null
+}
+
+const isInline = /^(a|abbr|acronym|b|bd[io]|big|br|button|cite|code|data(list)?|del|dfn|em|i|img|ins|kbd|label|map|mark|meter|output|q|ruby|s|samp|small|span|strong|su[bp]|time|u|tt|var)$/i
+
+export function readDOMChange(view: EditorView, from: number, to: number, typeOver: boolean, addedNodes: readonly DOMNode[]) {
+ let compositionID = view.input.compositionPendingChanges || (view.composing ? view.input.compositionID : 0)
+ view.input.compositionPendingChanges = 0
+
+ if (from < 0) {
+ let origin = view.input.lastSelectionTime > Date.now() - 50 ? view.input.lastSelectionOrigin : null
+ let newSel = selectionFromDOM(view, origin)
+ if (newSel && !view.state.selection.eq(newSel)) {
+ if (browser.chrome && browser.android &&
+ view.input.lastKeyCode === 13 && Date.now() - 100 < view.input.lastKeyCodeTime &&
+ view.someProp("handleKeyDown", f => f(view, keyEvent(13, "Enter"))))
+ return
+ let tr = view.state.tr.setSelection(newSel)
+ if (origin == "pointer") tr.setMeta("pointer", true)
+ else if (origin == "key") tr.scrollIntoView()
+ if (compositionID) tr.setMeta("composition", compositionID)
+ view.dispatch(tr)
+ }
+ return
+ }
+
+ let $before = view.state.doc.resolve(from)
+ let shared = $before.sharedDepth(to)
+ from = $before.before(shared + 1)
+ to = view.state.doc.resolve(to).after(shared + 1)
+
+ let sel = view.state.selection
+ let parse = parseBetween(view, from, to)
+
+ let doc = view.state.doc, compare = doc.slice(parse.from, parse.to)
+ let preferredPos, preferredSide: "start" | "end"
+ // Prefer anchoring to end when Backspace is pressed
+ if (view.input.lastKeyCode === 8 && Date.now() - 100 < view.input.lastKeyCodeTime) {
+ preferredPos = view.state.selection.to
+ preferredSide = "end"
+ } else {
+ preferredPos = view.state.selection.from
+ preferredSide = "start"
+ }
+ view.input.lastKeyCode = null
+
+ let change = findDiff(compare.content, parse.doc.content, parse.from, preferredPos, preferredSide)
+ if (change) view.input.domChangeCount++
+ if ((browser.ios && view.input.lastIOSEnter > Date.now() - 225 || browser.android) &&
+ addedNodes.some(n => n.nodeType == 1 && !isInline.test(n.nodeName)) &&
+ (!change || change.endA >= change.endB) &&
+ view.someProp("handleKeyDown", f => f(view, keyEvent(13, "Enter")))) {
+ view.input.lastIOSEnter = 0
+ return
+ }
+ if (!change) {
+ if (typeOver && sel instanceof TextSelection && !sel.empty && sel.$head.sameParent(sel.$anchor) &&
+ !view.composing && !(parse.sel && parse.sel.anchor != parse.sel.head)) {
+ change = {start: sel.from, endA: sel.to, endB: sel.to}
+ } else {
+ if (parse.sel) {
+ let sel = resolveSelection(view, view.state.doc, parse.sel)
+ if (sel && !sel.eq(view.state.selection)) {
+ let tr = view.state.tr.setSelection(sel)
+ if (compositionID) tr.setMeta("composition", compositionID)
+ view.dispatch(tr)
+ }
+ }
+ return
+ }
+ }
+
+ // Handle the case where overwriting a selection by typing matches
+ // the start or end of the selected content, creating a change
+ // that's smaller than what was actually overwritten.
+ if (view.state.selection.from < view.state.selection.to &&
+ change.start == change.endB &&
+ view.state.selection instanceof TextSelection) {
+ if (change.start > view.state.selection.from && change.start <= view.state.selection.from + 2 &&
+ view.state.selection.from >= parse.from) {
+ change.start = view.state.selection.from
+ } else if (change.endA < view.state.selection.to && change.endA >= view.state.selection.to - 2 &&
+ view.state.selection.to <= parse.to) {
+ change.endB += (view.state.selection.to - change.endA)
+ change.endA = view.state.selection.to
+ }
+ }
+
+ // IE11 will insert a non-breaking space _ahead_ of the space after
+ // the cursor space when adding a space before another space. When
+ // that happened, adjust the change to cover the space instead.
+ if (browser.ie && browser.ie_version <= 11 && change.endB == change.start + 1 &&
+ change.endA == change.start && change.start > parse.from &&
+ parse.doc.textBetween(change.start - parse.from - 1, change.start - parse.from + 1) == " \u00a0") {
+ change.start--
+ change.endA--
+ change.endB--
+ }
+
+ let $from = parse.doc.resolveNoCache(change.start - parse.from)
+ let $to = parse.doc.resolveNoCache(change.endB - parse.from)
+ let $fromA = doc.resolve(change.start)
+ let inlineChange = $from.sameParent($to) && $from.parent.inlineContent && $fromA.end() >= change.endA
+ // If this looks like the effect of pressing Enter (or was recorded
+ // as being an iOS enter press), just dispatch an Enter key instead.
+ if (((browser.ios && view.input.lastIOSEnter > Date.now() - 225 &&
+ (!inlineChange || addedNodes.some(n => n.nodeName == "DIV" || n.nodeName == "P"))) ||
+ (!inlineChange && $from.pos < parse.doc.content.size &&
+ (!$from.sameParent($to) || !$from.parent.inlineContent) &&
+ $from.pos < $to.pos && !/\S/.test(parse.doc.textBetween($from.pos, $to.pos, "", "")))) &&
+ view.someProp("handleKeyDown", f => f(view, keyEvent(13, "Enter")))) {
+ view.input.lastIOSEnter = 0
+ return
+ }
+ // Same for backspace
+ if (view.state.selection.anchor > change.start &&
+ looksLikeBackspace(doc, change.start, change.endA, $from, $to) &&
+ view.someProp("handleKeyDown", f => f(view, keyEvent(8, "Backspace")))) {
+ if (browser.android && browser.chrome) view.domObserver.suppressSelectionUpdates() // #820
+ return
+ }
+
+ // Chrome will occasionally, during composition, delete the
+ // entire composition and then immediately insert it again. This is
+ // used to detect that situation.
+ if (browser.chrome && change.endB == change.start)
+ view.input.lastChromeDelete = Date.now()
+
+ // This tries to detect Android virtual keyboard
+ // enter-and-pick-suggestion action. That sometimes (see issue
+ // #1059) first fires a DOM mutation, before moving the selection to
+ // the newly created block. And then, because ProseMirror cleans up
+ // the DOM selection, it gives up moving the selection entirely,
+ // leaving the cursor in the wrong place. When that happens, we drop
+ // the new paragraph from the initial change, and fire a simulated
+ // enter key afterwards.
+ if (browser.android && !inlineChange && $from.start() != $to.start() && $to.parentOffset == 0 && $from.depth == $to.depth &&
+ parse.sel && parse.sel.anchor == parse.sel.head && parse.sel.head == change.endA) {
+ change.endB -= 2
+ $to = parse.doc.resolveNoCache(change.endB - parse.from)
+ setTimeout(() => {
+ view.someProp("handleKeyDown", function (f) { return f(view, keyEvent(13, "Enter")); })
+ }, 20)
+ }
+
+ let chFrom = change.start, chTo = change.endA
+
+ let mkTr = (base?: Transaction) => {
+ let tr = base || view.state.tr.replace(chFrom, chTo, parse.doc.slice(change!.start - parse.from,
+ change!.endB - parse.from))
+ if (parse.sel) {
+ let sel = resolveSelection(view, tr.doc, parse.sel)
+ // Chrome will sometimes, during composition, report the
+ // selection in the wrong place. If it looks like that is
+ // happening, don't update the selection.
+ // Edge just doesn't move the cursor forward when you start typing
+ // in an empty block or between br nodes.
+ if (sel && !(browser.chrome && view.composing && sel.empty &&
+ (change!.start != change!.endB || view.input.lastChromeDelete < Date.now() - 100) &&
+ (sel.head == chFrom || sel.head == tr.mapping.map(chTo) - 1) ||
+ browser.ie && sel.empty && sel.head == chFrom))
+ tr.setSelection(sel)
+ }
+ if (compositionID) tr.setMeta("composition", compositionID)
+ return tr.scrollIntoView()
+ }
+
+ let markChange
+ if (inlineChange) {
+ if ($from.pos == $to.pos) { // Deletion
+ // IE11 sometimes weirdly moves the DOM selection around after
+ // backspacing out the first element in a textblock
+ if (browser.ie && browser.ie_version <= 11 && $from.parentOffset == 0) {
+ view.domObserver.suppressSelectionUpdates()
+ setTimeout(() => selectionToDOM(view), 20)
+ }
+ let tr = mkTr(view.state.tr.delete(chFrom, chTo))
+ let marks = doc.resolve(change.start).marksAcross(doc.resolve(change.endA))
+ if (marks) tr.ensureMarks(marks)
+ view.dispatch(tr)
+ } else if ( // Adding or removing a mark
+ change.endA == change.endB &&
+ (markChange = isMarkChange($from.parent.content.cut($from.parentOffset, $to.parentOffset),
+ $fromA.parent.content.cut($fromA.parentOffset, change.endA - $fromA.start())))
+ ) {
+ let tr = mkTr(view.state.tr)
+ if (markChange.type == "add") tr.addMark(chFrom, chTo, markChange.mark)
+ else tr.removeMark(chFrom, chTo, markChange.mark)
+ view.dispatch(tr)
+ } else if ($from.parent.child($from.index()).isText && $from.index() == $to.index() - ($to.textOffset ? 0 : 1)) {
+ // Both positions in the same text node -- simply insert text
+ let text = $from.parent.textBetween($from.parentOffset, $to.parentOffset)
+ let deflt = () => mkTr(view.state.tr.insertText(text, chFrom, chTo))
+ if (!view.someProp("handleTextInput", f => f(view, chFrom, chTo, text, deflt)))
+ view.dispatch(deflt())
+ } else {
+ view.dispatch(mkTr())
+ }
+ } else {
+ view.dispatch(mkTr())
+ }
+}
+
+function resolveSelection(view: EditorView, doc: Node, parsedSel: {anchor: number, head: number}) {
+ if (Math.max(parsedSel.anchor, parsedSel.head) > doc.content.size) return null
+ return selectionBetween(view, doc.resolve(parsedSel.anchor), doc.resolve(parsedSel.head))
+}
+
+// Given two same-length, non-empty fragments of inline content,
+// determine whether the first could be created from the second by
+// removing or adding a single mark type.
+function isMarkChange(cur: Fragment, prev: Fragment) {
+ let curMarks = cur.firstChild!.marks, prevMarks = prev.firstChild!.marks
+ let added = curMarks, removed = prevMarks, type, mark: Mark | undefined, update
+ for (let i = 0; i < prevMarks.length; i++) added = prevMarks[i].removeFromSet(added)
+ for (let i = 0; i < curMarks.length; i++) removed = curMarks[i].removeFromSet(removed)
+ if (added.length == 1 && removed.length == 0) {
+ mark = added[0]
+ type = "add"
+ update = (node: Node) => node.mark(mark!.addToSet(node.marks))
+ } else if (added.length == 0 && removed.length == 1) {
+ mark = removed[0]
+ type = "remove"
+ update = (node: Node) => node.mark(mark!.removeFromSet(node.marks))
+ } else {
+ return null
+ }
+ let updated = []
+ for (let i = 0; i < prev.childCount; i++) updated.push(update(prev.child(i)))
+ if (Fragment.from(updated).eq(cur)) return {mark, type}
+}
+
+function looksLikeBackspace(old: Node, start: number, end: number, $newStart: ResolvedPos, $newEnd: ResolvedPos) {
+ if (// The content must have shrunk
+ end - start <= $newEnd.pos - $newStart.pos ||
+ // newEnd must point directly at or after the end of the block that newStart points into
+ skipClosingAndOpening($newStart, true, false) < $newEnd.pos)
+ return false
+
+ let $start = old.resolve(start)
+
+ // Handle the case where, rather than joining blocks, the change just removed an entire block
+ if (!$newStart.parent.isTextblock) {
+ let after = $start.nodeAfter
+ return after != null && end == start + after.nodeSize
+ }
+
+ // Start must be at the end of a block
+ if ($start.parentOffset < $start.parent.content.size || !$start.parent.isTextblock)
+ return false
+ let $next = old.resolve(skipClosingAndOpening($start, true, true))
+ // The next textblock must start before end and end near it
+ if (!$next.parent.isTextblock || $next.pos > end ||
+ skipClosingAndOpening($next, true, false) < end)
+ return false
+
+ // The fragments after the join point must match
+ return $newStart.parent.content.cut($newStart.parentOffset).eq($next.parent.content)
+}
+
+function skipClosingAndOpening($pos: ResolvedPos, fromEnd: boolean, mayOpen: boolean) {
+ let depth = $pos.depth, end = fromEnd ? $pos.end() : $pos.pos
+ while (depth > 0 && (fromEnd || $pos.indexAfter(depth) == $pos.node(depth).childCount)) {
+ depth--
+ end++
+ fromEnd = false
+ }
+ if (mayOpen) {
+ let next = $pos.node(depth).maybeChild($pos.indexAfter(depth))
+ while (next && !next.isLeaf) {
+ next = next.firstChild
+ end++
+ }
+ }
+ return end
+}
+
+function findDiff(a: Fragment, b: Fragment, pos: number, preferredPos: number, preferredSide: "start" | "end") {
+ let start = a.findDiffStart(b, pos)
+ if (start == null) return null
+ let {a: endA, b: endB} = a.findDiffEnd(b, pos + a.size, pos + b.size)!
+ if (preferredSide == "end") {
+ let adjust = Math.max(0, start - Math.min(endA, endB))
+ preferredPos -= endA + adjust - start
+ }
+ if (endA < start && a.size < b.size) {
+ let move = preferredPos <= start && preferredPos >= endA ? start - preferredPos : 0
+ start -= move
+ if (start && start < b.size && isSurrogatePair(b.textBetween(start - 1, start + 1)))
+ start += move ? 1 : -1
+ endB = start + (endB - endA)
+ endA = start
+ } else if (endB < start) {
+ let move = preferredPos <= start && preferredPos >= endB ? start - preferredPos : 0
+ start -= move
+ if (start && start < a.size && isSurrogatePair(a.textBetween(start - 1, start + 1)))
+ start += move ? 1 : -1
+ endA = start + (endA - endB)
+ endB = start
+ }
+ return {start, endA, endB}
+}
+
+function isSurrogatePair(str: string) {
+ if (str.length != 2) return false
+ let a = str.charCodeAt(0), b = str.charCodeAt(1)
+ return a >= 0xDC00 && a <= 0xDFFF && b >= 0xD800 && b <= 0xDBFF
+}
diff --git a/third_party/js/prosemirror/prosemirror-view/src/domcoords.ts b/third_party/js/prosemirror/prosemirror-view/src/domcoords.ts
@@ -0,0 +1,515 @@
+import {EditorState} from "prosemirror-state"
+import {nodeSize, textRange, parentNode, caretFromPoint} from "./dom"
+import * as browser from "./browser"
+import {EditorView} from "./index"
+
+export type Rect = {left: number, right: number, top: number, bottom: number}
+
+function windowRect(doc: Document): Rect {
+ let vp = doc.defaultView && doc.defaultView.visualViewport
+ if (vp) return {
+ left: 0, right: vp.width,
+ top: 0, bottom: vp.height
+ }
+ return {left: 0, right: doc.documentElement.clientWidth,
+ top: 0, bottom: doc.documentElement.clientHeight}
+}
+
+function getSide(value: number | Rect, side: keyof Rect): number {
+ return typeof value == "number" ? value : value[side]
+}
+
+function clientRect(node: HTMLElement): Rect {
+ 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}
+}
+
+export function scrollRectIntoView(view: EditorView, rect: Rect, startDOM: Node) {
+ let scrollThreshold = view.someProp("scrollThreshold") || 0, scrollMargin = view.someProp("scrollMargin") || 5
+ let doc = view.dom.ownerDocument
+ for (let parent: Node | null = startDOM || view.dom;;) {
+ if (!parent) break
+ if (parent.nodeType != 1) { parent = parentNode(parent); continue }
+ let elt = parent as HTMLElement
+ let atTop = elt == doc.body
+ let bounding = atTop ? windowRect(doc) : clientRect(elt as HTMLElement)
+ 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 - rect.top > bounding.bottom - bounding.top
+ ? rect.top + getSide(scrollMargin, "top") - bounding.top
+ : 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}
+ }
+ }
+ let pos: string = atTop ? "fixed" : getComputedStyle(parent as HTMLElement).position
+ if (/^(fixed|sticky)$/.test(pos)) break
+ parent = pos == "absolute" ? (parent as HTMLElement).offsetParent : parentNode(parent)
+ }
+}
+
+// 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.
+export function storeScrollPos(view: EditorView): {
+ refDOM: HTMLElement,
+ refTop: number,
+ stack: {dom: HTMLElement, top: number, left: number}[]
+} {
+ let rect = view.dom.getBoundingClientRect(), startY = Math.max(0, rect.top)
+ let refDOM: HTMLElement, refTop: number
+ 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 as HTMLElement).getBoundingClientRect()
+ if (localRect.top >= startY - 20) {
+ refDOM = dom as HTMLElement
+ refTop = localRect.top
+ break
+ }
+ }
+ return {refDOM: refDOM!, refTop: refTop!, stack: scrollStack(view.dom)}
+}
+
+function scrollStack(dom: Node): {dom: HTMLElement, top: number, left: number}[] {
+ let stack = [], doc = dom.ownerDocument
+ for (let cur: Node | null = dom; cur; cur = parentNode(cur)) {
+ stack.push({dom: cur as HTMLElement, top: (cur as HTMLElement).scrollTop, left: (cur as HTMLElement).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.
+export function resetScrollPos({refDOM, refTop, stack}: {
+ refDOM: HTMLElement,
+ refTop: number,
+ stack: {dom: HTMLElement, top: number, left: number}[]
+}) {
+ let newRefTop = refDOM ? refDOM.getBoundingClientRect().top : 0
+ restoreScrollStack(stack, newRefTop == 0 ? 0 : newRefTop - refTop)
+}
+
+function restoreScrollStack(stack: {dom: HTMLElement, top: number, left: number}[], dTop: number) {
+ 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: false | null | {preventScroll: boolean} = null
+// Feature-detects support for .focus({preventScroll: true}), and uses
+// a fallback kludge when not supported.
+export function focusPreventScroll(dom: HTMLElement) {
+ if ((dom as any).setActive) return (dom as any).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: HTMLElement, coords: {top: number, left: number}): {node: Node, offset: number} {
+ let closest, dxClosest = 2e8, coordsClosest: {left: number, top: number} | undefined, offset = 0
+ let rowBot = coords.top, rowTop = coords.top
+ let firstBelow: Node | undefined, coordsBelow: {left: number, top: number} | undefined
+ for (let child = node.firstChild, childIndex = 0; child; child = child.nextSibling, childIndex++) {
+ let rects
+ if (child.nodeType == 1) rects = (child as HTMLElement).getClientRects()
+ else if (child.nodeType == 3) rects = textRange(child as Text).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
+ }
+ } else if (rect.top > coords.top && !firstBelow && rect.left <= coords.left && rect.right >= coords.left) {
+ firstBelow = child
+ coordsBelow = {left: Math.max(rect.left, Math.min(rect.right, coords.left)), top: rect.top}
+ }
+ if (!closest && (coords.left >= rect.right && coords.top >= rect.top ||
+ coords.left >= rect.left && coords.top >= rect.bottom))
+ offset = childIndex + 1
+ }
+ }
+ if (!closest && firstBelow) { closest = firstBelow; coordsClosest = coordsBelow; dxClosest = 0 }
+ if (closest && closest.nodeType == 3) return findOffsetInText(closest as Text, coordsClosest!)
+ if (!closest || (dxClosest && closest.nodeType == 1)) return {node, offset}
+ return findOffsetInNode(closest as HTMLElement, coordsClosest!)
+}
+
+function findOffsetInText(node: Text, coords: {top: number, left: number}) {
+ let len = node.nodeValue!.length
+ let range = document.createRange(), result: {node: Node, offset: number} | undefined
+ 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)) {
+ result = {node, offset: i + (coords.left >= (rect.left + rect.right) / 2 ? 1 : 0)}
+ break
+ }
+ }
+ range.detach()
+ return result || {node, offset: 0}
+}
+
+function inRect(coords: {top: number, left: number}, rect: 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: HTMLElement, coords: {top: number, left: number}) {
+ let parent = dom.parentNode
+ if (parent && /^li$/i.test(parent.nodeName) && coords.left < dom.getBoundingClientRect().left)
+ return parent as HTMLElement
+ return dom
+}
+
+function posFromElement(view: EditorView, elt: HTMLElement, coords: {top: number, left: number}) {
+ let {node, offset} = findOffsetInNode(elt, coords), bias = -1
+ if (node.nodeType == 1 && !node.firstChild) {
+ let rect = (node as HTMLElement).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: EditorView, node: Node, offset: number, coords: {top: number, left: number}) {
+ // 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 outsideBlock = -1
+ for (let cur = node, sawBlock = false;;) {
+ if (cur == view.dom) break
+ let desc = view.docView.nearestDesc(cur, true), rect
+ if (!desc) return null
+ if (desc.dom.nodeType == 1 && (desc.node.isBlock && desc.parent || !desc.contentDOM) &&
+ // Ignore elements with zero-size bounding rectangles
+ ((rect = (desc.dom as HTMLElement).getBoundingClientRect()).width || rect.height)) {
+ if (desc.node.isBlock && desc.parent && !/^T(R|BODY|HEAD|FOOT)$/.test(desc.dom!.nodeName)) {
+ // Only apply the horizontal test to the innermost block. Vertical for any parent.
+ if (!sawBlock && rect.left > coords.left || rect.top > coords.top) outsideBlock = desc.posBefore
+ else if (!sawBlock && rect.right < coords.left || rect.bottom < coords.top) outsideBlock = desc.posAfter
+ sawBlock = true
+ }
+ if (!desc.contentDOM && outsideBlock < 0 && !desc.node.isText) {
+ // If we are inside a leaf, return the side of the leaf closer to the coords
+ let before = desc.node.isBlock ? coords.top < (rect.top + rect.bottom) / 2
+ : coords.left < (rect.left + rect.right) / 2
+ return before ? desc.posBefore : desc.posAfter
+ }
+ }
+ cur = desc.dom.parentNode!
+ }
+ return outsideBlock > -1 ? outsideBlock : view.docView.posFromDOM(node, offset, -1)
+}
+
+function elementFromPoint(element: HTMLElement, coords: {top: number, left: number}, box: Rect): HTMLElement {
+ 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 as HTMLElement).getClientRects()
+ for (let j = 0; j < rects.length; j++) {
+ let rect = rects[j]
+ if (inRect(coords, rect)) return elementFromPoint(child as HTMLElement, 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.
+export function posAtCoords(view: EditorView, coords: {top: number, left: number}) {
+ let doc = view.dom.ownerDocument, node: Node | undefined, offset = 0
+ let caret = caretFromPoint(doc, coords.left, coords.top)
+ if (caret) ({node, offset} = caret)
+
+ let elt = ((view.root as any).elementFromPoint ? view.root : doc)
+ .elementFromPoint(coords.left, coords.top) as HTMLElement
+ 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 (browser.safari) {
+ for (let p: Node | null = elt; node && p; p = parentNode(p))
+ if ((p as HTMLElement).draggable) node = undefined
+ }
+ elt = targetKludge(elt, coords)
+ if (node) {
+ if (browser.gecko && node.nodeType == 1) {
+ // Firefox will sometimes return offsets into <input> 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 as HTMLElement).getBoundingClientRect()).right <= coords.left &&
+ box.bottom > coords.top)
+ offset++
+ }
+ }
+ let prev
+ // When clicking above the right side of an uneditable node, Chrome will report a cursor position after that node.
+ if (browser.webkit && offset && node.nodeType == 1 && (prev = node.childNodes[offset - 1]).nodeType == 1 &&
+ (prev as HTMLElement).contentEditable == "false" && (prev as HTMLElement).getBoundingClientRect().top >= 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 as HTMLElement).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 nonZero(rect: DOMRect) {
+ return rect.top < rect.bottom || rect.left < rect.right
+}
+
+function singleRect(target: HTMLElement | Range, bias: number): DOMRect {
+ let rects = target.getClientRects()
+ if (rects.length) {
+ let first = rects[bias < 0 ? 0 : rects.length - 1]
+ if (nonZero(first)) return first
+ }
+ return Array.prototype.find.call(rects, nonZero) || target.getBoundingClientRect()
+}
+
+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.
+export function coordsAtPos(view: EditorView, pos: number, side: number): Rect {
+ let {node, offset, atom} = view.docView.domFromPos(pos, side < 0 ? -1 : 1)
+
+ let supportEmptyRange = browser.webkit || browser.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 as Text, 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 (browser.gecko && offset && /\s/.test(node.nodeValue![offset - 1]) && offset < node.nodeValue!.length) {
+ let rectBefore = singleRect(textRange(node as Text, offset - 1, offset - 1), -1)
+ if (rectBefore.top == rect.top) {
+ let rectAfter = singleRect(textRange(node as Text, 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 as Text, 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 as HTMLElement).getBoundingClientRect(), false)
+ }
+ if (atom == null && offset < nodeSize(node)) {
+ let after = node.childNodes[offset]
+ if (after.nodeType == 1) return flattenH((after as HTMLElement).getBoundingClientRect(), true)
+ }
+ return flattenH((node as HTMLElement).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 as Text, 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 as Range | HTMLElement, 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 as Text, 0, (supportEmptyRange ? 0 : 1))
+ : after.nodeType == 1 ? after : null
+ if (target) return flattenV(singleRect(target as Range | HTMLElement, -1), true)
+ }
+ // All else failed, just try to get a rectangle for the target node
+ return flattenV(singleRect(node.nodeType == 3 ? textRange(node as Text) : node as HTMLElement, -side), side >= 0)
+}
+
+function flattenV(rect: DOMRect, left: boolean) {
+ 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: DOMRect, top: boolean) {
+ 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<T>(view: EditorView, state: EditorState, f: () => T): T {
+ let viewState = view.state, active = view.root.activeElement as HTMLElement
+ 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: EditorView, state: EditorState, dir: "up" | "down") {
+ 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.contentDOM || 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 as HTMLElement).getClientRects()
+ else if (child.nodeType == 3) boxes = textRange(child as Text, 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: EditorView, state: EditorState, dir: "left" | "right" | "forward" | "backward") {
+ let {$head} = state.selection
+ if (!$head.parent.isTextblock) return false
+ let offset = $head.parentOffset, atStart = !offset, atEnd = offset == $head.parent.content.size
+ let sel: Selection = view.domSelection()!
+ if (!sel) return $head.pos == $head.start() || $head.pos == $head.end()
+ // 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 as any).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 {focusNode: oldNode, focusOffset: oldOff, anchorNode, anchorOffset} = view.domSelectionRange()
+ let oldBidiLevel = (sel as any).caretBidiLevel // Only for Firefox
+ ;(sel as any).modify("move", dir, "character")
+ let parentDOM = $head.depth ? view.docView.domAfterPos($head.before()) : view.dom
+ let {focusNode: newNode, focusOffset: newOff} = view.domSelectionRange()
+ let result = newNode && !parentDOM.contains(newNode.nodeType == 1 ? newNode : newNode.parentNode) ||
+ (oldNode == newNode && oldOff == newOff)
+ // Restore the previous selection
+ try {
+ sel.collapse(anchorNode, anchorOffset)
+ if (oldNode && (oldNode != anchorNode || oldOff != anchorOffset) && sel.extend) sel.extend(oldNode, oldOff)
+ } catch (_) {}
+ if (oldBidiLevel != null) (sel as any).caretBidiLevel = oldBidiLevel
+ return result
+ })
+}
+
+export type TextblockDir = "up" | "down" | "left" | "right" | "forward" | "backward"
+
+let cachedState: EditorState | null = null
+let cachedDir: TextblockDir | null = null
+let cachedResult: boolean = false
+export function endOfTextblock(view: EditorView, state: EditorState, dir: TextblockDir) {
+ 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)
+}
diff --git a/third_party/js/prosemirror/prosemirror-view/src/domobserver.ts b/third_party/js/prosemirror/prosemirror-view/src/domobserver.ts
@@ -0,0 +1,349 @@
+import {Selection} from "prosemirror-state"
+import * as browser from "./browser"
+import {domIndex, isEquivalentPosition, selectionCollapsed, parentNode, DOMSelectionRange, DOMNode, DOMSelection} from "./dom"
+import {hasFocusAndSelection, selectionToDOM, selectionFromDOM} from "./selection"
+import {EditorView} from "./index"
+
+const observeOptions = {
+ childList: true,
+ characterData: true,
+ characterDataOldValue: true,
+ attributes: true,
+ attributeOldValue: true,
+ subtree: true
+}
+// IE11 has very broken mutation observers, so we also listen to DOMCharacterDataModified
+const useCharData = browser.ie && browser.ie_version <= 11
+
+class SelectionState {
+ anchorNode: Node | null = null
+ anchorOffset: number = 0
+ focusNode: Node | null = null
+ focusOffset: number = 0
+
+ set(sel: DOMSelectionRange) {
+ this.anchorNode = sel.anchorNode; this.anchorOffset = sel.anchorOffset
+ this.focusNode = sel.focusNode; this.focusOffset = sel.focusOffset
+ }
+
+ clear() {
+ this.anchorNode = this.focusNode = null
+ }
+
+ eq(sel: DOMSelectionRange) {
+ return sel.anchorNode == this.anchorNode && sel.anchorOffset == this.anchorOffset &&
+ sel.focusNode == this.focusNode && sel.focusOffset == this.focusOffset
+ }
+}
+
+export class DOMObserver {
+ queue: MutationRecord[] = []
+ flushingSoon = -1
+ observer: MutationObserver | null = null
+ currentSelection = new SelectionState
+ onCharData: ((e: Event) => void) | null = null
+ suppressingSelectionUpdates = false
+ lastChangedTextNode: Text | null = null
+
+ constructor(
+ readonly view: EditorView,
+ readonly handleDOMChange: (from: number, to: number, typeOver: boolean, added: Node[]) => void
+ ) {
+ this.observer = window.MutationObserver &&
+ new window.MutationObserver(mutations => {
+ for (let i = 0; i < mutations.length; i++) this.queue.push(mutations[i])
+ // IE11 will sometimes (on backspacing out a single character
+ // text node after a BR node) call the observer callback
+ // before actually updating the DOM, which will cause
+ // ProseMirror to miss the change (see #930)
+ if (browser.ie && browser.ie_version <= 11 && mutations.some(
+ m => m.type == "childList" && m.removedNodes.length ||
+ m.type == "characterData" && m.oldValue!.length > m.target.nodeValue!.length))
+ this.flushSoon()
+ else
+ this.flush()
+ })
+ if (useCharData) {
+ this.onCharData = e => {
+ this.queue.push({target: e.target as Node, type: "characterData", oldValue: (e as any).prevValue} as MutationRecord)
+ this.flushSoon()
+ }
+ }
+ this.onSelectionChange = this.onSelectionChange.bind(this)
+ }
+
+ flushSoon() {
+ if (this.flushingSoon < 0)
+ this.flushingSoon = window.setTimeout(() => { this.flushingSoon = -1; this.flush() }, 20)
+ }
+
+ forceFlush() {
+ if (this.flushingSoon > -1) {
+ window.clearTimeout(this.flushingSoon)
+ this.flushingSoon = -1
+ this.flush()
+ }
+ }
+
+ start() {
+ if (this.observer) {
+ this.observer.takeRecords()
+ this.observer.observe(this.view.dom, observeOptions)
+ }
+ if (this.onCharData)
+ this.view.dom.addEventListener("DOMCharacterDataModified", this.onCharData)
+ this.connectSelection()
+ }
+
+ stop() {
+ if (this.observer) {
+ let take = this.observer.takeRecords()
+ if (take.length) {
+ for (let i = 0; i < take.length; i++) this.queue.push(take[i])
+ window.setTimeout(() => this.flush(), 20)
+ }
+ this.observer.disconnect()
+ }
+ if (this.onCharData) this.view.dom.removeEventListener("DOMCharacterDataModified", this.onCharData)
+ this.disconnectSelection()
+ }
+
+ connectSelection() {
+ this.view.dom.ownerDocument.addEventListener("selectionchange", this.onSelectionChange)
+ }
+
+ disconnectSelection() {
+ this.view.dom.ownerDocument.removeEventListener("selectionchange", this.onSelectionChange)
+ }
+
+ suppressSelectionUpdates() {
+ this.suppressingSelectionUpdates = true
+ setTimeout(() => this.suppressingSelectionUpdates = false, 50)
+ }
+
+ onSelectionChange() {
+ if (!hasFocusAndSelection(this.view)) return
+ if (this.suppressingSelectionUpdates) return selectionToDOM(this.view)
+ // Deletions on IE11 fire their events in the wrong order, giving
+ // us a selection change event before the DOM changes are
+ // reported.
+ if (browser.ie && browser.ie_version <= 11 && !this.view.state.selection.empty) {
+ let sel = this.view.domSelectionRange()
+ // Selection.isCollapsed isn't reliable on IE
+ if (sel.focusNode && isEquivalentPosition(sel.focusNode, sel.focusOffset, sel.anchorNode!, sel.anchorOffset))
+ return this.flushSoon()
+ }
+ this.flush()
+ }
+
+ setCurSelection() {
+ this.currentSelection.set(this.view.domSelectionRange())
+ }
+
+ ignoreSelectionChange(sel: DOMSelectionRange) {
+ if (!sel.focusNode) return true
+ let ancestors: Set<Node> = new Set, container: DOMNode | undefined
+ for (let scan: DOMNode | null = sel.focusNode; scan; scan = parentNode(scan)) ancestors.add(scan)
+ for (let scan = sel.anchorNode; scan; scan = parentNode(scan)) if (ancestors.has(scan)) {
+ container = scan
+ break
+ }
+ let desc = container && this.view.docView.nearestDesc(container)
+ if (desc && desc.ignoreMutation({
+ type: "selection",
+ target: container!.nodeType == 3 ? container!.parentNode! : container!
+ })) {
+ this.setCurSelection()
+ return true
+ }
+ }
+
+ pendingRecords() {
+ if (this.observer) for (let mut of this.observer.takeRecords()) this.queue.push(mut)
+ return this.queue
+ }
+
+ flush() {
+ let {view} = this
+ if (!view.docView || this.flushingSoon > -1) return
+ let mutations = this.pendingRecords()
+ if (mutations.length) this.queue = []
+
+ let sel = view.domSelectionRange()
+ let newSel = !this.suppressingSelectionUpdates && !this.currentSelection.eq(sel) && hasFocusAndSelection(view) && !this.ignoreSelectionChange(sel)
+
+ let from = -1, to = -1, typeOver = false, added: Node[] = []
+ if (view.editable) {
+ for (let i = 0; i < mutations.length; i++) {
+ let result = this.registerMutation(mutations[i], added)
+ if (result) {
+ from = from < 0 ? result.from : Math.min(result.from, from)
+ to = to < 0 ? result.to : Math.max(result.to, to)
+ if (result.typeOver) typeOver = true
+ }
+ }
+ }
+
+ if (browser.gecko && added.length) {
+ let brs = added.filter(n => n.nodeName == "BR") as HTMLElement[]
+ if (brs.length == 2) {
+ let [a, b] = brs
+ if (a.parentNode && a.parentNode.parentNode == b.parentNode) b.remove()
+ else a.remove()
+ } else {
+ let {focusNode} = this.currentSelection
+ for (let br of brs) {
+ let parent = br.parentNode
+ if (parent && parent.nodeName == "LI" && (!focusNode || blockParent(view, focusNode) != parent))
+ br.remove()
+ }
+ }
+ } else if ((browser.chrome || browser.safari) && added.some(n => n.nodeName == "BR") &&
+ (view.input.lastKeyCode == 8 || view.input.lastKeyCode == 46)) {
+ // Chrome/Safari sometimes insert a bogus break node if you
+ // backspace out the last bit of text before an inline-flex node (#1552)
+ for (let node of added) if (node.nodeName == "BR" && node.parentNode) {
+ let after = node.nextSibling
+ if (after && after.nodeType == 1 && (after as HTMLElement).contentEditable == "false")
+ node.parentNode.removeChild(node)
+ }
+ }
+
+ let readSel: Selection | null = null
+ // If it looks like the browser has reset the selection to the
+ // start of the document after focus, restore the selection from
+ // the state
+ if (from < 0 && newSel && view.input.lastFocus > Date.now() - 200 &&
+ Math.max(view.input.lastTouch, view.input.lastClick.time) < Date.now() - 300 &&
+ selectionCollapsed(sel) && (readSel = selectionFromDOM(view)) &&
+ readSel.eq(Selection.near(view.state.doc.resolve(0), 1))) {
+ view.input.lastFocus = 0
+ selectionToDOM(view)
+ this.currentSelection.set(sel)
+ view.scrollToSelection()
+ } else if (from > -1 || newSel) {
+ if (from > -1) {
+ view.docView.markDirty(from, to)
+ checkCSS(view)
+ }
+ this.handleDOMChange(from, to, typeOver, added)
+ if (view.docView && view.docView.dirty) view.updateState(view.state)
+ else if (!this.currentSelection.eq(sel)) selectionToDOM(view)
+ this.currentSelection.set(sel)
+ }
+ }
+
+ registerMutation(mut: MutationRecord, added: Node[]) {
+ // Ignore mutations inside nodes that were already noted as inserted
+ if (added.indexOf(mut.target) > -1) return null
+ let desc = this.view.docView.nearestDesc(mut.target)
+ if (mut.type == "attributes" &&
+ (desc == this.view.docView || mut.attributeName == "contenteditable" ||
+ // Firefox sometimes fires spurious events for null/empty styles
+ (mut.attributeName == "style" && !mut.oldValue && !(mut.target as HTMLElement).getAttribute("style"))))
+ return null
+ if (!desc || desc.ignoreMutation(mut)) return null
+
+ if (mut.type == "childList") {
+ for (let i = 0; i < mut.addedNodes.length; i++) {
+ let node = mut.addedNodes[i]
+ added.push(node)
+ if (node.nodeType == 3) this.lastChangedTextNode = node as Text
+ }
+ if (desc.contentDOM && desc.contentDOM != desc.dom && !desc.contentDOM.contains(mut.target))
+ return {from: desc.posBefore, to: desc.posAfter}
+ let prev = mut.previousSibling, next = mut.nextSibling
+ if (browser.ie && browser.ie_version <= 11 && mut.addedNodes.length) {
+ // IE11 gives us incorrect next/prev siblings for some
+ // insertions, so if there are added nodes, recompute those
+ for (let i = 0; i < mut.addedNodes.length; i++) {
+ let {previousSibling, nextSibling} = mut.addedNodes[i]
+ if (!previousSibling || Array.prototype.indexOf.call(mut.addedNodes, previousSibling) < 0) prev = previousSibling
+ if (!nextSibling || Array.prototype.indexOf.call(mut.addedNodes, nextSibling) < 0) next = nextSibling
+ }
+ }
+ let fromOffset = prev && prev.parentNode == mut.target
+ ? domIndex(prev) + 1 : 0
+ let from = desc.localPosFromDOM(mut.target, fromOffset, -1)
+ let toOffset = next && next.parentNode == mut.target
+ ? domIndex(next) : mut.target.childNodes.length
+ let to = desc.localPosFromDOM(mut.target, toOffset, 1)
+ return {from, to}
+ } else if (mut.type == "attributes") {
+ return {from: desc.posAtStart - desc.border, to: desc.posAtEnd + desc.border}
+ } else { // "characterData"
+ this.lastChangedTextNode = mut.target as Text
+ return {
+ from: desc.posAtStart,
+ to: desc.posAtEnd,
+ // An event was generated for a text change that didn't change
+ // any text. Mark the dom change to fall back to assuming the
+ // selection was typed over with an identical value if it can't
+ // find another change.
+ typeOver: mut.target.nodeValue == mut.oldValue
+ }
+ }
+ }
+}
+
+let cssChecked: WeakMap<EditorView, null> = new WeakMap()
+let cssCheckWarned: boolean = false
+
+function checkCSS(view: EditorView) {
+ if (cssChecked.has(view)) return
+ cssChecked.set(view, null)
+ if (['normal', 'nowrap', 'pre-line'].indexOf(getComputedStyle(view.dom).whiteSpace) !== -1) {
+ view.requiresGeckoHackNode = browser.gecko
+ if (cssCheckWarned) return
+ console["warn"]("ProseMirror expects the CSS white-space property to be set, preferably to 'pre-wrap'. It is recommended to load style/prosemirror.css from the prosemirror-view package.")
+ cssCheckWarned = true
+ }
+}
+
+function rangeToSelectionRange(view: EditorView, range: StaticRange) {
+ let anchorNode = range.startContainer, anchorOffset = range.startOffset
+ let focusNode = range.endContainer, focusOffset = range.endOffset
+
+ let currentAnchor = view.domAtPos(view.state.selection.anchor)
+ // Since such a range doesn't distinguish between anchor and head,
+ // use a heuristic that flips it around if its end matches the
+ // current anchor.
+ if (isEquivalentPosition(currentAnchor.node, currentAnchor.offset, focusNode, focusOffset))
+ [anchorNode, anchorOffset, focusNode, focusOffset] = [focusNode, focusOffset, anchorNode, anchorOffset]
+ return {anchorNode, anchorOffset, focusNode, focusOffset}
+}
+
+// Used to work around a Safari Selection/shadow DOM bug
+// Based on https://github.com/codemirror/dev/issues/414 fix
+export function safariShadowSelectionRange(view: EditorView, selection: DOMSelection): DOMSelectionRange | null {
+ if ((selection as any).getComposedRanges) {
+ let range = (selection as any).getComposedRanges(view.root)[0] as StaticRange
+ if (range) return rangeToSelectionRange(view, range)
+ }
+
+ let found: StaticRange | undefined
+ function read(event: InputEvent) {
+ event.preventDefault()
+ event.stopImmediatePropagation()
+ found = event.getTargetRanges()[0]
+ }
+
+ // Because Safari (at least in 2018-2022) doesn't provide regular
+ // access to the selection inside a shadowRoot, we have to perform a
+ // ridiculous hack to get at it—using `execCommand` to trigger a
+ // `beforeInput` event so that we can read the target range from the
+ // event.
+ view.dom.addEventListener("beforeinput", read, true)
+ document.execCommand("indent")
+ view.dom.removeEventListener("beforeinput", read, true)
+
+ return found ? rangeToSelectionRange(view, found) : null
+}
+
+function blockParent(view: EditorView, node: DOMNode): Node | null {
+ for (let p = node.parentNode; p && p != view.dom; p = p.parentNode) {
+ let desc = view.docView.nearestDesc(p, true)
+ if (desc && desc.node.isBlock) return p
+ }
+ return null
+}
diff --git a/third_party/js/prosemirror/prosemirror-view/src/index.ts b/third_party/js/prosemirror/prosemirror-view/src/index.ts
@@ -0,0 +1,825 @@
+import {NodeSelection, EditorState, Plugin, PluginView, Transaction, Selection} from "prosemirror-state"
+import {Slice, ResolvedPos, DOMParser, DOMSerializer, Node, Mark} from "prosemirror-model"
+
+import {scrollRectIntoView, posAtCoords, coordsAtPos, endOfTextblock, storeScrollPos,
+ resetScrollPos, focusPreventScroll} from "./domcoords"
+import {docViewDesc, ViewDesc, NodeView, NodeViewDesc, MarkView} from "./viewdesc"
+import {initInput, destroyInput, dispatchEvent, ensureListeners, clearComposition,
+ InputState, doPaste, Dragging, findCompositionNode} from "./input"
+import {selectionToDOM, anchorInRightPlace, syncNodeSelection} from "./selection"
+import {Decoration, viewDecorations, DecorationSource} from "./decoration"
+import {DOMObserver, safariShadowSelectionRange} from "./domobserver"
+import {readDOMChange} from "./domchange"
+import {DOMSelection, DOMNode, DOMSelectionRange, deepActiveElement, clearReusedRange} from "./dom"
+import * as browser from "./browser"
+
+export {Decoration, DecorationSet, DecorationAttrs, DecorationSource} from "./decoration"
+export {NodeView, MarkView, ViewMutationRecord} from "./viewdesc"
+
+// Exported for testing
+import {serializeForClipboard, parseFromClipboard} from "./clipboard"
+import {endComposition} from "./input"
+/// @internal
+export const __parseFromClipboard = parseFromClipboard
+/// @internal
+export const __endComposition = endComposition
+
+/// An editor view manages the DOM structure that represents an
+/// editable document. Its state and behavior are determined by its
+/// [props](#view.DirectEditorProps).
+export class EditorView {
+ /// @internal
+ _props: DirectEditorProps
+ private directPlugins: readonly Plugin[]
+ private _root: Document | ShadowRoot | null = null
+ /// @internal
+ focused = false
+ /// Kludge used to work around a Chrome bug @internal
+ trackWrites: DOMNode | null = null
+ private mounted = false
+ /// @internal
+ markCursor: readonly Mark[] | null = null
+ /// @internal
+ cursorWrapper: {dom: DOMNode, deco: Decoration} | null = null
+ /// @internal
+ nodeViews: NodeViewSet
+ /// @internal
+ lastSelectedViewDesc: ViewDesc | undefined = undefined
+ /// @internal
+ docView: NodeViewDesc
+ /// @internal
+ input = new InputState
+ private prevDirectPlugins: readonly Plugin[] = []
+ private pluginViews: PluginView[] = []
+ /// @internal
+ declare domObserver: DOMObserver
+ /// Holds `true` when a hack node is needed in Firefox to prevent the
+ /// [space is eaten issue](https://github.com/ProseMirror/prosemirror/issues/651)
+ /// @internal
+ requiresGeckoHackNode: boolean = false
+
+ /// The view's current [state](#state.EditorState).
+ public state: EditorState
+
+ /// Create a view. `place` may be a DOM node that the editor should
+ /// be appended to, a function that will place it into the document,
+ /// or an object whose `mount` property holds the node to use as the
+ /// document container. If it is `null`, the editor will not be
+ /// added to the document.
+ constructor(place: null | DOMNode | ((editor: HTMLElement) => void) | {mount: HTMLElement}, props: DirectEditorProps) {
+ this._props = props
+ this.state = props.state
+ this.directPlugins = props.plugins || []
+ this.directPlugins.forEach(checkStateComponent)
+
+ this.dispatch = this.dispatch.bind(this)
+
+ this.dom = (place && (place as {mount: HTMLElement}).mount) || document.createElement("div")
+ if (place) {
+ if ((place as DOMNode).appendChild) (place as DOMNode).appendChild(this.dom)
+ else if (typeof place == "function") place(this.dom)
+ else if ((place as {mount: HTMLElement}).mount) this.mounted = true
+ }
+
+ this.editable = getEditable(this)
+ updateCursorWrapper(this)
+ this.nodeViews = buildNodeViews(this)
+ this.docView = docViewDesc(this.state.doc, computeDocDeco(this), viewDecorations(this), this.dom, this)
+
+ this.domObserver = new DOMObserver(this, (from, to, typeOver, added) => readDOMChange(this, from, to, typeOver, added))
+ this.domObserver.start()
+ initInput(this)
+ this.updatePluginViews()
+ }
+
+ /// An editable DOM node containing the document. (You probably
+ /// should not directly interfere with its content.)
+ readonly dom: HTMLElement
+
+ /// Indicates whether the editor is currently [editable](#view.EditorProps.editable).
+ editable: boolean
+
+ /// When editor content is being dragged, this object contains
+ /// information about the dragged slice and whether it is being
+ /// copied or moved. At any other time, it is null.
+ dragging: null | {slice: Slice, move: boolean} = null
+
+ /// Holds `true` when a
+ /// [composition](https://w3c.github.io/uievents/#events-compositionevents)
+ /// is active.
+ get composing() { return this.input.composing }
+
+ /// The view's current [props](#view.EditorProps).
+ get props() {
+ if (this._props.state != this.state) {
+ let prev = this._props
+ this._props = {} as any
+ for (let name in prev) (this._props as any)[name] = (prev as any)[name]
+ this._props.state = this.state
+ }
+ return this._props
+ }
+
+ /// Update the view's props. Will immediately cause an update to
+ /// the DOM.
+ update(props: DirectEditorProps) {
+ if (props.handleDOMEvents != this._props.handleDOMEvents) ensureListeners(this)
+ let prevProps = this._props
+ this._props = props
+ if (props.plugins) {
+ props.plugins.forEach(checkStateComponent)
+ this.directPlugins = props.plugins
+ }
+ this.updateStateInner(props.state, prevProps)
+ }
+
+ /// Update the view by updating existing props object with the object
+ /// given as argument. Equivalent to `view.update(Object.assign({},
+ /// view.props, props))`.
+ setProps(props: Partial<DirectEditorProps>) {
+ let updated = {} as DirectEditorProps
+ for (let name in this._props) (updated as any)[name] = (this._props as any)[name]
+ updated.state = this.state
+ for (let name in props) (updated as any)[name] = (props as any)[name]
+ this.update(updated)
+ }
+
+ /// Update the editor's `state` prop, without touching any of the
+ /// other props.
+ updateState(state: EditorState) {
+ this.updateStateInner(state, this._props)
+ }
+
+ private updateStateInner(state: EditorState, prevProps: DirectEditorProps) {
+ let prev = this.state, redraw = false, updateSel = false
+ // When stored marks are added, stop composition, so that they can
+ // be displayed.
+ if (state.storedMarks && this.composing) {
+ clearComposition(this)
+ updateSel = true
+ }
+ this.state = state
+ let pluginsChanged = prev.plugins != state.plugins || this._props.plugins != prevProps.plugins
+ if (pluginsChanged || this._props.plugins != prevProps.plugins || this._props.nodeViews != prevProps.nodeViews) {
+ let nodeViews = buildNodeViews(this)
+ if (changedNodeViews(nodeViews, this.nodeViews)) {
+ this.nodeViews = nodeViews
+ redraw = true
+ }
+ }
+ if (pluginsChanged || prevProps.handleDOMEvents != this._props.handleDOMEvents) {
+ ensureListeners(this)
+ }
+
+ this.editable = getEditable(this)
+ updateCursorWrapper(this)
+ let innerDeco = viewDecorations(this), outerDeco = computeDocDeco(this)
+
+ let scroll = prev.plugins != state.plugins && !prev.doc.eq(state.doc) ? "reset"
+ : (state as any).scrollToSelection > (prev as any).scrollToSelection ? "to selection" : "preserve"
+ let updateDoc = redraw || !this.docView.matchesNode(state.doc, outerDeco, innerDeco)
+ if (updateDoc || !state.selection.eq(prev.selection)) updateSel = true
+ let oldScrollPos = scroll == "preserve" && updateSel && this.dom.style.overflowAnchor == null && storeScrollPos(this)
+
+ if (updateSel) {
+ this.domObserver.stop()
+ // Work around an issue in Chrome, IE, and Edge where changing
+ // the DOM around an active selection puts it into a broken
+ // state where the thing the user sees differs from the
+ // selection reported by the Selection object (#710, #973,
+ // #1011, #1013, #1035).
+ let forceSelUpdate = updateDoc && (browser.ie || browser.chrome) && !this.composing &&
+ !prev.selection.empty && !state.selection.empty && selectionContextChanged(prev.selection, state.selection)
+ if (updateDoc) {
+ // If the node that the selection points into is written to,
+ // Chrome sometimes starts misreporting the selection, so this
+ // tracks that and forces a selection reset when our update
+ // did write to the node.
+ let chromeKludge = browser.chrome ? (this.trackWrites = this.domSelectionRange().focusNode) : null
+ if (this.composing) this.input.compositionNode = findCompositionNode(this)
+ if (redraw || !this.docView.update(state.doc, outerDeco, innerDeco, this)) {
+ this.docView.updateOuterDeco(outerDeco)
+ this.docView.destroy()
+ this.docView = docViewDesc(state.doc, outerDeco, innerDeco, this.dom, this)
+ }
+ if (chromeKludge && !this.trackWrites) forceSelUpdate = true
+ }
+ // Work around for an issue where an update arriving right between
+ // a DOM selection change and the "selectionchange" event for it
+ // can cause a spurious DOM selection update, disrupting mouse
+ // drag selection.
+ if (forceSelUpdate ||
+ !(this.input.mouseDown && this.domObserver.currentSelection.eq(this.domSelectionRange()) &&
+ anchorInRightPlace(this))) {
+ selectionToDOM(this, forceSelUpdate)
+ } else {
+ syncNodeSelection(this, state.selection)
+ this.domObserver.setCurSelection()
+ }
+ this.domObserver.start()
+ }
+
+ this.updatePluginViews(prev)
+ if ((this.dragging as Dragging)?.node && !prev.doc.eq(state.doc))
+ this.updateDraggedNode(this.dragging as Dragging, prev)
+
+ if (scroll == "reset") {
+ this.dom.scrollTop = 0
+ } else if (scroll == "to selection") {
+ this.scrollToSelection()
+ } else if (oldScrollPos) {
+ resetScrollPos(oldScrollPos)
+ }
+ }
+
+ /// @internal
+ scrollToSelection() {
+ let startDOM = this.domSelectionRange().focusNode
+ if (!startDOM || !this.dom.contains(startDOM.nodeType == 1 ? startDOM : startDOM.parentNode)) {
+ // Ignore selections outside the editor
+ } else if (this.someProp("handleScrollToSelection", f => f(this))) {
+ // Handled
+ } else if (this.state.selection instanceof NodeSelection) {
+ let target = this.docView.domAfterPos(this.state.selection.from)
+ if (target.nodeType == 1) scrollRectIntoView(this, (target as HTMLElement).getBoundingClientRect(), startDOM)
+ } else {
+ scrollRectIntoView(this, this.coordsAtPos(this.state.selection.head, 1), startDOM)
+ }
+ }
+
+ private destroyPluginViews() {
+ let view
+ while (view = this.pluginViews.pop()) if (view.destroy) view.destroy()
+ }
+
+ private updatePluginViews(prevState?: EditorState) {
+ if (!prevState || prevState.plugins != this.state.plugins || this.directPlugins != this.prevDirectPlugins) {
+ this.prevDirectPlugins = this.directPlugins
+ this.destroyPluginViews()
+ for (let i = 0; i < this.directPlugins.length; i++) {
+ let plugin = this.directPlugins[i]
+ if (plugin.spec.view) this.pluginViews.push(plugin.spec.view(this))
+ }
+ for (let i = 0; i < this.state.plugins.length; i++) {
+ let plugin = this.state.plugins[i]
+ if (plugin.spec.view) this.pluginViews.push(plugin.spec.view(this))
+ }
+ } else {
+ for (let i = 0; i < this.pluginViews.length; i++) {
+ let pluginView = this.pluginViews[i]
+ if (pluginView.update) pluginView.update(this, prevState)
+ }
+ }
+ }
+
+ private updateDraggedNode(dragging: Dragging, prev: EditorState) {
+ let sel = dragging.node!, found = -1
+ if (this.state.doc.nodeAt(sel.from) == sel.node) {
+ found = sel.from
+ } else {
+ let movedPos = sel.from + (this.state.doc.content.size - prev.doc.content.size)
+ let moved = movedPos > 0 && this.state.doc.nodeAt(movedPos)
+ if (moved == sel.node) found = movedPos
+ }
+ this.dragging = new Dragging(dragging.slice, dragging.move,
+ found < 0 ? undefined : NodeSelection.create(this.state.doc, found))
+ }
+
+ /// Goes over the values of a prop, first those provided directly,
+ /// then those from plugins given to the view, then from plugins in
+ /// the state (in order), and calls `f` every time a non-undefined
+ /// value is found. When `f` returns a truthy value, that is
+ /// immediately returned. When `f` isn't provided, it is treated as
+ /// the identity function (the prop value is returned directly).
+ someProp<PropName extends keyof EditorProps, Result>(
+ propName: PropName,
+ f: (value: NonNullable<EditorProps[PropName]>) => Result
+ ): Result | undefined
+ someProp<PropName extends keyof EditorProps>(propName: PropName): NonNullable<EditorProps[PropName]> | undefined
+ someProp<PropName extends keyof EditorProps, Result>(
+ propName: PropName,
+ f?: (value: NonNullable<EditorProps[PropName]>) => Result
+ ): Result | undefined {
+ let prop = this._props && this._props[propName], value
+ if (prop != null && (value = f ? f(prop as any) : prop)) return value as any
+ for (let i = 0; i < this.directPlugins.length; i++) {
+ let prop = this.directPlugins[i].props[propName]
+ if (prop != null && (value = f ? f(prop as any) : prop)) return value as any
+ }
+ let plugins = this.state.plugins
+ if (plugins) for (let i = 0; i < plugins.length; i++) {
+ let prop = plugins[i].props[propName]
+ if (prop != null && (value = f ? f(prop as any) : prop)) return value as any
+ }
+ }
+
+ /// Query whether the view has focus.
+ hasFocus() {
+ // Work around IE not handling focus correctly if resize handles are shown.
+ // If the cursor is inside an element with resize handles, activeElement
+ // will be that element instead of this.dom.
+ if (browser.ie) {
+ // If activeElement is within this.dom, and there are no other elements
+ // setting `contenteditable` to false in between, treat it as focused.
+ let node = this.root.activeElement
+ if (node == this.dom) return true
+ if (!node || !this.dom.contains(node)) return false
+ while (node && this.dom != node && this.dom.contains(node)) {
+ if ((node as HTMLElement).contentEditable == 'false') return false
+ node = node.parentElement
+ }
+ return true
+ }
+ return this.root.activeElement == this.dom
+ }
+
+ /// Focus the editor.
+ focus() {
+ this.domObserver.stop()
+ if (this.editable) focusPreventScroll(this.dom)
+ selectionToDOM(this)
+ this.domObserver.start()
+ }
+
+ /// Get the document root in which the editor exists. This will
+ /// usually be the top-level `document`, but might be a [shadow
+ /// DOM](https://developer.mozilla.org/en-US/docs/Web/Web_Components/Shadow_DOM)
+ /// root if the editor is inside one.
+ get root(): Document | ShadowRoot {
+ let cached = this._root
+ if (cached == null) for (let search = this.dom.parentNode; search; search = search.parentNode) {
+ if (search.nodeType == 9 || (search.nodeType == 11 && (search as any).host)) {
+ if (!(search as any).getSelection)
+ Object.getPrototypeOf(search).getSelection = () => (search as DOMNode).ownerDocument!.getSelection()
+ return this._root = search as Document | ShadowRoot
+ }
+ }
+ return cached || document
+ }
+
+ /// When an existing editor view is moved to a new document or
+ /// shadow tree, call this to make it recompute its root.
+ updateRoot() {
+ this._root = null
+ }
+
+ /// Given a pair of viewport coordinates, return the document
+ /// position that corresponds to them. May return null if the given
+ /// coordinates aren't inside of the editor. When an object is
+ /// returned, its `pos` property is the position nearest to the
+ /// coordinates, and its `inside` property holds the position of the
+ /// inner node that the position falls inside of, or -1 if it is at
+ /// the top level, not in any node.
+ posAtCoords(coords: {left: number, top: number}): {pos: number, inside: number} | null {
+ return posAtCoords(this, coords)
+ }
+
+ /// Returns the viewport rectangle at a given document position.
+ /// `left` and `right` will be the same number, as this returns a
+ /// flat cursor-ish rectangle. If the position is between two things
+ /// that aren't directly adjacent, `side` determines which element
+ /// is used. When < 0, the element before the position is used,
+ /// otherwise the element after.
+ coordsAtPos(pos: number, side = 1): {left: number, right: number, top: number, bottom: number} {
+ return coordsAtPos(this, pos, side)
+ }
+
+ /// Find the DOM position that corresponds to the given document
+ /// position. When `side` is negative, find the position as close as
+ /// possible to the content before the position. When positive,
+ /// prefer positions close to the content after the position. When
+ /// zero, prefer as shallow a position as possible.
+ ///
+ /// Note that you should **not** mutate the editor's internal DOM,
+ /// only inspect it (and even that is usually not necessary).
+ domAtPos(pos: number, side = 0): {node: DOMNode, offset: number} {
+ return this.docView.domFromPos(pos, side)
+ }
+
+ /// Find the DOM node that represents the document node after the
+ /// given position. May return `null` when the position doesn't point
+ /// in front of a node or if the node is inside an opaque node view.
+ ///
+ /// This is intended to be able to call things like
+ /// `getBoundingClientRect` on that DOM node. Do **not** mutate the
+ /// editor DOM directly, or add styling this way, since that will be
+ /// immediately overriden by the editor as it redraws the node.
+ nodeDOM(pos: number): DOMNode | null {
+ let desc = this.docView.descAt(pos)
+ return desc ? (desc as NodeViewDesc).nodeDOM : null
+ }
+
+ /// Find the document position that corresponds to a given DOM
+ /// position. (Whenever possible, it is preferable to inspect the
+ /// document structure directly, rather than poking around in the
+ /// DOM, but sometimes—for example when interpreting an event
+ /// target—you don't have a choice.)
+ ///
+ /// The `bias` parameter can be used to influence which side of a DOM
+ /// node to use when the position is inside a leaf node.
+ posAtDOM(node: DOMNode, offset: number, bias = -1): number {
+ let pos = this.docView.posFromDOM(node, offset, bias)
+ if (pos == null) throw new RangeError("DOM position not inside the editor")
+ return pos
+ }
+
+ /// Find out whether the selection is at the end of a textblock when
+ /// moving in a given direction. When, for example, given `"left"`,
+ /// it will return true if moving left from the current cursor
+ /// position would leave that position's parent textblock. Will apply
+ /// to the view's current state by default, but it is possible to
+ /// pass a different state.
+ endOfTextblock(dir: "up" | "down" | "left" | "right" | "forward" | "backward", state?: EditorState): boolean {
+ return endOfTextblock(this, state || this.state, dir)
+ }
+
+ /// Run the editor's paste logic with the given HTML string. The
+ /// `event`, if given, will be passed to the
+ /// [`handlePaste`](#view.EditorProps.handlePaste) hook.
+ pasteHTML(html: string, event?: ClipboardEvent) {
+ return doPaste(this, "", html, false, event || new ClipboardEvent("paste"))
+ }
+
+ /// Run the editor's paste logic with the given plain-text input.
+ pasteText(text: string, event?: ClipboardEvent) {
+ return doPaste(this, text, null, true, event || new ClipboardEvent("paste"))
+ }
+
+ /// Serialize the given slice as it would be if it was copied from
+ /// this editor. Returns a DOM element that contains a
+ /// representation of the slice as its children, a textual
+ /// representation, and the transformed slice (which can be
+ /// different from the given input due to hooks like
+ /// [`transformCopied`](#view.EditorProps.transformCopied)).
+ serializeForClipboard(slice: Slice): {dom: HTMLElement, text: string, slice: Slice} {
+ return serializeForClipboard(this, slice)
+ }
+
+ /// Removes the editor from the DOM and destroys all [node
+ /// views](#view.NodeView).
+ destroy() {
+ if (!this.docView) return
+ destroyInput(this)
+ this.destroyPluginViews()
+ if (this.mounted) {
+ this.docView.update(this.state.doc, [], viewDecorations(this), this)
+ this.dom.textContent = ""
+ } else if (this.dom.parentNode) {
+ this.dom.parentNode.removeChild(this.dom)
+ }
+ this.docView.destroy()
+ ;(this as any).docView = null
+ clearReusedRange()
+ }
+
+ /// This is true when the view has been
+ /// [destroyed](#view.EditorView.destroy) (and thus should not be
+ /// used anymore).
+ get isDestroyed() {
+ return this.docView == null
+ }
+
+ /// Used for testing.
+ dispatchEvent(event: Event) {
+ return dispatchEvent(this, event)
+ }
+
+ /// Dispatch a transaction. Will call
+ /// [`dispatchTransaction`](#view.DirectEditorProps.dispatchTransaction)
+ /// when given, and otherwise defaults to applying the transaction to
+ /// the current state and calling
+ /// [`updateState`](#view.EditorView.updateState) with the result.
+ /// This method is bound to the view instance, so that it can be
+ /// easily passed around.
+ declare dispatch: (tr: Transaction) => void
+
+ /// @internal
+ domSelectionRange(): DOMSelectionRange {
+ let sel = this.domSelection()
+ if (!sel) return {focusNode: null, focusOffset: 0, anchorNode: null, anchorOffset: 0}
+ return browser.safari && this.root.nodeType === 11 &&
+ deepActiveElement(this.dom.ownerDocument) == this.dom && safariShadowSelectionRange(this, sel) || sel
+ }
+
+ /// @internal
+ domSelection(): DOMSelection | null {
+ return (this.root as Document).getSelection()
+ }
+}
+
+EditorView.prototype.dispatch = function(tr: Transaction) {
+ let dispatchTransaction = this._props.dispatchTransaction
+ if (dispatchTransaction) dispatchTransaction.call(this, tr)
+ else this.updateState(this.state.apply(tr))
+}
+
+function computeDocDeco(view: EditorView) {
+ let attrs = Object.create(null)
+ attrs.class = "ProseMirror"
+ attrs.contenteditable = String(view.editable)
+
+ view.someProp("attributes", value => {
+ if (typeof value == "function") value = value(view.state)
+ if (value) for (let attr in value) {
+ if (attr == "class")
+ attrs.class += " " + value[attr]
+ else if (attr == "style")
+ attrs.style = (attrs.style ? attrs.style + ";" : "") + value[attr]
+ else if (!attrs[attr] && attr != "contenteditable" && attr != "nodeName")
+ attrs[attr] = String(value[attr])
+ }
+ })
+ if (!attrs.translate) attrs.translate = "no"
+
+ return [Decoration.node(0, view.state.doc.content.size, attrs)]
+}
+
+function updateCursorWrapper(view: EditorView) {
+ if (view.markCursor) {
+ let dom = document.createElement("img")
+ dom.className = "ProseMirror-separator"
+ dom.setAttribute("mark-placeholder", "true")
+ dom.setAttribute("alt", "")
+ view.cursorWrapper = {dom, deco: Decoration.widget(view.state.selection.from,
+ dom, {raw: true, marks: view.markCursor} as any)}
+ } else {
+ view.cursorWrapper = null
+ }
+}
+
+function getEditable(view: EditorView) {
+ return !view.someProp("editable", value => value(view.state) === false)
+}
+
+function selectionContextChanged(sel1: Selection, sel2: Selection) {
+ let depth = Math.min(sel1.$anchor.sharedDepth(sel1.head), sel2.$anchor.sharedDepth(sel2.head))
+ return sel1.$anchor.start(depth) != sel2.$anchor.start(depth)
+}
+
+function buildNodeViews(view: EditorView) {
+ let result: NodeViewSet = Object.create(null)
+ function add(obj: NodeViewSet) {
+ for (let prop in obj) if (!Object.prototype.hasOwnProperty.call(result, prop))
+ result[prop] = obj[prop]
+ }
+ view.someProp("nodeViews", add)
+ view.someProp("markViews", add)
+ return result
+}
+
+function changedNodeViews(a: NodeViewSet, b: NodeViewSet) {
+ let nA = 0, nB = 0
+ for (let prop in a) {
+ if (a[prop] != b[prop]) return true
+ nA++
+ }
+ for (let _ in b) nB++
+ return nA != nB
+}
+
+function checkStateComponent(plugin: Plugin) {
+ if (plugin.spec.state || plugin.spec.filterTransaction || plugin.spec.appendTransaction)
+ throw new RangeError("Plugins passed directly to the view must not have a state component")
+}
+
+/// The type of function [provided](#view.EditorProps.nodeViews) to
+/// create [node views](#view.NodeView).
+export type NodeViewConstructor = (node: Node, view: EditorView, getPos: () => number | undefined,
+ decorations: readonly Decoration[], innerDecorations: DecorationSource) => NodeView
+
+/// The function types [used](#view.EditorProps.markViews) to create
+/// mark views.
+export type MarkViewConstructor = (mark: Mark, view: EditorView, inline: boolean) => MarkView
+
+type NodeViewSet = {[name: string]: NodeViewConstructor | MarkViewConstructor}
+
+/// Helper type that maps event names to event object types, but
+/// includes events that TypeScript's HTMLElementEventMap doesn't know
+/// about.
+export interface DOMEventMap extends HTMLElementEventMap {
+ [event: string]: any
+}
+
+/// Props are configuration values that can be passed to an editor view
+/// or included in a plugin. This interface lists the supported props.
+///
+/// The various event-handling functions may all return `true` to
+/// indicate that they handled the given event. The view will then take
+/// care to call `preventDefault` on the event, except with
+/// `handleDOMEvents`, where the handler itself is responsible for that.
+///
+/// How a prop is resolved depends on the prop. Handler functions are
+/// called one at a time, starting with the base props and then
+/// searching through the plugins (in order of appearance) until one of
+/// them returns true. For some props, the first plugin that yields a
+/// value gets precedence.
+///
+/// The optional type parameter refers to the type of `this` in prop
+/// functions, and is used to pass in the plugin type when defining a
+/// [plugin](#state.Plugin).
+export interface EditorProps<P = any> {
+ /// Can be an object mapping DOM event type names to functions that
+ /// handle them. Such functions will be called before any handling
+ /// ProseMirror does of events fired on the editable DOM element.
+ /// Contrary to the other event handling props, when returning true
+ /// from such a function, you are responsible for calling
+ /// `preventDefault` yourself (or not, if you want to allow the
+ /// default behavior).
+ handleDOMEvents?: {
+ [event in keyof DOMEventMap]?: (this: P, view: EditorView, event: DOMEventMap[event]) => boolean | void
+ }
+
+ /// Called when the editor receives a `keydown` event.
+ handleKeyDown?: (this: P, view: EditorView, event: KeyboardEvent) => boolean | void
+
+ /// Handler for `keypress` events.
+ handleKeyPress?: (this: P, view: EditorView, event: KeyboardEvent) => boolean | void
+
+ /// Whenever the user directly input text, this handler is called
+ /// before the input is applied. If it returns `true`, the default
+ /// behavior of actually inserting the text is suppressed.
+ handleTextInput?: (this: P, view: EditorView, from: number, to: number, text: string, deflt: () => Transaction) => boolean | void
+
+ /// Called for each node around a click, from the inside out. The
+ /// `direct` flag will be true for the inner node.
+ handleClickOn?: (this: P, view: EditorView, pos: number, node: Node, nodePos: number, event: MouseEvent, direct: boolean) => boolean | void
+
+ /// Called when the editor is clicked, after `handleClickOn` handlers
+ /// have been called.
+ handleClick?: (this: P, view: EditorView, pos: number, event: MouseEvent) => boolean | void
+
+ /// Called for each node around a double click.
+ handleDoubleClickOn?: (this: P, view: EditorView, pos: number, node: Node, nodePos: number, event: MouseEvent, direct: boolean) => boolean | void
+
+ /// Called when the editor is double-clicked, after `handleDoubleClickOn`.
+ handleDoubleClick?: (this: P, view: EditorView, pos: number, event: MouseEvent) => boolean | void
+
+ /// Called for each node around a triple click.
+ handleTripleClickOn?: (this: P, view: EditorView, pos: number, node: Node, nodePos: number, event: MouseEvent, direct: boolean) => boolean | void
+
+ /// Called when the editor is triple-clicked, after `handleTripleClickOn`.
+ handleTripleClick?: (this: P, view: EditorView, pos: number, event: MouseEvent) => boolean | void
+
+ /// Can be used to override the behavior of pasting. `slice` is the
+ /// pasted content parsed by the editor, but you can directly access
+ /// the event to get at the raw content.
+ handlePaste?: (this: P, view: EditorView, event: ClipboardEvent, slice: Slice) => boolean | void
+
+ /// Called when something is dropped on the editor. `moved` will be
+ /// true if this drop moves from the current selection (which should
+ /// thus be deleted).
+ handleDrop?: (this: P, view: EditorView, event: DragEvent, slice: Slice, moved: boolean) => boolean | void
+
+ /// Called when the view, after updating its state, tries to scroll
+ /// the selection into view. A handler function may return false to
+ /// indicate that it did not handle the scrolling and further
+ /// handlers or the default behavior should be tried.
+ handleScrollToSelection?: (this: P, view: EditorView) => boolean
+
+ /// Determines whether an in-editor drag event should copy or move
+ /// the selection. When not given, the event's `altKey` property is
+ /// used on macOS, `ctrlKey` on other platforms.
+ dragCopies?: (event: DragEvent) => boolean
+
+ /// Can be used to override the way a selection is created when
+ /// reading a DOM selection between the given anchor and head.
+ createSelectionBetween?: (this: P, view: EditorView, anchor: ResolvedPos, head: ResolvedPos) => Selection | null
+
+ /// The [parser](#model.DOMParser) to use when reading editor changes
+ /// from the DOM. Defaults to calling
+ /// [`DOMParser.fromSchema`](#model.DOMParser^fromSchema) on the
+ /// editor's schema.
+ domParser?: DOMParser
+
+ /// Can be used to transform pasted HTML text, _before_ it is parsed,
+ /// for example to clean it up.
+ transformPastedHTML?: (this: P, html: string, view: EditorView) => string
+
+ /// The [parser](#model.DOMParser) to use when reading content from
+ /// the clipboard. When not given, the value of the
+ /// [`domParser`](#view.EditorProps.domParser) prop is used.
+ clipboardParser?: DOMParser
+
+ /// Transform pasted plain text. The `plain` flag will be true when
+ /// the text is pasted as plain text.
+ transformPastedText?: (this: P, text: string, plain: boolean, view: EditorView) => string
+
+ /// A function to parse text from the clipboard into a document
+ /// slice. Called after
+ /// [`transformPastedText`](#view.EditorProps.transformPastedText).
+ /// The default behavior is to split the text into lines, wrap them
+ /// in `<p>` tags, and call
+ /// [`clipboardParser`](#view.EditorProps.clipboardParser) on it.
+ /// The `plain` flag will be true when the text is pasted as plain text.
+ clipboardTextParser?: (this: P, text: string, $context: ResolvedPos, plain: boolean, view: EditorView) => Slice
+
+ /// Can be used to transform pasted or dragged-and-dropped content
+ /// before it is applied to the document. The `plain` flag will be
+ /// true when the text is pasted as plain text.
+ transformPasted?: (this: P, slice: Slice, view: EditorView, plain: boolean) => Slice
+
+ /// Can be used to transform copied or cut content before it is
+ /// serialized to the clipboard.
+ transformCopied?: (this: P, slice: Slice, view: EditorView) => Slice
+
+ /// Allows you to pass custom rendering and behavior logic for
+ /// nodes. Should map node names to constructor functions that
+ /// produce a [`NodeView`](#view.NodeView) object implementing the
+ /// node's display behavior. The third argument `getPos` is a
+ /// function that can be called to get the node's current position,
+ /// which can be useful when creating transactions to update it.
+ /// Note that if the node is not in the document, the position
+ /// returned by this function will be `undefined`.
+ ///
+ /// `decorations` is an array of node or inline decorations that are
+ /// active around the node. They are automatically drawn in the
+ /// normal way, and you will usually just want to ignore this, but
+ /// they can also be used as a way to provide context information to
+ /// the node view without adding it to the document itself.
+ ///
+ /// `innerDecorations` holds the decorations for the node's content.
+ /// You can safely ignore this if your view has no content or a
+ /// `contentDOM` property, since the editor will draw the decorations
+ /// on the content. But if you, for example, want to create a nested
+ /// editor with the content, it may make sense to provide it with the
+ /// inner decorations.
+ ///
+ /// (For backwards compatibility reasons, [mark
+ /// views](#view.EditorProps.markViews) can also be included in this
+ /// object.)
+ nodeViews?: {[node: string]: NodeViewConstructor}
+
+ /// Pass custom mark rendering functions. Note that these cannot
+ /// provide the kind of dynamic behavior that [node
+ /// views](#view.NodeView) can—they just provide custom rendering
+ /// logic. The third argument indicates whether the mark's content
+ /// is inline.
+ markViews?: {[mark: string]: MarkViewConstructor}
+
+ /// The DOM serializer to use when putting content onto the
+ /// clipboard. If not given, the result of
+ /// [`DOMSerializer.fromSchema`](#model.DOMSerializer^fromSchema)
+ /// will be used. This object will only have its
+ /// [`serializeFragment`](#model.DOMSerializer.serializeFragment)
+ /// method called, and you may provide an alternative object type
+ /// implementing a compatible method.
+ clipboardSerializer?: DOMSerializer
+
+ /// A function that will be called to get the text for the current
+ /// selection when copying text to the clipboard. By default, the
+ /// editor will use [`textBetween`](#model.Node.textBetween) on the
+ /// selected range.
+ clipboardTextSerializer?: (this: P, content: Slice, view: EditorView) => string
+
+ /// A set of [document decorations](#view.Decoration) to show in the
+ /// view.
+ decorations?: (this: P, state: EditorState) => DecorationSource | null | undefined
+
+ /// When this returns false, the content of the view is not directly
+ /// editable.
+ editable?: (this: P, state: EditorState) => boolean
+
+ /// Control the DOM attributes of the editable element. May be either
+ /// an object or a function going from an editor state to an object.
+ /// By default, the element will get a class `"ProseMirror"`, and
+ /// will have its `contentEditable` attribute determined by the
+ /// [`editable` prop](#view.EditorProps.editable). Additional classes
+ /// provided here will be added to the class. For other attributes,
+ /// the value provided first (as in
+ /// [`someProp`](#view.EditorView.someProp)) will be used.
+ attributes?: {[name: string]: string} | ((state: EditorState) => {[name: string]: string})
+
+ /// Determines the distance (in pixels) between the cursor and the
+ /// end of the visible viewport at which point, when scrolling the
+ /// cursor into view, scrolling takes place. Defaults to 0.
+ scrollThreshold?: number | {top: number, right: number, bottom: number, left: number}
+
+ /// Determines the extra space (in pixels) that is left above or
+ /// below the cursor when it is scrolled into view. Defaults to 5.
+ scrollMargin?: number | {top: number, right: number, bottom: number, left: number}
+}
+
+/// The props object given directly to the editor view supports some
+/// fields that can't be used in plugins:
+export interface DirectEditorProps extends EditorProps {
+ /// The current state of the editor.
+ state: EditorState
+
+ /// A set of plugins to use in the view, applying their [plugin
+ /// view](#state.PluginSpec.view) and
+ /// [props](#state.PluginSpec.props). Passing plugins with a state
+ /// component (a [state field](#state.PluginSpec.state) field or a
+ /// [transaction](#state.PluginSpec.filterTransaction) filter or
+ /// appender) will result in an error, since such plugins must be
+ /// present in the state to work.
+ plugins?: readonly Plugin[]
+
+ /// The callback over which to send transactions (state updates)
+ /// produced by the view. If you specify this, you probably want to
+ /// make sure this ends up calling the view's
+ /// [`updateState`](#view.EditorView.updateState) method with a new
+ /// state that has the transaction
+ /// [applied](#state.EditorState.apply). The callback will be bound to have
+ /// the view instance as its `this` binding.
+ dispatchTransaction?: (tr: Transaction) => void
+}
diff --git a/third_party/js/prosemirror/prosemirror-view/src/input.ts b/third_party/js/prosemirror/prosemirror-view/src/input.ts
@@ -0,0 +1,828 @@
+import {Selection, NodeSelection, TextSelection} from "prosemirror-state"
+import {dropPoint} from "prosemirror-transform"
+import {Slice, Node} from "prosemirror-model"
+
+import * as browser from "./browser"
+import {captureKeyDown} from "./capturekeys"
+import {parseFromClipboard, serializeForClipboard} from "./clipboard"
+import {selectionBetween, selectionToDOM, selectionFromDOM} from "./selection"
+import {keyEvent, DOMNode, textNodeBefore, textNodeAfter} from "./dom"
+import {EditorView} from "./index"
+import {ViewDesc} from "./viewdesc"
+
+// A collection of DOM events that occur within the editor, and callback functions
+// to invoke when the event fires.
+const handlers: {[event: string]: (view: EditorView, event: Event) => void} = {}
+const editHandlers: {[event: string]: (view: EditorView, event: Event) => void} = {}
+const passiveHandlers: Record<string, boolean> = {touchstart: true, touchmove: true}
+
+export class InputState {
+ shiftKey = false
+ mouseDown: MouseDown | null = null
+ lastKeyCode: number | null = null
+ lastKeyCodeTime = 0
+ lastClick = {time: 0, x: 0, y: 0, type: "", button: 0}
+ lastSelectionOrigin: string | null = null
+ lastSelectionTime = 0
+ lastIOSEnter = 0
+ lastIOSEnterFallbackTimeout = -1
+ lastFocus = 0
+ lastTouch = 0
+ lastChromeDelete = 0
+ composing = false
+ compositionNode: Text | null = null
+ composingTimeout = -1
+ compositionNodes: ViewDesc[] = []
+ compositionEndedAt = -2e8
+ compositionID = 1
+ // Set to a composition ID when there are pending changes at compositionend
+ compositionPendingChanges = 0
+ domChangeCount = 0
+ eventHandlers: {[event: string]: (event: Event) => void} = Object.create(null)
+ hideSelectionGuard: (() => void) | null = null
+}
+
+export function initInput(view: EditorView) {
+ for (let event in handlers) {
+ let handler = handlers[event]
+ view.dom.addEventListener(event, view.input.eventHandlers[event] = (event: Event) => {
+ if (eventBelongsToView(view, event) && !runCustomHandler(view, event) &&
+ (view.editable || !(event.type in editHandlers)))
+ handler(view, event)
+ }, passiveHandlers[event] ? {passive: true} : undefined)
+ }
+ // On Safari, for reasons beyond my understanding, adding an input
+ // event handler makes an issue where the composition vanishes when
+ // you press enter go away.
+ if (browser.safari) view.dom.addEventListener("input", () => null)
+
+ ensureListeners(view)
+}
+
+function setSelectionOrigin(view: EditorView, origin: string) {
+ view.input.lastSelectionOrigin = origin
+ view.input.lastSelectionTime = Date.now()
+}
+
+export function destroyInput(view: EditorView) {
+ view.domObserver.stop()
+ for (let type in view.input.eventHandlers)
+ view.dom.removeEventListener(type, view.input.eventHandlers[type])
+ clearTimeout(view.input.composingTimeout)
+ clearTimeout(view.input.lastIOSEnterFallbackTimeout)
+}
+
+export function ensureListeners(view: EditorView) {
+ view.someProp("handleDOMEvents", currentHandlers => {
+ for (let type in currentHandlers) if (!view.input.eventHandlers[type])
+ view.dom.addEventListener(type, view.input.eventHandlers[type] = event => runCustomHandler(view, event))
+ })
+}
+
+function runCustomHandler(view: EditorView, event: Event) {
+ return view.someProp("handleDOMEvents", handlers => {
+ let handler = handlers[event.type]
+ return handler ? handler(view, event) || event.defaultPrevented : false
+ })
+}
+
+function eventBelongsToView(view: EditorView, event: Event) {
+ if (!event.bubbles) return true
+ if (event.defaultPrevented) return false
+ for (let node = event.target as DOMNode; node != view.dom; node = node.parentNode!)
+ if (!node || node.nodeType == 11 ||
+ (node.pmViewDesc && node.pmViewDesc.stopEvent(event)))
+ return false
+ return true
+}
+
+export function dispatchEvent(view: EditorView, event: Event) {
+ if (!runCustomHandler(view, event) && handlers[event.type] &&
+ (view.editable || !(event.type in editHandlers)))
+ handlers[event.type](view, event)
+}
+
+editHandlers.keydown = (view: EditorView, _event: Event) => {
+ let event = _event as KeyboardEvent
+ view.input.shiftKey = event.keyCode == 16 || event.shiftKey
+ if (inOrNearComposition(view, event)) return
+ view.input.lastKeyCode = event.keyCode
+ view.input.lastKeyCodeTime = Date.now()
+ // Suppress enter key events on Chrome Android, because those tend
+ // to be part of a confused sequence of composition events fired,
+ // and handling them eagerly tends to corrupt the input.
+ if (browser.android && browser.chrome && event.keyCode == 13) return
+ if (event.keyCode != 229) view.domObserver.forceFlush()
+
+ // On iOS, if we preventDefault enter key presses, the virtual
+ // keyboard gets confused. So the hack here is to set a flag that
+ // makes the DOM change code recognize that what just happens should
+ // be replaced by whatever the Enter key handlers do.
+ if (browser.ios && event.keyCode == 13 && !event.ctrlKey && !event.altKey && !event.metaKey) {
+ let now = Date.now()
+ view.input.lastIOSEnter = now
+ view.input.lastIOSEnterFallbackTimeout = setTimeout(() => {
+ if (view.input.lastIOSEnter == now) {
+ view.someProp("handleKeyDown", f => f(view, keyEvent(13, "Enter")))
+ view.input.lastIOSEnter = 0
+ }
+ }, 200)
+ } else if (view.someProp("handleKeyDown", f => f(view, event)) || captureKeyDown(view, event)) {
+ event.preventDefault()
+ } else {
+ setSelectionOrigin(view, "key")
+ }
+}
+
+editHandlers.keyup = (view, event) => {
+ if ((event as KeyboardEvent).keyCode == 16) view.input.shiftKey = false
+}
+
+editHandlers.keypress = (view, _event) => {
+ let event = _event as KeyboardEvent
+ if (inOrNearComposition(view, event) || !event.charCode ||
+ event.ctrlKey && !event.altKey || browser.mac && event.metaKey) return
+
+ if (view.someProp("handleKeyPress", f => f(view, event))) {
+ event.preventDefault()
+ return
+ }
+
+ let sel = view.state.selection
+ if (!(sel instanceof TextSelection) || !sel.$from.sameParent(sel.$to)) {
+ let text = String.fromCharCode(event.charCode)
+ let deflt = () => view.state.tr.insertText(text).scrollIntoView()
+ if (!/[\r\n]/.test(text) && !view.someProp("handleTextInput", f => f(view, sel.$from.pos, sel.$to.pos, text, deflt)))
+ view.dispatch(deflt())
+ event.preventDefault()
+ }
+}
+
+function eventCoords(event: MouseEvent) { return {left: event.clientX, top: event.clientY} }
+
+function isNear(event: MouseEvent, click: {x: number, y: number}) {
+ let dx = click.x - event.clientX, dy = click.y - event.clientY
+ return dx * dx + dy * dy < 100
+}
+
+function runHandlerOnContext(
+ view: EditorView,
+ propName: "handleClickOn" | "handleDoubleClickOn" | "handleTripleClickOn",
+ pos: number,
+ inside: number,
+ event: MouseEvent
+) {
+ if (inside == -1) return false
+ let $pos = view.state.doc.resolve(inside)
+ for (let i = $pos.depth + 1; i > 0; i--) {
+ if (view.someProp(propName, f => i > $pos.depth ? f(view, pos, $pos.nodeAfter!, $pos.before(i), event, true)
+ : f(view, pos, $pos.node(i), $pos.before(i), event, false)))
+ return true
+ }
+ return false
+}
+
+function updateSelection(view: EditorView, selection: Selection, origin: string) {
+ if (!view.focused) view.focus()
+ if (view.state.selection.eq(selection)) return
+ let tr = view.state.tr.setSelection(selection)
+ if (origin == "pointer") tr.setMeta("pointer", true)
+ view.dispatch(tr)
+}
+
+function selectClickedLeaf(view: EditorView, inside: number) {
+ if (inside == -1) return false
+ let $pos = view.state.doc.resolve(inside), node = $pos.nodeAfter
+ if (node && node.isAtom && NodeSelection.isSelectable(node)) {
+ updateSelection(view, new NodeSelection($pos), "pointer")
+ return true
+ }
+ return false
+}
+
+function selectClickedNode(view: EditorView, inside: number) {
+ if (inside == -1) return false
+ let sel = view.state.selection, selectedNode, selectAt
+ if (sel instanceof NodeSelection) selectedNode = sel.node
+
+ let $pos = view.state.doc.resolve(inside)
+ for (let i = $pos.depth + 1; i > 0; i--) {
+ let node = i > $pos.depth ? $pos.nodeAfter! : $pos.node(i)
+ if (NodeSelection.isSelectable(node)) {
+ if (selectedNode && sel.$from.depth > 0 &&
+ i >= sel.$from.depth && $pos.before(sel.$from.depth + 1) == sel.$from.pos)
+ selectAt = $pos.before(sel.$from.depth)
+ else
+ selectAt = $pos.before(i)
+ break
+ }
+ }
+
+ if (selectAt != null) {
+ updateSelection(view, NodeSelection.create(view.state.doc, selectAt), "pointer")
+ return true
+ } else {
+ return false
+ }
+}
+
+function handleSingleClick(view: EditorView, pos: number, inside: number, event: MouseEvent, selectNode: boolean) {
+ return runHandlerOnContext(view, "handleClickOn", pos, inside, event) ||
+ view.someProp("handleClick", f => f(view, pos, event)) ||
+ (selectNode ? selectClickedNode(view, inside) : selectClickedLeaf(view, inside))
+}
+
+function handleDoubleClick(view: EditorView, pos: number, inside: number, event: MouseEvent) {
+ return runHandlerOnContext(view, "handleDoubleClickOn", pos, inside, event) ||
+ view.someProp("handleDoubleClick", f => f(view, pos, event))
+}
+
+function handleTripleClick(view: EditorView, pos: number, inside: number, event: MouseEvent) {
+ return runHandlerOnContext(view, "handleTripleClickOn", pos, inside, event) ||
+ view.someProp("handleTripleClick", f => f(view, pos, event)) ||
+ defaultTripleClick(view, inside, event)
+}
+
+function defaultTripleClick(view: EditorView, inside: number, event: MouseEvent) {
+ if (event.button != 0) return false
+ let doc = view.state.doc
+ if (inside == -1) {
+ if (doc.inlineContent) {
+ updateSelection(view, TextSelection.create(doc, 0, doc.content.size), "pointer")
+ return true
+ }
+ return false
+ }
+
+ let $pos = doc.resolve(inside)
+ for (let i = $pos.depth + 1; i > 0; i--) {
+ let node = i > $pos.depth ? $pos.nodeAfter! : $pos.node(i)
+ let nodePos = $pos.before(i)
+ if (node.inlineContent)
+ updateSelection(view, TextSelection.create(doc, nodePos + 1, nodePos + 1 + node.content.size), "pointer")
+ else if (NodeSelection.isSelectable(node))
+ updateSelection(view, NodeSelection.create(doc, nodePos), "pointer")
+ else
+ continue
+ return true
+ }
+}
+
+function forceDOMFlush(view: EditorView) {
+ return endComposition(view)
+}
+
+const selectNodeModifier: keyof MouseEvent = browser.mac ? "metaKey" : "ctrlKey"
+
+handlers.mousedown = (view, _event) => {
+ let event = _event as MouseEvent
+ view.input.shiftKey = event.shiftKey
+ let flushed = forceDOMFlush(view)
+ let now = Date.now(), type = "singleClick"
+ if (now - view.input.lastClick.time < 500 && isNear(event, view.input.lastClick) && !event[selectNodeModifier] &&
+ view.input.lastClick.button == event.button) {
+ if (view.input.lastClick.type == "singleClick") type = "doubleClick"
+ else if (view.input.lastClick.type == "doubleClick") type = "tripleClick"
+ }
+ view.input.lastClick = {time: now, x: event.clientX, y: event.clientY, type, button: event.button}
+
+ let pos = view.posAtCoords(eventCoords(event))
+ if (!pos) return
+
+ if (type == "singleClick") {
+ if (view.input.mouseDown) view.input.mouseDown.done()
+ view.input.mouseDown = new MouseDown(view, pos, event, !!flushed)
+ } else if ((type == "doubleClick" ? handleDoubleClick : handleTripleClick)(view, pos.pos, pos.inside, event)) {
+ event.preventDefault()
+ } else {
+ setSelectionOrigin(view, "pointer")
+ }
+}
+
+class MouseDown {
+ startDoc: Node
+ selectNode: boolean
+ allowDefault: boolean
+ delayedSelectionSync = false
+ mightDrag: {node: Node, pos: number, addAttr: boolean, setUneditable: boolean} | null = null
+ target: HTMLElement | null
+
+ constructor(
+ readonly view: EditorView,
+ readonly pos: {pos: number, inside: number},
+ readonly event: MouseEvent,
+ readonly flushed: boolean
+ ) {
+ this.startDoc = view.state.doc
+ this.selectNode = !!event[selectNodeModifier]
+ this.allowDefault = event.shiftKey
+
+ let targetNode: Node, targetPos
+ if (pos.inside > -1) {
+ targetNode = view.state.doc.nodeAt(pos.inside)!
+ targetPos = pos.inside
+ } else {
+ let $pos = view.state.doc.resolve(pos.pos)
+ targetNode = $pos.parent
+ targetPos = $pos.depth ? $pos.before() : 0
+ }
+
+ const target = flushed ? null : event.target as HTMLElement
+ const targetDesc = target ? view.docView.nearestDesc(target, true) : null
+ this.target = targetDesc && targetDesc.nodeDOM.nodeType == 1 ? targetDesc.nodeDOM as HTMLElement : null
+
+ let {selection} = view.state
+ if (event.button == 0 &&
+ targetNode.type.spec.draggable && targetNode.type.spec.selectable !== false ||
+ selection instanceof NodeSelection && selection.from <= targetPos && selection.to > targetPos)
+ this.mightDrag = {
+ node: targetNode,
+ pos: targetPos,
+ addAttr: !!(this.target && !this.target.draggable),
+ setUneditable: !!(this.target && browser.gecko && !this.target.hasAttribute("contentEditable"))
+ }
+
+ if (this.target && this.mightDrag && (this.mightDrag.addAttr || this.mightDrag.setUneditable)) {
+ this.view.domObserver.stop()
+ if (this.mightDrag.addAttr) this.target.draggable = true
+ if (this.mightDrag.setUneditable)
+ setTimeout(() => {
+ if (this.view.input.mouseDown == this) this.target!.setAttribute("contentEditable", "false")
+ }, 20)
+ this.view.domObserver.start()
+ }
+
+ view.root.addEventListener("mouseup", this.up = this.up.bind(this) as any)
+ view.root.addEventListener("mousemove", this.move = this.move.bind(this) as any)
+ setSelectionOrigin(view, "pointer")
+ }
+
+ done() {
+ this.view.root.removeEventListener("mouseup", this.up as any)
+ this.view.root.removeEventListener("mousemove", this.move as any)
+ if (this.mightDrag && this.target) {
+ this.view.domObserver.stop()
+ if (this.mightDrag.addAttr) this.target.removeAttribute("draggable")
+ if (this.mightDrag.setUneditable) this.target.removeAttribute("contentEditable")
+ this.view.domObserver.start()
+ }
+ if (this.delayedSelectionSync) setTimeout(() => selectionToDOM(this.view))
+ this.view.input.mouseDown = null
+ }
+
+ up(event: MouseEvent) {
+ this.done()
+
+ if (!this.view.dom.contains(event.target as HTMLElement))
+ return
+
+ let pos: {pos: number, inside: number} | null = this.pos
+ if (this.view.state.doc != this.startDoc) pos = this.view.posAtCoords(eventCoords(event))
+
+ this.updateAllowDefault(event)
+ if (this.allowDefault || !pos) {
+ setSelectionOrigin(this.view, "pointer")
+ } else if (handleSingleClick(this.view, pos.pos, pos.inside, event, this.selectNode)) {
+ event.preventDefault()
+ } else if (event.button == 0 &&
+ (this.flushed ||
+ // Safari ignores clicks on draggable elements
+ (browser.safari && this.mightDrag && !this.mightDrag.node.isAtom) ||
+ // Chrome will sometimes treat a node selection as a
+ // cursor, but still report that the node is selected
+ // when asked through getSelection. You'll then get a
+ // situation where clicking at the point where that
+ // (hidden) cursor is doesn't change the selection, and
+ // thus doesn't get a reaction from ProseMirror. This
+ // works around that.
+ (browser.chrome && !this.view.state.selection.visible &&
+ Math.min(Math.abs(pos.pos - this.view.state.selection.from),
+ Math.abs(pos.pos - this.view.state.selection.to)) <= 2))) {
+ updateSelection(this.view, Selection.near(this.view.state.doc.resolve(pos.pos)), "pointer")
+ event.preventDefault()
+ } else {
+ setSelectionOrigin(this.view, "pointer")
+ }
+ }
+
+ move(event: MouseEvent) {
+ this.updateAllowDefault(event)
+ setSelectionOrigin(this.view, "pointer")
+ if (event.buttons == 0) this.done()
+ }
+
+ updateAllowDefault(event: MouseEvent) {
+ if (!this.allowDefault && (Math.abs(this.event.x - event.clientX) > 4 ||
+ Math.abs(this.event.y - event.clientY) > 4))
+ this.allowDefault = true
+ }
+}
+
+handlers.touchstart = view => {
+ view.input.lastTouch = Date.now()
+ forceDOMFlush(view)
+ setSelectionOrigin(view, "pointer")
+}
+
+handlers.touchmove = view => {
+ view.input.lastTouch = Date.now()
+ setSelectionOrigin(view, "pointer")
+}
+
+handlers.contextmenu = view => forceDOMFlush(view)
+
+function inOrNearComposition(view: EditorView, event: Event) {
+ if (view.composing) return true
+ // See https://www.stum.de/2016/06/24/handling-ime-events-in-javascript/.
+ // On Japanese input method editors (IMEs), the Enter key is used to confirm character
+ // selection. On Safari, when Enter is pressed, compositionend and keydown events are
+ // emitted. The keydown event triggers newline insertion, which we don't want.
+ // This method returns true if the keydown event should be ignored.
+ // We only ignore it once, as pressing Enter a second time *should* insert a newline.
+ // Furthermore, the keydown event timestamp must be close to the compositionEndedAt timestamp.
+ // This guards against the case where compositionend is triggered without the keyboard
+ // (e.g. character confirmation may be done with the mouse), and keydown is triggered
+ // afterwards- we wouldn't want to ignore the keydown event in this case.
+ if (browser.safari && Math.abs(event.timeStamp - view.input.compositionEndedAt) < 500) {
+ view.input.compositionEndedAt = -2e8
+ return true
+ }
+ return false
+}
+
+// Drop active composition after 5 seconds of inactivity on Android
+const timeoutComposition = browser.android ? 5000 : -1
+
+editHandlers.compositionstart = editHandlers.compositionupdate = view => {
+ if (!view.composing) {
+ view.domObserver.flush()
+ let {state} = view, $pos = state.selection.$to
+ if (state.selection instanceof TextSelection &&
+ (state.storedMarks ||
+ (!$pos.textOffset && $pos.parentOffset && $pos.nodeBefore!.marks.some(m => m.type.spec.inclusive === false)) ||
+ browser.chrome && browser.windows && selectionBeforeUneditable(view))) { // Issue #1500
+ // Need to wrap the cursor in mark nodes different from the ones in the DOM context
+ view.markCursor = view.state.storedMarks || $pos.marks()
+ endComposition(view, true)
+ view.markCursor = null
+ } else {
+ endComposition(view, !state.selection.empty)
+ // In firefox, if the cursor is after but outside a marked node,
+ // the inserted text won't inherit the marks. So this moves it
+ // inside if necessary.
+ if (browser.gecko && state.selection.empty && $pos.parentOffset && !$pos.textOffset && $pos.nodeBefore!.marks.length) {
+ let sel = view.domSelectionRange()
+ for (let node = sel.focusNode, offset = sel.focusOffset; node && node.nodeType == 1 && offset != 0;) {
+ let before = offset < 0 ? node.lastChild : node.childNodes[offset - 1]
+ if (!before) break
+ if (before.nodeType == 3) {
+ let sel = view.domSelection()
+ if (sel) sel.collapse(before, before.nodeValue!.length)
+ break
+ } else {
+ node = before
+ offset = -1
+ }
+ }
+ }
+ }
+ view.input.composing = true
+ }
+ scheduleComposeEnd(view, timeoutComposition)
+}
+
+function selectionBeforeUneditable(view: EditorView) {
+ let {focusNode, focusOffset} = view.domSelectionRange()
+ if (!focusNode || focusNode.nodeType != 1 || focusOffset >= focusNode.childNodes.length) return false
+ let next = focusNode.childNodes[focusOffset]
+ return next.nodeType == 1 && (next as HTMLElement).contentEditable == "false"
+}
+
+editHandlers.compositionend = (view, event) => {
+ if (view.composing) {
+ view.input.composing = false
+ view.input.compositionEndedAt = event.timeStamp
+ view.input.compositionPendingChanges = view.domObserver.pendingRecords().length ? view.input.compositionID : 0
+ view.input.compositionNode = null
+ if (view.input.compositionPendingChanges) Promise.resolve().then(() => view.domObserver.flush())
+ view.input.compositionID++
+ scheduleComposeEnd(view, 20)
+ }
+}
+
+function scheduleComposeEnd(view: EditorView, delay: number) {
+ clearTimeout(view.input.composingTimeout)
+ if (delay > -1) view.input.composingTimeout = setTimeout(() => endComposition(view), delay)
+}
+
+export function clearComposition(view: EditorView) {
+ if (view.composing) {
+ view.input.composing = false
+ view.input.compositionEndedAt = timestampFromCustomEvent()
+ }
+ while (view.input.compositionNodes.length > 0) view.input.compositionNodes.pop()!.markParentsDirty()
+}
+
+export function findCompositionNode(view: EditorView) {
+ let sel = view.domSelectionRange()
+ if (!sel.focusNode) return null
+ let textBefore = textNodeBefore(sel.focusNode, sel.focusOffset)
+ let textAfter = textNodeAfter(sel.focusNode, sel.focusOffset)
+ if (textBefore && textAfter && textBefore != textAfter) {
+ let descAfter = textAfter.pmViewDesc, lastChanged = view.domObserver.lastChangedTextNode
+ if (textBefore == lastChanged || textAfter == lastChanged) return lastChanged
+ if (!descAfter || !descAfter.isText(textAfter.nodeValue!)) {
+ return textAfter
+ } else if (view.input.compositionNode == textAfter) {
+ let descBefore = textBefore.pmViewDesc
+ if (!(!descBefore || !descBefore.isText(textBefore.nodeValue!)))
+ return textAfter
+ }
+ }
+ return textBefore || textAfter
+}
+
+function timestampFromCustomEvent() {
+ let event = document.createEvent("Event")
+ event.initEvent("event", true, true)
+ return event.timeStamp
+}
+
+/// @internal
+export function endComposition(view: EditorView, restarting = false) {
+ if (browser.android && view.domObserver.flushingSoon >= 0) return
+ view.domObserver.forceFlush()
+ clearComposition(view)
+ if (restarting || view.docView && view.docView.dirty) {
+ let sel = selectionFromDOM(view), cur = view.state.selection
+ if (sel && !sel.eq(cur)) view.dispatch(view.state.tr.setSelection(sel))
+ else if ((view.markCursor || restarting) && !cur.$from.node(cur.$from.sharedDepth(cur.to)).inlineContent) view.dispatch(view.state.tr.deleteSelection())
+ else view.updateState(view.state)
+ return true
+ }
+ return false
+}
+
+function captureCopy(view: EditorView, dom: HTMLElement) {
+ // The extra wrapper is somehow necessary on IE/Edge to prevent the
+ // content from being mangled when it is put onto the clipboard
+ if (!view.dom.parentNode) return
+ let wrap = view.dom.parentNode.appendChild(document.createElement("div"))
+ wrap.appendChild(dom)
+ wrap.style.cssText = "position: fixed; left: -10000px; top: 10px"
+ let sel = getSelection()!, range = document.createRange()
+ range.selectNodeContents(dom)
+ // Done because IE will fire a selectionchange moving the selection
+ // to its start when removeAllRanges is called and the editor still
+ // has focus (which will mess up the editor's selection state).
+ view.dom.blur()
+ sel.removeAllRanges()
+ sel.addRange(range)
+ setTimeout(() => {
+ if (wrap.parentNode) wrap.parentNode.removeChild(wrap)
+ view.focus()
+ }, 50)
+}
+
+// This is very crude, but unfortunately both these browsers _pretend_
+// that they have a clipboard API—all the objects and methods are
+// there, they just don't work, and they are hard to test.
+const brokenClipboardAPI = (browser.ie && browser.ie_version < 15) ||
+ (browser.ios && browser.webkit_version < 604)
+
+handlers.copy = editHandlers.cut = (view, _event) => {
+ let event = _event as ClipboardEvent
+ let sel = view.state.selection, cut = event.type == "cut"
+ if (sel.empty) return
+
+ // IE and Edge's clipboard interface is completely broken
+ let data = brokenClipboardAPI ? null : event.clipboardData
+ let slice = sel.content(), {dom, text} = serializeForClipboard(view, slice)
+ if (data) {
+ event.preventDefault()
+ data.clearData()
+ data.setData("text/html", dom.innerHTML)
+ data.setData("text/plain", text)
+ } else {
+ captureCopy(view, dom)
+ }
+ if (cut) view.dispatch(view.state.tr.deleteSelection().scrollIntoView().setMeta("uiEvent", "cut"))
+}
+
+function sliceSingleNode(slice: Slice) {
+ return slice.openStart == 0 && slice.openEnd == 0 && slice.content.childCount == 1 ? slice.content.firstChild : null
+}
+
+function capturePaste(view: EditorView, event: ClipboardEvent) {
+ if (!view.dom.parentNode) return
+ let plainText = view.input.shiftKey || view.state.selection.$from.parent.type.spec.code
+ let target = view.dom.parentNode.appendChild(document.createElement(plainText ? "textarea" : "div"))
+ if (!plainText) target.contentEditable = "true"
+ target.style.cssText = "position: fixed; left: -10000px; top: 10px"
+ target.focus()
+ let plain = view.input.shiftKey && view.input.lastKeyCode != 45
+ setTimeout(() => {
+ view.focus()
+ if (target.parentNode) target.parentNode.removeChild(target)
+ if (plainText) doPaste(view, (target as HTMLTextAreaElement).value, null, plain, event)
+ else doPaste(view, target.textContent!, target.innerHTML, plain, event)
+ }, 50)
+}
+
+export function doPaste(view: EditorView, text: string, html: string | null, preferPlain: boolean, event: ClipboardEvent) {
+ let slice = parseFromClipboard(view, text, html, preferPlain, view.state.selection.$from)
+ if (view.someProp("handlePaste", f => f(view, event, slice || Slice.empty))) return true
+ if (!slice) return false
+
+ let singleNode = sliceSingleNode(slice)
+ let tr = singleNode
+ ? view.state.tr.replaceSelectionWith(singleNode, preferPlain)
+ : view.state.tr.replaceSelection(slice)
+ view.dispatch(tr.scrollIntoView().setMeta("paste", true).setMeta("uiEvent", "paste"))
+ return true
+}
+
+function getText(clipboardData: DataTransfer) {
+ let text = clipboardData.getData("text/plain") || clipboardData.getData("Text")
+ if (text) return text
+ let uris = clipboardData.getData("text/uri-list")
+ return uris ? uris.replace(/\r?\n/g, " ") : ""
+}
+
+editHandlers.paste = (view, _event) => {
+ let event = _event as ClipboardEvent
+ // Handling paste from JavaScript during composition is very poorly
+ // handled by browsers, so as a dodgy but preferable kludge, we just
+ // let the browser do its native thing there, except on Android,
+ // where the editor is almost always composing.
+ if (view.composing && !browser.android) return
+ let data = brokenClipboardAPI ? null : event.clipboardData
+ let plain = view.input.shiftKey && view.input.lastKeyCode != 45
+ if (data && doPaste(view, getText(data), data.getData("text/html"), plain, event))
+ event.preventDefault()
+ else
+ capturePaste(view, event)
+}
+
+export class Dragging {
+ constructor(readonly slice: Slice, readonly move: boolean, readonly node?: NodeSelection) {}
+}
+
+const dragCopyModifier: keyof DragEvent = browser.mac ? "altKey" : "ctrlKey"
+
+function dragMoves(view: EditorView, event: DragEvent) {
+ let moves = view.someProp("dragCopies", test => !test(event))
+ return moves != null ? moves : !event[dragCopyModifier]
+}
+
+handlers.dragstart = (view, _event) => {
+ let event = _event as DragEvent
+ let mouseDown = view.input.mouseDown
+ if (mouseDown) mouseDown.done()
+ if (!event.dataTransfer) return
+
+ let sel = view.state.selection
+ let pos = sel.empty ? null : view.posAtCoords(eventCoords(event))
+ let node: undefined | NodeSelection
+ if (pos && pos.pos >= sel.from && pos.pos <= (sel instanceof NodeSelection ? sel.to - 1: sel.to)) {
+ // In selection
+ } else if (mouseDown && mouseDown.mightDrag) {
+ node = NodeSelection.create(view.state.doc, mouseDown.mightDrag.pos)
+ } else if (event.target && (event.target as HTMLElement).nodeType == 1) {
+ let desc = view.docView.nearestDesc(event.target as HTMLElement, true)
+ if (desc && desc.node.type.spec.draggable && desc != view.docView)
+ node = NodeSelection.create(view.state.doc, desc.posBefore)
+ }
+ let draggedSlice = (node || view.state.selection).content()
+ let {dom, text, slice} = serializeForClipboard(view, draggedSlice)
+ // Pre-120 Chrome versions clear files when calling `clearData` (#1472)
+ if (!event.dataTransfer.files.length || !browser.chrome || browser.chrome_version > 120)
+ event.dataTransfer.clearData()
+ event.dataTransfer.setData(brokenClipboardAPI ? "Text" : "text/html", dom.innerHTML)
+ // See https://github.com/ProseMirror/prosemirror/issues/1156
+ event.dataTransfer.effectAllowed = "copyMove"
+ if (!brokenClipboardAPI) event.dataTransfer.setData("text/plain", text)
+ view.dragging = new Dragging(slice, dragMoves(view, event), node)
+}
+
+handlers.dragend = view => {
+ let dragging = view.dragging
+ window.setTimeout(() => {
+ if (view.dragging == dragging) view.dragging = null
+ }, 50)
+}
+
+editHandlers.dragover = editHandlers.dragenter = (_, e) => e.preventDefault()
+
+editHandlers.drop = (view, event) => {
+ try {
+ handleDrop(view, event as DragEvent, view.dragging)
+ } finally {
+ view.dragging = null
+ }
+}
+
+function handleDrop(view: EditorView, event: DragEvent, dragging: Dragging | null) {
+ if (!event.dataTransfer) return
+
+ let eventPos = view.posAtCoords(eventCoords(event))
+ if (!eventPos) return
+ let $mouse = view.state.doc.resolve(eventPos.pos)
+ let slice = dragging && dragging.slice
+ if (slice) {
+ view.someProp("transformPasted", f => { slice = f(slice!, view, false) })
+ } else {
+ slice = parseFromClipboard(view, getText(event.dataTransfer),
+ brokenClipboardAPI ? null : event.dataTransfer.getData("text/html"), false, $mouse)
+ }
+ let move = !!(dragging && dragMoves(view, event))
+ if (view.someProp("handleDrop", f => f(view, event, slice || Slice.empty, move))) {
+ event.preventDefault()
+ return
+ }
+ if (!slice) return
+
+ event.preventDefault()
+ let insertPos = slice ? dropPoint(view.state.doc, $mouse.pos, slice) : $mouse.pos
+ if (insertPos == null) insertPos = $mouse.pos
+
+ let tr = view.state.tr
+ if (move) {
+ let {node} = dragging as Dragging
+ if (node) node.replace(tr)
+ else tr.deleteSelection()
+ }
+
+ let pos = tr.mapping.map(insertPos)
+ let isNode = slice.openStart == 0 && slice.openEnd == 0 && slice.content.childCount == 1
+ let beforeInsert = tr.doc
+ if (isNode)
+ tr.replaceRangeWith(pos, pos, slice.content.firstChild!)
+ else
+ tr.replaceRange(pos, pos, slice)
+ if (tr.doc.eq(beforeInsert)) return
+
+ let $pos = tr.doc.resolve(pos)
+ if (isNode && NodeSelection.isSelectable(slice.content.firstChild!) &&
+ $pos.nodeAfter && $pos.nodeAfter.sameMarkup(slice.content.firstChild!)) {
+ tr.setSelection(new NodeSelection($pos))
+ } else {
+ let end = tr.mapping.map(insertPos)
+ tr.mapping.maps[tr.mapping.maps.length - 1].forEach((_from, _to, _newFrom, newTo) => end = newTo)
+ tr.setSelection(selectionBetween(view, $pos, tr.doc.resolve(end)))
+ }
+ view.focus()
+ view.dispatch(tr.setMeta("uiEvent", "drop"))
+}
+
+handlers.focus = view => {
+ view.input.lastFocus = Date.now()
+ if (!view.focused) {
+ view.domObserver.stop()
+ view.dom.classList.add("ProseMirror-focused")
+ view.domObserver.start()
+ view.focused = true
+ setTimeout(() => {
+ if (view.docView && view.hasFocus() && !view.domObserver.currentSelection.eq(view.domSelectionRange()))
+ selectionToDOM(view)
+ }, 20)
+ }
+}
+
+handlers.blur = (view, _event) => {
+ let event = _event as FocusEvent
+ if (view.focused) {
+ view.domObserver.stop()
+ view.dom.classList.remove("ProseMirror-focused")
+ view.domObserver.start()
+ if (event.relatedTarget && view.dom.contains(event.relatedTarget as HTMLElement))
+ view.domObserver.currentSelection.clear()
+ view.focused = false
+ }
+}
+
+handlers.beforeinput = (view, _event: Event) => {
+ let event = _event as InputEvent
+ // We should probably do more with beforeinput events, but support
+ // is so spotty that I'm still waiting to see where they are going.
+
+ // Very specific hack to deal with backspace sometimes failing on
+ // Chrome Android when after an uneditable node.
+ if (browser.chrome && browser.android && event.inputType == "deleteContentBackward") {
+ view.domObserver.flushSoon()
+ let {domChangeCount} = view.input
+ setTimeout(() => {
+ if (view.input.domChangeCount != domChangeCount) return // Event already had some effect
+ // This bug tends to close the virtual keyboard, so we refocus
+ view.dom.blur()
+ view.focus()
+ if (view.someProp("handleKeyDown", f => f(view, keyEvent(8, "Backspace")))) return
+ let {$cursor} = view.state.selection as TextSelection
+ // Crude approximation of backspace behavior when no command handled it
+ if ($cursor && $cursor.pos > 0) view.dispatch(view.state.tr.delete($cursor.pos - 1, $cursor.pos).scrollIntoView())
+ }, 50)
+ }
+}
+
+// Make sure all handlers get registered
+for (let prop in editHandlers) handlers[prop] = editHandlers[prop]
diff --git a/third_party/js/prosemirror/prosemirror-view/src/selection.ts b/third_party/js/prosemirror/prosemirror-view/src/selection.ts
@@ -0,0 +1,216 @@
+import {TextSelection, NodeSelection, Selection} from "prosemirror-state"
+import {ResolvedPos} from "prosemirror-model"
+
+import * as browser from "./browser"
+import {isEquivalentPosition, domIndex, isOnEdge, selectionCollapsed} from "./dom"
+import {EditorView} from "./index"
+import {NodeViewDesc} from "./viewdesc"
+
+export function selectionFromDOM(view: EditorView, origin: string | null = null) {
+ let domSel = view.domSelectionRange(), 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 as NodeViewDesc).node
+ if (nearestDesc && nearestDescNode.isAtom && NodeSelection.isSelectable(nearestDescNode) && nearestDesc.parent
+ && !(nearestDescNode.isInline && isOnEdge(domSel.focusNode, domSel.focusOffset, nearestDesc.dom))) {
+ let pos = nearestDesc.posBefore
+ selection = new NodeSelection(head == pos ? $head : doc.resolve(pos))
+ }
+ } else {
+ if (domSel instanceof view.dom.ownerDocument.defaultView!.Selection && domSel.rangeCount > 1) {
+ let min = head, max = head
+ for (let i = 0; i < domSel.rangeCount; i++) {
+ let range = domSel.getRangeAt(i)
+ min = Math.min(min, view.docView.posFromDOM(range.startContainer, range.startOffset, 1))
+ max = Math.max(max, view.docView.posFromDOM(range.endContainer, range.endOffset, -1))
+ }
+ if (min < 0) return null
+ ;[anchor, head] = max == view.state.selection.anchor ? [max, min] : [min, max]
+ $head = doc.resolve(head)
+ } else {
+ anchor = view.docView.posFromDOM(domSel.anchorNode!, domSel.anchorOffset, 1)
+ }
+ if (anchor < 0) return null
+ }
+ let $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: EditorView) {
+ return view.editable ? view.hasFocus() :
+ hasSelection(view) && document.activeElement && document.activeElement.contains(view.dom)
+}
+
+export function selectionToDOM(view: EditorView, 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 && browser.chrome) {
+ let domSel = view.domSelectionRange(), 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, 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 = browser.safari || browser.chrome && browser.chrome_version < 63
+
+function temporarilyEditableNear(view: EditorView, pos: number) {
+ 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 (browser.safari && after && (after as HTMLElement).contentEditable == "false") return setEditable(after as HTMLElement)
+ if ((!after || (after as HTMLElement).contentEditable == "false") &&
+ (!before || (before as HTMLElement).contentEditable == "false")) {
+ if (after) return setEditable(after as HTMLElement)
+ else if (before) return setEditable(before as HTMLElement)
+ }
+}
+
+function setEditable(element: HTMLElement) {
+ element.contentEditable = "true"
+ if (browser.safari && element.draggable) { element.draggable = false; (element as any).wasDraggable = true }
+ return element
+}
+
+function resetEditable(element: HTMLElement) {
+ element.contentEditable = "false"
+ if ((element as any).wasDraggable) { element.draggable = true; (element as any).wasDraggable = null }
+}
+
+function removeClassOnSelectionChange(view: EditorView) {
+ let doc = view.dom.ownerDocument
+ doc.removeEventListener("selectionchange", view.input.hideSelectionGuard!)
+ let domSel = view.domSelectionRange()
+ 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: EditorView) {
+ let domSel = view.domSelection()
+ if (!domSel) return
+ let node = view.cursorWrapper!.dom, img = node.nodeName == "IMG"
+ if (img) domSel.collapse(node.parentNode!, domIndex(node) + 1)
+ else domSel.collapse(node, 0)
+ // 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 && browser.ie && browser.ie_version <= 11) {
+ ;(node as any).disabled = true
+ ;(node as any).disabled = false
+ }
+}
+
+export function syncNodeSelection(view: EditorView, sel: Selection) {
+ if (sel instanceof NodeSelection) {
+ let desc = view.docView.descAt(sel.from)
+ if (desc != view.lastSelectedViewDesc) {
+ clearNodeSelection(view)
+ if (desc) (desc as NodeViewDesc).selectNode()
+ view.lastSelectedViewDesc = desc
+ }
+ } else {
+ clearNodeSelection(view)
+ }
+}
+
+// Clear all DOM statefulness of the last node selection.
+function clearNodeSelection(view: EditorView) {
+ if (view.lastSelectedViewDesc) {
+ if (view.lastSelectedViewDesc.parent)
+ (view.lastSelectedViewDesc as NodeViewDesc).deselectNode()
+ view.lastSelectedViewDesc = undefined
+ }
+}
+
+export function selectionBetween(view: EditorView, $anchor: ResolvedPos, $head: ResolvedPos, bias?: number) {
+ return view.someProp("createSelectionBetween", f => f(view, $anchor, $head))
+ || TextSelection.between($anchor, $head, bias)
+}
+
+export function hasFocusAndSelection(view: EditorView) {
+ if (view.editable && !view.hasFocus()) return false
+ return hasSelection(view)
+}
+
+export function hasSelection(view: EditorView) {
+ let sel = view.domSelectionRange()
+ 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
+ }
+}
+
+export function anchorInRightPlace(view: EditorView) {
+ let anchorDOM = view.docView.domFromPos(view.state.selection.anchor, 0)
+ let domSel = view.domSelectionRange()
+ return isEquivalentPosition(anchorDOM.node, anchorDOM.offset, domSel.anchorNode!, domSel.anchorOffset)
+}
diff --git a/third_party/js/prosemirror/prosemirror-view/src/viewdesc.ts b/third_party/js/prosemirror/prosemirror-view/src/viewdesc.ts
@@ -0,0 +1,1586 @@
+import {DOMSerializer, Fragment, Mark, Node, TagParseRule} from "prosemirror-model"
+import {TextSelection} from "prosemirror-state"
+
+import {domIndex, isEquivalentPosition, DOMNode} from "./dom"
+import * as browser from "./browser"
+import {Decoration, DecorationSource, WidgetConstructor, WidgetType, NodeType} from "./decoration"
+import {EditorView} from "./index"
+
+declare global {
+ interface Node {
+ /// @internal
+ pmViewDesc?: ViewDesc
+ }
+}
+
+/// A ViewMutationRecord represents a DOM
+/// [mutation](https://developer.mozilla.org/en-US/docs/Web/API/MutationObserver)
+/// or a selection change happens within the view. When the change is
+/// a selection change, the record will have a `type` property of
+/// `"selection"` (which doesn't occur for native mutation records).
+export type ViewMutationRecord = MutationRecord | { type: "selection", target: DOMNode }
+
+/// By default, document nodes are rendered using the result of the
+/// [`toDOM`](#model.NodeSpec.toDOM) method of their spec, and managed
+/// entirely by the editor. For some use cases, such as embedded
+/// node-specific editing interfaces, you want more control over
+/// the behavior of a node's in-editor representation, and need to
+/// [define](#view.EditorProps.nodeViews) a custom node view.
+///
+/// Objects returned as node views must conform to this interface.
+export interface NodeView {
+ /// The outer DOM node that represents the document node.
+ dom: DOMNode
+
+ /// The DOM node that should hold the node's content. Only meaningful
+ /// if the node view also defines a `dom` property and if its node
+ /// type is not a leaf node type. When this is present, ProseMirror
+ /// will take care of rendering the node's children into it. When it
+ /// is not present, the node view itself is responsible for rendering
+ /// (or deciding not to render) its child nodes.
+ contentDOM?: HTMLElement | null
+
+ /// When given, this will be called when the view is updating
+ /// itself. It will be given a node, an array of active decorations
+ /// around the node (which are automatically drawn, and the node
+ /// view may ignore if it isn't interested in them), and a
+ /// [decoration source](#view.DecorationSource) that represents any
+ /// decorations that apply to the content of the node (which again
+ /// may be ignored). It should return true if it was able to update
+ /// to that node, and false otherwise. If the node view has a
+ /// `contentDOM` property (or no `dom` property), updating its child
+ /// nodes will be handled by ProseMirror.
+ update?: (node: Node, decorations: readonly Decoration[], innerDecorations: DecorationSource) => boolean
+
+ /// By default, `update` will only be called when a node of the same
+ /// node type appears in this view's position. When you set this to
+ /// true, it will be called for any node, making it possible to have
+ /// a node view that representsmultiple types of nodes. You will
+ /// need to check the type of the nodes you get in `update` and
+ /// return `false` for types you cannot handle.
+ multiType?: boolean
+
+ /// Can be used to override the way the node's selected status (as a
+ /// node selection) is displayed.
+ selectNode?: () => void
+
+ /// When defining a `selectNode` method, you should also provide a
+ /// `deselectNode` method to remove the effect again.
+ deselectNode?: () => void
+
+ /// This will be called to handle setting the selection inside the
+ /// node. The `anchor` and `head` positions are relative to the start
+ /// of the node. By default, a DOM selection will be created between
+ /// the DOM positions corresponding to those positions, but if you
+ /// override it you can do something else.
+ setSelection?: (anchor: number, head: number, root: Document | ShadowRoot) => void
+
+ /// Can be used to prevent the editor view from trying to handle some
+ /// or all DOM events that bubble up from the node view. Events for
+ /// which this returns true are not handled by the editor.
+ stopEvent?: (event: Event) => boolean
+
+ /// Called when a [mutation](#view.ViewMutationRecord) happens within the
+ /// view. Return false if the editor should re-read the selection or re-parse
+ /// the range around the mutation, true if it can safely be ignored.
+ ignoreMutation?: (mutation: ViewMutationRecord) => boolean
+
+ /// Called when the node view is removed from the editor or the whole
+ /// editor is destroyed.
+ destroy?: () => void
+}
+
+/// By default, document marks are rendered using the result of the
+/// [`toDOM`](#model.MarkSpec.toDOM) method of their spec, and managed entirely
+/// by the editor. For some use cases, you want more control over the behavior
+/// of a mark's in-editor representation, and need to
+/// [define](#view.EditorProps.markViews) a custom mark view.
+///
+/// Objects returned as mark views must conform to this interface.
+export interface MarkView {
+ /// The outer DOM node that represents the document node.
+ dom: DOMNode
+
+ /// The DOM node that should hold the mark's content. When this is not
+ /// present, the `dom` property is used as the content DOM.
+ contentDOM?: HTMLElement | null
+
+ /// Called when a [mutation](#view.ViewMutationRecord) happens within the
+ /// view. Return false if the editor should re-read the selection or re-parse
+ /// the range around the mutation, true if it can safely be ignored.
+ ignoreMutation?: (mutation: ViewMutationRecord) => boolean
+
+
+ /// Called when the mark view is removed from the editor or the whole
+ /// editor is destroyed.
+ destroy?: () => void
+}
+
+// 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.
+export class ViewDesc {
+ dirty = NOT_DIRTY
+ declare node: Node | null
+
+ constructor(
+ public parent: ViewDesc | undefined,
+ public children: ViewDesc[],
+ public dom: DOMNode,
+ // This is the node that holds the child views. It may be null for
+ // descs that don't have children.
+ public contentDOM: HTMLElement | null
+ ) {
+ // 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: Decoration) { return false }
+ matchesMark(mark: Mark) { return false }
+ matchesNode(node: Node, outerDeco: readonly Decoration[], innerDeco: DecorationSource) { return false }
+ matchesHack(nodeName: string) { 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(): Omit<TagParseRule, "tag"> | null { return null }
+
+ // Used by the editor's event handler to ignore events that come
+ // from certain descs.
+ stopEvent(event: 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: ViewDesc): number {
+ 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: DOMNode, offset: number, bias: number): number {
+ // 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: ViewDesc | undefined
+ 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: ViewDesc | undefined
+ 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: DOMNode): ViewDesc | undefined
+ nearestDesc(dom: DOMNode, onlyNodes: true): NodeViewDesc | undefined
+ nearestDesc(dom: DOMNode, onlyNodes: boolean = false) {
+ for (let first = true, cur: DOMNode | null = 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 as NodeViewDesc).nodeDOM) &&
+ !(nodeDOM.nodeType == 1 ? nodeDOM.contains(dom.nodeType == 1 ? dom : dom.parentNode) : nodeDOM == dom))
+ first = false
+ else
+ return desc
+ }
+ }
+ }
+
+ getDesc(dom: DOMNode) {
+ let desc = dom.pmViewDesc
+ for (let cur: ViewDesc | undefined = desc; cur; cur = cur.parent) if (cur == this) return desc
+ }
+
+ posFromDOM(dom: DOMNode, offset: number, bias: number) {
+ for (let scan: DOMNode | null = 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: number): ViewDesc | undefined {
+ 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) {
+ for (let i = 0; i < child.children.length; i++) {
+ let inner = child.children[i]
+ if (inner.size) { child = inner; break }
+ }
+ }
+ return child
+ }
+ if (pos < end) return child.descAt(pos - offset - child.border)
+ offset = end
+ }
+ }
+
+ domFromPos(pos: number, side: number): {node: DOMNode, offset: number, atom?: number} {
+ 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: number, to: number, base = 0
+ ): {node: DOMNode, from: number, to: number, fromOffset: number, toOffset: number} {
+ 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: number): boolean {
+ 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: number): DOMNode {
+ 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: number, head: number, view: EditorView, force = false): void {
+ // 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, view, force)
+ offset = end
+ }
+
+ let anchorDOM = this.domFromPos(anchor, anchor ? -1 : 1)
+ let headDOM = head == anchor ? anchorDOM : this.domFromPos(head, head ? -1 : 1)
+ let domSel = (view.root as Document).getSelection()!
+ let selRange = view.domSelectionRange()
+
+ 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 ((browser.gecko || browser.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: DOMNode | null = 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 as HTMLElement).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 (browser.gecko && selRange.focusNode && selRange.focusNode != headDOM.node && selRange.focusNode.nodeType == 1) {
+ let after = selRange.focusNode.childNodes[selRange.focusOffset]
+ if (after && (after as HTMLElement).contentEditable == "false") force = true
+ }
+
+ if (!(force || brKludge && browser.safari) &&
+ isEquivalentPosition(anchorDOM.node, anchorDOM.offset, selRange.anchorNode!, selRange.anchorOffset) &&
+ isEquivalentPosition(headDOM.node, headDOM.offset, selRange.focusNode!, selRange.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 && browser.gecko)) {
+ domSel.collapse(anchorDOM.node, anchorDOM.offset)
+ try {
+ if (anchor != head)
+ domSel.extend(headDOM.node, headDOM.offset)
+ domSelExtended = true
+ } catch (_) {
+ // 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.
+ // Similarly, this could crash on Safari if the editor is hidden, and
+ // there was no selection.
+ }
+ }
+ 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: ViewMutationRecord): boolean {
+ 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: number, to: number) {
+ 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 }
+
+ get ignoreForSelection() { return false }
+
+ isText(text: string) { 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: ViewDesc, readonly widget: Decoration, view: EditorView, pos: number) {
+ let self: WidgetViewDesc, dom = (widget.type as any).toDOM as WidgetConstructor
+ 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 as HTMLElement).contentEditable = "false"
+ ;(dom as HTMLElement).classList.add("ProseMirror-widget")
+ }
+ super(parent, [], dom, null)
+ this.widget = widget
+ self = this
+ }
+
+ matchesWidget(widget: Decoration) {
+ return this.dirty == NOT_DIRTY && widget.type.eq(this.widget.type)
+ }
+
+ parseRule() { return {ignore: true} }
+
+ stopEvent(event: Event) {
+ let stop = this.widget.spec.stopEvent
+ return stop ? stop(event) : false
+ }
+
+ ignoreMutation(mutation: ViewMutationRecord) {
+ return mutation.type != "selection" || this.widget.spec.ignoreSelection
+ }
+
+ destroy() {
+ this.widget.type.destroy(this.dom)
+ super.destroy()
+ }
+
+ get domAtom() { return true }
+
+ get ignoreForSelection() { return !!this.widget.type.spec.relaxedSide }
+
+ get side() { return (this.widget.type as any).side as number }
+}
+
+class CompositionViewDesc extends ViewDesc {
+ constructor(parent: ViewDesc, dom: DOMNode, readonly textDOM: Text, readonly text: string) {
+ super(parent, [], dom, null)
+ }
+
+ get size() { return this.text.length }
+
+ localPosFromDOM(dom: DOMNode, offset: number) {
+ if (dom != this.textDOM) return this.posAtStart + (offset ? this.size : 0)
+ return this.posAtStart + offset
+ }
+
+ domFromPos(pos: number) {
+ return {node: this.textDOM, offset: pos}
+ }
+
+ ignoreMutation(mut: ViewMutationRecord) {
+ 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: ViewDesc, readonly mark: Mark, dom: DOMNode, contentDOM: HTMLElement, readonly spec: MarkView) {
+ super(parent, [], dom, contentDOM)
+ }
+
+ static create(parent: ViewDesc, mark: Mark, inline: boolean, view: EditorView) {
+ let custom = view.nodeViews[mark.type.name]
+ let spec: {dom: HTMLElement, contentDOM?: HTMLElement} = custom && (custom as any)(mark, view, inline)
+ if (!spec || !spec.dom)
+ spec = (DOMSerializer.renderSpec as any)(document, mark.type.spec.toDOM!(mark, inline), null, mark.attrs) as any
+ return new MarkViewDesc(parent, mark, spec.dom, spec.contentDOM || spec.dom as HTMLElement, spec)
+ }
+
+ 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!}
+ }
+
+ matchesMark(mark: Mark) { return this.dirty != NODE_DIRTY && this.mark.eq(mark) }
+
+ markDirty(from: number, to: number) {
+ 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: number, to: number, view: EditorView) {
+ 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
+ }
+
+ ignoreMutation(mutation: ViewMutationRecord) {
+ return this.spec.ignoreMutation ? this.spec.ignoreMutation(mutation) : super.ignoreMutation(mutation)
+ }
+
+ destroy() {
+ if (this.spec.destroy) this.spec.destroy()
+ super.destroy()
+ }
+}
+
+// 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.
+export class NodeViewDesc extends ViewDesc {
+ constructor(
+ parent: ViewDesc | undefined,
+ public node: Node,
+ public outerDeco: readonly Decoration[],
+ public innerDeco: DecorationSource,
+ dom: DOMNode,
+ contentDOM: HTMLElement | null,
+ readonly nodeDOM: DOMNode,
+ view: EditorView,
+ pos: number
+ ) {
+ super(parent, [], dom, contentDOM)
+ }
+
+ // 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: ViewDesc | undefined, node: Node, outerDeco: readonly Decoration[],
+ innerDeco: DecorationSource, view: EditorView, pos: number) {
+ let custom = view.nodeViews[node.type.name], descObj: ViewDesc
+ let spec: NodeView | undefined = custom && (custom as any)(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) {
+ let spec = (DOMSerializer.renderSpec as any)(document, node.type.spec.toDOM!(node), null, node.attrs)
+ ;({dom, contentDOM} = spec as {dom: DOMNode, contentDOM?: HTMLElement})
+ }
+ if (!contentDOM && !node.isText && dom.nodeName != "BR") { // Chrome gets confused by <br contenteditable=false>
+ if (!(dom as HTMLElement).hasAttribute("contenteditable")) (dom as HTMLElement).contentEditable = "false"
+ if (node.type.spec.draggable) (dom as HTMLElement).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: Omit<TagParseRule, "tag"> = {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 as HTMLElement
+ break
+ }
+ }
+ if (!rule.contentElement) rule.getContent = () => Fragment.empty
+ }
+ return rule
+ }
+
+ matchesNode(node: Node, outerDeco: readonly Decoration[], innerDeco: DecorationSource) {
+ 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: EditorView, pos: number) {
+ let inline = this.node.inlineContent, off = pos
+ let composition = view.composing ? this.localCompositionInfo(view, pos) : null
+ let localComposition = composition && composition.pos > -1 ? composition : null
+ let compositionInChild = composition && composition.pos < 0
+ let updater = new ViewTreeUpdater(this, localComposition && localComposition.node, view)
+ iterDeco(this.node, this.innerDeco, (widget, i, insideNode) => {
+ if (widget.spec.marks)
+ updater.syncToMarks(widget.spec.marks, inline, view)
+ else if ((widget.type as WidgetType).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)) {
+ // Found precise match with existing node view
+ } 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)) {
+ // Updated the specific node that holds the composition
+ } else if (updater.updateNextNode(child, outerDeco, innerDeco, view, i, off)) {
+ // Could update an existing node to reflect this node
+ } 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 (browser.ios) iosHacks(this.dom as HTMLElement)
+ }
+ }
+
+ localCompositionInfo(view: EditorView, pos: number): {node: Text, pos: number, text: string} | null {
+ // 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 textNode = view.input.compositionNode
+ 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: EditorView, {node, pos, text}: {node: Text, pos: number, text: string}) {
+ // 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: DOMNode = 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: Node, outerDeco: readonly Decoration[], innerDeco: DecorationSource, view: EditorView) {
+ if (this.dirty == NODE_DIRTY ||
+ !node.sameMarkup(this.node)) return false
+ this.updateInner(node, outerDeco, innerDeco, view)
+ return true
+ }
+
+ updateInner(node: Node, outerDeco: readonly Decoration[], innerDeco: DecorationSource, view: EditorView) {
+ this.updateOuterDeco(outerDeco)
+ this.node = node
+ this.innerDeco = innerDeco
+ if (this.contentDOM) this.updateChildren(view, this.posAtStart)
+ this.dirty = NOT_DIRTY
+ }
+
+ updateOuterDeco(outerDeco: readonly Decoration[]) {
+ 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 as HTMLElement).classList.add("ProseMirror-selectednode")
+ if (this.contentDOM || !this.node.type.spec.draggable) (this.nodeDOM as HTMLElement).draggable = true
+ }
+ }
+
+ // Remove selected node marking from this node.
+ deselectNode() {
+ if (this.nodeDOM.nodeType == 1) {
+ ;(this.nodeDOM as HTMLElement).classList.remove("ProseMirror-selectednode")
+ if (this.contentDOM || !this.node.type.spec.draggable) (this.nodeDOM as HTMLElement).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.
+export function docViewDesc(doc: Node, outerDeco: readonly Decoration[], innerDeco: DecorationSource,
+ dom: HTMLElement, view: EditorView): NodeViewDesc {
+ applyOuterDeco(dom, outerDeco, doc)
+ let docView = new NodeViewDesc(undefined, doc, outerDeco, innerDeco, dom, dom, dom, view, 0)
+ if (docView.contentDOM) docView.updateChildren(view, 0)
+ return docView
+}
+
+class TextViewDesc extends NodeViewDesc {
+ constructor(parent: ViewDesc | undefined, node: Node, outerDeco: readonly Decoration[],
+ innerDeco: DecorationSource, dom: DOMNode, nodeDOM: DOMNode, view: EditorView) {
+ super(parent, node, outerDeco, innerDeco, dom, null, nodeDOM, view, 0)
+ }
+
+ parseRule() {
+ let skip = this.nodeDOM.parentNode
+ while (skip && skip != this.dom && !(skip as any).pmIsDeco) skip = skip.parentNode
+ return {skip: (skip || true) as any}
+ }
+
+ update(node: Node, outerDeco: readonly Decoration[], innerDeco: DecorationSource, view: EditorView) {
+ 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: DOMNode | null = this.nodeDOM; n; n = n.parentNode) if (n == parentDOM) return true
+ return false
+ }
+
+ domFromPos(pos: number) {
+ return {node: this.nodeDOM, offset: pos}
+ }
+
+ localPosFromDOM(dom: DOMNode, offset: number, bias: number) {
+ if (dom == this.nodeDOM) return this.posAtStart + Math.min(offset, this.node.text!.length)
+ return super.localPosFromDOM(dom, offset, bias)
+ }
+
+ ignoreMutation(mutation: ViewMutationRecord) {
+ return mutation.type != "characterData" && mutation.type != "selection"
+ }
+
+ slice(from: number, to: number, view: EditorView) {
+ 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: number, to: number) {
+ super.markDirty(from, to)
+ if (this.dom != this.nodeDOM && (from == 0 || to == this.nodeDOM.nodeValue!.length))
+ this.dirty = NODE_DIRTY
+ }
+
+ get domAtom() { return false }
+
+ isText(text: string) { return this.node.text == text }
+}
+
+// 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: string) { 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: ViewDesc | undefined, node: Node, outerDeco: readonly Decoration[], innerDeco: DecorationSource,
+ dom: DOMNode, contentDOM: HTMLElement | null, nodeDOM: DOMNode, readonly spec: NodeView,
+ view: EditorView, pos: number) {
+ super(parent, node, outerDeco, innerDeco, dom, contentDOM, nodeDOM, view, pos)
+ }
+
+ // 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: Node, outerDeco: readonly Decoration[], innerDeco: DecorationSource, view: EditorView) {
+ if (this.dirty == NODE_DIRTY) return false
+ if (this.spec.update && (this.node.type == node.type || this.spec.multiType)) {
+ 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: number, head: number, view: EditorView, force: boolean) {
+ this.spec.setSelection ? this.spec.setSelection(anchor, head, view.root)
+ : super.setSelection(anchor, head, view, force)
+ }
+
+ destroy() {
+ if (this.spec.destroy) this.spec.destroy()
+ super.destroy()
+ }
+
+ stopEvent(event: Event) {
+ return this.spec.stopEvent ? this.spec.stopEvent(event) : false
+ }
+
+ ignoreMutation(mutation: ViewMutationRecord) {
+ 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: HTMLElement, descs: readonly ViewDesc[], view: EditorView) {
+ 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
+}
+
+type OuterDecoLevel = {[attr: string]: string}
+
+const OuterDecoLevel: {new (nodeName?: string): OuterDecoLevel} = function(this: any, nodeName?: string) {
+ if (nodeName) this.nodeName = nodeName
+} as any
+OuterDecoLevel.prototype = Object.create(null)
+
+const noDeco = [new OuterDecoLevel]
+
+function computeOuterDeco(outerDeco: readonly Decoration[], node: Node, needsWrap: boolean) {
+ 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 as NodeType).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: DOMNode, nodeDOM: DOMNode,
+ prevComputed: readonly OuterDecoLevel[], curComputed: readonly OuterDecoLevel[]) {
+ // 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: DOMNode | null
+ 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 as any).pmIsDeco = true
+ parent.appendChild(curDOM)
+ prev = noDeco[0]
+ curDOM = parent
+ }
+ }
+ patchAttributes(curDOM as HTMLElement, prev || noDeco[0], deco)
+ }
+ return curDOM
+}
+
+function patchAttributes(dom: HTMLElement, prev: {[name: string]: string}, cur: {[name: string]: string}) {
+ 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: DOMNode, deco: readonly Decoration[], node: Node) {
+ return patchOuterDeco(dom, dom, noDeco, computeOuterDeco(deco, node, dom.nodeType != 1))
+}
+
+function sameOuterDeco(a: readonly Decoration[], b: readonly Decoration[]) {
+ 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: DOMNode) {
+ 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 {
+ // Index into `this.top`'s child array, represents the current
+ // update position.
+ index = 0
+ // When entering a mark, the current top and index are pushed
+ // onto this.
+ stack: (ViewDesc | number)[] = []
+ // Tracks whether anything was changed
+ changed = false
+ preMatch: {index: number, matched: Map<ViewDesc, number>, matches: readonly ViewDesc[]}
+ top: ViewDesc
+
+ constructor(top: NodeViewDesc, readonly lock: DOMNode | null, private readonly view: EditorView) {
+ this.top = top
+ this.preMatch = preMatch(top.node.content, top)
+ }
+
+ // Destroy and remove the children between the given indices in
+ // `this.top`.
+ destroyBetween(start: number, end: number) {
+ 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: readonly Mark[], inline: boolean, view: EditorView) {
+ 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] as ViewDesc)
+ .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() as number
+ this.top = this.stack.pop() as ViewDesc
+ 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++) {
+ let next = this.top.children[i]
+ if (next.matchesMark(marks[depth]) && !this.isLocked(next.dom)) { 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: Node, outerDeco: readonly Decoration[], innerDeco: DecorationSource, index: number): boolean {
+ 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: Node, outerDeco: readonly Decoration[], innerDeco: DecorationSource, index: number, view: EditorView) {
+ let child = this.top.children[index] as NodeViewDesc
+ 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: 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: Node, outerDeco: readonly Decoration[], innerDeco: DecorationSource,
+ view: EditorView, index: number, pos: number): boolean {
+ 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, updated
+
+ // 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.isLocked(nextDOM) &&
+ !(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
+ } else if (!locked && (updated = this.recreateWrapper(next, node, outerDeco, innerDeco, view, pos))) {
+ this.destroyBetween(this.index, i)
+ this.top.children[this.index] = updated
+ if (updated.contentDOM) {
+ updated.dirty = CONTENT_DIRTY
+ updated.updateChildren(view, pos + 1)
+ updated.dirty = NOT_DIRTY
+ }
+ this.changed = true
+ this.index++
+ return true
+ }
+ break
+ }
+ }
+ return false
+ }
+
+ // When a node with content is replaced by a different node with
+ // identical content, move over its children.
+ recreateWrapper(next: NodeViewDesc, node: Node, outerDeco: readonly Decoration[], innerDeco: DecorationSource,
+ view: EditorView, pos: number) {
+ if (next.dirty || node.isAtom || !next.children.length ||
+ !next.node.content.eq(node.content) ||
+ !sameOuterDeco(outerDeco, next.outerDeco) || !innerDeco.eq(next.innerDeco)) return null
+ let wrapper = NodeViewDesc.create(this.top, node, outerDeco, innerDeco, view, pos)
+ if (wrapper.contentDOM) {
+ wrapper.children = next.children
+ next.children = []
+ for (let ch of wrapper.children) ch.parent = wrapper
+ }
+ next.destroy()
+ return wrapper
+ }
+
+ // Insert the node as a newly created node desc.
+ addNode(node: Node, outerDeco: readonly Decoration[], innerDeco: DecorationSource, view: EditorView, pos: number) {
+ let desc = NodeViewDesc.create(this.top, node, outerDeco, innerDeco, view, pos)
+ if (desc.contentDOM) desc.updateChildren(view, pos + 1)
+ this.top.children.splice(this.index++, 0, desc)
+ this.changed = true
+ }
+
+ placeWidget(widget: Decoration, view: EditorView, pos: number) {
+ let next = this.index < this.top.children.length ? this.top.children[this.index] : null
+ if (next && next.matchesWidget(widget) &&
+ (widget == (next as WidgetViewDesc).widget || !(next as any).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!) ||
+ (this.view.requiresGeckoHackNode && /\s$/.test(lastChild.node.text!))) {
+ // Avoid bugs in Safari's cursor drawing (#1165) and Chrome's mouse selection (#1152)
+ if ((browser.safari || browser.chrome) && lastChild && (lastChild.dom as HTMLElement).contentEditable == "false")
+ this.addHackNode("IMG", parent)
+ this.addHackNode("BR", this.top)
+ }
+ }
+
+ addHackNode(nodeName: string, parent: ViewDesc) {
+ 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 as HTMLImageElement).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
+ }
+ }
+
+ isLocked(node: DOMNode) {
+ return this.lock && (node == this.lock || node.nodeType == 1 && node.contains(this.lock.parentNode))
+ }
+}
+
+// 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: Fragment, parentDesc: ViewDesc
+): {index: number, matched: Map<ViewDesc, number>, matches: readonly ViewDesc[]} {
+ 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: Decoration, b: Decoration) {
+ return (a.type as WidgetType).side - (b.type as WidgetType).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: Node,
+ deco: DecorationSource,
+ onWidget: (widget: Decoration, index: number, insideNode: boolean) => void,
+ onNode: (node: Node, outerDeco: readonly Decoration[], innerDeco: DecorationSource, index: number) => void
+) {
+ 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;;) {
+ let widget, widgets
+ while (decoIndex < locals.length && locals[decoIndex].to == offset) {
+ let next = locals[decoIndex++]
+ if (next.widget) {
+ if (!widget) widget = next
+ else (widgets || (widgets = [widget])).push(next)
+ }
+ }
+ if (widget) {
+ 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
+ }
+ } else {
+ while (decoIndex < locals.length && locals[decoIndex].to < end) decoIndex++
+ }
+
+ 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: HTMLElement) {
+ 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
+ }
+}
+
+// Find a piece of text in an inline fragment, overlapping from-to
+function findTextInFragment(frag: Fragment, text: string, from: number, to: number) {
+ 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) {
+ if (pos >= to && str.slice(to - text.length - childStart, to - childStart) == text)
+ return to - text.length
+ 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: readonly ViewDesc[], from: number, to: number, view: EditorView, replacement?: ViewDesc) {
+ 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 as MarkViewDesc | TextViewDesc).slice(0, from - start, view))
+ if (replacement) {
+ result.push(replacement)
+ replacement = undefined
+ }
+ if (end > to) result.push((child as MarkViewDesc | TextViewDesc).slice(to - start, child.size, view))
+ }
+ }
+ return result
+}
diff --git a/third_party/js/prosemirror/prosemirror-view/style/prosemirror.css b/third_party/js/prosemirror/prosemirror-view/style/prosemirror.css
@@ -0,0 +1,54 @@
+.ProseMirror {
+ position: relative;
+}
+
+.ProseMirror {
+ word-wrap: break-word;
+ white-space: pre-wrap;
+ white-space: break-spaces;
+ -webkit-font-variant-ligatures: none;
+ font-variant-ligatures: none;
+ font-feature-settings: "liga" 0; /* the above doesn't seem to work in Edge */
+}
+
+.ProseMirror pre {
+ white-space: pre-wrap;
+}
+
+.ProseMirror li {
+ position: relative;
+}
+
+.ProseMirror-hideselection *::selection { background: transparent; }
+.ProseMirror-hideselection *::-moz-selection { background: transparent; }
+.ProseMirror-hideselection { caret-color: transparent; }
+
+/* See https://github.com/ProseMirror/prosemirror/issues/1421#issuecomment-1759320191 */
+.ProseMirror [draggable][contenteditable=false] { user-select: text }
+
+.ProseMirror-selectednode {
+ outline: 2px solid #8cf;
+}
+
+/* Make sure li selections wrap around markers */
+
+li.ProseMirror-selectednode {
+ outline: none;
+}
+
+li.ProseMirror-selectednode:after {
+ content: "";
+ position: absolute;
+ left: -32px;
+ right: -2px; top: -2px; bottom: -2px;
+ border: 2px solid #8cf;
+ pointer-events: none;
+}
+
+/* Protect against generic img rules */
+
+img.ProseMirror-separator {
+ display: inline !important;
+ border: none !important;
+ margin: 0 !important;
+}
diff --git a/third_party/js/prosemirror/prosemirror.bundle.mjs b/third_party/js/prosemirror/prosemirror.bundle.mjs
@@ -0,0 +1 @@
+function e(e){this.content=e}function t(e,n,r){for(let i=0;;i++){if(i==e.childCount||i==n.childCount)return e.childCount==n.childCount?null:r;let o=e.child(i),s=n.child(i);if(o!=s){if(!o.sameMarkup(s))return r;if(o.isText&&o.text!=s.text){for(let e=0;o.text[e]==s.text[e];e++)r++;return r}if(o.content.size||s.content.size){let e=t(o.content,s.content,r+1);if(null!=e)return e}r+=o.nodeSize}else r+=o.nodeSize}}function n(e,t,r,i){for(let o=e.childCount,s=t.childCount;;){if(0==o||0==s)return o==s?null:{a:r,b:i};let l=e.child(--o),a=t.child(--s),c=l.nodeSize;if(l!=a){if(!l.sameMarkup(a))return{a:r,b:i};if(l.isText&&l.text!=a.text){let e=0,t=Math.min(l.text.length,a.text.length);for(;e<t&&l.text[l.text.length-e-1]==a.text[a.text.length-e-1];)e++,r--,i--;return{a:r,b:i}}if(l.content.size||a.content.size){let e=n(l.content,a.content,r-1,i-1);if(e)return e}r-=c,i-=c}else r-=c,i-=c}}e.prototype={constructor:e,find:function(e){for(var t=0;t<this.content.length;t+=2)if(this.content[t]===e)return t;return-1},get:function(e){var t=this.find(e);return-1==t?void 0:this.content[t+1]},update:function(t,n,r){var i=r&&r!=t?this.remove(r):this,o=i.find(t),s=i.content.slice();return-1==o?s.push(r||t,n):(s[o+1]=n,r&&(s[o]=r)),new e(s)},remove:function(t){var n=this.find(t);if(-1==n)return this;var r=this.content.slice();return r.splice(n,2),new e(r)},addToStart:function(t,n){return new e([t,n].concat(this.remove(t).content))},addToEnd:function(t,n){var r=this.remove(t).content.slice();return r.push(t,n),new e(r)},addBefore:function(t,n,r){var i=this.remove(n),o=i.content.slice(),s=i.find(t);return o.splice(-1==s?o.length:s,0,n,r),new e(o)},forEach:function(e){for(var t=0;t<this.content.length;t+=2)e(this.content[t],this.content[t+1])},prepend:function(t){return(t=e.from(t)).size?new e(t.content.concat(this.subtract(t).content)):this},append:function(t){return(t=e.from(t)).size?new e(this.subtract(t).content.concat(t.content)):this},subtract:function(t){var n=this;t=e.from(t);for(var r=0;r<t.content.length;r+=2)n=n.remove(t.content[r]);return n},toObject:function(){var e={};return this.forEach(function(t,n){e[t]=n}),e},get size(){return this.content.length>>1}},e.from=function(t){if(t instanceof e)return t;var n=[];if(t)for(var r in t)n.push(r,t[r]);return new e(n)};class r{constructor(e,t){if(this.content=e,this.size=t||0,null==t)for(let t=0;t<e.length;t++)this.size+=e[t].nodeSize}nodesBetween(e,t,n,r=0,i){for(let o=0,s=0;s<t;o++){let l=this.content[o],a=s+l.nodeSize;if(a>e&&!1!==n(l,r+s,i||null,o)&&l.content.size){let i=s+1;l.nodesBetween(Math.max(0,e-i),Math.min(l.content.size,t-i),n,r+i)}s=a}}descendants(e){this.nodesBetween(0,this.size,e)}textBetween(e,t,n,r){let i="",o=!0;return this.nodesBetween(e,t,(s,l)=>{let a=s.isText?s.text.slice(Math.max(e,l)-l,t-l):s.isLeaf?r?"function"==typeof r?r(s):r:s.type.spec.leafText?s.type.spec.leafText(s):"":"";s.isBlock&&(s.isLeaf&&a||s.isTextblock)&&n&&(o?o=!1:i+=n),i+=a},0),i}append(e){if(!e.size)return this;if(!this.size)return e;let t=this.lastChild,n=e.firstChild,i=this.content.slice(),o=0;for(t.isText&&t.sameMarkup(n)&&(i[i.length-1]=t.withText(t.text+n.text),o=1);o<e.content.length;o++)i.push(e.content[o]);return new r(i,this.size+e.size)}cut(e,t=this.size){if(0==e&&t==this.size)return this;let n=[],i=0;if(t>e)for(let r=0,o=0;o<t;r++){let s=this.content[r],l=o+s.nodeSize;l>e&&((o<e||l>t)&&(s=s.isText?s.cut(Math.max(0,e-o),Math.min(s.text.length,t-o)):s.cut(Math.max(0,e-o-1),Math.min(s.content.size,t-o-1))),n.push(s),i+=s.nodeSize),o=l}return new r(n,i)}cutByIndex(e,t){return e==t?r.empty:0==e&&t==this.content.length?this:new r(this.content.slice(e,t))}replaceChild(e,t){let n=this.content[e];if(n==t)return this;let i=this.content.slice(),o=this.size+t.nodeSize-n.nodeSize;return i[e]=t,new r(i,o)}addToStart(e){return new r([e].concat(this.content),this.size+e.nodeSize)}addToEnd(e){return new r(this.content.concat(e),this.size+e.nodeSize)}eq(e){if(this.content.length!=e.content.length)return!1;for(let t=0;t<this.content.length;t++)if(!this.content[t].eq(e.content[t]))return!1;return!0}get firstChild(){return this.content.length?this.content[0]:null}get lastChild(){return this.content.length?this.content[this.content.length-1]:null}get childCount(){return this.content.length}child(e){let t=this.content[e];if(!t)throw new RangeError("Index "+e+" out of range for "+this);return t}maybeChild(e){return this.content[e]||null}forEach(e){for(let t=0,n=0;t<this.content.length;t++){let r=this.content[t];e(r,n,t),n+=r.nodeSize}}findDiffStart(e,n=0){return t(this,e,n)}findDiffEnd(e,t=this.size,r=e.size){return n(this,e,t,r)}findIndex(e){if(0==e)return o(0,e);if(e==this.size)return o(this.content.length,e);if(e>this.size||e<0)throw new RangeError(`Position ${e} outside of fragment (${this})`);for(let t=0,n=0;;t++){let r=n+this.child(t).nodeSize;if(r>=e)return r==e?o(t+1,r):o(t,n);n=r}}toString(){return"<"+this.toStringInner()+">"}toStringInner(){return this.content.join(", ")}toJSON(){return this.content.length?this.content.map(e=>e.toJSON()):null}static fromJSON(e,t){if(!t)return r.empty;if(!Array.isArray(t))throw new RangeError("Invalid input for Fragment.fromJSON");return new r(t.map(e.nodeFromJSON))}static fromArray(e){if(!e.length)return r.empty;let t,n=0;for(let r=0;r<e.length;r++){let i=e[r];n+=i.nodeSize,r&&i.isText&&e[r-1].sameMarkup(i)?(t||(t=e.slice(0,r)),t[t.length-1]=i.withText(t[t.length-1].text+i.text)):t&&t.push(i)}return new r(t||e,n)}static from(e){if(!e)return r.empty;if(e instanceof r)return e;if(Array.isArray(e))return this.fromArray(e);if(e.attrs)return new r([e],e.nodeSize);throw new RangeError("Can not convert "+e+" to a Fragment"+(e.nodesBetween?" (looks like multiple versions of prosemirror-model were loaded)":""))}}r.empty=new r([],0);const i={index:0,offset:0};function o(e,t){return i.index=e,i.offset=t,i}function s(e,t){if(e===t)return!0;if(!e||"object"!=typeof e||!t||"object"!=typeof t)return!1;let n=Array.isArray(e);if(Array.isArray(t)!=n)return!1;if(n){if(e.length!=t.length)return!1;for(let n=0;n<e.length;n++)if(!s(e[n],t[n]))return!1}else{for(let n in e)if(!(n in t)||!s(e[n],t[n]))return!1;for(let n in t)if(!(n in e))return!1}return!0}class l{constructor(e,t){this.type=e,this.attrs=t}addToSet(e){let t,n=!1;for(let r=0;r<e.length;r++){let i=e[r];if(this.eq(i))return e;if(this.type.excludes(i.type))t||(t=e.slice(0,r));else{if(i.type.excludes(this.type))return e;!n&&i.type.rank>this.type.rank&&(t||(t=e.slice(0,r)),t.push(this),n=!0),t&&t.push(i)}}return t||(t=e.slice()),n||t.push(this),t}removeFromSet(e){for(let t=0;t<e.length;t++)if(this.eq(e[t]))return e.slice(0,t).concat(e.slice(t+1));return e}isInSet(e){for(let t=0;t<e.length;t++)if(this.eq(e[t]))return!0;return!1}eq(e){return this==e||this.type==e.type&&s(this.attrs,e.attrs)}toJSON(){let e={type:this.type.name};for(let t in this.attrs){e.attrs=this.attrs;break}return e}static fromJSON(e,t){if(!t)throw new RangeError("Invalid input for Mark.fromJSON");let n=e.marks[t.type];if(!n)throw new RangeError(`There is no mark type ${t.type} in this schema`);let r=n.create(t.attrs);return n.checkAttrs(r.attrs),r}static sameSet(e,t){if(e==t)return!0;if(e.length!=t.length)return!1;for(let n=0;n<e.length;n++)if(!e[n].eq(t[n]))return!1;return!0}static setFrom(e){if(!e||Array.isArray(e)&&0==e.length)return l.none;if(e instanceof l)return[e];let t=e.slice();return t.sort((e,t)=>e.type.rank-t.type.rank),t}}l.none=[];class a extends Error{}class c{constructor(e,t,n){this.content=e,this.openStart=t,this.openEnd=n}get size(){return this.content.size-this.openStart-this.openEnd}insertAt(e,t){let n=u(this.content,e+this.openStart,t);return n&&new c(n,this.openStart,this.openEnd)}removeBetween(e,t){return new c(h(this.content,e+this.openStart,t+this.openStart),this.openStart,this.openEnd)}eq(e){return this.content.eq(e.content)&&this.openStart==e.openStart&&this.openEnd==e.openEnd}toString(){return this.content+"("+this.openStart+","+this.openEnd+")"}toJSON(){if(!this.content.size)return null;let e={content:this.content.toJSON()};return this.openStart>0&&(e.openStart=this.openStart),this.openEnd>0&&(e.openEnd=this.openEnd),e}static fromJSON(e,t){if(!t)return c.empty;let n=t.openStart||0,i=t.openEnd||0;if("number"!=typeof n||"number"!=typeof i)throw new RangeError("Invalid input for Slice.fromJSON");return new c(r.fromJSON(e,t.content),n,i)}static maxOpen(e,t=!0){let n=0,r=0;for(let r=e.firstChild;r&&!r.isLeaf&&(t||!r.type.spec.isolating);r=r.firstChild)n++;for(let n=e.lastChild;n&&!n.isLeaf&&(t||!n.type.spec.isolating);n=n.lastChild)r++;return new c(e,n,r)}}function h(e,t,n){let{index:r,offset:i}=e.findIndex(t),o=e.maybeChild(r),{index:s,offset:l}=e.findIndex(n);if(i==t||o.isText){if(l!=n&&!e.child(s).isText)throw new RangeError("Removing non-flat range");return e.cut(0,t).append(e.cut(n))}if(r!=s)throw new RangeError("Removing non-flat range");return e.replaceChild(r,o.copy(h(o.content,t-i-1,n-i-1)))}function u(e,t,n,r){let{index:i,offset:o}=e.findIndex(t),s=e.maybeChild(i);if(o==t||s.isText)return r&&!r.canReplace(i,i,n)?null:e.cut(0,t).append(n).append(e.cut(t));let l=u(s.content,t-o-1,n,s);return l&&e.replaceChild(i,s.copy(l))}function p(e,t,n){if(n.openStart>e.depth)throw new a("Inserted content deeper than insertion position");if(e.depth-n.openStart!=t.depth-n.openEnd)throw new a("Inconsistent open depths");return d(e,t,n,0)}function d(e,t,n,i){let o=e.index(i),s=e.node(i);if(o==t.index(i)&&i<e.depth-n.openStart){let r=d(e,t,n,i+1);return s.copy(s.content.replaceChild(o,r))}if(n.content.size){if(n.openStart||n.openEnd||e.depth!=i||t.depth!=i){let{start:o,end:l}=function(e,t){let n=t.depth-e.openStart,i=t.node(n).copy(e.content);for(let e=n-1;e>=0;e--)i=t.node(e).copy(r.from(i));return{start:i.resolveNoCache(e.openStart+n),end:i.resolveNoCache(i.content.size-e.openEnd-n)}}(n,e);return k(s,b(e,o,l,t,i))}{let r=e.parent,i=r.content;return k(r,i.cut(0,e.parentOffset).append(n.content).append(i.cut(t.parentOffset)))}}return k(s,w(e,t,i))}function f(e,t){if(!t.type.compatibleContent(e.type))throw new a("Cannot join "+t.type.name+" onto "+e.type.name)}function m(e,t,n){let r=e.node(n);return f(r,t.node(n)),r}function g(e,t){let n=t.length-1;n>=0&&e.isText&&e.sameMarkup(t[n])?t[n]=e.withText(t[n].text+e.text):t.push(e)}function y(e,t,n,r){let i=(t||e).node(n),o=0,s=t?t.index(n):i.childCount;e&&(o=e.index(n),e.depth>n?o++:e.textOffset&&(g(e.nodeAfter,r),o++));for(let e=o;e<s;e++)g(i.child(e),r);t&&t.depth==n&&t.textOffset&&g(t.nodeBefore,r)}function k(e,t){return e.type.checkContent(t),e.copy(t)}function b(e,t,n,i,o){let s=e.depth>o&&m(e,t,o+1),l=i.depth>o&&m(n,i,o+1),a=[];return y(null,e,o,a),s&&l&&t.index(o)==n.index(o)?(f(s,l),g(k(s,b(e,t,n,i,o+1)),a)):(s&&g(k(s,w(e,t,o+1)),a),y(t,n,o,a),l&&g(k(l,w(n,i,o+1)),a)),y(i,null,o,a),new r(a)}function w(e,t,n){let i=[];if(y(null,e,n,i),e.depth>n){g(k(m(e,t,n+1),w(e,t,n+1)),i)}return y(t,null,n,i),new r(i)}c.empty=new c(r.empty,0,0);class C{constructor(e,t,n){this.pos=e,this.path=t,this.parentOffset=n,this.depth=t.length/3-1}resolveDepth(e){return null==e?this.depth:e<0?this.depth+e:e}get parent(){return this.node(this.depth)}get doc(){return this.node(0)}node(e){return this.path[3*this.resolveDepth(e)]}index(e){return this.path[3*this.resolveDepth(e)+1]}indexAfter(e){return e=this.resolveDepth(e),this.index(e)+(e!=this.depth||this.textOffset?1:0)}start(e){return 0==(e=this.resolveDepth(e))?0:this.path[3*e-1]+1}end(e){return e=this.resolveDepth(e),this.start(e)+this.node(e).content.size}before(e){if(!(e=this.resolveDepth(e)))throw new RangeError("There is no position before the top-level node");return e==this.depth+1?this.pos:this.path[3*e-1]}after(e){if(!(e=this.resolveDepth(e)))throw new RangeError("There is no position after the top-level node");return e==this.depth+1?this.pos:this.path[3*e-1]+this.path[3*e].nodeSize}get textOffset(){return this.pos-this.path[this.path.length-1]}get nodeAfter(){let e=this.parent,t=this.index(this.depth);if(t==e.childCount)return null;let n=this.pos-this.path[this.path.length-1],r=e.child(t);return n?e.child(t).cut(n):r}get nodeBefore(){let e=this.index(this.depth),t=this.pos-this.path[this.path.length-1];return t?this.parent.child(e).cut(0,t):0==e?null:this.parent.child(e-1)}posAtIndex(e,t){t=this.resolveDepth(t);let n=this.path[3*t],r=0==t?0:this.path[3*t-1]+1;for(let t=0;t<e;t++)r+=n.child(t).nodeSize;return r}marks(){let e=this.parent,t=this.index();if(0==e.content.size)return l.none;if(this.textOffset)return e.child(t).marks;let n=e.maybeChild(t-1),r=e.maybeChild(t);if(!n){let e=n;n=r,r=e}let i=n.marks;for(var o=0;o<i.length;o++)!1!==i[o].type.spec.inclusive||r&&i[o].isInSet(r.marks)||(i=i[o--].removeFromSet(i));return i}marksAcross(e){let t=this.parent.maybeChild(this.index());if(!t||!t.isInline)return null;let n=t.marks,r=e.parent.maybeChild(e.index());for(var i=0;i<n.length;i++)!1!==n[i].type.spec.inclusive||r&&n[i].isInSet(r.marks)||(n=n[i--].removeFromSet(n));return n}sharedDepth(e){for(let t=this.depth;t>0;t--)if(this.start(t)<=e&&this.end(t)>=e)return t;return 0}blockRange(e=this,t){if(e.pos<this.pos)return e.blockRange(this);for(let n=this.depth-(this.parent.inlineContent||this.pos==e.pos?1:0);n>=0;n--)if(e.pos<=this.end(n)&&(!t||t(this.node(n))))return new _(this,e,n);return null}sameParent(e){return this.pos-this.parentOffset==e.pos-e.parentOffset}max(e){return e.pos>this.pos?e:this}min(e){return e.pos<this.pos?e:this}toString(){let e="";for(let t=1;t<=this.depth;t++)e+=(e?"/":"")+this.node(t).type.name+"_"+this.index(t-1);return e+":"+this.parentOffset}static resolve(e,t){if(!(t>=0&&t<=e.content.size))throw new RangeError("Position "+t+" out of range");let n=[],r=0,i=t;for(let t=e;;){let{index:e,offset:o}=t.content.findIndex(i),s=i-o;if(n.push(t,e,r+o),!s)break;if(t=t.child(e),t.isText)break;i=s-1,r+=o+1}return new C(t,n,i)}static resolveCached(e,t){let n=v.get(e);if(n)for(let e=0;e<n.elts.length;e++){let r=n.elts[e];if(r.pos==t)return r}else v.set(e,n=new x);let r=n.elts[n.i]=C.resolve(e,t);return n.i=(n.i+1)%D,r}}class x{constructor(){this.elts=[],this.i=0}}const D=12,v=new WeakMap;class _{constructor(e,t,n){this.$from=e,this.$to=t,this.depth=n}get start(){return this.$from.before(this.depth+1)}get end(){return this.$to.after(this.depth+1)}get parent(){return this.$from.node(this.depth)}get startIndex(){return this.$from.index(this.depth)}get endIndex(){return this.$to.indexAfter(this.depth)}}const S=Object.create(null);class E{constructor(e,t,n,i=l.none){this.type=e,this.attrs=t,this.marks=i,this.content=n||r.empty}get children(){return this.content.content}get nodeSize(){return this.isLeaf?1:2+this.content.size}get childCount(){return this.content.childCount}child(e){return this.content.child(e)}maybeChild(e){return this.content.maybeChild(e)}forEach(e){this.content.forEach(e)}nodesBetween(e,t,n,r=0){this.content.nodesBetween(e,t,n,r,this)}descendants(e){this.nodesBetween(0,this.content.size,e)}get textContent(){return this.isLeaf&&this.type.spec.leafText?this.type.spec.leafText(this):this.textBetween(0,this.content.size,"")}textBetween(e,t,n,r){return this.content.textBetween(e,t,n,r)}get firstChild(){return this.content.firstChild}get lastChild(){return this.content.lastChild}eq(e){return this==e||this.sameMarkup(e)&&this.content.eq(e.content)}sameMarkup(e){return this.hasMarkup(e.type,e.attrs,e.marks)}hasMarkup(e,t,n){return this.type==e&&s(this.attrs,t||e.defaultAttrs||S)&&l.sameSet(this.marks,n||l.none)}copy(e=null){return e==this.content?this:new E(this.type,this.attrs,e,this.marks)}mark(e){return e==this.marks?this:new E(this.type,this.attrs,this.content,e)}cut(e,t=this.content.size){return 0==e&&t==this.content.size?this:this.copy(this.content.cut(e,t))}slice(e,t=this.content.size,n=!1){if(e==t)return c.empty;let r=this.resolve(e),i=this.resolve(t),o=n?0:r.sharedDepth(t),s=r.start(o),l=r.node(o).content.cut(r.pos-s,i.pos-s);return new c(l,r.depth-o,i.depth-o)}replace(e,t,n){return p(this.resolve(e),this.resolve(t),n)}nodeAt(e){for(let t=this;;){let{index:n,offset:r}=t.content.findIndex(e);if(t=t.maybeChild(n),!t)return null;if(r==e||t.isText)return t;e-=r+1}}childAfter(e){let{index:t,offset:n}=this.content.findIndex(e);return{node:this.content.maybeChild(t),index:t,offset:n}}childBefore(e){if(0==e)return{node:null,index:0,offset:0};let{index:t,offset:n}=this.content.findIndex(e);if(n<e)return{node:this.content.child(t),index:t,offset:n};let r=this.content.child(t-1);return{node:r,index:t-1,offset:n-r.nodeSize}}resolve(e){return C.resolveCached(this,e)}resolveNoCache(e){return C.resolve(this,e)}rangeHasMark(e,t,n){let r=!1;return t>e&&this.nodesBetween(e,t,e=>(n.isInSet(e.marks)&&(r=!0),!r)),r}get isBlock(){return this.type.isBlock}get isTextblock(){return this.type.isTextblock}get inlineContent(){return this.type.inlineContent}get isInline(){return this.type.isInline}get isText(){return this.type.isText}get isLeaf(){return this.type.isLeaf}get isAtom(){return this.type.isAtom}toString(){if(this.type.spec.toDebugString)return this.type.spec.toDebugString(this);let e=this.type.name;return this.content.size&&(e+="("+this.content.toStringInner()+")"),M(this.marks,e)}contentMatchAt(e){let t=this.type.contentMatch.matchFragment(this.content,0,e);if(!t)throw new Error("Called contentMatchAt on a node with invalid content");return t}canReplace(e,t,n=r.empty,i=0,o=n.childCount){let s=this.contentMatchAt(e).matchFragment(n,i,o),l=s&&s.matchFragment(this.content,t);if(!l||!l.validEnd)return!1;for(let e=i;e<o;e++)if(!this.type.allowsMarks(n.child(e).marks))return!1;return!0}canReplaceWith(e,t,n,r){if(r&&!this.type.allowsMarks(r))return!1;let i=this.contentMatchAt(e).matchType(n),o=i&&i.matchFragment(this.content,t);return!!o&&o.validEnd}canAppend(e){return e.content.size?this.canReplace(this.childCount,this.childCount,e.content):this.type.compatibleContent(e.type)}check(){this.type.checkContent(this.content),this.type.checkAttrs(this.attrs);let e=l.none;for(let t=0;t<this.marks.length;t++){let n=this.marks[t];n.type.checkAttrs(n.attrs),e=n.addToSet(e)}if(!l.sameSet(e,this.marks))throw new RangeError(`Invalid collection of marks for node ${this.type.name}: ${this.marks.map(e=>e.type.name)}`);this.content.forEach(e=>e.check())}toJSON(){let e={type:this.type.name};for(let t in this.attrs){e.attrs=this.attrs;break}return this.content.size&&(e.content=this.content.toJSON()),this.marks.length&&(e.marks=this.marks.map(e=>e.toJSON())),e}static fromJSON(e,t){if(!t)throw new RangeError("Invalid input for Node.fromJSON");let n;if(t.marks){if(!Array.isArray(t.marks))throw new RangeError("Invalid mark data for Node.fromJSON");n=t.marks.map(e.markFromJSON)}if("text"==t.type){if("string"!=typeof t.text)throw new RangeError("Invalid text node in JSON");return e.text(t.text,n)}let i=r.fromJSON(e,t.content),o=e.nodeType(t.type).create(t.attrs,i,n);return o.type.checkAttrs(o.attrs),o}}E.prototype.text=void 0;class A extends E{constructor(e,t,n,r){if(super(e,t,null,r),!n)throw new RangeError("Empty text nodes are not allowed");this.text=n}toString(){return this.type.spec.toDebugString?this.type.spec.toDebugString(this):M(this.marks,JSON.stringify(this.text))}get textContent(){return this.text}textBetween(e,t){return this.text.slice(e,t)}get nodeSize(){return this.text.length}mark(e){return e==this.marks?this:new A(this.type,this.attrs,this.text,e)}withText(e){return e==this.text?this:new A(this.type,this.attrs,e,this.marks)}cut(e=0,t=this.text.length){return 0==e&&t==this.text.length?this:this.withText(this.text.slice(e,t))}eq(e){return this.sameMarkup(e)&&this.text==e.text}toJSON(){let e=super.toJSON();return e.text=this.text,e}}function M(e,t){for(let n=e.length-1;n>=0;n--)t=e[n].type.name+"("+t+")";return t}class O{constructor(e){this.validEnd=e,this.next=[],this.wrapCache=[]}static parse(e,t){let n=new N(e,t);if(null==n.next)return O.empty;let r=F(n);n.next&&n.err("Unexpected trailing text");let i=function(e){let t=Object.create(null);return n(P(e,0));function n(r){let i=[];r.forEach(t=>{e[t].forEach(({term:t,to:n})=>{if(!t)return;let r;for(let e=0;e<i.length;e++)i[e][0]==t&&(r=i[e][1]);P(e,n).forEach(e=>{r||i.push([t,r=[]]),-1==r.indexOf(e)&&r.push(e)})})});let o=t[r.join(",")]=new O(r.indexOf(e.length-1)>-1);for(let e=0;e<i.length;e++){let r=i[e][1].sort(B);o.next.push({type:i[e][0],next:t[r.join(",")]||n(r)})}return o}}(function(e){let t=[[]];return i(o(e,0),n()),t;function n(){return t.push([])-1}function r(e,n,r){let i={term:r,to:n};return t[e].push(i),i}function i(e,t){e.forEach(e=>e.to=t)}function o(e,t){if("choice"==e.type)return e.exprs.reduce((e,n)=>e.concat(o(n,t)),[]);if("seq"!=e.type){if("star"==e.type){let s=n();return r(t,s),i(o(e.expr,s),s),[r(s)]}if("plus"==e.type){let s=n();return i(o(e.expr,t),s),i(o(e.expr,s),s),[r(s)]}if("opt"==e.type)return[r(t)].concat(o(e.expr,t));if("range"==e.type){let s=t;for(let t=0;t<e.min;t++){let t=n();i(o(e.expr,s),t),s=t}if(-1==e.max)i(o(e.expr,s),s);else for(let t=e.min;t<e.max;t++){let t=n();r(s,t),i(o(e.expr,s),t),s=t}return[r(s)]}if("name"==e.type)return[r(t,void 0,e.value)];throw new Error("Unknown expr type")}for(let r=0;;r++){let s=o(e.exprs[r],t);if(r==e.exprs.length-1)return s;i(s,t=n())}}}(r));return function(e,t){for(let n=0,r=[e];n<r.length;n++){let e=r[n],i=!e.validEnd,o=[];for(let t=0;t<e.next.length;t++){let{type:n,next:s}=e.next[t];o.push(n.name),!i||n.isText||n.hasRequiredAttrs()||(i=!1),-1==r.indexOf(s)&&r.push(s)}i&&t.err("Only non-generatable nodes ("+o.join(", ")+") in a required position (see https://prosemirror.net/docs/guide/#generatable)")}}(i,n),i}matchType(e){for(let t=0;t<this.next.length;t++)if(this.next[t].type==e)return this.next[t].next;return null}matchFragment(e,t=0,n=e.childCount){let r=this;for(let i=t;r&&i<n;i++)r=r.matchType(e.child(i).type);return r}get inlineContent(){return 0!=this.next.length&&this.next[0].type.isInline}get defaultType(){for(let e=0;e<this.next.length;e++){let{type:t}=this.next[e];if(!t.isText&&!t.hasRequiredAttrs())return t}return null}compatible(e){for(let t=0;t<this.next.length;t++)for(let n=0;n<e.next.length;n++)if(this.next[t].type==e.next[n].type)return!0;return!1}fillBefore(e,t=!1,n=0){let i=[this];return function o(s,l){let a=s.matchFragment(e,n);if(a&&(!t||a.validEnd))return r.from(l.map(e=>e.createAndFill()));for(let e=0;e<s.next.length;e++){let{type:t,next:n}=s.next[e];if(!t.isText&&!t.hasRequiredAttrs()&&-1==i.indexOf(n)){i.push(n);let e=o(n,l.concat(t));if(e)return e}}return null}(this,[])}findWrapping(e){for(let t=0;t<this.wrapCache.length;t+=2)if(this.wrapCache[t]==e)return this.wrapCache[t+1];let t=this.computeWrapping(e);return this.wrapCache.push(e,t),t}computeWrapping(e){let t=Object.create(null),n=[{match:this,type:null,via:null}];for(;n.length;){let r=n.shift(),i=r.match;if(i.matchType(e)){let e=[];for(let t=r;t.type;t=t.via)e.push(t.type);return e.reverse()}for(let e=0;e<i.next.length;e++){let{type:o,next:s}=i.next[e];o.isLeaf||o.hasRequiredAttrs()||o.name in t||r.type&&!s.validEnd||(n.push({match:o.contentMatch,type:o,via:r}),t[o.name]=!0)}}return null}get edgeCount(){return this.next.length}edge(e){if(e>=this.next.length)throw new RangeError(`There's no ${e}th edge in this content match`);return this.next[e]}toString(){let e=[];return function t(n){e.push(n);for(let r=0;r<n.next.length;r++)-1==e.indexOf(n.next[r].next)&&t(n.next[r].next)}(this),e.map((t,n)=>{let r=n+(t.validEnd?"*":" ")+" ";for(let n=0;n<t.next.length;n++)r+=(n?", ":"")+t.next[n].type.name+"->"+e.indexOf(t.next[n].next);return r}).join("\n")}}O.empty=new O(!0);class N{constructor(e,t){this.string=e,this.nodeTypes=t,this.inline=null,this.pos=0,this.tokens=e.split(/\s*(?=\b|\W|$)/),""==this.tokens[this.tokens.length-1]&&this.tokens.pop(),""==this.tokens[0]&&this.tokens.shift()}get next(){return this.tokens[this.pos]}eat(e){return this.next==e&&(this.pos++||!0)}err(e){throw new SyntaxError(e+" (in content expression '"+this.string+"')")}}function F(e){let t=[];do{t.push(T(e))}while(e.eat("|"));return 1==t.length?t[0]:{type:"choice",exprs:t}}function T(e){let t=[];do{t.push(R(e))}while(e.next&&")"!=e.next&&"|"!=e.next);return 1==t.length?t[0]:{type:"seq",exprs:t}}function R(e){let t=function(e){if(e.eat("(")){let t=F(e);return e.eat(")")||e.err("Missing closing paren"),t}if(!/\W/.test(e.next)){let t=function(e,t){let n=e.nodeTypes,r=n[t];if(r)return[r];let i=[];for(let e in n){let r=n[e];r.isInGroup(t)&&i.push(r)}0==i.length&&e.err("No node type or group '"+t+"' found");return i}(e,e.next).map(t=>(null==e.inline?e.inline=t.isInline:e.inline!=t.isInline&&e.err("Mixing inline and block content"),{type:"name",value:t}));return e.pos++,1==t.length?t[0]:{type:"choice",exprs:t}}e.err("Unexpected token '"+e.next+"'")}(e);for(;;)if(e.eat("+"))t={type:"plus",expr:t};else if(e.eat("*"))t={type:"star",expr:t};else if(e.eat("?"))t={type:"opt",expr:t};else{if(!e.eat("{"))break;t=I(e,t)}return t}function z(e){/\D/.test(e.next)&&e.err("Expected number, got '"+e.next+"'");let t=Number(e.next);return e.pos++,t}function I(e,t){let n=z(e),r=n;return e.eat(",")&&(r="}"!=e.next?z(e):-1),e.eat("}")||e.err("Unclosed braced range"),{type:"range",min:n,max:r,expr:t}}function B(e,t){return t-e}function P(e,t){let n=[];return function t(r){let i=e[r];if(1==i.length&&!i[0].term)return t(i[0].to);n.push(r);for(let e=0;e<i.length;e++){let{term:r,to:o}=i[e];r||-1!=n.indexOf(o)||t(o)}}(t),n.sort(B)}function L(e){let t=Object.create(null);for(let n in e){let r=e[n];if(!r.hasDefault)return null;t[n]=r.default}return t}function q(e,t){let n=Object.create(null);for(let r in e){let i=t&&t[r];if(void 0===i){let t=e[r];if(!t.hasDefault)throw new RangeError("No value supplied for attribute "+r);i=t.default}n[r]=i}return n}function $(e,t,n,r){for(let r in t)if(!(r in e))throw new RangeError(`Unsupported attribute ${r} for ${n} of type ${r}`);for(let n in e){let r=e[n];r.validate&&r.validate(t[n])}}function V(e,t){let n=Object.create(null);if(t)for(let r in t)n[r]=new J(e,r,t[r]);return n}let j=class e{constructor(e,t,n){this.name=e,this.schema=t,this.spec=n,this.markSet=null,this.groups=n.group?n.group.split(" "):[],this.attrs=V(e,n.attrs),this.defaultAttrs=L(this.attrs),this.contentMatch=null,this.inlineContent=null,this.isBlock=!(n.inline||"text"==e),this.isText="text"==e}get isInline(){return!this.isBlock}get isTextblock(){return this.isBlock&&this.inlineContent}get isLeaf(){return this.contentMatch==O.empty}get isAtom(){return this.isLeaf||!!this.spec.atom}isInGroup(e){return this.groups.indexOf(e)>-1}get whitespace(){return this.spec.whitespace||(this.spec.code?"pre":"normal")}hasRequiredAttrs(){for(let e in this.attrs)if(this.attrs[e].isRequired)return!0;return!1}compatibleContent(e){return this==e||this.contentMatch.compatible(e.contentMatch)}computeAttrs(e){return!e&&this.defaultAttrs?this.defaultAttrs:q(this.attrs,e)}create(e=null,t,n){if(this.isText)throw new Error("NodeType.create can't construct text nodes");return new E(this,this.computeAttrs(e),r.from(t),l.setFrom(n))}createChecked(e=null,t,n){return t=r.from(t),this.checkContent(t),new E(this,this.computeAttrs(e),t,l.setFrom(n))}createAndFill(e=null,t,n){if(e=this.computeAttrs(e),(t=r.from(t)).size){let e=this.contentMatch.fillBefore(t);if(!e)return null;t=e.append(t)}let i=this.contentMatch.matchFragment(t),o=i&&i.fillBefore(r.empty,!0);return o?new E(this,e,t.append(o),l.setFrom(n)):null}validContent(e){let t=this.contentMatch.matchFragment(e);if(!t||!t.validEnd)return!1;for(let t=0;t<e.childCount;t++)if(!this.allowsMarks(e.child(t).marks))return!1;return!0}checkContent(e){if(!this.validContent(e))throw new RangeError(`Invalid content for node ${this.name}: ${e.toString().slice(0,50)}`)}checkAttrs(e){$(this.attrs,e,"node",this.name)}allowsMarkType(e){return null==this.markSet||this.markSet.indexOf(e)>-1}allowsMarks(e){if(null==this.markSet)return!0;for(let t=0;t<e.length;t++)if(!this.allowsMarkType(e[t].type))return!1;return!0}allowedMarks(e){if(null==this.markSet)return e;let t;for(let n=0;n<e.length;n++)this.allowsMarkType(e[n].type)?t&&t.push(e[n]):t||(t=e.slice(0,n));return t?t.length?t:l.none:e}static compile(t,n){let r=Object.create(null);t.forEach((t,i)=>r[t]=new e(t,n,i));let i=n.spec.topNode||"doc";if(!r[i])throw new RangeError("Schema is missing its top node type ('"+i+"')");if(!r.text)throw new RangeError("Every schema needs a 'text' type");for(let e in r.text.attrs)throw new RangeError("The text node type should not have attributes");return r}};class J{constructor(e,t,n){this.hasDefault=Object.prototype.hasOwnProperty.call(n,"default"),this.default=n.default,this.validate="string"==typeof n.validate?function(e,t,n){let r=n.split("|");return n=>{let i=null===n?"null":typeof n;if(r.indexOf(i)<0)throw new RangeError(`Expected value of type ${r} for attribute ${t} on type ${e}, got ${i}`)}}(e,t,n.validate):n.validate}get isRequired(){return!this.hasDefault}}class W{constructor(e,t,n,r){this.name=e,this.rank=t,this.schema=n,this.spec=r,this.attrs=V(e,r.attrs),this.excluded=null;let i=L(this.attrs);this.instance=i?new l(this,i):null}create(e=null){return!e&&this.instance?this.instance:new l(this,q(this.attrs,e))}static compile(e,t){let n=Object.create(null),r=0;return e.forEach((e,i)=>n[e]=new W(e,r++,t,i)),n}removeFromSet(e){for(var t=0;t<e.length;t++)e[t].type==this&&(e=e.slice(0,t).concat(e.slice(t+1)),t--);return e}isInSet(e){for(let t=0;t<e.length;t++)if(e[t].type==this)return e[t]}checkAttrs(e){$(this.attrs,e,"mark",this.name)}excludes(e){return this.excluded.indexOf(e)>-1}}class H{constructor(t){this.linebreakReplacement=null,this.cached=Object.create(null);let n=this.spec={};for(let e in t)n[e]=t[e];n.nodes=e.from(t.nodes),n.marks=e.from(t.marks||{}),this.nodes=j.compile(this.spec.nodes,this),this.marks=W.compile(this.spec.marks,this);let r=Object.create(null);for(let e in this.nodes){if(e in this.marks)throw new RangeError(e+" can not be both a node and a mark");let t=this.nodes[e],n=t.spec.content||"",i=t.spec.marks;if(t.contentMatch=r[n]||(r[n]=O.parse(n,this.nodes)),t.inlineContent=t.contentMatch.inlineContent,t.spec.linebreakReplacement){if(this.linebreakReplacement)throw new RangeError("Multiple linebreak nodes defined");if(!t.isInline||!t.isLeaf)throw new RangeError("Linebreak replacement nodes must be inline leaf nodes");this.linebreakReplacement=t}t.markSet="_"==i?null:i?U(this,i.split(" ")):""!=i&&t.inlineContent?null:[]}for(let e in this.marks){let t=this.marks[e],n=t.spec.excludes;t.excluded=null==n?[t]:""==n?[]:U(this,n.split(" "))}this.nodeFromJSON=e=>E.fromJSON(this,e),this.markFromJSON=e=>l.fromJSON(this,e),this.topNodeType=this.nodes[this.spec.topNode||"doc"],this.cached.wrappings=Object.create(null)}node(e,t=null,n,r){if("string"==typeof e)e=this.nodeType(e);else{if(!(e instanceof j))throw new RangeError("Invalid node type: "+e);if(e.schema!=this)throw new RangeError("Node type from different schema used ("+e.name+")")}return e.createChecked(t,n,r)}text(e,t){let n=this.nodes.text;return new A(n,n.defaultAttrs,e,l.setFrom(t))}mark(e,t){return"string"==typeof e&&(e=this.marks[e]),e.create(t)}nodeType(e){let t=this.nodes[e];if(!t)throw new RangeError("Unknown node type: "+e);return t}}function U(e,t){let n=[];for(let r=0;r<t.length;r++){let i=t[r],o=e.marks[i],s=o;if(o)n.push(o);else for(let t in e.marks){let r=e.marks[t];("_"==i||r.spec.group&&r.spec.group.split(" ").indexOf(i)>-1)&&n.push(s=r)}if(!s)throw new SyntaxError("Unknown mark type: '"+t[r]+"'")}return n}class K{constructor(e,t){this.schema=e,this.rules=t,this.tags=[],this.styles=[];let n=this.matchedStyles=[];t.forEach(e=>{if(function(e){return null!=e.tag}(e))this.tags.push(e);else if(function(e){return null!=e.style}(e)){let t=/[^=]*/.exec(e.style)[0];n.indexOf(t)<0&&n.push(t),this.styles.push(e)}}),this.normalizeLists=!this.tags.some(t=>{if(!/^(ul|ol)\b/.test(t.tag)||!t.node)return!1;let n=e.nodes[t.node];return n.contentMatch.matchType(n)})}parse(e,t={}){let n=new ee(this,t,!1);return n.addAll(e,l.none,t.from,t.to),n.finish()}parseSlice(e,t={}){let n=new ee(this,t,!0);return n.addAll(e,l.none,t.from,t.to),c.maxOpen(n.finish())}matchTag(e,t,n){for(let r=n?this.tags.indexOf(n)+1:0;r<this.tags.length;r++){let n=this.tags[r];if(te(e,n.tag)&&(void 0===n.namespace||e.namespaceURI==n.namespace)&&(!n.context||t.matchesContext(n.context))){if(n.getAttrs){let t=n.getAttrs(e);if(!1===t)continue;n.attrs=t||void 0}return n}}}matchStyle(e,t,n,r){for(let i=r?this.styles.indexOf(r)+1:0;i<this.styles.length;i++){let r=this.styles[i],o=r.style;if(!(0!=o.indexOf(e)||r.context&&!n.matchesContext(r.context)||o.length>e.length&&(61!=o.charCodeAt(e.length)||o.slice(e.length+1)!=t))){if(r.getAttrs){let e=r.getAttrs(t);if(!1===e)continue;r.attrs=e||void 0}return r}}}static schemaRules(e){let t=[];function n(e){let n=null==e.priority?50:e.priority,r=0;for(;r<t.length;r++){let e=t[r];if((null==e.priority?50:e.priority)<n)break}t.splice(r,0,e)}for(let t in e.marks){let r=e.marks[t].spec.parseDOM;r&&r.forEach(e=>{n(e=ne(e)),e.mark||e.ignore||e.clearMark||(e.mark=t)})}for(let t in e.nodes){let r=e.nodes[t].spec.parseDOM;r&&r.forEach(e=>{n(e=ne(e)),e.node||e.ignore||e.mark||(e.node=t)})}return t}static fromSchema(e){return e.cached.domParser||(e.cached.domParser=new K(e,K.schemaRules(e)))}}const Z={address:!0,article:!0,aside:!0,blockquote:!0,canvas:!0,dd:!0,div:!0,dl:!0,fieldset:!0,figcaption:!0,figure:!0,footer:!0,form:!0,h1:!0,h2:!0,h3:!0,h4:!0,h5:!0,h6:!0,header:!0,hgroup:!0,hr:!0,li:!0,noscript:!0,ol:!0,output:!0,p:!0,pre:!0,section:!0,table:!0,tfoot:!0,ul:!0},G={head:!0,noscript:!0,object:!0,script:!0,style:!0,title:!0},Q={ol:!0,ul:!0};function X(e,t,n){return null!=t?(t?1:0)|("full"===t?2:0):e&&"pre"==e.whitespace?3:-5&n}class Y{constructor(e,t,n,r,i,o){this.type=e,this.attrs=t,this.marks=n,this.solid=r,this.options=o,this.content=[],this.activeMarks=l.none,this.match=i||(4&o?null:e.contentMatch)}findWrapping(e){if(!this.match){if(!this.type)return[];let t=this.type.contentMatch.fillBefore(r.from(e));if(!t){let t,n=this.type.contentMatch;return(t=n.findWrapping(e.type))?(this.match=n,t):null}this.match=this.type.contentMatch.matchFragment(t)}return this.match.findWrapping(e.type)}finish(e){if(!(1&this.options)){let e,t=this.content[this.content.length-1];if(t&&t.isText&&(e=/[ \t\r\n\u000c]+$/.exec(t.text))){let n=t;t.text.length==e[0].length?this.content.pop():this.content[this.content.length-1]=n.withText(n.text.slice(0,n.text.length-e[0].length))}}let t=r.from(this.content);return!e&&this.match&&(t=t.append(this.match.fillBefore(r.empty,!0))),this.type?this.type.create(this.attrs,t,this.marks):t}inlineContext(e){return this.type?this.type.inlineContent:this.content.length?this.content[0].isInline:e.parentNode&&!Z.hasOwnProperty(e.parentNode.nodeName.toLowerCase())}}class ee{constructor(e,t,n){this.parser=e,this.options=t,this.isOpen=n,this.open=0,this.localPreserveWS=!1;let r,i=t.topNode,o=X(null,t.preserveWhitespace,0)|(n?4:0);r=i?new Y(i.type,i.attrs,l.none,!0,t.topMatch||i.type.contentMatch,o):new Y(n?null:e.schema.topNodeType,null,l.none,!0,null,o),this.nodes=[r],this.find=t.findPositions,this.needsBlock=!1}get top(){return this.nodes[this.open]}addDOM(e,t){3==e.nodeType?this.addTextNode(e,t):1==e.nodeType&&this.addElement(e,t)}addTextNode(e,t){let n=e.nodeValue,r=this.top,i=2&r.options?"full":this.localPreserveWS||(1&r.options)>0,{schema:o}=this.parser;if("full"===i||r.inlineContext(e)||/[^ \t\r\n\u000c]/.test(n)){if(i)if("full"===i)n=n.replace(/\r\n?/g,"\n");else if(o.linebreakReplacement&&/[\r\n]/.test(n)&&this.top.findWrapping(o.linebreakReplacement.create())){let e=n.split(/\r?\n|\r/);for(let n=0;n<e.length;n++)n&&this.insertNode(o.linebreakReplacement.create(),t,!0),e[n]&&this.insertNode(o.text(e[n]),t,!/\S/.test(e[n]));n=""}else n=n.replace(/\r?\n|\r/g," ");else if(n=n.replace(/[ \t\r\n\u000c]+/g," "),/^[ \t\r\n\u000c]/.test(n)&&this.open==this.nodes.length-1){let t=r.content[r.content.length-1],i=e.previousSibling;(!t||i&&"BR"==i.nodeName||t.isText&&/[ \t\r\n\u000c]$/.test(t.text))&&(n=n.slice(1))}n&&this.insertNode(o.text(n),t,!/\S/.test(n)),this.findInText(e)}else this.findInside(e)}addElement(e,t,n){let r=this.localPreserveWS,i=this.top;("PRE"==e.tagName||/pre/.test(e.style&&e.style.whiteSpace))&&(this.localPreserveWS=!0);let o,s=e.nodeName.toLowerCase();Q.hasOwnProperty(s)&&this.parser.normalizeLists&&function(e){for(let t=e.firstChild,n=null;t;t=t.nextSibling){let e=1==t.nodeType?t.nodeName.toLowerCase():null;e&&Q.hasOwnProperty(e)&&n?(n.appendChild(t),t=n):"li"==e?n=t:e&&(n=null)}}(e);let l=this.options.ruleFromNode&&this.options.ruleFromNode(e)||(o=this.parser.matchTag(e,this,n));e:if(l?l.ignore:G.hasOwnProperty(s))this.findInside(e),this.ignoreFallback(e,t);else if(!l||l.skip||l.closeParent){l&&l.closeParent?this.open=Math.max(0,this.open-1):l&&l.skip.nodeType&&(e=l.skip);let n,r=this.needsBlock;if(Z.hasOwnProperty(s))i.content.length&&i.content[0].isInline&&this.open&&(this.open--,i=this.top),n=!0,i.type||(this.needsBlock=!0);else if(!e.firstChild){this.leafFallback(e,t);break e}let o=l&&l.skip?t:this.readStyles(e,t);o&&this.addAll(e,o),n&&this.sync(i),this.needsBlock=r}else{let n=this.readStyles(e,t);n&&this.addElementByRule(e,l,n,!1===l.consuming?o:void 0)}this.localPreserveWS=r}leafFallback(e,t){"BR"==e.nodeName&&this.top.type&&this.top.type.inlineContent&&this.addTextNode(e.ownerDocument.createTextNode("\n"),t)}ignoreFallback(e,t){"BR"!=e.nodeName||this.top.type&&this.top.type.inlineContent||this.findPlace(this.parser.schema.text("-"),t,!0)}readStyles(e,t){let n=e.style;if(n&&n.length)for(let e=0;e<this.parser.matchedStyles.length;e++){let r=this.parser.matchedStyles[e],i=n.getPropertyValue(r);if(i)for(let e;;){let n=this.parser.matchStyle(r,i,this,e);if(!n)break;if(n.ignore)return null;if(t=n.clearMark?t.filter(e=>!n.clearMark(e)):t.concat(this.parser.schema.marks[n.mark].create(n.attrs)),!1!==n.consuming)break;e=n}}return t}addElementByRule(e,t,n,r){let i,o;if(t.node)if(o=this.parser.schema.nodes[t.node],o.isLeaf)this.insertNode(o.create(t.attrs),n,"BR"==e.nodeName)||this.leafFallback(e,n);else{let e=this.enter(o,t.attrs||null,n,t.preserveWhitespace);e&&(i=!0,n=e)}else{let e=this.parser.schema.marks[t.mark];n=n.concat(e.create(t.attrs))}let s=this.top;if(o&&o.isLeaf)this.findInside(e);else if(r)this.addElement(e,n,r);else if(t.getContent)this.findInside(e),t.getContent(e,this.parser.schema).forEach(e=>this.insertNode(e,n,!1));else{let r=e;"string"==typeof t.contentElement?r=e.querySelector(t.contentElement):"function"==typeof t.contentElement?r=t.contentElement(e):t.contentElement&&(r=t.contentElement),this.findAround(e,r,!0),this.addAll(r,n),this.findAround(e,r,!1)}i&&this.sync(s)&&this.open--}addAll(e,t,n,r){let i=n||0;for(let o=n?e.childNodes[n]:e.firstChild,s=null==r?null:e.childNodes[r];o!=s;o=o.nextSibling,++i)this.findAtPoint(e,i),this.addDOM(o,t);this.findAtPoint(e,i)}findPlace(e,t,n){let r,i;for(let t=this.open,o=0;t>=0;t--){let s=this.nodes[t],l=s.findWrapping(e);if(l&&(!r||r.length>l.length+o)&&(r=l,i=s,!l.length))break;if(s.solid){if(n)break;o+=2}}if(!r)return null;this.sync(i);for(let e=0;e<r.length;e++)t=this.enterInner(r[e],null,t,!1);return t}insertNode(e,t,n){if(e.isInline&&this.needsBlock&&!this.top.type){let e=this.textblockFromContext();e&&(t=this.enterInner(e,null,t))}let r=this.findPlace(e,t,n);if(r){this.closeExtra();let t=this.top;t.match&&(t.match=t.match.matchType(e.type));let n=l.none;for(let i of r.concat(e.marks))(t.type?t.type.allowsMarkType(i.type):re(i.type,e.type))&&(n=i.addToSet(n));return t.content.push(e.mark(n)),!0}return!1}enter(e,t,n,r){let i=this.findPlace(e.create(t),n,!1);return i&&(i=this.enterInner(e,t,n,!0,r)),i}enterInner(e,t,n,r=!1,i){this.closeExtra();let o=this.top;o.match=o.match&&o.match.matchType(e);let s=X(e,i,o.options);4&o.options&&0==o.content.length&&(s|=4);let a=l.none;return n=n.filter(t=>!(o.type?o.type.allowsMarkType(t.type):re(t.type,e))||(a=t.addToSet(a),!1)),this.nodes.push(new Y(e,t,a,r,null,s)),this.open++,n}closeExtra(e=!1){let t=this.nodes.length-1;if(t>this.open){for(;t>this.open;t--)this.nodes[t-1].content.push(this.nodes[t].finish(e));this.nodes.length=this.open+1}}finish(){return this.open=0,this.closeExtra(this.isOpen),this.nodes[0].finish(!(!this.isOpen&&!this.options.topOpen))}sync(e){for(let t=this.open;t>=0;t--){if(this.nodes[t]==e)return this.open=t,!0;this.localPreserveWS&&(this.nodes[t].options|=1)}return!1}get currentPos(){this.closeExtra();let e=0;for(let t=this.open;t>=0;t--){let n=this.nodes[t].content;for(let t=n.length-1;t>=0;t--)e+=n[t].nodeSize;t&&e++}return e}findAtPoint(e,t){if(this.find)for(let n=0;n<this.find.length;n++)this.find[n].node==e&&this.find[n].offset==t&&(this.find[n].pos=this.currentPos)}findInside(e){if(this.find)for(let t=0;t<this.find.length;t++)null==this.find[t].pos&&1==e.nodeType&&e.contains(this.find[t].node)&&(this.find[t].pos=this.currentPos)}findAround(e,t,n){if(e!=t&&this.find)for(let r=0;r<this.find.length;r++)if(null==this.find[r].pos&&1==e.nodeType&&e.contains(this.find[r].node)){t.compareDocumentPosition(this.find[r].node)&(n?2:4)&&(this.find[r].pos=this.currentPos)}}findInText(e){if(this.find)for(let t=0;t<this.find.length;t++)this.find[t].node==e&&(this.find[t].pos=this.currentPos-(e.nodeValue.length-this.find[t].offset))}matchesContext(e){if(e.indexOf("|")>-1)return e.split(/\s*\|\s*/).some(this.matchesContext,this);let t=e.split("/"),n=this.options.context,r=!(this.isOpen||n&&n.parent.type!=this.nodes[0].type),i=-(n?n.depth+1:0)+(r?0:1),o=(e,s)=>{for(;e>=0;e--){let l=t[e];if(""==l){if(e==t.length-1||0==e)continue;for(;s>=i;s--)if(o(e-1,s))return!0;return!1}{let e=s>0||0==s&&r?this.nodes[s].type:n&&s>=i?n.node(s-i).type:null;if(!e||e.name!=l&&!e.isInGroup(l))return!1;s--}}return!0};return o(t.length-1,this.open)}textblockFromContext(){let e=this.options.context;if(e)for(let t=e.depth;t>=0;t--){let n=e.node(t).contentMatchAt(e.indexAfter(t)).defaultType;if(n&&n.isTextblock&&n.defaultAttrs)return n}for(let e in this.parser.schema.nodes){let t=this.parser.schema.nodes[e];if(t.isTextblock&&t.defaultAttrs)return t}}}function te(e,t){return(e.matches||e.msMatchesSelector||e.webkitMatchesSelector||e.mozMatchesSelector).call(e,t)}function ne(e){let t={};for(let n in e)t[n]=e[n];return t}function re(e,t){let n=t.schema.nodes;for(let r in n){let i=n[r];if(!i.allowsMarkType(e))continue;let o=[],s=e=>{o.push(e);for(let n=0;n<e.edgeCount;n++){let{type:r,next:i}=e.edge(n);if(r==t)return!0;if(o.indexOf(i)<0&&s(i))return!0}};if(s(i.contentMatch))return!0}}class ie{constructor(e,t){this.nodes=e,this.marks=t}serializeFragment(e,t={},n){n||(n=se(t).createDocumentFragment());let r=n,i=[];return e.forEach(e=>{if(i.length||e.marks.length){let n=0,o=0;for(;n<i.length&&o<e.marks.length;){let t=e.marks[o];if(this.marks[t.type.name]){if(!t.eq(i[n][0])||!1===t.type.spec.spanning)break;n++,o++}else o++}for(;n<i.length;)r=i.pop()[1];for(;o<e.marks.length;){let n=e.marks[o++],s=this.serializeMark(n,e.isInline,t);s&&(i.push([n,r]),r.appendChild(s.dom),r=s.contentDOM||s.dom)}}r.appendChild(this.serializeNodeInner(e,t))}),n}serializeNodeInner(e,t){let{dom:n,contentDOM:r}=ce(se(t),this.nodes[e.type.name](e),null,e.attrs);if(r){if(e.isLeaf)throw new RangeError("Content hole not allowed in a leaf node spec");this.serializeFragment(e.content,t,r)}return n}serializeNode(e,t={}){let n=this.serializeNodeInner(e,t);for(let r=e.marks.length-1;r>=0;r--){let i=this.serializeMark(e.marks[r],e.isInline,t);i&&((i.contentDOM||i.dom).appendChild(n),n=i.dom)}return n}serializeMark(e,t,n={}){let r=this.marks[e.type.name];return r&&ce(se(n),r(e,t),null,e.attrs)}static renderSpec(e,t,n=null,r){return ce(e,t,n,r)}static fromSchema(e){return e.cached.domSerializer||(e.cached.domSerializer=new ie(this.nodesFromSchema(e),this.marksFromSchema(e)))}static nodesFromSchema(e){let t=oe(e.nodes);return t.text||(t.text=e=>e.text),t}static marksFromSchema(e){return oe(e.marks)}}function oe(e){let t={};for(let n in e){let r=e[n].spec.toDOM;r&&(t[n]=r)}return t}function se(e){return e.document||window.document}const le=new WeakMap;function ae(e){let t=le.get(e);return void 0===t&&le.set(e,t=function(e){let t=null;function n(e){if(e&&"object"==typeof e)if(Array.isArray(e))if("string"==typeof e[0])t||(t=[]),t.push(e);else for(let t=0;t<e.length;t++)n(e[t]);else for(let t in e)n(e[t])}return n(e),t}(e)),t}function ce(e,t,n,r){if("string"==typeof t)return{dom:e.createTextNode(t)};if(null!=t.nodeType)return{dom:t};if(t.dom&&null!=t.dom.nodeType)return t;let i,o=t[0];if("string"!=typeof o)throw new RangeError("Invalid array passed to renderSpec");if(r&&(i=ae(r))&&i.indexOf(t)>-1)throw new RangeError("Using an array from an attribute object as a DOM spec. This may be an attempted cross site scripting attack.");let s,l=o.indexOf(" ");l>0&&(n=o.slice(0,l),o=o.slice(l+1));let a=n?e.createElementNS(n,o):e.createElement(o),c=t[1],h=1;if(c&&"object"==typeof c&&null==c.nodeType&&!Array.isArray(c)){h=2;for(let e in c)if(null!=c[e]){let t=e.indexOf(" ");t>0?a.setAttributeNS(e.slice(0,t),e.slice(t+1),c[e]):"style"==e&&a.style?a.style.cssText=c[e]:a.setAttribute(e,c[e])}}for(let i=h;i<t.length;i++){let o=t[i];if(0===o){if(i<t.length-1||i>h)throw new RangeError("Content hole must be the only child of its parent node");return{dom:a,contentDOM:a}}{let{dom:t,contentDOM:i}=ce(e,o,n,r);if(a.appendChild(t),i){if(s)throw new RangeError("Multiple content holes");s=i}}}return{dom:a,contentDOM:s}}const he=Math.pow(2,16);function ue(e,t){return e+t*he}function pe(e){return 65535&e}class de{constructor(e,t,n){this.pos=e,this.delInfo=t,this.recover=n}get deleted(){return(8&this.delInfo)>0}get deletedBefore(){return(5&this.delInfo)>0}get deletedAfter(){return(6&this.delInfo)>0}get deletedAcross(){return(4&this.delInfo)>0}}class fe{constructor(e,t=!1){if(this.ranges=e,this.inverted=t,!e.length&&fe.empty)return fe.empty}recover(e){let t=0,n=pe(e);if(!this.inverted)for(let e=0;e<n;e++)t+=this.ranges[3*e+2]-this.ranges[3*e+1];return this.ranges[3*n]+t+function(e){return(e-(65535&e))/he}(e)}mapResult(e,t=1){return this._map(e,t,!1)}map(e,t=1){return this._map(e,t,!0)}_map(e,t,n){let r=0,i=this.inverted?2:1,o=this.inverted?1:2;for(let s=0;s<this.ranges.length;s+=3){let l=this.ranges[s]-(this.inverted?r:0);if(l>e)break;let a=this.ranges[s+i],c=this.ranges[s+o],h=l+a;if(e<=h){let i=l+r+((a?e==l?-1:e==h?1:t:t)<0?0:c);if(n)return i;let o=e==(t<0?l:h)?null:ue(s/3,e-l),u=e==l?2:e==h?1:4;return(t<0?e!=l:e!=h)&&(u|=8),new de(i,u,o)}r+=c-a}return n?e+r:new de(e+r,0,null)}touches(e,t){let n=0,r=pe(t),i=this.inverted?2:1,o=this.inverted?1:2;for(let t=0;t<this.ranges.length;t+=3){let s=this.ranges[t]-(this.inverted?n:0);if(s>e)break;let l=this.ranges[t+i];if(e<=s+l&&t==3*r)return!0;n+=this.ranges[t+o]-l}return!1}forEach(e){let t=this.inverted?2:1,n=this.inverted?1:2;for(let r=0,i=0;r<this.ranges.length;r+=3){let o=this.ranges[r],s=o-(this.inverted?i:0),l=o+(this.inverted?0:i),a=this.ranges[r+t],c=this.ranges[r+n];e(s,s+a,l,l+c),i+=c-a}}invert(){return new fe(this.ranges,!this.inverted)}toString(){return(this.inverted?"-":"")+JSON.stringify(this.ranges)}static offset(e){return 0==e?fe.empty:new fe(e<0?[0,-e,0]:[0,0,e])}}fe.empty=new fe([]);class me{constructor(e,t,n=0,r=(e?e.length:0)){this.mirror=t,this.from=n,this.to=r,this._maps=e||[],this.ownData=!(e||t)}get maps(){return this._maps}slice(e=0,t=this.maps.length){return new me(this._maps,this.mirror,e,t)}appendMap(e,t){this.ownData||(this._maps=this._maps.slice(),this.mirror=this.mirror&&this.mirror.slice(),this.ownData=!0),this.to=this._maps.push(e),null!=t&&this.setMirror(this._maps.length-1,t)}appendMapping(e){for(let t=0,n=this._maps.length;t<e._maps.length;t++){let r=e.getMirror(t);this.appendMap(e._maps[t],null!=r&&r<t?n+r:void 0)}}getMirror(e){if(this.mirror)for(let t=0;t<this.mirror.length;t++)if(this.mirror[t]==e)return this.mirror[t+(t%2?-1:1)]}setMirror(e,t){this.mirror||(this.mirror=[]),this.mirror.push(e,t)}appendMappingInverted(e){for(let t=e.maps.length-1,n=this._maps.length+e._maps.length;t>=0;t--){let r=e.getMirror(t);this.appendMap(e._maps[t].invert(),null!=r&&r>t?n-r-1:void 0)}}invert(){let e=new me;return e.appendMappingInverted(this),e}map(e,t=1){if(this.mirror)return this._map(e,t,!0);for(let n=this.from;n<this.to;n++)e=this._maps[n].map(e,t);return e}mapResult(e,t=1){return this._map(e,t,!1)}_map(e,t,n){let r=0;for(let n=this.from;n<this.to;n++){let i=this._maps[n].mapResult(e,t);if(null!=i.recover){let t=this.getMirror(n);if(null!=t&&t>n&&t<this.to){n=t,e=this._maps[t].recover(i.recover);continue}}r|=i.delInfo,e=i.pos}return n?e:new de(e,r,null)}}const ge=Object.create(null);class ye{getMap(){return fe.empty}merge(e){return null}static fromJSON(e,t){if(!t||!t.stepType)throw new RangeError("Invalid input for Step.fromJSON");let n=ge[t.stepType];if(!n)throw new RangeError(`No step type ${t.stepType} defined`);return n.fromJSON(e,t)}static jsonID(e,t){if(e in ge)throw new RangeError("Duplicate use of step JSON ID "+e);return ge[e]=t,t.prototype.jsonID=e,t}}class ke{constructor(e,t){this.doc=e,this.failed=t}static ok(e){return new ke(e,null)}static fail(e){return new ke(null,e)}static fromReplace(e,t,n,r){try{return ke.ok(e.replace(t,n,r))}catch(e){if(e instanceof a)return ke.fail(e.message);throw e}}}function be(e,t,n){let i=[];for(let r=0;r<e.childCount;r++){let o=e.child(r);o.content.size&&(o=o.copy(be(o.content,t,o))),o.isInline&&(o=t(o,n,r)),i.push(o)}return r.fromArray(i)}class we extends ye{constructor(e,t,n){super(),this.from=e,this.to=t,this.mark=n}apply(e){let t=e.slice(this.from,this.to),n=e.resolve(this.from),r=n.node(n.sharedDepth(this.to)),i=new c(be(t.content,(e,t)=>e.isAtom&&t.type.allowsMarkType(this.mark.type)?e.mark(this.mark.addToSet(e.marks)):e,r),t.openStart,t.openEnd);return ke.fromReplace(e,this.from,this.to,i)}invert(){return new Ce(this.from,this.to,this.mark)}map(e){let t=e.mapResult(this.from,1),n=e.mapResult(this.to,-1);return t.deleted&&n.deleted||t.pos>=n.pos?null:new we(t.pos,n.pos,this.mark)}merge(e){return e instanceof we&&e.mark.eq(this.mark)&&this.from<=e.to&&this.to>=e.from?new we(Math.min(this.from,e.from),Math.max(this.to,e.to),this.mark):null}toJSON(){return{stepType:"addMark",mark:this.mark.toJSON(),from:this.from,to:this.to}}static fromJSON(e,t){if("number"!=typeof t.from||"number"!=typeof t.to)throw new RangeError("Invalid input for AddMarkStep.fromJSON");return new we(t.from,t.to,e.markFromJSON(t.mark))}}ye.jsonID("addMark",we);class Ce extends ye{constructor(e,t,n){super(),this.from=e,this.to=t,this.mark=n}apply(e){let t=e.slice(this.from,this.to),n=new c(be(t.content,e=>e.mark(this.mark.removeFromSet(e.marks)),e),t.openStart,t.openEnd);return ke.fromReplace(e,this.from,this.to,n)}invert(){return new we(this.from,this.to,this.mark)}map(e){let t=e.mapResult(this.from,1),n=e.mapResult(this.to,-1);return t.deleted&&n.deleted||t.pos>=n.pos?null:new Ce(t.pos,n.pos,this.mark)}merge(e){return e instanceof Ce&&e.mark.eq(this.mark)&&this.from<=e.to&&this.to>=e.from?new Ce(Math.min(this.from,e.from),Math.max(this.to,e.to),this.mark):null}toJSON(){return{stepType:"removeMark",mark:this.mark.toJSON(),from:this.from,to:this.to}}static fromJSON(e,t){if("number"!=typeof t.from||"number"!=typeof t.to)throw new RangeError("Invalid input for RemoveMarkStep.fromJSON");return new Ce(t.from,t.to,e.markFromJSON(t.mark))}}ye.jsonID("removeMark",Ce);class xe extends ye{constructor(e,t){super(),this.pos=e,this.mark=t}apply(e){let t=e.nodeAt(this.pos);if(!t)return ke.fail("No node at mark step's position");let n=t.type.create(t.attrs,null,this.mark.addToSet(t.marks));return ke.fromReplace(e,this.pos,this.pos+1,new c(r.from(n),0,t.isLeaf?0:1))}invert(e){let t=e.nodeAt(this.pos);if(t){let e=this.mark.addToSet(t.marks);if(e.length==t.marks.length){for(let n=0;n<t.marks.length;n++)if(!t.marks[n].isInSet(e))return new xe(this.pos,t.marks[n]);return new xe(this.pos,this.mark)}}return new De(this.pos,this.mark)}map(e){let t=e.mapResult(this.pos,1);return t.deletedAfter?null:new xe(t.pos,this.mark)}toJSON(){return{stepType:"addNodeMark",pos:this.pos,mark:this.mark.toJSON()}}static fromJSON(e,t){if("number"!=typeof t.pos)throw new RangeError("Invalid input for AddNodeMarkStep.fromJSON");return new xe(t.pos,e.markFromJSON(t.mark))}}ye.jsonID("addNodeMark",xe);class De extends ye{constructor(e,t){super(),this.pos=e,this.mark=t}apply(e){let t=e.nodeAt(this.pos);if(!t)return ke.fail("No node at mark step's position");let n=t.type.create(t.attrs,null,this.mark.removeFromSet(t.marks));return ke.fromReplace(e,this.pos,this.pos+1,new c(r.from(n),0,t.isLeaf?0:1))}invert(e){let t=e.nodeAt(this.pos);return t&&this.mark.isInSet(t.marks)?new xe(this.pos,this.mark):this}map(e){let t=e.mapResult(this.pos,1);return t.deletedAfter?null:new De(t.pos,this.mark)}toJSON(){return{stepType:"removeNodeMark",pos:this.pos,mark:this.mark.toJSON()}}static fromJSON(e,t){if("number"!=typeof t.pos)throw new RangeError("Invalid input for RemoveNodeMarkStep.fromJSON");return new De(t.pos,e.markFromJSON(t.mark))}}ye.jsonID("removeNodeMark",De);class ve extends ye{constructor(e,t,n,r=!1){super(),this.from=e,this.to=t,this.slice=n,this.structure=r}apply(e){return this.structure&&Se(e,this.from,this.to)?ke.fail("Structure replace would overwrite content"):ke.fromReplace(e,this.from,this.to,this.slice)}getMap(){return new fe([this.from,this.to-this.from,this.slice.size])}invert(e){return new ve(this.from,this.from+this.slice.size,e.slice(this.from,this.to))}map(e){let t=e.mapResult(this.from,1),n=e.mapResult(this.to,-1);return t.deletedAcross&&n.deletedAcross?null:new ve(t.pos,Math.max(t.pos,n.pos),this.slice,this.structure)}merge(e){if(!(e instanceof ve)||e.structure||this.structure)return null;if(this.from+this.slice.size!=e.from||this.slice.openEnd||e.slice.openStart){if(e.to!=this.from||this.slice.openStart||e.slice.openEnd)return null;{let t=this.slice.size+e.slice.size==0?c.empty:new c(e.slice.content.append(this.slice.content),e.slice.openStart,this.slice.openEnd);return new ve(e.from,this.to,t,this.structure)}}{let t=this.slice.size+e.slice.size==0?c.empty:new c(this.slice.content.append(e.slice.content),this.slice.openStart,e.slice.openEnd);return new ve(this.from,this.to+(e.to-e.from),t,this.structure)}}toJSON(){let e={stepType:"replace",from:this.from,to:this.to};return this.slice.size&&(e.slice=this.slice.toJSON()),this.structure&&(e.structure=!0),e}static fromJSON(e,t){if("number"!=typeof t.from||"number"!=typeof t.to)throw new RangeError("Invalid input for ReplaceStep.fromJSON");return new ve(t.from,t.to,c.fromJSON(e,t.slice),!!t.structure)}}ye.jsonID("replace",ve);class _e extends ye{constructor(e,t,n,r,i,o,s=!1){super(),this.from=e,this.to=t,this.gapFrom=n,this.gapTo=r,this.slice=i,this.insert=o,this.structure=s}apply(e){if(this.structure&&(Se(e,this.from,this.gapFrom)||Se(e,this.gapTo,this.to)))return ke.fail("Structure gap-replace would overwrite content");let t=e.slice(this.gapFrom,this.gapTo);if(t.openStart||t.openEnd)return ke.fail("Gap is not a flat range");let n=this.slice.insertAt(this.insert,t.content);return n?ke.fromReplace(e,this.from,this.to,n):ke.fail("Content does not fit in gap")}getMap(){return new fe([this.from,this.gapFrom-this.from,this.insert,this.gapTo,this.to-this.gapTo,this.slice.size-this.insert])}invert(e){let t=this.gapTo-this.gapFrom;return new _e(this.from,this.from+this.slice.size+t,this.from+this.insert,this.from+this.insert+t,e.slice(this.from,this.to).removeBetween(this.gapFrom-this.from,this.gapTo-this.from),this.gapFrom-this.from,this.structure)}map(e){let t=e.mapResult(this.from,1),n=e.mapResult(this.to,-1),r=this.from==this.gapFrom?t.pos:e.map(this.gapFrom,-1),i=this.to==this.gapTo?n.pos:e.map(this.gapTo,1);return t.deletedAcross&&n.deletedAcross||r<t.pos||i>n.pos?null:new _e(t.pos,n.pos,r,i,this.slice,this.insert,this.structure)}toJSON(){let e={stepType:"replaceAround",from:this.from,to:this.to,gapFrom:this.gapFrom,gapTo:this.gapTo,insert:this.insert};return this.slice.size&&(e.slice=this.slice.toJSON()),this.structure&&(e.structure=!0),e}static fromJSON(e,t){if("number"!=typeof t.from||"number"!=typeof t.to||"number"!=typeof t.gapFrom||"number"!=typeof t.gapTo||"number"!=typeof t.insert)throw new RangeError("Invalid input for ReplaceAroundStep.fromJSON");return new _e(t.from,t.to,t.gapFrom,t.gapTo,c.fromJSON(e,t.slice),t.insert,!!t.structure)}}function Se(e,t,n){let r=e.resolve(t),i=n-t,o=r.depth;for(;i>0&&o>0&&r.indexAfter(o)==r.node(o).childCount;)o--,i--;if(i>0){let e=r.node(o).maybeChild(r.indexAfter(o));for(;i>0;){if(!e||e.isLeaf)return!0;e=e.firstChild,i--}}return!1}function Ee(e,t,n,i=n.contentMatch,o=!0){let s=e.doc.nodeAt(t),l=[],a=t+1;for(let t=0;t<s.childCount;t++){let h=s.child(t),u=a+h.nodeSize,p=i.matchType(h.type);if(p){i=p;for(let t=0;t<h.marks.length;t++)n.allowsMarkType(h.marks[t].type)||e.step(new Ce(a,u,h.marks[t]));if(o&&h.isText&&"pre"!=n.whitespace){let e,t,i=/\r?\n|\r/g;for(;e=i.exec(h.text);)t||(t=new c(r.from(n.schema.text(" ",n.allowedMarks(h.marks))),0,0)),l.push(new ve(a+e.index,a+e.index+e[0].length,t))}}else l.push(new ve(a,u,c.empty));a=u}if(!i.validEnd){let t=i.fillBefore(r.empty,!0);e.replace(a,a,new c(t,0,0))}for(let t=l.length-1;t>=0;t--)e.step(l[t])}function Ae(e,t,n){return(0==t||e.canReplace(t,e.childCount))&&(n==e.childCount||e.canReplace(0,n))}function Me(e){let t=e.parent.content.cutByIndex(e.startIndex,e.endIndex);for(let n=e.depth,r=0,i=0;;--n){let o=e.$from.node(n),s=e.$from.index(n)+r,l=e.$to.indexAfter(n)-i;if(n<e.depth&&o.canReplace(s,l,t))return n;if(0==n||o.type.spec.isolating||!Ae(o,s,l))break;s&&(r=1),l<o.childCount&&(i=1)}return null}function Oe(e,t,n,r){t.forEach((i,o)=>{if(i.isText){let s,l=/\r?\n|\r/g;for(;s=l.exec(i.text);){let i=e.mapping.slice(r).map(n+1+o+s.index);e.replaceWith(i,i+1,t.type.schema.linebreakReplacement.create())}}})}function Ne(e,t,n,r){t.forEach((i,o)=>{if(i.type==i.type.schema.linebreakReplacement){let i=e.mapping.slice(r).map(n+1+o);e.replaceWith(i,i+1,t.type.schema.text("\n"))}})}function Fe(e,t,n=1,r){let i=e.resolve(t),o=i.depth-n,s=r&&r[r.length-1]||i.parent;if(o<0||i.parent.type.spec.isolating||!i.parent.canReplace(i.index(),i.parent.childCount)||!s.type.validContent(i.parent.content.cutByIndex(i.index(),i.parent.childCount)))return!1;for(let e=i.depth-1,t=n-2;e>o;e--,t--){let n=i.node(e),o=i.index(e);if(n.type.spec.isolating)return!1;let s=n.content.cutByIndex(o,n.childCount),l=r&&r[t+1];l&&(s=s.replaceChild(0,l.type.create(l.attrs)));let a=r&&r[t]||n;if(!n.canReplace(o+1,n.childCount)||!a.type.validContent(s))return!1}let l=i.indexAfter(o),a=r&&r[0];return i.node(o).canReplaceWith(l,l,a?a.type:i.node(o+1).type)}function Te(e,t){let n=e.resolve(t),r=n.index();return i=n.nodeBefore,o=n.nodeAfter,!(!i||!o||i.isLeaf||!function(e,t){t.content.size||e.type.compatibleContent(t.type);let n=e.contentMatchAt(e.childCount),{linebreakReplacement:r}=e.type.schema;for(let i=0;i<t.childCount;i++){let o=t.child(i),s=o.type==r?e.type.schema.nodes.text:o.type;if(n=n.matchType(s),!n)return!1;if(!e.type.allowsMarks(o.marks))return!1}return n.validEnd}(i,o))&&n.parent.canReplace(r,r+1);var i,o}function Re(e,t,n=t,r=c.empty){if(t==n&&!r.size)return null;let i=e.resolve(t),o=e.resolve(n);return ze(i,o,r)?new ve(t,n,r):new Ie(i,o,r).fit()}function ze(e,t,n){return!n.openStart&&!n.openEnd&&e.start()==t.start()&&e.parent.canReplace(e.index(),t.index(),n.content)}ye.jsonID("replaceAround",_e);class Ie{constructor(e,t,n){this.$from=e,this.$to=t,this.unplaced=n,this.frontier=[],this.placed=r.empty;for(let t=0;t<=e.depth;t++){let n=e.node(t);this.frontier.push({type:n.type,match:n.contentMatchAt(e.indexAfter(t))})}for(let t=e.depth;t>0;t--)this.placed=r.from(e.node(t).copy(this.placed))}get depth(){return this.frontier.length-1}fit(){for(;this.unplaced.size;){let e=this.findFittable();e?this.placeNodes(e):this.openMore()||this.dropNode()}let e=this.mustMoveInline(),t=this.placed.size-this.depth-this.$from.depth,n=this.$from,r=this.close(e<0?this.$to:n.doc.resolve(e));if(!r)return null;let i=this.placed,o=n.depth,s=r.depth;for(;o&&s&&1==i.childCount;)i=i.firstChild.content,o--,s--;let l=new c(i,o,s);return e>-1?new _e(n.pos,e,this.$to.pos,this.$to.end(),l,t):l.size||n.pos!=this.$to.pos?new ve(n.pos,r.pos,l):null}findFittable(){let e=this.unplaced.openStart;for(let t=this.unplaced.content,n=0,r=this.unplaced.openEnd;n<e;n++){let i=t.firstChild;if(t.childCount>1&&(r=0),i.type.spec.isolating&&r<=n){e=n;break}t=i.content}for(let t=1;t<=2;t++)for(let n=1==t?e:this.unplaced.openStart;n>=0;n--){let e,i=null;n?(i=Le(this.unplaced.content,n-1).firstChild,e=i.content):e=this.unplaced.content;let o=e.firstChild;for(let e=this.depth;e>=0;e--){let s,{type:l,match:a}=this.frontier[e],c=null;if(1==t&&(o?a.matchType(o.type)||(c=a.fillBefore(r.from(o),!1)):i&&l.compatibleContent(i.type)))return{sliceDepth:n,frontierDepth:e,parent:i,inject:c};if(2==t&&o&&(s=a.findWrapping(o.type)))return{sliceDepth:n,frontierDepth:e,parent:i,wrap:s};if(i&&a.matchType(i.type))break}}}openMore(){let{content:e,openStart:t,openEnd:n}=this.unplaced,r=Le(e,t);return!(!r.childCount||r.firstChild.isLeaf)&&(this.unplaced=new c(e,t+1,Math.max(n,r.size+t>=e.size-n?t+1:0)),!0)}dropNode(){let{content:e,openStart:t,openEnd:n}=this.unplaced,r=Le(e,t);if(r.childCount<=1&&t>0){let i=e.size-t<=t+r.size;this.unplaced=new c(Be(e,t-1,1),t-1,i?t-1:n)}else this.unplaced=new c(Be(e,t,1),t,n)}placeNodes({sliceDepth:e,frontierDepth:t,parent:n,inject:i,wrap:o}){for(;this.depth>t;)this.closeFrontierNode();if(o)for(let e=0;e<o.length;e++)this.openFrontierNode(o[e]);let s=this.unplaced,l=n?n.content:s.content,a=s.openStart-e,h=0,u=[],{match:p,type:d}=this.frontier[t];if(i){for(let e=0;e<i.childCount;e++)u.push(i.child(e));p=p.matchFragment(i)}let f=l.size+e-(s.content.size-s.openEnd);for(;h<l.childCount;){let e=l.child(h),t=p.matchType(e.type);if(!t)break;h++,(h>1||0==a||e.content.size)&&(p=t,u.push(qe(e.mark(d.allowedMarks(e.marks)),1==h?a:0,h==l.childCount?f:-1)))}let m=h==l.childCount;m||(f=-1),this.placed=Pe(this.placed,t,r.from(u)),this.frontier[t].match=p,m&&f<0&&n&&n.type==this.frontier[this.depth].type&&this.frontier.length>1&&this.closeFrontierNode();for(let e=0,t=l;e<f;e++){let e=t.lastChild;this.frontier.push({type:e.type,match:e.contentMatchAt(e.childCount)}),t=e.content}this.unplaced=m?0==e?c.empty:new c(Be(s.content,e-1,1),e-1,f<0?s.openEnd:e-1):new c(Be(s.content,e,h),s.openStart,s.openEnd)}mustMoveInline(){if(!this.$to.parent.isTextblock)return-1;let e,t=this.frontier[this.depth];if(!t.type.isTextblock||!$e(this.$to,this.$to.depth,t.type,t.match,!1)||this.$to.depth==this.depth&&(e=this.findCloseLevel(this.$to))&&e.depth==this.depth)return-1;let{depth:n}=this.$to,r=this.$to.after(n);for(;n>1&&r==this.$to.end(--n);)++r;return r}findCloseLevel(e){e:for(let t=Math.min(this.depth,e.depth);t>=0;t--){let{match:n,type:r}=this.frontier[t],i=t<e.depth&&e.end(t+1)==e.pos+(e.depth-(t+1)),o=$e(e,t,r,n,i);if(o){for(let n=t-1;n>=0;n--){let{match:t,type:r}=this.frontier[n],i=$e(e,n,r,t,!0);if(!i||i.childCount)continue e}return{depth:t,fit:o,move:i?e.doc.resolve(e.after(t+1)):e}}}}close(e){let t=this.findCloseLevel(e);if(!t)return null;for(;this.depth>t.depth;)this.closeFrontierNode();t.fit.childCount&&(this.placed=Pe(this.placed,t.depth,t.fit)),e=t.move;for(let n=t.depth+1;n<=e.depth;n++){let t=e.node(n),r=t.type.contentMatch.fillBefore(t.content,!0,e.index(n));this.openFrontierNode(t.type,t.attrs,r)}return e}openFrontierNode(e,t=null,n){let i=this.frontier[this.depth];i.match=i.match.matchType(e),this.placed=Pe(this.placed,this.depth,r.from(e.create(t,n))),this.frontier.push({type:e,match:e.contentMatch})}closeFrontierNode(){let e=this.frontier.pop().match.fillBefore(r.empty,!0);e.childCount&&(this.placed=Pe(this.placed,this.frontier.length,e))}}function Be(e,t,n){return 0==t?e.cutByIndex(n,e.childCount):e.replaceChild(0,e.firstChild.copy(Be(e.firstChild.content,t-1,n)))}function Pe(e,t,n){return 0==t?e.append(n):e.replaceChild(e.childCount-1,e.lastChild.copy(Pe(e.lastChild.content,t-1,n)))}function Le(e,t){for(let n=0;n<t;n++)e=e.firstChild.content;return e}function qe(e,t,n){if(t<=0)return e;let i=e.content;return t>1&&(i=i.replaceChild(0,qe(i.firstChild,t-1,1==i.childCount?n-1:0))),t>0&&(i=e.type.contentMatch.fillBefore(i).append(i),n<=0&&(i=i.append(e.type.contentMatch.matchFragment(i).fillBefore(r.empty,!0)))),e.copy(i)}function $e(e,t,n,r,i){let o=e.node(t),s=i?e.indexAfter(t):e.index(t);if(s==o.childCount&&!n.compatibleContent(o.type))return null;let l=r.fillBefore(o.content,!0,s);return l&&!function(e,t,n){for(let r=n;r<t.childCount;r++)if(!e.allowsMarks(t.child(r).marks))return!0;return!1}(n,o.content,s)?l:null}function Ve(e){return e.spec.defining||e.spec.definingForContent}function je(e,t,n,i,o){if(t<n){let r=e.firstChild;e=e.replaceChild(0,r.copy(je(r.content,t+1,n,i,r)))}if(t>i){let t=o.contentMatchAt(0),n=t.fillBefore(e).append(e);e=n.append(t.matchFragment(n).fillBefore(r.empty,!0))}return e}function Je(e,t){let n=[];for(let r=Math.min(e.depth,t.depth);r>=0;r--){let i=e.start(r);if(i<e.pos-(e.depth-r)||t.end(r)>t.pos+(t.depth-r)||e.node(r).type.spec.isolating||t.node(r).type.spec.isolating)break;(i==t.start(r)||r==e.depth&&r==t.depth&&e.parent.inlineContent&&t.parent.inlineContent&&r&&t.start(r-1)==i-1)&&n.push(r)}return n}class We extends ye{constructor(e,t,n){super(),this.pos=e,this.attr=t,this.value=n}apply(e){let t=e.nodeAt(this.pos);if(!t)return ke.fail("No node at attribute step's position");let n=Object.create(null);for(let e in t.attrs)n[e]=t.attrs[e];n[this.attr]=this.value;let i=t.type.create(n,null,t.marks);return ke.fromReplace(e,this.pos,this.pos+1,new c(r.from(i),0,t.isLeaf?0:1))}getMap(){return fe.empty}invert(e){return new We(this.pos,this.attr,e.nodeAt(this.pos).attrs[this.attr])}map(e){let t=e.mapResult(this.pos,1);return t.deletedAfter?null:new We(t.pos,this.attr,this.value)}toJSON(){return{stepType:"attr",pos:this.pos,attr:this.attr,value:this.value}}static fromJSON(e,t){if("number"!=typeof t.pos||"string"!=typeof t.attr)throw new RangeError("Invalid input for AttrStep.fromJSON");return new We(t.pos,t.attr,t.value)}}ye.jsonID("attr",We);class He extends ye{constructor(e,t){super(),this.attr=e,this.value=t}apply(e){let t=Object.create(null);for(let n in e.attrs)t[n]=e.attrs[n];t[this.attr]=this.value;let n=e.type.create(t,e.content,e.marks);return ke.ok(n)}getMap(){return fe.empty}invert(e){return new He(this.attr,e.attrs[this.attr])}map(e){return this}toJSON(){return{stepType:"docAttr",attr:this.attr,value:this.value}}static fromJSON(e,t){if("string"!=typeof t.attr)throw new RangeError("Invalid input for DocAttrStep.fromJSON");return new He(t.attr,t.value)}}ye.jsonID("docAttr",He);let Ue=class extends Error{};Ue=function e(t){let n=Error.call(this,t);return n.__proto__=e.prototype,n},(Ue.prototype=Object.create(Error.prototype)).constructor=Ue,Ue.prototype.name="TransformError";class Ke{constructor(e){this.doc=e,this.steps=[],this.docs=[],this.mapping=new me}get before(){return this.docs.length?this.docs[0]:this.doc}step(e){let t=this.maybeStep(e);if(t.failed)throw new Ue(t.failed);return this}maybeStep(e){let t=e.apply(this.doc);return t.failed||this.addStep(e,t.doc),t}get docChanged(){return this.steps.length>0}addStep(e,t){this.docs.push(this.doc),this.steps.push(e),this.mapping.appendMap(e.getMap()),this.doc=t}replace(e,t=e,n=c.empty){let r=Re(this.doc,e,t,n);return r&&this.step(r),this}replaceWith(e,t,n){return this.replace(e,t,new c(r.from(n),0,0))}delete(e,t){return this.replace(e,t,c.empty)}insert(e,t){return this.replaceWith(e,e,t)}replaceRange(e,t,n){return function(e,t,n,r){if(!r.size)return e.deleteRange(t,n);let i=e.doc.resolve(t),o=e.doc.resolve(n);if(ze(i,o,r))return e.step(new ve(t,n,r));let s=Je(i,o);0==s[s.length-1]&&s.pop();let l=-(i.depth+1);s.unshift(l);for(let e=i.depth,t=i.pos-1;e>0;e--,t--){let n=i.node(e).type.spec;if(n.defining||n.definingAsContext||n.isolating)break;s.indexOf(e)>-1?l=e:i.before(e)==t&&s.splice(1,0,-e)}let a=s.indexOf(l),h=[],u=r.openStart;for(let e=r.content,t=0;;t++){let n=e.firstChild;if(h.push(n),t==r.openStart)break;e=n.content}for(let e=u-1;e>=0;e--){let t=h[e],n=Ve(t.type);if(n&&!t.sameMarkup(i.node(Math.abs(l)-1)))u=e;else if(n||!t.type.isTextblock)break}for(let t=r.openStart;t>=0;t--){let l=(t+u+1)%(r.openStart+1),p=h[l];if(p)for(let t=0;t<s.length;t++){let h=s[(t+a)%s.length],u=!0;h<0&&(u=!1,h=-h);let d=i.node(h-1),f=i.index(h-1);if(d.canReplaceWith(f,f,p.type,p.marks))return e.replace(i.before(h),u?o.after(h):n,new c(je(r.content,0,r.openStart,l),l,r.openEnd))}}let p=e.steps.length;for(let l=s.length-1;l>=0&&(e.replace(t,n,r),!(e.steps.length>p));l--){let e=s[l];e<0||(t=i.before(e),n=o.after(e))}}(this,e,t,n),this}replaceRangeWith(e,t,n){return function(e,t,n,i){if(!i.isInline&&t==n&&e.doc.resolve(t).parent.content.size){let r=function(e,t,n){let r=e.resolve(t);if(r.parent.canReplaceWith(r.index(),r.index(),n))return t;if(0==r.parentOffset)for(let e=r.depth-1;e>=0;e--){let t=r.index(e);if(r.node(e).canReplaceWith(t,t,n))return r.before(e+1);if(t>0)return null}if(r.parentOffset==r.parent.content.size)for(let e=r.depth-1;e>=0;e--){let t=r.indexAfter(e);if(r.node(e).canReplaceWith(t,t,n))return r.after(e+1);if(t<r.node(e).childCount)return null}return null}(e.doc,t,i.type);null!=r&&(t=n=r)}e.replaceRange(t,n,new c(r.from(i),0,0))}(this,e,t,n),this}deleteRange(e,t){return function(e,t,n){let r=e.doc.resolve(t),i=e.doc.resolve(n),o=Je(r,i);for(let t=0;t<o.length;t++){let n=o[t],s=t==o.length-1;if(s&&0==n||r.node(n).type.contentMatch.validEnd)return e.delete(r.start(n),i.end(n));if(n>0&&(s||r.node(n-1).canReplace(r.index(n-1),i.indexAfter(n-1))))return e.delete(r.before(n),i.after(n))}for(let o=1;o<=r.depth&&o<=i.depth;o++)if(t-r.start(o)==r.depth-o&&n>r.end(o)&&i.end(o)-n!=i.depth-o&&r.start(o-1)==i.start(o-1)&&r.node(o-1).canReplace(r.index(o-1),i.index(o-1)))return e.delete(r.before(o),n);e.delete(t,n)}(this,e,t),this}lift(e,t){return function(e,t,n){let{$from:i,$to:o,depth:s}=t,l=i.before(s+1),a=o.after(s+1),h=l,u=a,p=r.empty,d=0;for(let e=s,t=!1;e>n;e--)t||i.index(e)>0?(t=!0,p=r.from(i.node(e).copy(p)),d++):h--;let f=r.empty,m=0;for(let e=s,t=!1;e>n;e--)t||o.after(e+1)<o.end(e)?(t=!0,f=r.from(o.node(e).copy(f)),m++):u++;e.step(new _e(h,u,l,a,new c(p.append(f),d,m),p.size-d,!0))}(this,e,t),this}join(e,t=1){return function(e,t,n){let r=null,{linebreakReplacement:i}=e.doc.type.schema,o=e.doc.resolve(t-n),s=o.node().type;if(i&&s.inlineContent){let e="pre"==s.whitespace,t=!!s.contentMatch.matchType(i);e&&!t?r=!1:!e&&t&&(r=!0)}let l=e.steps.length;if(!1===r){let r=e.doc.resolve(t+n);Ne(e,r.node(),r.before(),l)}s.inlineContent&&Ee(e,t+n-1,s,o.node().contentMatchAt(o.index()),null==r);let a=e.mapping.slice(l),h=a.map(t-n);if(e.step(new ve(h,a.map(t+n,-1),c.empty,!0)),!0===r){let t=e.doc.resolve(h);Oe(e,t.node(),t.before(),e.steps.length)}}(this,e,t),this}wrap(e,t){return function(e,t,n){let i=r.empty;for(let e=n.length-1;e>=0;e--){if(i.size){let t=n[e].type.contentMatch.matchFragment(i);if(!t||!t.validEnd)throw new RangeError("Wrapper type given to Transform.wrap does not form valid content of its parent wrapper")}i=r.from(n[e].type.create(n[e].attrs,i))}let o=t.start,s=t.end;e.step(new _e(o,s,o,s,new c(i,0,0),n.length,!0))}(this,e,t),this}setBlockType(e,t=e,n,i=null){return function(e,t,n,i,o){if(!i.isTextblock)throw new RangeError("Type given to setBlockType should be a textblock");let s=e.steps.length;e.doc.nodesBetween(t,n,(t,n)=>{let l="function"==typeof o?o(t):o;if(t.isTextblock&&!t.hasMarkup(i,l)&&function(e,t,n){let r=e.resolve(t),i=r.index();return r.parent.canReplaceWith(i,i+1,n)}(e.doc,e.mapping.slice(s).map(n),i)){let o=null;if(i.schema.linebreakReplacement){let e="pre"==i.whitespace,t=!!i.contentMatch.matchType(i.schema.linebreakReplacement);e&&!t?o=!1:!e&&t&&(o=!0)}!1===o&&Ne(e,t,n,s),Ee(e,e.mapping.slice(s).map(n,1),i,void 0,null===o);let a=e.mapping.slice(s),h=a.map(n,1),u=a.map(n+t.nodeSize,1);return e.step(new _e(h,u,h+1,u-1,new c(r.from(i.create(l,null,t.marks)),0,0),1,!0)),!0===o&&Oe(e,t,n,s),!1}})}(this,e,t,n,i),this}setNodeMarkup(e,t,n=null,i){return function(e,t,n,i,o){let s=e.doc.nodeAt(t);if(!s)throw new RangeError("No node at given position");n||(n=s.type);let l=n.create(i,null,o||s.marks);if(s.isLeaf)return e.replaceWith(t,t+s.nodeSize,l);if(!n.validContent(s.content))throw new RangeError("Invalid content for node type "+n.name);e.step(new _e(t,t+s.nodeSize,t+1,t+s.nodeSize-1,new c(r.from(l),0,0),1,!0))}(this,e,t,n,i),this}setNodeAttribute(e,t,n){return this.step(new We(e,t,n)),this}setDocAttribute(e,t){return this.step(new He(e,t)),this}addNodeMark(e,t){return this.step(new xe(e,t)),this}removeNodeMark(e,t){let n=this.doc.nodeAt(e);if(!n)throw new RangeError("No node at position "+e);if(t instanceof l)t.isInSet(n.marks)&&this.step(new De(e,t));else{let r,i=n.marks,o=[];for(;r=t.isInSet(i);)o.push(new De(e,r)),i=r.removeFromSet(i);for(let e=o.length-1;e>=0;e--)this.step(o[e])}return this}split(e,t=1,n){return function(e,t,n=1,i){let o=e.doc.resolve(t),s=r.empty,l=r.empty;for(let e=o.depth,t=o.depth-n,a=n-1;e>t;e--,a--){s=r.from(o.node(e).copy(s));let t=i&&i[a];l=r.from(t?t.type.create(t.attrs,l):o.node(e).copy(l))}e.step(new ve(t,t,new c(s.append(l),n,n),!0))}(this,e,t,n),this}addMark(e,t,n){return function(e,t,n,r){let i,o,s=[],l=[];e.doc.nodesBetween(t,n,(e,a,c)=>{if(!e.isInline)return;let h=e.marks;if(!r.isInSet(h)&&c.type.allowsMarkType(r.type)){let c=Math.max(a,t),u=Math.min(a+e.nodeSize,n),p=r.addToSet(h);for(let e=0;e<h.length;e++)h[e].isInSet(p)||(i&&i.to==c&&i.mark.eq(h[e])?i.to=u:s.push(i=new Ce(c,u,h[e])));o&&o.to==c?o.to=u:l.push(o=new we(c,u,r))}}),s.forEach(t=>e.step(t)),l.forEach(t=>e.step(t))}(this,e,t,n),this}removeMark(e,t,n){return function(e,t,n,r){let i=[],o=0;e.doc.nodesBetween(t,n,(e,s)=>{if(!e.isInline)return;o++;let l=null;if(r instanceof W){let t,n=e.marks;for(;t=r.isInSet(n);)(l||(l=[])).push(t),n=t.removeFromSet(n)}else r?r.isInSet(e.marks)&&(l=[r]):l=e.marks;if(l&&l.length){let r=Math.min(s+e.nodeSize,n);for(let e=0;e<l.length;e++){let n,a=l[e];for(let e=0;e<i.length;e++){let t=i[e];t.step==o-1&&a.eq(i[e].style)&&(n=t)}n?(n.to=r,n.step=o):i.push({style:a,from:Math.max(s,t),to:r,step:o})}}}),i.forEach(t=>e.step(new Ce(t.from,t.to,t.style)))}(this,e,t,n),this}clearIncompatible(e,t,n){return Ee(this,e,t,n),this}}const Ze=Object.create(null);class Ge{constructor(e,t,n){this.$anchor=e,this.$head=t,this.ranges=n||[new Qe(e.min(t),e.max(t))]}get anchor(){return this.$anchor.pos}get head(){return this.$head.pos}get from(){return this.$from.pos}get to(){return this.$to.pos}get $from(){return this.ranges[0].$from}get $to(){return this.ranges[0].$to}get empty(){let e=this.ranges;for(let t=0;t<e.length;t++)if(e[t].$from.pos!=e[t].$to.pos)return!1;return!0}content(){return this.$from.doc.slice(this.from,this.to,!0)}replace(e,t=c.empty){let n=t.content.lastChild,r=null;for(let e=0;e<t.openEnd;e++)r=n,n=n.lastChild;let i=e.steps.length,o=this.ranges;for(let s=0;s<o.length;s++){let{$from:l,$to:a}=o[s],h=e.mapping.slice(i);e.replaceRange(h.map(l.pos),h.map(a.pos),s?c.empty:t),0==s&<(e,i,(n?n.isInline:r&&r.isTextblock)?-1:1)}}replaceWith(e,t){let n=e.steps.length,r=this.ranges;for(let i=0;i<r.length;i++){let{$from:o,$to:s}=r[i],l=e.mapping.slice(n),a=l.map(o.pos),c=l.map(s.pos);i?e.deleteRange(a,c):(e.replaceRangeWith(a,c,t),lt(e,n,t.isInline?-1:1))}}static findFrom(e,t,n=!1){let r=e.parent.inlineContent?new et(e):st(e.node(0),e.parent,e.pos,e.index(),t,n);if(r)return r;for(let r=e.depth-1;r>=0;r--){let i=t<0?st(e.node(0),e.node(r),e.before(r+1),e.index(r),t,n):st(e.node(0),e.node(r),e.after(r+1),e.index(r)+1,t,n);if(i)return i}return null}static near(e,t=1){return this.findFrom(e,t)||this.findFrom(e,-t)||new it(e.node(0))}static atStart(e){return st(e,e,0,0,1)||new it(e)}static atEnd(e){return st(e,e,e.content.size,e.childCount,-1)||new it(e)}static fromJSON(e,t){if(!t||!t.type)throw new RangeError("Invalid input for Selection.fromJSON");let n=Ze[t.type];if(!n)throw new RangeError(`No selection type ${t.type} defined`);return n.fromJSON(e,t)}static jsonID(e,t){if(e in Ze)throw new RangeError("Duplicate use of selection JSON ID "+e);return Ze[e]=t,t.prototype.jsonID=e,t}getBookmark(){return et.between(this.$anchor,this.$head).getBookmark()}}Ge.prototype.visible=!0;class Qe{constructor(e,t){this.$from=e,this.$to=t}}let Xe=!1;function Ye(e){Xe||e.parent.inlineContent||(Xe=!0,console.warn("TextSelection endpoint not pointing into a node with inline content ("+e.parent.type.name+")"))}class et extends Ge{constructor(e,t=e){Ye(e),Ye(t),super(e,t)}get $cursor(){return this.$anchor.pos==this.$head.pos?this.$head:null}map(e,t){let n=e.resolve(t.map(this.head));if(!n.parent.inlineContent)return Ge.near(n);let r=e.resolve(t.map(this.anchor));return new et(r.parent.inlineContent?r:n,n)}replace(e,t=c.empty){if(super.replace(e,t),t==c.empty){let t=this.$from.marksAcross(this.$to);t&&e.ensureMarks(t)}}eq(e){return e instanceof et&&e.anchor==this.anchor&&e.head==this.head}getBookmark(){return new tt(this.anchor,this.head)}toJSON(){return{type:"text",anchor:this.anchor,head:this.head}}static fromJSON(e,t){if("number"!=typeof t.anchor||"number"!=typeof t.head)throw new RangeError("Invalid input for TextSelection.fromJSON");return new et(e.resolve(t.anchor),e.resolve(t.head))}static create(e,t,n=t){let r=e.resolve(t);return new this(r,n==t?r:e.resolve(n))}static between(e,t,n){let r=e.pos-t.pos;if(n&&!r||(n=r>=0?1:-1),!t.parent.inlineContent){let e=Ge.findFrom(t,n,!0)||Ge.findFrom(t,-n,!0);if(!e)return Ge.near(t,n);t=e.$head}return e.parent.inlineContent||(0==r||(e=(Ge.findFrom(e,-n,!0)||Ge.findFrom(e,n,!0)).$anchor).pos<t.pos!=r<0)&&(e=t),new et(e,t)}}Ge.jsonID("text",et);class tt{constructor(e,t){this.anchor=e,this.head=t}map(e){return new tt(e.map(this.anchor),e.map(this.head))}resolve(e){return et.between(e.resolve(this.anchor),e.resolve(this.head))}}class nt extends Ge{constructor(e){let t=e.nodeAfter,n=e.node(0).resolve(e.pos+t.nodeSize);super(e,n),this.node=t}map(e,t){let{deleted:n,pos:r}=t.mapResult(this.anchor),i=e.resolve(r);return n?Ge.near(i):new nt(i)}content(){return new c(r.from(this.node),0,0)}eq(e){return e instanceof nt&&e.anchor==this.anchor}toJSON(){return{type:"node",anchor:this.anchor}}getBookmark(){return new rt(this.anchor)}static fromJSON(e,t){if("number"!=typeof t.anchor)throw new RangeError("Invalid input for NodeSelection.fromJSON");return new nt(e.resolve(t.anchor))}static create(e,t){return new nt(e.resolve(t))}static isSelectable(e){return!e.isText&&!1!==e.type.spec.selectable}}nt.prototype.visible=!1,Ge.jsonID("node",nt);class rt{constructor(e){this.anchor=e}map(e){let{deleted:t,pos:n}=e.mapResult(this.anchor);return t?new tt(n,n):new rt(n)}resolve(e){let t=e.resolve(this.anchor),n=t.nodeAfter;return n&&nt.isSelectable(n)?new nt(t):Ge.near(t)}}class it extends Ge{constructor(e){super(e.resolve(0),e.resolve(e.content.size))}replace(e,t=c.empty){if(t==c.empty){e.delete(0,e.doc.content.size);let t=Ge.atStart(e.doc);t.eq(e.selection)||e.setSelection(t)}else super.replace(e,t)}toJSON(){return{type:"all"}}static fromJSON(e){return new it(e)}map(e){return new it(e)}eq(e){return e instanceof it}getBookmark(){return ot}}Ge.jsonID("all",it);const ot={map(){return this},resolve:e=>new it(e)};function st(e,t,n,r,i,o=!1){if(t.inlineContent)return et.create(e,n);for(let s=r-(i>0?0:1);i>0?s<t.childCount:s>=0;s+=i){let r=t.child(s);if(r.isAtom){if(!o&&nt.isSelectable(r))return nt.create(e,n-(i<0?r.nodeSize:0))}else{let t=st(e,r,n+i,i<0?r.childCount:0,i,o);if(t)return t}n+=r.nodeSize*i}return null}function lt(e,t,n){let r=e.steps.length-1;if(r<t)return;let i,o=e.steps[r];(o instanceof ve||o instanceof _e)&&(e.mapping.maps[r].forEach((e,t,n,r)=>{null==i&&(i=r)}),e.setSelection(Ge.near(e.doc.resolve(i),n)))}class at extends Ke{constructor(e){super(e.doc),this.curSelectionFor=0,this.updated=0,this.meta=Object.create(null),this.time=Date.now(),this.curSelection=e.selection,this.storedMarks=e.storedMarks}get selection(){return this.curSelectionFor<this.steps.length&&(this.curSelection=this.curSelection.map(this.doc,this.mapping.slice(this.curSelectionFor)),this.curSelectionFor=this.steps.length),this.curSelection}setSelection(e){if(e.$from.doc!=this.doc)throw new RangeError("Selection passed to setSelection must point at the current document");return this.curSelection=e,this.curSelectionFor=this.steps.length,this.updated=-3&this.updated|1,this.storedMarks=null,this}get selectionSet(){return(1&this.updated)>0}setStoredMarks(e){return this.storedMarks=e,this.updated|=2,this}ensureMarks(e){return l.sameSet(this.storedMarks||this.selection.$from.marks(),e)||this.setStoredMarks(e),this}addStoredMark(e){return this.ensureMarks(e.addToSet(this.storedMarks||this.selection.$head.marks()))}removeStoredMark(e){return this.ensureMarks(e.removeFromSet(this.storedMarks||this.selection.$head.marks()))}get storedMarksSet(){return(2&this.updated)>0}addStep(e,t){super.addStep(e,t),this.updated=-3&this.updated,this.storedMarks=null}setTime(e){return this.time=e,this}replaceSelection(e){return this.selection.replace(this,e),this}replaceSelectionWith(e,t=!0){let n=this.selection;return t&&(e=e.mark(this.storedMarks||(n.empty?n.$from.marks():n.$from.marksAcross(n.$to)||l.none))),n.replaceWith(this,e),this}deleteSelection(){return this.selection.replace(this),this}insertText(e,t,n){let r=this.doc.type.schema;if(null==t)return e?this.replaceSelectionWith(r.text(e),!0):this.deleteSelection();{if(null==n&&(n=t),!e)return this.deleteRange(t,n);let i=this.storedMarks;if(!i){let e=this.doc.resolve(t);i=n==t?e.marks():e.marksAcross(this.doc.resolve(n))}return this.replaceRangeWith(t,n,r.text(e,i)),this.selection.empty||this.selection.to!=t+e.length||this.setSelection(Ge.near(this.selection.$to)),this}}setMeta(e,t){return this.meta["string"==typeof e?e:e.key]=t,this}getMeta(e){return this.meta["string"==typeof e?e:e.key]}get isGeneric(){for(let e in this.meta)return!1;return!0}scrollIntoView(){return this.updated|=4,this}get scrolledIntoView(){return(4&this.updated)>0}}function ct(e,t){return t&&e?e.bind(t):e}class ht{constructor(e,t,n){this.name=e,this.init=ct(t.init,n),this.apply=ct(t.apply,n)}}const ut=[new ht("doc",{init:e=>e.doc||e.schema.topNodeType.createAndFill(),apply:e=>e.doc}),new ht("selection",{init:(e,t)=>e.selection||Ge.atStart(t.doc),apply:e=>e.selection}),new ht("storedMarks",{init:e=>e.storedMarks||null,apply:(e,t,n,r)=>r.selection.$cursor?e.storedMarks:null}),new ht("scrollToSelection",{init:()=>0,apply:(e,t)=>e.scrolledIntoView?t+1:t})];class pt{constructor(e,t){this.schema=e,this.plugins=[],this.pluginsByKey=Object.create(null),this.fields=ut.slice(),t&&t.forEach(e=>{if(this.pluginsByKey[e.key])throw new RangeError("Adding different instances of a keyed plugin ("+e.key+")");this.plugins.push(e),this.pluginsByKey[e.key]=e,e.spec.state&&this.fields.push(new ht(e.key,e.spec.state,e))})}}class dt{constructor(e){this.config=e}get schema(){return this.config.schema}get plugins(){return this.config.plugins}apply(e){return this.applyTransaction(e).state}filterTransaction(e,t=-1){for(let n=0;n<this.config.plugins.length;n++)if(n!=t){let t=this.config.plugins[n];if(t.spec.filterTransaction&&!t.spec.filterTransaction.call(t,e,this))return!1}return!0}applyTransaction(e){if(!this.filterTransaction(e))return{state:this,transactions:[]};let t=[e],n=this.applyInner(e),r=null;for(;;){let i=!1;for(let o=0;o<this.config.plugins.length;o++){let s=this.config.plugins[o];if(s.spec.appendTransaction){let l=r?r[o].n:0,a=r?r[o].state:this,c=l<t.length&&s.spec.appendTransaction.call(s,l?t.slice(l):t,a,n);if(c&&n.filterTransaction(c,o)){if(c.setMeta("appendedTransaction",e),!r){r=[];for(let e=0;e<this.config.plugins.length;e++)r.push(e<o?{state:n,n:t.length}:{state:this,n:0})}t.push(c),n=n.applyInner(c),i=!0}r&&(r[o]={state:n,n:t.length})}}if(!i)return{state:n,transactions:t}}}applyInner(e){if(!e.before.eq(this.doc))throw new RangeError("Applying a mismatched transaction");let t=new dt(this.config),n=this.config.fields;for(let r=0;r<n.length;r++){let i=n[r];t[i.name]=i.apply(e,this[i.name],this,t)}return t}get tr(){return new at(this)}static create(e){let t=new pt(e.doc?e.doc.type.schema:e.schema,e.plugins),n=new dt(t);for(let r=0;r<t.fields.length;r++)n[t.fields[r].name]=t.fields[r].init(e,n);return n}reconfigure(e){let t=new pt(this.schema,e.plugins),n=t.fields,r=new dt(t);for(let t=0;t<n.length;t++){let i=n[t].name;r[i]=this.hasOwnProperty(i)?this[i]:n[t].init(e,r)}return r}toJSON(e){let t={doc:this.doc.toJSON(),selection:this.selection.toJSON()};if(this.storedMarks&&(t.storedMarks=this.storedMarks.map(e=>e.toJSON())),e&&"object"==typeof e)for(let n in e){if("doc"==n||"selection"==n)throw new RangeError("The JSON fields `doc` and `selection` are reserved");let r=e[n],i=r.spec.state;i&&i.toJSON&&(t[n]=i.toJSON.call(r,this[r.key]))}return t}static fromJSON(e,t,n){if(!t)throw new RangeError("Invalid input for EditorState.fromJSON");if(!e.schema)throw new RangeError("Required config field 'schema' missing");let r=new pt(e.schema,e.plugins),i=new dt(r);return r.fields.forEach(r=>{if("doc"==r.name)i.doc=E.fromJSON(e.schema,t.doc);else if("selection"==r.name)i.selection=Ge.fromJSON(i.doc,t.selection);else if("storedMarks"==r.name)t.storedMarks&&(i.storedMarks=t.storedMarks.map(e.schema.markFromJSON));else{if(n)for(let o in n){let s=n[o],l=s.spec.state;if(s.key==r.name&&l&&l.fromJSON&&Object.prototype.hasOwnProperty.call(t,o))return void(i[r.name]=l.fromJSON.call(s,e,t[o],i))}i[r.name]=r.init(e,i)}}),i}}function ft(e,t,n){for(let r in e){let i=e[r];i instanceof Function?i=i.bind(t):"handleDOMEvents"==r&&(i=ft(i,t,{})),n[r]=i}return n}class mt{constructor(e){this.spec=e,this.props={},e.props&&ft(e.props,this,this.props),this.key=e.key?e.key.key:yt("plugin")}getState(e){return e[this.key]}}const gt=Object.create(null);function yt(e){return e in gt?e+"$"+ ++gt[e]:(gt[e]=0,e+"$")}class kt{constructor(e="key"){this.key=yt(e)}get(e){return e.config.pluginsByKey[this.key]}getState(e){return e[this.key]}}const bt=(e,t)=>!e.selection.empty&&(t&&t(e.tr.deleteSelection().scrollIntoView()),!0);function wt(e,t,n=!1){for(let r=e;r;r="start"==t?r.firstChild:r.lastChild){if(r.isTextblock)return!0;if(n&&1!=r.childCount)return!1}return!1}function Ct(e){if(!e.parent.type.spec.isolating)for(let t=e.depth-1;t>=0;t--){if(e.index(t)>0)return e.doc.resolve(e.before(t+1));if(e.node(t).type.spec.isolating)break}return null}function xt(e){if(!e.parent.type.spec.isolating)for(let t=e.depth-1;t>=0;t--){let n=e.node(t);if(e.index(t)+1<n.childCount)return e.doc.resolve(e.after(t+1));if(n.type.spec.isolating)break}return null}function Dt(e){for(let t=0;t<e.edgeCount;t++){let{type:n}=e.edge(t);if(n.isTextblock&&!n.hasRequiredAttrs())return n}return null}const vt=(e,t)=>{let{$from:n,$to:r}=e.selection;if(e.selection instanceof nt&&e.selection.node.isBlock)return!(!n.parentOffset||!Fe(e.doc,n.pos)||(t&&t(e.tr.split(n.pos).scrollIntoView()),0));if(!n.depth)return!1;let i,o,s=[],l=!1,a=!1;for(let e=n.depth;;e--){if(n.node(e).isBlock){l=n.end(e)==n.pos+(n.depth-e),a=n.start(e)==n.pos-(n.depth-e),o=Dt(n.node(e-1).contentMatchAt(n.indexAfter(e-1)));let t=_t&&_t(r.parent,l,n);s.unshift(t||(l&&o?{type:o}:null)),i=e;break}if(1==e)return!1;s.unshift(null)}let c=e.tr;(e.selection instanceof et||e.selection instanceof it)&&c.deleteSelection();let h=c.mapping.map(n.pos),u=Fe(c.doc,h,s.length,s);if(u||(s[0]=o?{type:o}:null,u=Fe(c.doc,h,s.length,s)),!u)return!1;if(c.split(h,s.length,s),!l&&a&&n.node(i).type!=o){let e=c.mapping.map(n.before(i)),t=c.doc.resolve(e);o&&n.node(i-1).canReplaceWith(t.index(),t.index()+1,o)&&c.setNodeMarkup(c.mapping.map(n.before(i)),o)}return t&&t(c.scrollIntoView()),!0};var _t;function St(e,t,n,i){let o,s,l=t.nodeBefore,a=t.nodeAfter,h=l.type.spec.isolating||a.type.spec.isolating;if(!h&&function(e,t,n){let r=t.nodeBefore,i=t.nodeAfter,o=t.index();return!(!(r&&i&&r.type.compatibleContent(i.type))||(!r.content.size&&t.parent.canReplace(o-1,o)?(n&&n(e.tr.delete(t.pos-r.nodeSize,t.pos).scrollIntoView()),0):!t.parent.canReplace(o,o+1)||!i.isTextblock&&!Te(e.doc,t.pos)||(n&&n(e.tr.join(t.pos).scrollIntoView()),0)))}(e,t,n))return!0;let u=!h&&t.parent.canReplace(t.index(),t.index()+1);if(u&&(o=(s=l.contentMatchAt(l.childCount)).findWrapping(a.type))&&s.matchType(o[0]||a.type).validEnd){if(n){let i=t.pos+a.nodeSize,s=r.empty;for(let e=o.length-1;e>=0;e--)s=r.from(o[e].create(null,s));s=r.from(l.copy(s));let h=e.tr.step(new _e(t.pos-1,i,t.pos,i,new c(s,1,0),o.length,!0)),u=h.doc.resolve(i+2*o.length);u.nodeAfter&&u.nodeAfter.type==l.type&&Te(h.doc,u.pos)&&h.join(u.pos),n(h.scrollIntoView())}return!0}let p=a.type.spec.isolating||i>0&&h?null:Ge.findFrom(t,1),d=p&&p.$from.blockRange(p.$to),f=d&&Me(d);if(null!=f&&f>=t.depth)return n&&n(e.tr.lift(d,f).scrollIntoView()),!0;if(u&&wt(a,"start",!0)&&wt(l,"end")){let i=l,o=[];for(;o.push(i),!i.isTextblock;)i=i.lastChild;let s=a,h=1;for(;!s.isTextblock;s=s.firstChild)h++;if(i.canReplace(i.childCount,i.childCount,s.content)){if(n){let i=r.empty;for(let e=o.length-1;e>=0;e--)i=r.from(o[e].copy(i));n(e.tr.step(new _e(t.pos-o.length,t.pos+a.nodeSize,t.pos+h,t.pos+a.nodeSize-h,new c(i,o.length,0),0,!0)).scrollIntoView())}return!0}}return!1}function Et(e){return function(t,n){let r=t.selection,i=e<0?r.$from:r.$to,o=i.depth;for(;i.node(o).isInline;){if(!o)return!1;o--}return!!i.node(o).isTextblock&&(n&&n(t.tr.setSelection(et.create(t.doc,e<0?i.start(o):i.end(o)))),!0)}}const At=Et(-1),Mt=Et(1);function Ot(...e){return function(t,n,r){for(let i=0;i<e.length;i++)if(e[i](t,n,r))return!0;return!1}}let Nt=Ot(bt,(e,t,n)=>{let r=function(e,t){let{$cursor:n}=e.selection;return!n||(t?!t.endOfTextblock("backward",e):n.parentOffset>0)?null:n}(e,n);if(!r)return!1;let i=Ct(r);if(!i){let n=r.blockRange(),i=n&&Me(n);return null!=i&&(t&&t(e.tr.lift(n,i).scrollIntoView()),!0)}let o=i.nodeBefore;if(St(e,i,t,-1))return!0;if(0==r.parent.content.size&&(wt(o,"end")||nt.isSelectable(o)))for(let n=r.depth;;n--){let s=Re(e.doc,r.before(n),r.after(n),c.empty);if(s&&s.slice.size<s.to-s.from){if(t){let n=e.tr.step(s);n.setSelection(wt(o,"end")?Ge.findFrom(n.doc.resolve(n.mapping.map(i.pos,-1)),-1):nt.create(n.doc,i.pos-o.nodeSize)),t(n.scrollIntoView())}return!0}if(1==n||r.node(n-1).childCount>1)break}return!(!o.isAtom||i.depth!=r.depth-1)&&(t&&t(e.tr.delete(i.pos-o.nodeSize,i.pos).scrollIntoView()),!0)},(e,t,n)=>{let{$head:r,empty:i}=e.selection,o=r;if(!i)return!1;if(r.parent.isTextblock){if(n?!n.endOfTextblock("backward",e):r.parentOffset>0)return!1;o=Ct(r)}let s=o&&o.nodeBefore;return!(!s||!nt.isSelectable(s))&&(t&&t(e.tr.setSelection(nt.create(e.doc,o.pos-s.nodeSize)).scrollIntoView()),!0)}),Ft=Ot(bt,(e,t,n)=>{let r=function(e,t){let{$cursor:n}=e.selection;return!n||(t?!t.endOfTextblock("forward",e):n.parentOffset<n.parent.content.size)?null:n}(e,n);if(!r)return!1;let i=xt(r);if(!i)return!1;let o=i.nodeAfter;if(St(e,i,t,1))return!0;if(0==r.parent.content.size&&(wt(o,"start")||nt.isSelectable(o))){let n=Re(e.doc,r.before(),r.after(),c.empty);if(n&&n.slice.size<n.to-n.from){if(t){let r=e.tr.step(n);r.setSelection(wt(o,"start")?Ge.findFrom(r.doc.resolve(r.mapping.map(i.pos)),1):nt.create(r.doc,r.mapping.map(i.pos))),t(r.scrollIntoView())}return!0}}return!(!o.isAtom||i.depth!=r.depth-1)&&(t&&t(e.tr.delete(i.pos,i.pos+o.nodeSize).scrollIntoView()),!0)},(e,t,n)=>{let{$head:r,empty:i}=e.selection,o=r;if(!i)return!1;if(r.parent.isTextblock){if(n?!n.endOfTextblock("forward",e):r.parentOffset<r.parent.content.size)return!1;o=xt(r)}let s=o&&o.nodeAfter;return!(!s||!nt.isSelectable(s))&&(t&&t(e.tr.setSelection(nt.create(e.doc,o.pos)).scrollIntoView()),!0)});const Tt={Enter:Ot((e,t)=>{let{$head:n,$anchor:r}=e.selection;return!(!n.parent.type.spec.code||!n.sameParent(r))&&(t&&t(e.tr.insertText("\n").scrollIntoView()),!0)},(e,t)=>{let n=e.selection,{$from:r,$to:i}=n;if(n instanceof it||r.parent.inlineContent||i.parent.inlineContent)return!1;let o=Dt(i.parent.contentMatchAt(i.indexAfter()));if(!o||!o.isTextblock)return!1;if(t){let n=(!r.parentOffset&&i.index()<i.parent.childCount?r:i).pos,s=e.tr.insert(n,o.createAndFill());s.setSelection(et.create(s.doc,n+1)),t(s.scrollIntoView())}return!0},(e,t)=>{let{$cursor:n}=e.selection;if(!n||n.parent.content.size)return!1;if(n.depth>1&&n.after()!=n.end(-1)){let r=n.before();if(Fe(e.doc,r))return t&&t(e.tr.split(r).scrollIntoView()),!0}let r=n.blockRange(),i=r&&Me(r);return null!=i&&(t&&t(e.tr.lift(r,i).scrollIntoView()),!0)},vt),"Mod-Enter":(e,t)=>{let{$head:n,$anchor:r}=e.selection;if(!n.parent.type.spec.code||!n.sameParent(r))return!1;let i=n.node(-1),o=n.indexAfter(-1),s=Dt(i.contentMatchAt(o));if(!s||!i.canReplaceWith(o,o,s))return!1;if(t){let r=n.after(),i=e.tr.replaceWith(r,r,s.createAndFill());i.setSelection(Ge.near(i.doc.resolve(r),1)),t(i.scrollIntoView())}return!0},Backspace:Nt,"Mod-Backspace":Nt,"Shift-Backspace":Nt,Delete:Ft,"Mod-Delete":Ft,"Mod-a":(e,t)=>(t&&t(e.tr.setSelection(new it(e.doc))),!0)},Rt={"Ctrl-h":Tt.Backspace,"Alt-Backspace":Tt["Mod-Backspace"],"Ctrl-d":Tt.Delete,"Ctrl-Alt-Backspace":Tt["Mod-Delete"],"Alt-Delete":Tt["Mod-Delete"],"Alt-d":Tt["Mod-Delete"],"Ctrl-a":At,"Ctrl-e":Mt};for(let e in Tt)Rt[e]=Tt[e];const zt=("undefined"!=typeof navigator?/Mac|iP(hone|[oa]d)/.test(navigator.platform):!("undefined"==typeof os||!os.platform)&&"darwin"==os.platform())?Rt:Tt;var It=200,Bt=function(){};Bt.prototype.append=function(e){return e.length?(e=Bt.from(e),!this.length&&e||e.length<It&&this.leafAppend(e)||this.length<It&&e.leafPrepend(this)||this.appendInner(e)):this},Bt.prototype.prepend=function(e){return e.length?Bt.from(e).append(this):this},Bt.prototype.appendInner=function(e){return new Lt(this,e)},Bt.prototype.slice=function(e,t){return void 0===e&&(e=0),void 0===t&&(t=this.length),e>=t?Bt.empty:this.sliceInner(Math.max(0,e),Math.min(this.length,t))},Bt.prototype.get=function(e){if(!(e<0||e>=this.length))return this.getInner(e)},Bt.prototype.forEach=function(e,t,n){void 0===t&&(t=0),void 0===n&&(n=this.length),t<=n?this.forEachInner(e,t,n,0):this.forEachInvertedInner(e,t,n,0)},Bt.prototype.map=function(e,t,n){void 0===t&&(t=0),void 0===n&&(n=this.length);var r=[];return this.forEach(function(t,n){return r.push(e(t,n))},t,n),r},Bt.from=function(e){return e instanceof Bt?e:e&&e.length?new Pt(e):Bt.empty};var Pt=function(e){function t(t){e.call(this),this.values=t}e&&(t.__proto__=e),t.prototype=Object.create(e&&e.prototype),t.prototype.constructor=t;var n={length:{configurable:!0},depth:{configurable:!0}};return t.prototype.flatten=function(){return this.values},t.prototype.sliceInner=function(e,n){return 0==e&&n==this.length?this:new t(this.values.slice(e,n))},t.prototype.getInner=function(e){return this.values[e]},t.prototype.forEachInner=function(e,t,n,r){for(var i=t;i<n;i++)if(!1===e(this.values[i],r+i))return!1},t.prototype.forEachInvertedInner=function(e,t,n,r){for(var i=t-1;i>=n;i--)if(!1===e(this.values[i],r+i))return!1},t.prototype.leafAppend=function(e){if(this.length+e.length<=It)return new t(this.values.concat(e.flatten()))},t.prototype.leafPrepend=function(e){if(this.length+e.length<=It)return new t(e.flatten().concat(this.values))},n.length.get=function(){return this.values.length},n.depth.get=function(){return 0},Object.defineProperties(t.prototype,n),t}(Bt);Bt.empty=new Pt([]);var Lt=function(e){function t(t,n){e.call(this),this.left=t,this.right=n,this.length=t.length+n.length,this.depth=Math.max(t.depth,n.depth)+1}return e&&(t.__proto__=e),t.prototype=Object.create(e&&e.prototype),t.prototype.constructor=t,t.prototype.flatten=function(){return this.left.flatten().concat(this.right.flatten())},t.prototype.getInner=function(e){return e<this.left.length?this.left.get(e):this.right.get(e-this.left.length)},t.prototype.forEachInner=function(e,t,n,r){var i=this.left.length;return!(t<i&&!1===this.left.forEachInner(e,t,Math.min(n,i),r))&&(!(n>i&&!1===this.right.forEachInner(e,Math.max(t-i,0),Math.min(this.length,n)-i,r+i))&&void 0)},t.prototype.forEachInvertedInner=function(e,t,n,r){var i=this.left.length;return!(t>i&&!1===this.right.forEachInvertedInner(e,t-i,Math.max(n,i)-i,r+i))&&(!(n<i&&!1===this.left.forEachInvertedInner(e,Math.min(t,i),n,r))&&void 0)},t.prototype.sliceInner=function(e,t){if(0==e&&t==this.length)return this;var n=this.left.length;return t<=n?this.left.slice(e,t):e>=n?this.right.slice(e-n,t-n):this.left.slice(e,n).append(this.right.slice(0,t-n))},t.prototype.leafAppend=function(e){var n=this.right.leafAppend(e);if(n)return new t(this.left,n)},t.prototype.leafPrepend=function(e){var n=this.left.leafPrepend(e);if(n)return new t(n,this.right)},t.prototype.appendInner=function(e){return this.left.depth>=Math.max(this.right.depth,e.depth)+1?new t(this.left,new t(this.right,e)):new t(this,e)},t}(Bt);class qt{constructor(e,t){this.items=e,this.eventCount=t}popEvent(e,t){if(0==this.eventCount)return null;let n,r,i=this.items.length;for(;;i--){if(this.items.get(i-1).selection){--i;break}}t&&(n=this.remapping(i,this.items.length),r=n.maps.length);let o,s,l=e.tr,a=[],c=[];return this.items.forEach((e,t)=>{if(!e.step)return n||(n=this.remapping(i,t+1),r=n.maps.length),r--,void c.push(e);if(n){c.push(new $t(e.map));let t,i=e.step.map(n.slice(r));i&&l.maybeStep(i).doc&&(t=l.mapping.maps[l.mapping.maps.length-1],a.push(new $t(t,void 0,void 0,a.length+c.length))),r--,t&&n.appendMap(t,r)}else l.maybeStep(e.step);return e.selection?(o=n?e.selection.map(n.slice(r)):e.selection,s=new qt(this.items.slice(0,i).append(c.reverse().concat(a)),this.eventCount-1),!1):void 0},this.items.length,0),{remaining:s,transform:l,selection:o}}addTransform(e,t,n,r){let i=[],o=this.eventCount,s=this.items,l=!r&&s.length?s.get(s.length-1):null;for(let n=0;n<e.steps.length;n++){let a,c=e.steps[n].invert(e.docs[n]),h=new $t(e.mapping.maps[n],c,t);(a=l&&l.merge(h))&&(h=a,n?i.pop():s=s.slice(0,s.length-1)),i.push(h),t&&(o++,t=void 0),r||(l=h)}let a=o-n.depth;return a>jt&&(s=function(e,t){let n;return e.forEach((e,r)=>{if(e.selection&&0==t--)return n=r,!1}),e.slice(n)}(s,a),o-=a),new qt(s.append(i),o)}remapping(e,t){let n=new me;return this.items.forEach((t,r)=>{let i=null!=t.mirrorOffset&&r-t.mirrorOffset>=e?n.maps.length-t.mirrorOffset:void 0;n.appendMap(t.map,i)},e,t),n}addMaps(e){return 0==this.eventCount?this:new qt(this.items.append(e.map(e=>new $t(e))),this.eventCount)}rebased(e,t){if(!this.eventCount)return this;let n=[],r=Math.max(0,this.items.length-t),i=e.mapping,o=e.steps.length,s=this.eventCount;this.items.forEach(e=>{e.selection&&s--},r);let l=t;this.items.forEach(t=>{let r=i.getMirror(--l);if(null==r)return;o=Math.min(o,r);let a=i.maps[r];if(t.step){let o=e.steps[r].invert(e.docs[r]),c=t.selection&&t.selection.map(i.slice(l+1,r));c&&s++,n.push(new $t(a,o,c))}else n.push(new $t(a))},r);let a=[];for(let e=t;e<o;e++)a.push(new $t(i.maps[e]));let c=this.items.slice(0,r).append(a).append(n),h=new qt(c,s);return h.emptyItemCount()>500&&(h=h.compress(this.items.length-n.length)),h}emptyItemCount(){let e=0;return this.items.forEach(t=>{t.step||e++}),e}compress(e=this.items.length){let t=this.remapping(0,e),n=t.maps.length,r=[],i=0;return this.items.forEach((o,s)=>{if(s>=e)r.push(o),o.selection&&i++;else if(o.step){let e=o.step.map(t.slice(n)),s=e&&e.getMap();if(n--,s&&t.appendMap(s,n),e){let l=o.selection&&o.selection.map(t.slice(n));l&&i++;let a,c=new $t(s.invert(),e,l),h=r.length-1;(a=r.length&&r[h].merge(c))?r[h]=a:r.push(c)}}else o.map&&n--},this.items.length,0),new qt(Bt.from(r.reverse()),i)}}qt.empty=new qt(Bt.empty,0);class $t{constructor(e,t,n,r){this.map=e,this.step=t,this.selection=n,this.mirrorOffset=r}merge(e){if(this.step&&e.step&&!e.selection){let t=e.step.merge(this.step);if(t)return new $t(t.getMap().invert(),t,this.selection)}}}class Vt{constructor(e,t,n,r,i){this.done=e,this.undone=t,this.prevRanges=n,this.prevTime=r,this.prevComposition=i}}const jt=20;function Jt(e){let t=[];for(let n=e.length-1;n>=0&&0==t.length;n--)e[n].forEach((e,n,r,i)=>t.push(r,i));return t}function Wt(e,t){if(!e)return null;let n=[];for(let r=0;r<e.length;r+=2){let i=t.map(e[r],1),o=t.map(e[r+1],-1);i<=o&&n.push(i,o)}return n}let Ht=!1,Ut=null;function Kt(e){let t=e.plugins;if(Ut!=t){Ht=!1,Ut=t;for(let e=0;e<t.length;e++)if(t[e].spec.historyPreserveItems){Ht=!0;break}}return Ht}const Zt=new kt("history"),Gt=new kt("closeHistory");function Qt(e={}){return e={depth:e.depth||100,newGroupDelay:e.newGroupDelay||500},new mt({key:Zt,state:{init:()=>new Vt(qt.empty,qt.empty,null,0,-1),apply:(t,n,r)=>function(e,t,n,r){let i,o=n.getMeta(Zt);if(o)return o.historyState;n.getMeta(Gt)&&(e=new Vt(e.done,e.undone,null,0,-1));let s=n.getMeta("appendedTransaction");if(0==n.steps.length)return e;if(s&&s.getMeta(Zt))return s.getMeta(Zt).redo?new Vt(e.done.addTransform(n,void 0,r,Kt(t)),e.undone,Jt(n.mapping.maps),e.prevTime,e.prevComposition):new Vt(e.done,e.undone.addTransform(n,void 0,r,Kt(t)),null,e.prevTime,e.prevComposition);if(!1===n.getMeta("addToHistory")||s&&!1===s.getMeta("addToHistory"))return(i=n.getMeta("rebased"))?new Vt(e.done.rebased(n,i),e.undone.rebased(n,i),Wt(e.prevRanges,n.mapping),e.prevTime,e.prevComposition):new Vt(e.done.addMaps(n.mapping.maps),e.undone.addMaps(n.mapping.maps),Wt(e.prevRanges,n.mapping),e.prevTime,e.prevComposition);{let i=n.getMeta("composition"),o=0==e.prevTime||!s&&e.prevComposition!=i&&(e.prevTime<(n.time||0)-r.newGroupDelay||!function(e,t){if(!t)return!1;if(!e.docChanged)return!0;let n=!1;return e.mapping.maps[0].forEach((e,r)=>{for(let i=0;i<t.length;i+=2)e<=t[i+1]&&r>=t[i]&&(n=!0)}),n}(n,e.prevRanges)),l=s?Wt(e.prevRanges,n.mapping):Jt(n.mapping.maps);return new Vt(e.done.addTransform(n,o?t.selection.getBookmark():void 0,r,Kt(t)),qt.empty,l,n.time,null==i?e.prevComposition:i)}}(n,r,t,e)},config:e,props:{handleDOMEvents:{beforeinput(e,t){let n=t.inputType,r="historyUndo"==n?Yt:"historyRedo"==n?en:null;return!(!r||!e.editable)&&(t.preventDefault(),r(e.state,e.dispatch))}}}})}function Xt(e,t){return(n,r)=>{let i=Zt.getState(n);if(!i||0==(e?i.undone:i.done).eventCount)return!1;if(r){let o=function(e,t,n){let r=Kt(t),i=Zt.get(t).spec.config,o=(n?e.undone:e.done).popEvent(t,r);if(!o)return null;let s=o.selection.resolve(o.transform.doc),l=(n?e.done:e.undone).addTransform(o.transform,t.selection.getBookmark(),i,r),a=new Vt(n?l:o.remaining,n?o.remaining:l,null,0,-1);return o.transform.setSelection(s).setMeta(Zt,{redo:n,historyState:a})}(i,n,e);o&&r(t?o.scrollIntoView():o)}return!0}}const Yt=Xt(!1,!0),en=Xt(!0,!0);for(var tn={8:"Backspace",9:"Tab",10:"Enter",12:"NumLock",13:"Enter",16:"Shift",17:"Control",18:"Alt",20:"CapsLock",27:"Escape",32:" ",33:"PageUp",34:"PageDown",35:"End",36:"Home",37:"ArrowLeft",38:"ArrowUp",39:"ArrowRight",40:"ArrowDown",44:"PrintScreen",45:"Insert",46:"Delete",59:";",61:"=",91:"Meta",92:"Meta",106:"*",107:"+",108:",",109:"-",110:".",111:"/",144:"NumLock",145:"ScrollLock",160:"Shift",161:"Shift",162:"Control",163:"Control",164:"Alt",165:"Alt",173:"-",186:";",187:"=",188:",",189:"-",190:".",191:"/",192:"`",219:"[",220:"\\",221:"]",222:"'"},nn={48:")",49:"!",50:"@",51:"#",52:"$",53:"%",54:"^",55:"&",56:"*",57:"(",59:":",61:"+",173:"_",186:":",187:"+",188:"<",189:"_",190:">",191:"?",192:"~",219:"{",220:"|",221:"}",222:'"'},rn="undefined"!=typeof navigator&&/Mac/.test(navigator.platform),on="undefined"!=typeof navigator&&/MSIE \d|Trident\/(?:[7-9]|\d{2,})\..*rv:(\d+)/.exec(navigator.userAgent),sn=0;sn<10;sn++)tn[48+sn]=tn[96+sn]=String(sn);for(sn=1;sn<=24;sn++)tn[sn+111]="F"+sn;for(sn=65;sn<=90;sn++)tn[sn]=String.fromCharCode(sn+32),nn[sn]=String.fromCharCode(sn);for(var ln in tn)nn.hasOwnProperty(ln)||(nn[ln]=tn[ln]);const an="undefined"!=typeof navigator&&/Mac|iP(hone|[oa]d)/.test(navigator.platform),cn="undefined"!=typeof navigator&&/Win/.test(navigator.platform);function hn(e){let t,n,r,i,o=e.split(/-(?!$)/),s=o[o.length-1];"Space"==s&&(s=" ");for(let e=0;e<o.length-1;e++){let s=o[e];if(/^(cmd|meta|m)$/i.test(s))i=!0;else if(/^a(lt)?$/i.test(s))t=!0;else if(/^(c|ctrl|control)$/i.test(s))n=!0;else if(/^s(hift)?$/i.test(s))r=!0;else{if(!/^mod$/i.test(s))throw new Error("Unrecognized modifier name: "+s);an?i=!0:n=!0}}return t&&(s="Alt-"+s),n&&(s="Ctrl-"+s),i&&(s="Meta-"+s),r&&(s="Shift-"+s),s}function un(e,t,n=!0){return t.altKey&&(e="Alt-"+e),t.ctrlKey&&(e="Ctrl-"+e),t.metaKey&&(e="Meta-"+e),n&&t.shiftKey&&(e="Shift-"+e),e}function pn(e){return new mt({props:{handleKeyDown:dn(e)}})}function dn(e){let t=function(e){let t=Object.create(null);for(let n in e)t[hn(n)]=e[n];return t}(e);return function(e,n){let r,i=function(e){var t=!(rn&&e.metaKey&&e.shiftKey&&!e.ctrlKey&&!e.altKey||on&&e.shiftKey&&e.key&&1==e.key.length||"Unidentified"==e.key)&&e.key||(e.shiftKey?nn:tn)[e.keyCode]||e.key||"Unidentified";return"Esc"==t&&(t="Escape"),"Del"==t&&(t="Delete"),"Left"==t&&(t="ArrowLeft"),"Up"==t&&(t="ArrowUp"),"Right"==t&&(t="ArrowRight"),"Down"==t&&(t="ArrowDown"),t}(n),o=t[un(i,n)];if(o&&o(e.state,e.dispatch,e))return!0;if(1==i.length&&" "!=i){if(n.shiftKey){let r=t[un(i,n,!1)];if(r&&r(e.state,e.dispatch,e))return!0}if((n.altKey||n.metaKey||n.ctrlKey)&&!(cn&&n.ctrlKey&&n.altKey)&&(r=tn[n.keyCode])&&r!=i){let i=t[un(r,n)];if(i&&i(e.state,e.dispatch,e))return!0}}return!1}}const fn=["p",0],mn=["blockquote",0],gn=["hr"],yn=["pre",["code",0]],kn=["br"],bn=["em",0],wn=["strong",0],Cn=["code",0],xn=new H({nodes:{doc:{content:"block+"},paragraph:{content:"inline*",group:"block",parseDOM:[{tag:"p"}],toDOM:()=>fn},blockquote:{content:"block+",group:"block",defining:!0,parseDOM:[{tag:"blockquote"}],toDOM:()=>mn},horizontal_rule:{group:"block",parseDOM:[{tag:"hr"}],toDOM:()=>gn},heading:{attrs:{level:{default:1,validate:"number"}},content:"inline*",group:"block",defining:!0,parseDOM:[{tag:"h1",attrs:{level:1}},{tag:"h2",attrs:{level:2}},{tag:"h3",attrs:{level:3}},{tag:"h4",attrs:{level:4}},{tag:"h5",attrs:{level:5}},{tag:"h6",attrs:{level:6}}],toDOM:e=>["h"+e.attrs.level,0]},code_block:{content:"text*",marks:"",group:"block",code:!0,defining:!0,parseDOM:[{tag:"pre",preserveWhitespace:"full"}],toDOM:()=>yn},text:{group:"inline"},image:{inline:!0,attrs:{src:{validate:"string"},alt:{default:null,validate:"string|null"},title:{default:null,validate:"string|null"}},group:"inline",draggable:!0,parseDOM:[{tag:"img[src]",getAttrs:e=>({src:e.getAttribute("src"),title:e.getAttribute("title"),alt:e.getAttribute("alt")})}],toDOM(e){let{src:t,alt:n,title:r}=e.attrs;return["img",{src:t,alt:n,title:r}]}},hard_break:{inline:!0,group:"inline",selectable:!1,parseDOM:[{tag:"br"}],toDOM:()=>kn}},marks:{link:{attrs:{href:{validate:"string"},title:{default:null,validate:"string|null"}},inclusive:!1,parseDOM:[{tag:"a[href]",getAttrs:e=>({href:e.getAttribute("href"),title:e.getAttribute("title")})}],toDOM(e){let{href:t,title:n}=e.attrs;return["a",{href:t,title:n},0]}},em:{parseDOM:[{tag:"i"},{tag:"em"},{style:"font-style=italic"},{style:"font-style=normal",clearMark:e=>"em"==e.type.name}],toDOM:()=>bn},strong:{parseDOM:[{tag:"strong"},{tag:"b",getAttrs:e=>"normal"!=e.style.fontWeight&&null},{style:"font-weight=400",clearMark:e=>"strong"==e.type.name},{style:"font-weight",getAttrs:e=>/^(bold(er)?|[5-9]\d{2,})$/.test(e)&&null}],toDOM:()=>wn},code:{code:!0,parseDOM:[{tag:"code"}],toDOM:()=>Cn}}}),Dn=function(e){for(var t=0;;t++)if(!(e=e.previousSibling))return t},vn=function(e){let t=e.assignedSlot||e.parentNode;return t&&11==t.nodeType?t.host:t};let _n=null;const Sn=function(e,t,n){let r=_n||(_n=document.createRange());return r.setEnd(e,null==n?e.nodeValue.length:n),r.setStart(e,t||0),r},En=function(e,t,n,r){return n&&(Mn(e,t,n,r,-1)||Mn(e,t,n,r,1))},An=/^(img|br|input|textarea|hr)$/i;function Mn(e,t,n,r,i){for(var o;;){if(e==n&&t==r)return!0;if(t==(i<0?0:On(e))){let n=e.parentNode;if(!n||1!=n.nodeType||Nn(e)||An.test(e.nodeName)||"false"==e.contentEditable)return!1;t=Dn(e)+(i<0?0:1),e=n}else{if(1!=e.nodeType)return!1;{let n=e.childNodes[t+(i<0?-1:0)];if(1==n.nodeType&&"false"==n.contentEditable){if(!(null===(o=n.pmViewDesc)||void 0===o?void 0:o.ignoreForSelection))return!1;t+=i}else e=n,t=i<0?On(e):0}}}}function On(e){return 3==e.nodeType?e.nodeValue.length:e.childNodes.length}function Nn(e){let t;for(let n=e;n&&!(t=n.pmViewDesc);n=n.parentNode);return t&&t.node&&t.node.isBlock&&(t.dom==e||t.contentDOM==e)}const Fn=function(e){return e.focusNode&&En(e.focusNode,e.focusOffset,e.anchorNode,e.anchorOffset)};function Tn(e,t){let n=document.createEvent("Event");return n.initEvent("keydown",!0,!0),n.keyCode=e,n.key=n.code=t,n}const Rn="undefined"!=typeof navigator?navigator:null,zn="undefined"!=typeof document?document:null,In=Rn&&Rn.userAgent||"",Bn=/Edge\/(\d+)/.exec(In),Pn=/MSIE \d/.exec(In),Ln=/Trident\/(?:[7-9]|\d{2,})\..*rv:(\d+)/.exec(In),qn=!!(Pn||Ln||Bn),$n=Pn?document.documentMode:Ln?+Ln[1]:Bn?+Bn[1]:0,Vn=!qn&&/gecko\/(\d+)/i.test(In);Vn&&(/Firefox\/(\d+)/.exec(In)||[0,0])[1];const jn=!qn&&/Chrome\/(\d+)/.exec(In),Jn=!!jn,Wn=jn?+jn[1]:0,Hn=!qn&&!!Rn&&/Apple Computer/.test(Rn.vendor),Un=Hn&&(/Mobile\/\w+/.test(In)||!!Rn&&Rn.maxTouchPoints>2),Kn=Un||!!Rn&&/Mac/.test(Rn.platform),Zn=!!Rn&&/Win/.test(Rn.platform),Gn=/Android \d/.test(In),Qn=!!zn&&"webkitFontSmoothing"in zn.documentElement.style,Xn=Qn?+(/\bAppleWebKit\/(\d+)/.exec(navigator.userAgent)||[0,0])[1]:0;function Yn(e){let t=e.defaultView&&e.defaultView.visualViewport;return t?{left:0,right:t.width,top:0,bottom:t.height}:{left:0,right:e.documentElement.clientWidth,top:0,bottom:e.documentElement.clientHeight}}function er(e,t){return"number"==typeof e?e:e[t]}function tr(e){let t=e.getBoundingClientRect(),n=t.width/e.offsetWidth||1,r=t.height/e.offsetHeight||1;return{left:t.left,right:t.left+e.clientWidth*n,top:t.top,bottom:t.top+e.clientHeight*r}}function nr(e,t,n){let r=e.someProp("scrollThreshold")||0,i=e.someProp("scrollMargin")||5,o=e.dom.ownerDocument;for(let s=n||e.dom;s;){if(1!=s.nodeType){s=vn(s);continue}let e=s,n=e==o.body,l=n?Yn(o):tr(e),a=0,c=0;if(t.top<l.top+er(r,"top")?c=-(l.top-t.top+er(i,"top")):t.bottom>l.bottom-er(r,"bottom")&&(c=t.bottom-t.top>l.bottom-l.top?t.top+er(i,"top")-l.top:t.bottom-l.bottom+er(i,"bottom")),t.left<l.left+er(r,"left")?a=-(l.left-t.left+er(i,"left")):t.right>l.right-er(r,"right")&&(a=t.right-l.right+er(i,"right")),a||c)if(n)o.defaultView.scrollBy(a,c);else{let n=e.scrollLeft,r=e.scrollTop;c&&(e.scrollTop+=c),a&&(e.scrollLeft+=a);let i=e.scrollLeft-n,o=e.scrollTop-r;t={left:t.left-i,top:t.top-o,right:t.right-i,bottom:t.bottom-o}}let h=n?"fixed":getComputedStyle(s).position;if(/^(fixed|sticky)$/.test(h))break;s="absolute"==h?s.offsetParent:vn(s)}}function rr(e){let t=[],n=e.ownerDocument;for(let r=e;r&&(t.push({dom:r,top:r.scrollTop,left:r.scrollLeft}),e!=n);r=vn(r));return t}function ir(e,t){for(let n=0;n<e.length;n++){let{dom:r,top:i,left:o}=e[n];r.scrollTop!=i+t&&(r.scrollTop=i+t),r.scrollLeft!=o&&(r.scrollLeft=o)}}let or=null;function sr(e,t){let n,r,i,o,s=2e8,l=0,a=t.top,c=t.top;for(let h=e.firstChild,u=0;h;h=h.nextSibling,u++){let e;if(1==h.nodeType)e=h.getClientRects();else{if(3!=h.nodeType)continue;e=Sn(h).getClientRects()}for(let p=0;p<e.length;p++){let d=e[p];if(d.top<=a&&d.bottom>=c){a=Math.max(d.bottom,a),c=Math.min(d.top,c);let e=d.left>t.left?d.left-t.left:d.right<t.left?t.left-d.right:0;if(e<s){n=h,s=e,r=e&&3==n.nodeType?{left:d.right<t.left?d.right:d.left,top:t.top}:t,1==h.nodeType&&e&&(l=u+(t.left>=(d.left+d.right)/2?1:0));continue}}else d.top>t.top&&!i&&d.left<=t.left&&d.right>=t.left&&(i=h,o={left:Math.max(d.left,Math.min(d.right,t.left)),top:d.top});!n&&(t.left>=d.right&&t.top>=d.top||t.left>=d.left&&t.top>=d.bottom)&&(l=u+1)}}return!n&&i&&(n=i,r=o,s=0),n&&3==n.nodeType?function(e,t){let n,r=e.nodeValue.length,i=document.createRange();for(let o=0;o<r;o++){i.setEnd(e,o+1),i.setStart(e,o);let r=ur(i,1);if(r.top!=r.bottom&&lr(t,r)){n={node:e,offset:o+(t.left>=(r.left+r.right)/2?1:0)};break}}return i.detach(),n||{node:e,offset:0}}(n,r):!n||s&&1==n.nodeType?{node:e,offset:l}:sr(n,r)}function lr(e,t){return e.left>=t.left-1&&e.left<=t.right+1&&e.top>=t.top-1&&e.top<=t.bottom+1}function ar(e,t,n){let r=e.childNodes.length;if(r&&n.top<n.bottom)for(let i=Math.max(0,Math.min(r-1,Math.floor(r*(t.top-n.top)/(n.bottom-n.top))-2)),o=i;;){let n=e.childNodes[o];if(1==n.nodeType){let e=n.getClientRects();for(let r=0;r<e.length;r++){let i=e[r];if(lr(t,i))return ar(n,t,i)}}if((o=(o+1)%r)==i)break}return e}function cr(e,t){let n,r=e.dom.ownerDocument,i=0,o=function(e,t,n){if(e.caretPositionFromPoint)try{let r=e.caretPositionFromPoint(t,n);if(r)return{node:r.offsetNode,offset:Math.min(On(r.offsetNode),r.offset)}}catch(e){}if(e.caretRangeFromPoint){let r=e.caretRangeFromPoint(t,n);if(r)return{node:r.startContainer,offset:Math.min(On(r.startContainer),r.startOffset)}}}(r,t.left,t.top);o&&({node:n,offset:i}=o);let s,l=(e.root.elementFromPoint?e.root:r).elementFromPoint(t.left,t.top);if(!l||!e.dom.contains(1!=l.nodeType?l.parentNode:l)){let n=e.dom.getBoundingClientRect();if(!lr(t,n))return null;if(l=ar(e.dom,t,n),!l)return null}if(Hn)for(let e=l;n&&e;e=vn(e))e.draggable&&(n=void 0);if(l=function(e,t){let n=e.parentNode;return n&&/^li$/i.test(n.nodeName)&&t.left<e.getBoundingClientRect().left?n:e}(l,t),n){if(Vn&&1==n.nodeType&&(i=Math.min(i,n.childNodes.length),i<n.childNodes.length)){let e,r=n.childNodes[i];"IMG"==r.nodeName&&(e=r.getBoundingClientRect()).right<=t.left&&e.bottom>t.top&&i++}let r;Qn&&i&&1==n.nodeType&&1==(r=n.childNodes[i-1]).nodeType&&"false"==r.contentEditable&&r.getBoundingClientRect().top>=t.top&&i--,n==e.dom&&i==n.childNodes.length-1&&1==n.lastChild.nodeType&&t.top>n.lastChild.getBoundingClientRect().bottom?s=e.state.doc.content.size:0!=i&&1==n.nodeType&&"BR"==n.childNodes[i-1].nodeName||(s=function(e,t,n,r){let i=-1;for(let n=t,o=!1;n!=e.dom;){let t,s=e.docView.nearestDesc(n,!0);if(!s)return null;if(1==s.dom.nodeType&&(s.node.isBlock&&s.parent||!s.contentDOM)&&((t=s.dom.getBoundingClientRect()).width||t.height)&&(s.node.isBlock&&s.parent&&!/^T(R|BODY|HEAD|FOOT)$/.test(s.dom.nodeName)&&(!o&&t.left>r.left||t.top>r.top?i=s.posBefore:(!o&&t.right<r.left||t.bottom<r.top)&&(i=s.posAfter),o=!0),!s.contentDOM&&i<0&&!s.node.isText))return(s.node.isBlock?r.top<(t.top+t.bottom)/2:r.left<(t.left+t.right)/2)?s.posBefore:s.posAfter;n=s.dom.parentNode}return i>-1?i:e.docView.posFromDOM(t,n,-1)}(e,n,i,t))}null==s&&(s=function(e,t,n){let{node:r,offset:i}=sr(t,n),o=-1;if(1==r.nodeType&&!r.firstChild){let e=r.getBoundingClientRect();o=e.left!=e.right&&n.left>(e.left+e.right)/2?1:-1}return e.docView.posFromDOM(r,i,o)}(e,l,t));let a=e.docView.nearestDesc(l,!0);return{pos:s,inside:a?a.posAtStart-a.border:-1}}function hr(e){return e.top<e.bottom||e.left<e.right}function ur(e,t){let n=e.getClientRects();if(n.length){let e=n[t<0?0:n.length-1];if(hr(e))return e}return Array.prototype.find.call(n,hr)||e.getBoundingClientRect()}const pr=/[\u0590-\u05f4\u0600-\u06ff\u0700-\u08ac]/;function dr(e,t,n){let{node:r,offset:i,atom:o}=e.docView.domFromPos(t,n<0?-1:1),s=Qn||Vn;if(3==r.nodeType){if(!s||!pr.test(r.nodeValue)&&(n<0?i:i!=r.nodeValue.length)){let e=i,t=i,o=n<0?1:-1;return n<0&&!i?(t++,o=-1):n>=0&&i==r.nodeValue.length?(e--,o=1):n<0?e--:t++,fr(ur(Sn(r,e,t),o),o<0)}{let e=ur(Sn(r,i,i),n);if(Vn&&i&&/\s/.test(r.nodeValue[i-1])&&i<r.nodeValue.length){let t=ur(Sn(r,i-1,i-1),-1);if(t.top==e.top){let n=ur(Sn(r,i,i+1),-1);if(n.top!=e.top)return fr(n,n.left<t.left)}}return e}}if(!e.state.doc.resolve(t-(o||0)).parent.inlineContent){if(null==o&&i&&(n<0||i==On(r))){let e=r.childNodes[i-1];if(1==e.nodeType)return mr(e.getBoundingClientRect(),!1)}if(null==o&&i<On(r)){let e=r.childNodes[i];if(1==e.nodeType)return mr(e.getBoundingClientRect(),!0)}return mr(r.getBoundingClientRect(),n>=0)}if(null==o&&i&&(n<0||i==On(r))){let e=r.childNodes[i-1],t=3==e.nodeType?Sn(e,On(e)-(s?0:1)):1!=e.nodeType||"BR"==e.nodeName&&e.nextSibling?null:e;if(t)return fr(ur(t,1),!1)}if(null==o&&i<On(r)){let e=r.childNodes[i];for(;e.pmViewDesc&&e.pmViewDesc.ignoreForCoords;)e=e.nextSibling;let t=e?3==e.nodeType?Sn(e,0,s?0:1):1==e.nodeType?e:null:null;if(t)return fr(ur(t,-1),!0)}return fr(ur(3==r.nodeType?Sn(r):r,-n),n>=0)}function fr(e,t){if(0==e.width)return e;let n=t?e.left:e.right;return{top:e.top,bottom:e.bottom,left:n,right:n}}function mr(e,t){if(0==e.height)return e;let n=t?e.top:e.bottom;return{top:n,bottom:n,left:e.left,right:e.right}}function gr(e,t,n){let r=e.state,i=e.root.activeElement;r!=t&&e.updateState(t),i!=e.dom&&e.focus();try{return n()}finally{r!=t&&e.updateState(r),i!=e.dom&&i&&i.focus()}}const yr=/[\u0590-\u08ac]/;let kr=null,br=null,wr=!1;function Cr(e,t,n){return kr==t&&br==n?wr:(kr=t,br=n,wr="up"==n||"down"==n?function(e,t,n){let r=t.selection,i="up"==n?r.$from:r.$to;return gr(e,t,()=>{let{node:t}=e.docView.domFromPos(i.pos,"up"==n?-1:1);for(;;){let n=e.docView.nearestDesc(t,!0);if(!n)break;if(n.node.isBlock){t=n.contentDOM||n.dom;break}t=n.dom.parentNode}let r=dr(e,i.pos,1);for(let e=t.firstChild;e;e=e.nextSibling){let t;if(1==e.nodeType)t=e.getClientRects();else{if(3!=e.nodeType)continue;t=Sn(e,0,e.nodeValue.length).getClientRects()}for(let e=0;e<t.length;e++){let i=t[e];if(i.bottom>i.top+1&&("up"==n?r.top-i.top>2*(i.bottom-r.top):i.bottom-r.bottom>2*(r.bottom-i.top)))return!1}}return!0})}(e,t,n):function(e,t,n){let{$head:r}=t.selection;if(!r.parent.isTextblock)return!1;let i=r.parentOffset,o=!i,s=i==r.parent.content.size,l=e.domSelection();return l?yr.test(r.parent.textContent)&&l.modify?gr(e,t,()=>{let{focusNode:t,focusOffset:i,anchorNode:o,anchorOffset:s}=e.domSelectionRange(),a=l.caretBidiLevel;l.modify("move",n,"character");let c=r.depth?e.docView.domAfterPos(r.before()):e.dom,{focusNode:h,focusOffset:u}=e.domSelectionRange(),p=h&&!c.contains(1==h.nodeType?h:h.parentNode)||t==h&&i==u;try{l.collapse(o,s),t&&(t!=o||i!=s)&&l.extend&&l.extend(t,i)}catch(e){}return null!=a&&(l.caretBidiLevel=a),p}):"left"==n||"backward"==n?o:s:r.pos==r.start()||r.pos==r.end()}(e,t,n))}class xr{constructor(e,t,n,r){this.parent=e,this.children=t,this.dom=n,this.contentDOM=r,this.dirty=0,n.pmViewDesc=this}matchesWidget(e){return!1}matchesMark(e){return!1}matchesNode(e,t,n){return!1}matchesHack(e){return!1}parseRule(){return null}stopEvent(e){return!1}get size(){let e=0;for(let t=0;t<this.children.length;t++)e+=this.children[t].size;return e}get border(){return 0}destroy(){this.parent=void 0,this.dom.pmViewDesc==this&&(this.dom.pmViewDesc=void 0);for(let e=0;e<this.children.length;e++)this.children[e].destroy()}posBeforeChild(e){for(let t=0,n=this.posAtStart;;t++){let r=this.children[t];if(r==e)return n;n+=r.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(e,t,n){if(this.contentDOM&&this.contentDOM.contains(1==e.nodeType?e:e.parentNode)){if(n<0){let n,r;if(e==this.contentDOM)n=e.childNodes[t-1];else{for(;e.parentNode!=this.contentDOM;)e=e.parentNode;n=e.previousSibling}for(;n&&(!(r=n.pmViewDesc)||r.parent!=this);)n=n.previousSibling;return n?this.posBeforeChild(r)+r.size:this.posAtStart}{let n,r;if(e==this.contentDOM)n=e.childNodes[t];else{for(;e.parentNode!=this.contentDOM;)e=e.parentNode;n=e.nextSibling}for(;n&&(!(r=n.pmViewDesc)||r.parent!=this);)n=n.nextSibling;return n?this.posBeforeChild(r):this.posAtEnd}}let r;if(e==this.dom&&this.contentDOM)r=t>Dn(this.contentDOM);else if(this.contentDOM&&this.contentDOM!=this.dom&&this.dom.contains(this.contentDOM))r=2&e.compareDocumentPosition(this.contentDOM);else if(this.dom.firstChild){if(0==t)for(let t=e;;t=t.parentNode){if(t==this.dom){r=!1;break}if(t.previousSibling)break}if(null==r&&t==e.childNodes.length)for(let t=e;;t=t.parentNode){if(t==this.dom){r=!0;break}if(t.nextSibling)break}}return(null==r?n>0:r)?this.posAtEnd:this.posAtStart}nearestDesc(e,t=!1){for(let n=!0,r=e;r;r=r.parentNode){let i,o=this.getDesc(r);if(o&&(!t||o.node)){if(!n||!(i=o.nodeDOM)||(1==i.nodeType?i.contains(1==e.nodeType?e:e.parentNode):i==e))return o;n=!1}}}getDesc(e){let t=e.pmViewDesc;for(let e=t;e;e=e.parent)if(e==this)return t}posFromDOM(e,t,n){for(let r=e;r;r=r.parentNode){let i=this.getDesc(r);if(i)return i.localPosFromDOM(e,t,n)}return-1}descAt(e){for(let t=0,n=0;t<this.children.length;t++){let r=this.children[t],i=n+r.size;if(n==e&&i!=n){for(;!r.border&&r.children.length;)for(let e=0;e<r.children.length;e++){let t=r.children[e];if(t.size){r=t;break}}return r}if(e<i)return r.descAt(e-n-r.border);n=i}}domFromPos(e,t){if(!this.contentDOM)return{node:this.dom,offset:0,atom:e+1};let n=0,r=0;for(let t=0;n<this.children.length;n++){let i=this.children[n],o=t+i.size;if(o>e||i instanceof Mr){r=e-t;break}t=o}if(r)return this.children[n].domFromPos(r-this.children[n].border,t);for(let e;n&&!(e=this.children[n-1]).size&&e instanceof Dr&&e.side>=0;n--);if(t<=0){let e,r=!0;for(;e=n?this.children[n-1]:null,e&&e.dom.parentNode!=this.contentDOM;n--,r=!1);return e&&t&&r&&!e.border&&!e.domAtom?e.domFromPos(e.size,t):{node:this.contentDOM,offset:e?Dn(e.dom)+1:0}}{let e,r=!0;for(;e=n<this.children.length?this.children[n]:null,e&&e.dom.parentNode!=this.contentDOM;n++,r=!1);return e&&r&&!e.border&&!e.domAtom?e.domFromPos(0,t):{node:this.contentDOM,offset:e?Dn(e.dom):this.contentDOM.childNodes.length}}}parseRange(e,t,n=0){if(0==this.children.length)return{node:this.contentDOM,from:e,to:t,fromOffset:0,toOffset:this.contentDOM.childNodes.length};let r=-1,i=-1;for(let o=n,s=0;;s++){let n=this.children[s],l=o+n.size;if(-1==r&&e<=l){let i=o+n.border;if(e>=i&&t<=l-n.border&&n.node&&n.contentDOM&&this.contentDOM.contains(n.contentDOM))return n.parseRange(e,t,i);e=o;for(let t=s;t>0;t--){let n=this.children[t-1];if(n.size&&n.dom.parentNode==this.contentDOM&&!n.emptyChildAt(1)){r=Dn(n.dom)+1;break}e-=n.size}-1==r&&(r=0)}if(r>-1&&(l>t||s==this.children.length-1)){t=l;for(let e=s+1;e<this.children.length;e++){let n=this.children[e];if(n.size&&n.dom.parentNode==this.contentDOM&&!n.emptyChildAt(-1)){i=Dn(n.dom);break}t+=n.size}-1==i&&(i=this.contentDOM.childNodes.length);break}o=l}return{node:this.contentDOM,from:e,to:t,fromOffset:r,toOffset:i}}emptyChildAt(e){if(this.border||!this.contentDOM||!this.children.length)return!1;let t=this.children[e<0?0:this.children.length-1];return 0==t.size||t.emptyChildAt(e)}domAfterPos(e){let{node:t,offset:n}=this.domFromPos(e,0);if(1!=t.nodeType||n==t.childNodes.length)throw new RangeError("No node after pos "+e);return t.childNodes[n]}setSelection(e,t,n,r=!1){let i=Math.min(e,t),o=Math.max(e,t);for(let s=0,l=0;s<this.children.length;s++){let a=this.children[s],c=l+a.size;if(i>l&&o<c)return a.setSelection(e-l-a.border,t-l-a.border,n,r);l=c}let s=this.domFromPos(e,e?-1:1),l=t==e?s:this.domFromPos(t,t?-1:1),a=n.root.getSelection(),c=n.domSelectionRange(),h=!1;if((Vn||Hn)&&e==t){let{node:e,offset:t}=s;if(3==e.nodeType){if(h=!(!t||"\n"!=e.nodeValue[t-1]),h&&t==e.nodeValue.length)for(let t,n=e;n;n=n.parentNode){if(t=n.nextSibling){"BR"==t.nodeName&&(s=l={node:t.parentNode,offset:Dn(t)+1});break}let e=n.pmViewDesc;if(e&&e.node&&e.node.isBlock)break}}else{let n=e.childNodes[t-1];h=n&&("BR"==n.nodeName||"false"==n.contentEditable)}}if(Vn&&c.focusNode&&c.focusNode!=l.node&&1==c.focusNode.nodeType){let e=c.focusNode.childNodes[c.focusOffset];e&&"false"==e.contentEditable&&(r=!0)}if(!(r||h&&Hn)&&En(s.node,s.offset,c.anchorNode,c.anchorOffset)&&En(l.node,l.offset,c.focusNode,c.focusOffset))return;let u=!1;if((a.extend||e==t)&&(!h||!Vn)){a.collapse(s.node,s.offset);try{e!=t&&a.extend(l.node,l.offset),u=!0}catch(e){}}if(!u){if(e>t){let e=s;s=l,l=e}let n=document.createRange();n.setEnd(l.node,l.offset),n.setStart(s.node,s.offset),a.removeAllRanges(),a.addRange(n)}}ignoreMutation(e){return!this.contentDOM&&"selection"!=e.type}get contentLost(){return this.contentDOM&&this.contentDOM!=this.dom&&!this.dom.contains(this.contentDOM)}markDirty(e,t){for(let n=0,r=0;r<this.children.length;r++){let i=this.children[r],o=n+i.size;if(n==o?e<=o&&t>=n:e<o&&t>n){let r=n+i.border,s=o-i.border;if(e>=r&&t<=s)return this.dirty=e==n||t==o?2:1,void(e!=r||t!=s||!i.contentLost&&i.dom.parentNode==this.contentDOM?i.markDirty(e-r,t-r):i.dirty=3);i.dirty=i.dom!=i.contentDOM||i.dom.parentNode!=this.contentDOM||i.children.length?3:2}n=o}this.dirty=2}markParentsDirty(){let e=1;for(let t=this.parent;t;t=t.parent,e++){let n=1==e?2:1;t.dirty<n&&(t.dirty=n)}}get domAtom(){return!1}get ignoreForCoords(){return!1}get ignoreForSelection(){return!1}isText(e){return!1}}class Dr extends xr{constructor(e,t,n,r){let i,o=t.type.toDOM;if("function"==typeof o&&(o=o(n,()=>i?i.parent?i.parent.posBeforeChild(i):void 0:r)),!t.type.spec.raw){if(1!=o.nodeType){let e=document.createElement("span");e.appendChild(o),o=e}o.contentEditable="false",o.classList.add("ProseMirror-widget")}super(e,[],o,null),this.widget=t,this.widget=t,i=this}matchesWidget(e){return 0==this.dirty&&e.type.eq(this.widget.type)}parseRule(){return{ignore:!0}}stopEvent(e){let t=this.widget.spec.stopEvent;return!!t&&t(e)}ignoreMutation(e){return"selection"!=e.type||this.widget.spec.ignoreSelection}destroy(){this.widget.type.destroy(this.dom),super.destroy()}get domAtom(){return!0}get ignoreForSelection(){return!!this.widget.type.spec.relaxedSide}get side(){return this.widget.type.side}}class vr extends xr{constructor(e,t,n,r){super(e,[],t,null),this.textDOM=n,this.text=r}get size(){return this.text.length}localPosFromDOM(e,t){return e!=this.textDOM?this.posAtStart+(t?this.size:0):this.posAtStart+t}domFromPos(e){return{node:this.textDOM,offset:e}}ignoreMutation(e){return"characterData"===e.type&&e.target.nodeValue==e.oldValue}}class _r extends xr{constructor(e,t,n,r,i){super(e,[],n,r),this.mark=t,this.spec=i}static create(e,t,n,r){let i=r.nodeViews[t.type.name],o=i&&i(t,r,n);return o&&o.dom||(o=ie.renderSpec(document,t.type.spec.toDOM(t,n),null,t.attrs)),new _r(e,t,o.dom,o.contentDOM||o.dom,o)}parseRule(){return 3&this.dirty||this.mark.type.spec.reparseInView?null:{mark:this.mark.type.name,attrs:this.mark.attrs,contentElement:this.contentDOM}}matchesMark(e){return 3!=this.dirty&&this.mark.eq(e)}markDirty(e,t){if(super.markDirty(e,t),0!=this.dirty){let e=this.parent;for(;!e.node;)e=e.parent;e.dirty<this.dirty&&(e.dirty=this.dirty),this.dirty=0}}slice(e,t,n){let r=_r.create(this.parent,this.mark,!0,n),i=this.children,o=this.size;t<o&&(i=Vr(i,t,o,n)),e>0&&(i=Vr(i,0,e,n));for(let e=0;e<i.length;e++)i[e].parent=r;return r.children=i,r}ignoreMutation(e){return this.spec.ignoreMutation?this.spec.ignoreMutation(e):super.ignoreMutation(e)}destroy(){this.spec.destroy&&this.spec.destroy(),super.destroy()}}class Sr extends xr{constructor(e,t,n,r,i,o,s,l,a){super(e,[],i,o),this.node=t,this.outerDeco=n,this.innerDeco=r,this.nodeDOM=s}static create(e,t,n,r,i,o){let s,l=i.nodeViews[t.type.name],a=l&&l(t,i,()=>s?s.parent?s.parent.posBeforeChild(s):void 0:o,n,r),c=a&&a.dom,h=a&&a.contentDOM;if(t.isText)if(c){if(3!=c.nodeType)throw new RangeError("Text must be rendered as a DOM text node")}else c=document.createTextNode(t.text);else if(!c){let e=ie.renderSpec(document,t.type.spec.toDOM(t),null,t.attrs);({dom:c,contentDOM:h}=e)}h||t.isText||"BR"==c.nodeName||(c.hasAttribute("contenteditable")||(c.contentEditable="false"),t.type.spec.draggable&&(c.draggable=!0));let u=c;return c=Br(c,n,t),a?s=new Or(e,t,n,r,c,h||null,u,a,i,o+1):t.isText?new Ar(e,t,n,r,c,u,i):new Sr(e,t,n,r,c,h||null,u,i,o+1)}parseRule(){if(this.node.type.spec.reparseInView)return null;let e={node:this.node.type.name,attrs:this.node.attrs};if("pre"==this.node.type.whitespace&&(e.preserveWhitespace="full"),this.contentDOM)if(this.contentLost){for(let t=this.children.length-1;t>=0;t--){let n=this.children[t];if(this.dom.contains(n.dom.parentNode)){e.contentElement=n.dom.parentNode;break}}e.contentElement||(e.getContent=()=>r.empty)}else e.contentElement=this.contentDOM;else e.getContent=()=>this.node.content;return e}matchesNode(e,t,n){return 0==this.dirty&&e.eq(this.node)&&Pr(t,this.outerDeco)&&n.eq(this.innerDeco)}get size(){return this.node.nodeSize}get border(){return this.node.isLeaf?0:1}updateChildren(e,t){let n=this.node.inlineContent,r=t,i=e.composing?this.localCompositionInfo(e,t):null,o=i&&i.pos>-1?i:null,s=i&&i.pos<0,a=new qr(this,o&&o.node,e);!function(e,t,n,r){let i=t.locals(e),o=0;if(0==i.length){for(let n=0;n<e.childCount;n++){let s=e.child(n);r(s,i,t.forChild(o,s),n),o+=s.nodeSize}return}let s=0,l=[],a=null;for(let c=0;;){let h,u,p,d;for(;s<i.length&&i[s].to==o;){let e=i[s++];e.widget&&(h?(u||(u=[h])).push(e):h=e)}if(h)if(u){u.sort($r);for(let e=0;e<u.length;e++)n(u[e],c,!!a)}else n(h,c,!!a);if(a)d=-1,p=a,a=null;else{if(!(c<e.childCount))break;d=c,p=e.child(c++)}for(let e=0;e<l.length;e++)l[e].to<=o&&l.splice(e--,1);for(;s<i.length&&i[s].from<=o&&i[s].to>o;)l.push(i[s++]);let f=o+p.nodeSize;if(p.isText){let e=f;s<i.length&&i[s].from<e&&(e=i[s].from);for(let t=0;t<l.length;t++)l[t].to<e&&(e=l[t].to);e<f&&(a=p.cut(e-o),p=p.cut(0,e-o),f=e,d=-1)}else for(;s<i.length&&i[s].to<f;)s++;r(p,p.isInline&&!p.isLeaf?l.filter(e=>!e.inline):l.slice(),t.forChild(o,p),d),o=f}}(this.node,this.innerDeco,(t,i,o)=>{t.spec.marks?a.syncToMarks(t.spec.marks,n,e):t.type.side>=0&&!o&&a.syncToMarks(i==this.node.childCount?l.none:this.node.child(i).marks,n,e),a.placeWidget(t,e,r)},(t,o,l,c)=>{let h;a.syncToMarks(t.marks,n,e),a.findNodeMatch(t,o,l,c)||s&&e.state.selection.from>r&&e.state.selection.to<r+t.nodeSize&&(h=a.findIndexWithChild(i.node))>-1&&a.updateNodeAt(t,o,l,h,e)||a.updateNextNode(t,o,l,e,c,r)||a.addNode(t,o,l,e,r),r+=t.nodeSize}),a.syncToMarks([],n,e),this.node.isTextblock&&a.addTextblockHacks(),a.destroyRest(),(a.changed||2==this.dirty)&&(o&&this.protectLocalComposition(e,o),Nr(this.contentDOM,this.children,e),Un&&function(e){if("UL"==e.nodeName||"OL"==e.nodeName){let t=e.style.cssText;e.style.cssText=t+"; list-style: square !important",window.getComputedStyle(e).listStyle,e.style.cssText=t}}(this.dom))}localCompositionInfo(e,t){let{from:n,to:r}=e.state.selection;if(!(e.state.selection instanceof et)||n<t||r>t+this.node.content.size)return null;let i=e.input.compositionNode;if(!i||!this.dom.contains(i.parentNode))return null;if(this.node.inlineContent){let e=i.nodeValue,o=function(e,t,n,r){for(let i=0,o=0;i<e.childCount&&o<=r;){let s=e.child(i++),l=o;if(o+=s.nodeSize,!s.isText)continue;let a=s.text;for(;i<e.childCount;){let t=e.child(i++);if(o+=t.nodeSize,!t.isText)break;a+=t.text}if(o>=n){if(o>=r&&a.slice(r-t.length-l,r-l)==t)return r-t.length;let e=l<r?a.lastIndexOf(t,r-l-1):-1;if(e>=0&&e+t.length+l>=n)return l+e;if(n==r&&a.length>=r+t.length-l&&a.slice(r-l,r-l+t.length)==t)return r}}return-1}(this.node.content,e,n-t,r-t);return o<0?null:{node:i,pos:o,text:e}}return{node:i,pos:-1,text:""}}protectLocalComposition(e,{node:t,pos:n,text:r}){if(this.getDesc(t))return;let i=t;for(;i.parentNode!=this.contentDOM;i=i.parentNode){for(;i.previousSibling;)i.parentNode.removeChild(i.previousSibling);for(;i.nextSibling;)i.parentNode.removeChild(i.nextSibling);i.pmViewDesc&&(i.pmViewDesc=void 0)}let o=new vr(this,i,t,r);e.input.compositionNodes.push(o),this.children=Vr(this.children,n,n+r.length,e,o)}update(e,t,n,r){return!(3==this.dirty||!e.sameMarkup(this.node))&&(this.updateInner(e,t,n,r),!0)}updateInner(e,t,n,r){this.updateOuterDeco(t),this.node=e,this.innerDeco=n,this.contentDOM&&this.updateChildren(r,this.posAtStart),this.dirty=0}updateOuterDeco(e){if(Pr(e,this.outerDeco))return;let t=1!=this.nodeDOM.nodeType,n=this.dom;this.dom=zr(this.dom,this.nodeDOM,Rr(this.outerDeco,this.node,t),Rr(e,this.node,t)),this.dom!=n&&(n.pmViewDesc=void 0,this.dom.pmViewDesc=this),this.outerDeco=e}selectNode(){1==this.nodeDOM.nodeType&&(this.nodeDOM.classList.add("ProseMirror-selectednode"),!this.contentDOM&&this.node.type.spec.draggable||(this.nodeDOM.draggable=!0))}deselectNode(){1==this.nodeDOM.nodeType&&(this.nodeDOM.classList.remove("ProseMirror-selectednode"),!this.contentDOM&&this.node.type.spec.draggable||this.nodeDOM.removeAttribute("draggable"))}get domAtom(){return this.node.isAtom}}function Er(e,t,n,r,i){Br(r,t,e);let o=new Sr(void 0,e,t,n,r,r,r,i,0);return o.contentDOM&&o.updateChildren(i,0),o}class Ar extends Sr{constructor(e,t,n,r,i,o,s){super(e,t,n,r,i,null,o,s,0)}parseRule(){let e=this.nodeDOM.parentNode;for(;e&&e!=this.dom&&!e.pmIsDeco;)e=e.parentNode;return{skip:e||!0}}update(e,t,n,r){return!(3==this.dirty||0!=this.dirty&&!this.inParent()||!e.sameMarkup(this.node))&&(this.updateOuterDeco(t),0==this.dirty&&e.text==this.node.text||e.text==this.nodeDOM.nodeValue||(this.nodeDOM.nodeValue=e.text,r.trackWrites==this.nodeDOM&&(r.trackWrites=null)),this.node=e,this.dirty=0,!0)}inParent(){let e=this.parent.contentDOM;for(let t=this.nodeDOM;t;t=t.parentNode)if(t==e)return!0;return!1}domFromPos(e){return{node:this.nodeDOM,offset:e}}localPosFromDOM(e,t,n){return e==this.nodeDOM?this.posAtStart+Math.min(t,this.node.text.length):super.localPosFromDOM(e,t,n)}ignoreMutation(e){return"characterData"!=e.type&&"selection"!=e.type}slice(e,t,n){let r=this.node.cut(e,t),i=document.createTextNode(r.text);return new Ar(this.parent,r,this.outerDeco,this.innerDeco,i,i,n)}markDirty(e,t){super.markDirty(e,t),this.dom==this.nodeDOM||0!=e&&t!=this.nodeDOM.nodeValue.length||(this.dirty=3)}get domAtom(){return!1}isText(e){return this.node.text==e}}class Mr extends xr{parseRule(){return{ignore:!0}}matchesHack(e){return 0==this.dirty&&this.dom.nodeName==e}get domAtom(){return!0}get ignoreForCoords(){return"IMG"==this.dom.nodeName}}class Or extends Sr{constructor(e,t,n,r,i,o,s,l,a,c){super(e,t,n,r,i,o,s,a,c),this.spec=l}update(e,t,n,r){if(3==this.dirty)return!1;if(this.spec.update&&(this.node.type==e.type||this.spec.multiType)){let i=this.spec.update(e,t,n);return i&&this.updateInner(e,t,n,r),i}return!(!this.contentDOM&&!e.isLeaf)&&super.update(e,t,n,r)}selectNode(){this.spec.selectNode?this.spec.selectNode():super.selectNode()}deselectNode(){this.spec.deselectNode?this.spec.deselectNode():super.deselectNode()}setSelection(e,t,n,r){this.spec.setSelection?this.spec.setSelection(e,t,n.root):super.setSelection(e,t,n,r)}destroy(){this.spec.destroy&&this.spec.destroy(),super.destroy()}stopEvent(e){return!!this.spec.stopEvent&&this.spec.stopEvent(e)}ignoreMutation(e){return this.spec.ignoreMutation?this.spec.ignoreMutation(e):super.ignoreMutation(e)}}function Nr(e,t,n){let r=e.firstChild,i=!1;for(let o=0;o<t.length;o++){let s=t[o],l=s.dom;if(l.parentNode==e){for(;l!=r;)r=Lr(r),i=!0;r=r.nextSibling}else i=!0,e.insertBefore(l,r);if(s instanceof _r){let t=r?r.previousSibling:e.lastChild;Nr(s.contentDOM,s.children,n),r=t?t.nextSibling:e.firstChild}}for(;r;)r=Lr(r),i=!0;i&&n.trackWrites==e&&(n.trackWrites=null)}const Fr=function(e){e&&(this.nodeName=e)};Fr.prototype=Object.create(null);const Tr=[new Fr];function Rr(e,t,n){if(0==e.length)return Tr;let r=n?Tr[0]:new Fr,i=[r];for(let o=0;o<e.length;o++){let s=e[o].type.attrs;if(s){s.nodeName&&i.push(r=new Fr(s.nodeName));for(let e in s){let o=s[e];null!=o&&(n&&1==i.length&&i.push(r=new Fr(t.isInline?"span":"div")),"class"==e?r.class=(r.class?r.class+" ":"")+o:"style"==e?r.style=(r.style?r.style+";":"")+o:"nodeName"!=e&&(r[e]=o))}}}return i}function zr(e,t,n,r){if(n==Tr&&r==Tr)return t;let i=t;for(let t=0;t<r.length;t++){let o=r[t],s=n[t];if(t){let t;s&&s.nodeName==o.nodeName&&i!=e&&(t=i.parentNode)&&t.nodeName.toLowerCase()==o.nodeName||(t=document.createElement(o.nodeName),t.pmIsDeco=!0,t.appendChild(i),s=Tr[0]),i=t}Ir(i,s||Tr[0],o)}return i}function Ir(e,t,n){for(let r in t)"class"==r||"style"==r||"nodeName"==r||r in n||e.removeAttribute(r);for(let r in n)"class"!=r&&"style"!=r&&"nodeName"!=r&&n[r]!=t[r]&&e.setAttribute(r,n[r]);if(t.class!=n.class){let r=t.class?t.class.split(" ").filter(Boolean):[],i=n.class?n.class.split(" ").filter(Boolean):[];for(let t=0;t<r.length;t++)-1==i.indexOf(r[t])&&e.classList.remove(r[t]);for(let t=0;t<i.length;t++)-1==r.indexOf(i[t])&&e.classList.add(i[t]);0==e.classList.length&&e.removeAttribute("class")}if(t.style!=n.style){if(t.style){let n,r=/\s*([\w\-\xa1-\uffff]+)\s*:(?:"(?:\\.|[^"])*"|'(?:\\.|[^'])*'|\(.*?\)|[^;])*/g;for(;n=r.exec(t.style);)e.style.removeProperty(n[1])}n.style&&(e.style.cssText+=n.style)}}function Br(e,t,n){return zr(e,e,Tr,Rr(t,n,1!=e.nodeType))}function Pr(e,t){if(e.length!=t.length)return!1;for(let n=0;n<e.length;n++)if(!e[n].type.eq(t[n].type))return!1;return!0}function Lr(e){let t=e.nextSibling;return e.parentNode.removeChild(e),t}class qr{constructor(e,t,n){this.lock=t,this.view=n,this.index=0,this.stack=[],this.changed=!1,this.top=e,this.preMatch=function(e,t){let n=t,r=n.children.length,i=e.childCount,o=new Map,s=[];e:for(;i>0;){let l;for(;;)if(r){let e=n.children[r-1];if(!(e instanceof _r)){l=e,r--;break}n=e,r=e.children.length}else{if(n==t)break e;r=n.parent.children.indexOf(n),n=n.parent}let a=l.node;if(a){if(a!=e.child(i-1))break;--i,o.set(l,i),s.push(l)}}return{index:i,matched:o,matches:s.reverse()}}(e.node.content,e)}destroyBetween(e,t){if(e!=t){for(let n=e;n<t;n++)this.top.children[n].destroy();this.top.children.splice(e,t-e),this.changed=!0}}destroyRest(){this.destroyBetween(this.index,this.top.children.length)}syncToMarks(e,t,n){let r=0,i=this.stack.length>>1,o=Math.min(i,e.length);for(;r<o&&(r==i-1?this.top:this.stack[r+1<<1]).matchesMark(e[r])&&!1!==e[r].type.spec.spanning;)r++;for(;r<i;)this.destroyRest(),this.top.dirty=0,this.index=this.stack.pop(),this.top=this.stack.pop(),i--;for(;i<e.length;){this.stack.push(this.top,this.index+1);let r=-1;for(let t=this.index;t<Math.min(this.index+3,this.top.children.length);t++){let n=this.top.children[t];if(n.matchesMark(e[i])&&!this.isLocked(n.dom)){r=t;break}}if(r>-1)r>this.index&&(this.changed=!0,this.destroyBetween(this.index,r)),this.top=this.top.children[this.index];else{let r=_r.create(this.top,e[i],t,n);this.top.children.splice(this.index,0,r),this.top=r,this.changed=!0}this.index=0,i++}}findNodeMatch(e,t,n,r){let i,o=-1;if(r>=this.preMatch.index&&(i=this.preMatch.matches[r-this.preMatch.index]).parent==this.top&&i.matchesNode(e,t,n))o=this.top.children.indexOf(i,this.index);else for(let r=this.index,i=Math.min(this.top.children.length,r+5);r<i;r++){let i=this.top.children[r];if(i.matchesNode(e,t,n)&&!this.preMatch.matched.has(i)){o=r;break}}return!(o<0)&&(this.destroyBetween(this.index,o),this.index++,!0)}updateNodeAt(e,t,n,r,i){let o=this.top.children[r];return 3==o.dirty&&o.dom==o.contentDOM&&(o.dirty=2),!!o.update(e,t,n,i)&&(this.destroyBetween(this.index,r),this.index++,!0)}findIndexWithChild(e){for(;;){let t=e.parentNode;if(!t)return-1;if(t==this.top.contentDOM){let t=e.pmViewDesc;if(t)for(let e=this.index;e<this.top.children.length;e++)if(this.top.children[e]==t)return e;return-1}e=t}}updateNextNode(e,t,n,r,i,o){for(let s=this.index;s<this.top.children.length;s++){let l=this.top.children[s];if(l instanceof Sr){let a=this.preMatch.matched.get(l);if(null!=a&&a!=i)return!1;let c,h=l.dom,u=this.isLocked(h)&&!(e.isText&&l.node&&l.node.isText&&l.nodeDOM.nodeValue==e.text&&3!=l.dirty&&Pr(t,l.outerDeco));if(!u&&l.update(e,t,n,r))return this.destroyBetween(this.index,s),l.dom!=h&&(this.changed=!0),this.index++,!0;if(!u&&(c=this.recreateWrapper(l,e,t,n,r,o)))return this.destroyBetween(this.index,s),this.top.children[this.index]=c,c.contentDOM&&(c.dirty=2,c.updateChildren(r,o+1),c.dirty=0),this.changed=!0,this.index++,!0;break}}return!1}recreateWrapper(e,t,n,r,i,o){if(e.dirty||t.isAtom||!e.children.length||!e.node.content.eq(t.content)||!Pr(n,e.outerDeco)||!r.eq(e.innerDeco))return null;let s=Sr.create(this.top,t,n,r,i,o);if(s.contentDOM){s.children=e.children,e.children=[];for(let e of s.children)e.parent=s}return e.destroy(),s}addNode(e,t,n,r,i){let o=Sr.create(this.top,e,t,n,r,i);o.contentDOM&&o.updateChildren(r,i+1),this.top.children.splice(this.index++,0,o),this.changed=!0}placeWidget(e,t,n){let r=this.index<this.top.children.length?this.top.children[this.index]:null;if(!r||!r.matchesWidget(e)||e!=r.widget&&r.widget.type.toDOM.parentNode){let r=new Dr(this.top,e,t,n);this.top.children.splice(this.index++,0,r),this.changed=!0}else this.index++}addTextblockHacks(){let e=this.top.children[this.index-1],t=this.top;for(;e instanceof _r;)t=e,e=t.children[t.children.length-1];(!e||!(e instanceof Ar)||/\n$/.test(e.node.text)||this.view.requiresGeckoHackNode&&/\s$/.test(e.node.text))&&((Hn||Jn)&&e&&"false"==e.dom.contentEditable&&this.addHackNode("IMG",t),this.addHackNode("BR",this.top))}addHackNode(e,t){if(t==this.top&&this.index<t.children.length&&t.children[this.index].matchesHack(e))this.index++;else{let n=document.createElement(e);"IMG"==e&&(n.className="ProseMirror-separator",n.alt=""),"BR"==e&&(n.className="ProseMirror-trailingBreak");let r=new Mr(this.top,[],n,null);t!=this.top?t.children.push(r):t.children.splice(this.index++,0,r),this.changed=!0}}isLocked(e){return this.lock&&(e==this.lock||1==e.nodeType&&e.contains(this.lock.parentNode))}}function $r(e,t){return e.type.side-t.type.side}function Vr(e,t,n,r,i){let o=[];for(let s=0,l=0;s<e.length;s++){let a=e[s],c=l,h=l+=a.size;c>=n||h<=t?o.push(a):(c<t&&o.push(a.slice(0,t-c,r)),i&&(o.push(i),i=void 0),h>n&&o.push(a.slice(n-c,a.size,r)))}return o}function jr(e,t=null){let n=e.domSelectionRange(),r=e.state.doc;if(!n.focusNode)return null;let i=e.docView.nearestDesc(n.focusNode),o=i&&0==i.size,s=e.docView.posFromDOM(n.focusNode,n.focusOffset,1);if(s<0)return null;let l,a,c=r.resolve(s);if(Fn(n)){for(l=s;i&&!i.node;)i=i.parent;let e=i.node;if(i&&e.isAtom&&nt.isSelectable(e)&&i.parent&&(!e.isInline||!function(e,t,n){for(let r=0==t,i=t==On(e);r||i;){if(e==n)return!0;let t=Dn(e);if(!(e=e.parentNode))return!1;r=r&&0==t,i=i&&t==On(e)}}(n.focusNode,n.focusOffset,i.dom))){let e=i.posBefore;a=new nt(s==e?c:r.resolve(e))}}else{if(n instanceof e.dom.ownerDocument.defaultView.Selection&&n.rangeCount>1){let t=s,i=s;for(let r=0;r<n.rangeCount;r++){let o=n.getRangeAt(r);t=Math.min(t,e.docView.posFromDOM(o.startContainer,o.startOffset,1)),i=Math.max(i,e.docView.posFromDOM(o.endContainer,o.endOffset,-1))}if(t<0)return null;[l,s]=i==e.state.selection.anchor?[i,t]:[t,i],c=r.resolve(s)}else l=e.docView.posFromDOM(n.anchorNode,n.anchorOffset,1);if(l<0)return null}let h=r.resolve(l);if(!a){a=Xr(e,h,c,"pointer"==t||e.state.selection.head<c.pos&&!o?1:-1)}return a}function Jr(e){return e.editable?e.hasFocus():ei(e)&&document.activeElement&&document.activeElement.contains(e.dom)}function Wr(e,t=!1){let n=e.state.selection;if(Gr(e,n),Jr(e)){if(!t&&e.input.mouseDown&&e.input.mouseDown.allowDefault&&Jn){let t=e.domSelectionRange(),n=e.domObserver.currentSelection;if(t.anchorNode&&n.anchorNode&&En(t.anchorNode,t.anchorOffset,n.anchorNode,n.anchorOffset))return e.input.mouseDown.delayedSelectionSync=!0,void e.domObserver.setCurSelection()}if(e.domObserver.disconnectSelection(),e.cursorWrapper)!function(e){let t=e.domSelection();if(!t)return;let n=e.cursorWrapper.dom,r="IMG"==n.nodeName;r?t.collapse(n.parentNode,Dn(n)+1):t.collapse(n,0);!r&&!e.state.selection.visible&&qn&&$n<=11&&(n.disabled=!0,n.disabled=!1)}(e);else{let r,i,{anchor:o,head:s}=n;!Hr||n instanceof et||(n.$from.parent.inlineContent||(r=Ur(e,n.from)),n.empty||n.$from.parent.inlineContent||(i=Ur(e,n.to))),e.docView.setSelection(o,s,e,t),Hr&&(r&&Zr(r),i&&Zr(i)),n.visible?e.dom.classList.remove("ProseMirror-hideselection"):(e.dom.classList.add("ProseMirror-hideselection"),"onselectionchange"in document&&function(e){let t=e.dom.ownerDocument;t.removeEventListener("selectionchange",e.input.hideSelectionGuard);let n=e.domSelectionRange(),r=n.anchorNode,i=n.anchorOffset;t.addEventListener("selectionchange",e.input.hideSelectionGuard=()=>{n.anchorNode==r&&n.anchorOffset==i||(t.removeEventListener("selectionchange",e.input.hideSelectionGuard),setTimeout(()=>{Jr(e)&&!e.state.selection.visible||e.dom.classList.remove("ProseMirror-hideselection")},20))})}(e))}e.domObserver.setCurSelection(),e.domObserver.connectSelection()}}const Hr=Hn||Jn&&Wn<63;function Ur(e,t){let{node:n,offset:r}=e.docView.domFromPos(t,0),i=r<n.childNodes.length?n.childNodes[r]:null,o=r?n.childNodes[r-1]:null;if(Hn&&i&&"false"==i.contentEditable)return Kr(i);if(!(i&&"false"!=i.contentEditable||o&&"false"!=o.contentEditable)){if(i)return Kr(i);if(o)return Kr(o)}}function Kr(e){return e.contentEditable="true",Hn&&e.draggable&&(e.draggable=!1,e.wasDraggable=!0),e}function Zr(e){e.contentEditable="false",e.wasDraggable&&(e.draggable=!0,e.wasDraggable=null)}function Gr(e,t){if(t instanceof nt){let n=e.docView.descAt(t.from);n!=e.lastSelectedViewDesc&&(Qr(e),n&&n.selectNode(),e.lastSelectedViewDesc=n)}else Qr(e)}function Qr(e){e.lastSelectedViewDesc&&(e.lastSelectedViewDesc.parent&&e.lastSelectedViewDesc.deselectNode(),e.lastSelectedViewDesc=void 0)}function Xr(e,t,n,r){return e.someProp("createSelectionBetween",r=>r(e,t,n))||et.between(t,n,r)}function Yr(e){return!(e.editable&&!e.hasFocus())&&ei(e)}function ei(e){let t=e.domSelectionRange();if(!t.anchorNode)return!1;try{return e.dom.contains(3==t.anchorNode.nodeType?t.anchorNode.parentNode:t.anchorNode)&&(e.editable||e.dom.contains(3==t.focusNode.nodeType?t.focusNode.parentNode:t.focusNode))}catch(e){return!1}}function ti(e,t){let{$anchor:n,$head:r}=e.selection,i=t>0?n.max(r):n.min(r),o=i.parent.inlineContent?i.depth?e.doc.resolve(t>0?i.after():i.before()):null:i;return o&&Ge.findFrom(o,t)}function ni(e,t){return e.dispatch(e.state.tr.setSelection(t).scrollIntoView()),!0}function ri(e,t,n){let r=e.state.selection;if(!(r instanceof et)){if(r instanceof nt&&r.node.isInline)return ni(e,new et(t>0?r.$to:r.$from));{let n=ti(e.state,t);return!!n&&ni(e,n)}}if(n.indexOf("s")>-1){let{$head:n}=r,i=n.textOffset?null:t<0?n.nodeBefore:n.nodeAfter;if(!i||i.isText||!i.isLeaf)return!1;let o=e.state.doc.resolve(n.pos+i.nodeSize*(t<0?-1:1));return ni(e,new et(r.$anchor,o))}if(!r.empty)return!1;if(e.endOfTextblock(t>0?"forward":"backward")){let n=ti(e.state,t);return!!(n&&n instanceof nt)&&ni(e,n)}if(!(Kn&&n.indexOf("m")>-1)){let n,i=r.$head,o=i.textOffset?null:t<0?i.nodeBefore:i.nodeAfter;if(!o||o.isText)return!1;let s=t<0?i.pos-o.nodeSize:i.pos;return!!(o.isAtom||(n=e.docView.descAt(s))&&!n.contentDOM)&&(nt.isSelectable(o)?ni(e,new nt(t<0?e.state.doc.resolve(i.pos-o.nodeSize):i)):!!Qn&&ni(e,new et(e.state.doc.resolve(t<0?s:s+o.nodeSize))))}}function ii(e){return 3==e.nodeType?e.nodeValue.length:e.childNodes.length}function oi(e,t){let n=e.pmViewDesc;return n&&0==n.size&&(t<0||e.nextSibling||"BR"!=e.nodeName)}function si(e,t){return t<0?function(e){let t=e.domSelectionRange(),n=t.focusNode,r=t.focusOffset;if(!n)return;let i,o,s=!1;Vn&&1==n.nodeType&&r<ii(n)&&oi(n.childNodes[r],-1)&&(s=!0);for(;;)if(r>0){if(1!=n.nodeType)break;{let e=n.childNodes[r-1];if(oi(e,-1))i=n,o=--r;else{if(3!=e.nodeType)break;n=e,r=n.nodeValue.length}}}else{if(li(n))break;{let t=n.previousSibling;for(;t&&oi(t,-1);)i=n.parentNode,o=Dn(t),t=t.previousSibling;if(t)n=t,r=ii(n);else{if(n=n.parentNode,n==e.dom)break;r=0}}}s?ai(e,n,r):i&&ai(e,i,o)}(e):function(e){let t=e.domSelectionRange(),n=t.focusNode,r=t.focusOffset;if(!n)return;let i,o,s=ii(n);for(;;)if(r<s){if(1!=n.nodeType)break;if(!oi(n.childNodes[r],1))break;i=n,o=++r}else{if(li(n))break;{let t=n.nextSibling;for(;t&&oi(t,1);)i=t.parentNode,o=Dn(t)+1,t=t.nextSibling;if(t)n=t,r=0,s=ii(n);else{if(n=n.parentNode,n==e.dom)break;r=s=0}}}i&&ai(e,i,o)}(e)}function li(e){let t=e.pmViewDesc;return t&&t.node&&t.node.isBlock}function ai(e,t,n){if(3!=t.nodeType){let e,r;(r=function(e,t){for(;e&&t==e.childNodes.length&&!Nn(e);)t=Dn(e)+1,e=e.parentNode;for(;e&&t<e.childNodes.length;){let n=e.childNodes[t];if(3==n.nodeType)return n;if(1==n.nodeType&&"false"==n.contentEditable)break;e=n,t=0}}(t,n))?(t=r,n=0):(e=function(e,t){for(;e&&!t&&!Nn(e);)t=Dn(e),e=e.parentNode;for(;e&&t;){let n=e.childNodes[t-1];if(3==n.nodeType)return n;if(1==n.nodeType&&"false"==n.contentEditable)break;t=(e=n).childNodes.length}}(t,n))&&(t=e,n=e.nodeValue.length)}let r=e.domSelection();if(!r)return;if(Fn(r)){let e=document.createRange();e.setEnd(t,n),e.setStart(t,n),r.removeAllRanges(),r.addRange(e)}else r.extend&&r.extend(t,n);e.domObserver.setCurSelection();let{state:i}=e;setTimeout(()=>{e.state==i&&Wr(e)},50)}function ci(e,t){let n=e.state.doc.resolve(t);if(!Jn&&!Zn&&n.parent.inlineContent){let r=e.coordsAtPos(t);if(t>n.start()){let n=e.coordsAtPos(t-1),i=(n.top+n.bottom)/2;if(i>r.top&&i<r.bottom&&Math.abs(n.left-r.left)>1)return n.left<r.left?"ltr":"rtl"}if(t<n.end()){let n=e.coordsAtPos(t+1),i=(n.top+n.bottom)/2;if(i>r.top&&i<r.bottom&&Math.abs(n.left-r.left)>1)return n.left>r.left?"ltr":"rtl"}}return"rtl"==getComputedStyle(e.dom).direction?"rtl":"ltr"}function hi(e,t,n){let r=e.state.selection;if(r instanceof et&&!r.empty||n.indexOf("s")>-1)return!1;if(Kn&&n.indexOf("m")>-1)return!1;let{$from:i,$to:o}=r;if(!i.parent.inlineContent||e.endOfTextblock(t<0?"up":"down")){let n=ti(e.state,t);if(n&&n instanceof nt)return ni(e,n)}if(!i.parent.inlineContent){let n=t<0?i:o,s=r instanceof it?Ge.near(n,t):Ge.findFrom(n,t);return!!s&&ni(e,s)}return!1}function ui(e,t){if(!(e.state.selection instanceof et))return!0;let{$head:n,$anchor:r,empty:i}=e.state.selection;if(!n.sameParent(r))return!0;if(!i)return!1;if(e.endOfTextblock(t>0?"forward":"backward"))return!0;let o=!n.textOffset&&(t<0?n.nodeBefore:n.nodeAfter);if(o&&!o.isText){let r=e.state.tr;return t<0?r.delete(n.pos-o.nodeSize,n.pos):r.delete(n.pos,n.pos+o.nodeSize),e.dispatch(r),!0}return!1}function pi(e,t,n){e.domObserver.stop(),t.contentEditable=n,e.domObserver.start()}function di(e,t){let n=t.keyCode,r=function(e){let t="";return e.ctrlKey&&(t+="c"),e.metaKey&&(t+="m"),e.altKey&&(t+="a"),e.shiftKey&&(t+="s"),t}(t);if(8==n||Kn&&72==n&&"c"==r)return ui(e,-1)||si(e,-1);if(46==n&&!t.shiftKey||Kn&&68==n&&"c"==r)return ui(e,1)||si(e,1);if(13==n||27==n)return!0;if(37==n||Kn&&66==n&&"c"==r){let t=37==n?"ltr"==ci(e,e.state.selection.from)?-1:1:-1;return ri(e,t,r)||si(e,t)}if(39==n||Kn&&70==n&&"c"==r){let t=39==n?"ltr"==ci(e,e.state.selection.from)?1:-1:1;return ri(e,t,r)||si(e,t)}return 38==n||Kn&&80==n&&"c"==r?hi(e,-1,r)||si(e,-1):40==n||Kn&&78==n&&"c"==r?function(e){if(!Hn||e.state.selection.$head.parentOffset>0)return!1;let{focusNode:t,focusOffset:n}=e.domSelectionRange();if(t&&1==t.nodeType&&0==n&&t.firstChild&&"false"==t.firstChild.contentEditable){let n=t.firstChild;pi(e,n,"true"),setTimeout(()=>pi(e,n,"false"),20)}return!1}(e)||hi(e,1,r)||si(e,1):r==(Kn?"m":"c")&&(66==n||73==n||89==n||90==n)}function fi(e,t){e.someProp("transformCopied",n=>{t=n(t,e)});let n=[],{content:r,openStart:i,openEnd:o}=t;for(;i>1&&o>1&&1==r.childCount&&1==r.firstChild.childCount;){i--,o--;let e=r.firstChild;n.push(e.type.name,e.attrs!=e.type.defaultAttrs?e.attrs:null),r=e.content}let s=e.someProp("clipboardSerializer")||ie.fromSchema(e.state.schema),l=vi(),a=l.createElement("div");a.appendChild(s.serializeFragment(r,{document:l}));let c,h=a.firstChild,u=0;for(;h&&1==h.nodeType&&(c=xi[h.nodeName.toLowerCase()]);){for(let e=c.length-1;e>=0;e--){let t=l.createElement(c[e]);for(;a.firstChild;)t.appendChild(a.firstChild);a.appendChild(t),u++}h=a.firstChild}return h&&1==h.nodeType&&h.setAttribute("data-pm-slice",`${i} ${o}${u?` -${u}`:""} ${JSON.stringify(n)}`),{dom:a,text:e.someProp("clipboardTextSerializer",n=>n(t,e))||t.content.textBetween(0,t.content.size,"\n\n"),slice:t}}function mi(e,t,n,i,o){let s,l,a=o.parent.type.spec.code;if(!n&&!t)return null;let h=!!t&&(i||a||!n);if(h){if(e.someProp("transformPastedText",n=>{t=n(t,a||i,e)}),a)return l=new c(r.from(e.state.schema.text(t.replace(/\r\n?/g,"\n"))),0,0),e.someProp("transformPasted",t=>{l=t(l,e,!0)}),l;let n=e.someProp("clipboardTextParser",n=>n(t,o,i,e));if(n)l=n;else{let n=o.marks(),{schema:r}=e.state,i=ie.fromSchema(r);s=document.createElement("div"),t.split(/(?:\r\n?|\n)+/).forEach(e=>{let t=s.appendChild(document.createElement("p"));e&&t.appendChild(i.serializeNode(r.text(e,n)))})}}else e.someProp("transformPastedHTML",t=>{n=t(n,e)}),s=function(e){let t=/^(\s*<meta [^>]*>)*/.exec(e);t&&(e=e.slice(t[0].length));let n,r=vi().createElement("div"),i=/<([a-z][^>\s]+)/i.exec(e);(n=i&&xi[i[1].toLowerCase()])&&(e=n.map(e=>"<"+e+">").join("")+e+n.map(e=>"</"+e+">").reverse().join(""));if(r.innerHTML=function(e){let t=window.trustedTypes;if(!t)return e;_i||(_i=t.defaultPolicy||t.createPolicy("ProseMirrorClipboard",{createHTML:e=>e}));return _i.createHTML(e)}(e),n)for(let e=0;e<n.length;e++)r=r.querySelector(n[e])||r;return r}(n),Qn&&function(e){let t=e.querySelectorAll(Jn?"span:not([class]):not([style])":"span.Apple-converted-space");for(let n=0;n<t.length;n++){let r=t[n];1==r.childNodes.length&&" "==r.textContent&&r.parentNode&&r.parentNode.replaceChild(e.ownerDocument.createTextNode(" "),r)}}(s);let u=s&&s.querySelector("[data-pm-slice]"),p=u&&/^(\d+) (\d+)(?: -(\d+))? (.*)/.exec(u.getAttribute("data-pm-slice")||"");if(p&&p[3])for(let e=+p[3];e>0;e--){let e=s.firstChild;for(;e&&1!=e.nodeType;)e=e.nextSibling;if(!e)break;s=e}if(!l){let t=e.someProp("clipboardParser")||e.someProp("domParser")||K.fromSchema(e.state.schema);l=t.parseSlice(s,{preserveWhitespace:!(!h&&!p),context:o,ruleFromNode:e=>"BR"!=e.nodeName||e.nextSibling||!e.parentNode||gi.test(e.parentNode.nodeName)?null:{ignore:!0}})}if(p)l=function(e,t){if(!e.size)return e;let n,i=e.content.firstChild.type.schema;try{n=JSON.parse(t)}catch(t){return e}let{content:o,openStart:s,openEnd:l}=e;for(let e=n.length-2;e>=0;e-=2){let t=i.nodes[n[e]];if(!t||t.hasRequiredAttrs())break;o=r.from(t.create(n[e+1],o)),s++,l++}return new c(o,s,l)}(Ci(l,+p[1],+p[2]),p[4]);else if(l=c.maxOpen(function(e,t){if(e.childCount<2)return e;for(let n=t.depth;n>=0;n--){let i,o=t.node(n).contentMatchAt(t.index(n)),s=[];if(e.forEach(e=>{if(!s)return;let t,n=o.findWrapping(e.type);if(!n)return s=null;if(t=s.length&&i.length&&ki(n,i,e,s[s.length-1],0))s[s.length-1]=t;else{s.length&&(s[s.length-1]=bi(s[s.length-1],i.length));let t=yi(e,n);s.push(t),o=o.matchType(t.type),i=n}}),s)return r.from(s)}return e}(l.content,o),!0),l.openStart||l.openEnd){let e=0,t=0;for(let t=l.content.firstChild;e<l.openStart&&!t.type.spec.isolating;e++,t=t.firstChild);for(let e=l.content.lastChild;t<l.openEnd&&!e.type.spec.isolating;t++,e=e.lastChild);l=Ci(l,e,t)}return e.someProp("transformPasted",t=>{l=t(l,e,h)}),l}const gi=/^(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;function yi(e,t,n=0){for(let i=t.length-1;i>=n;i--)e=t[i].create(null,r.from(e));return e}function ki(e,t,n,i,o){if(o<e.length&&o<t.length&&e[o]==t[o]){let s=ki(e,t,n,i.lastChild,o+1);if(s)return i.copy(i.content.replaceChild(i.childCount-1,s));if(i.contentMatchAt(i.childCount).matchType(o==e.length-1?n.type:e[o+1]))return i.copy(i.content.append(r.from(yi(n,e,o+1))))}}function bi(e,t){if(0==t)return e;let n=e.content.replaceChild(e.childCount-1,bi(e.lastChild,t-1)),i=e.contentMatchAt(e.childCount).fillBefore(r.empty,!0);return e.copy(n.append(i))}function wi(e,t,n,i,o,s){let l=t<0?e.firstChild:e.lastChild,a=l.content;return e.childCount>1&&(s=0),o<i-1&&(a=wi(a,t,n,i,o+1,s)),o>=n&&(a=t<0?l.contentMatchAt(0).fillBefore(a,s<=o).append(a):a.append(l.contentMatchAt(l.childCount).fillBefore(r.empty,!0))),e.replaceChild(t<0?0:e.childCount-1,l.copy(a))}function Ci(e,t,n){return t<e.openStart&&(e=new c(wi(e.content,-1,t,e.openStart,0,e.openEnd),t,e.openEnd)),n<e.openEnd&&(e=new c(wi(e.content,1,n,e.openEnd,0,0),e.openStart,n)),e}const xi={thead:["table"],tbody:["table"],tfoot:["table"],caption:["table"],colgroup:["table"],col:["table","colgroup"],tr:["table","tbody"],td:["table","tbody","tr"],th:["table","tbody","tr"]};let Di=null;function vi(){return Di||(Di=document.implementation.createHTMLDocument("title"))}let _i=null;const Si={},Ei={},Ai={touchstart:!0,touchmove:!0};class Mi{constructor(){this.shiftKey=!1,this.mouseDown=null,this.lastKeyCode=null,this.lastKeyCodeTime=0,this.lastClick={time:0,x:0,y:0,type:"",button:0},this.lastSelectionOrigin=null,this.lastSelectionTime=0,this.lastIOSEnter=0,this.lastIOSEnterFallbackTimeout=-1,this.lastFocus=0,this.lastTouch=0,this.lastChromeDelete=0,this.composing=!1,this.compositionNode=null,this.composingTimeout=-1,this.compositionNodes=[],this.compositionEndedAt=-2e8,this.compositionID=1,this.compositionPendingChanges=0,this.domChangeCount=0,this.eventHandlers=Object.create(null),this.hideSelectionGuard=null}}function Oi(e,t){e.input.lastSelectionOrigin=t,e.input.lastSelectionTime=Date.now()}function Ni(e){e.someProp("handleDOMEvents",t=>{for(let n in t)e.input.eventHandlers[n]||e.dom.addEventListener(n,e.input.eventHandlers[n]=t=>Fi(e,t))})}function Fi(e,t){return e.someProp("handleDOMEvents",n=>{let r=n[t.type];return!!r&&(r(e,t)||t.defaultPrevented)})}function Ti(e,t){if(!t.bubbles)return!0;if(t.defaultPrevented)return!1;for(let n=t.target;n!=e.dom;n=n.parentNode)if(!n||11==n.nodeType||n.pmViewDesc&&n.pmViewDesc.stopEvent(t))return!1;return!0}function Ri(e){return{left:e.clientX,top:e.clientY}}function zi(e,t,n,r,i){if(-1==r)return!1;let o=e.state.doc.resolve(r);for(let r=o.depth+1;r>0;r--)if(e.someProp(t,t=>r>o.depth?t(e,n,o.nodeAfter,o.before(r),i,!0):t(e,n,o.node(r),o.before(r),i,!1)))return!0;return!1}function Ii(e,t,n){if(e.focused||e.focus(),e.state.selection.eq(t))return;let r=e.state.tr.setSelection(t);r.setMeta("pointer",!0),e.dispatch(r)}function Bi(e,t,n,r,i){return zi(e,"handleClickOn",t,n,r)||e.someProp("handleClick",n=>n(e,t,r))||(i?function(e,t){if(-1==t)return!1;let n,r,i=e.state.selection;i instanceof nt&&(n=i.node);let o=e.state.doc.resolve(t);for(let e=o.depth+1;e>0;e--){let t=e>o.depth?o.nodeAfter:o.node(e);if(nt.isSelectable(t)){r=n&&i.$from.depth>0&&e>=i.$from.depth&&o.before(i.$from.depth+1)==i.$from.pos?o.before(i.$from.depth):o.before(e);break}}return null!=r&&(Ii(e,nt.create(e.state.doc,r)),!0)}(e,n):function(e,t){if(-1==t)return!1;let n=e.state.doc.resolve(t),r=n.nodeAfter;return!!(r&&r.isAtom&&nt.isSelectable(r))&&(Ii(e,new nt(n)),!0)}(e,n))}function Pi(e,t,n,r){return zi(e,"handleDoubleClickOn",t,n,r)||e.someProp("handleDoubleClick",n=>n(e,t,r))}function Li(e,t,n,r){return zi(e,"handleTripleClickOn",t,n,r)||e.someProp("handleTripleClick",n=>n(e,t,r))||function(e,t,n){if(0!=n.button)return!1;let r=e.state.doc;if(-1==t)return!!r.inlineContent&&(Ii(e,et.create(r,0,r.content.size)),!0);let i=r.resolve(t);for(let t=i.depth+1;t>0;t--){let n=t>i.depth?i.nodeAfter:i.node(t),o=i.before(t);if(n.inlineContent)Ii(e,et.create(r,o+1,o+1+n.content.size));else{if(!nt.isSelectable(n))continue;Ii(e,nt.create(r,o))}return!0}}(e,n,r)}function qi(e){return Ki(e)}Ei.keydown=(e,t)=>{let n=t;if(e.input.shiftKey=16==n.keyCode||n.shiftKey,!ji(e,n)&&(e.input.lastKeyCode=n.keyCode,e.input.lastKeyCodeTime=Date.now(),!Gn||!Jn||13!=n.keyCode))if(229!=n.keyCode&&e.domObserver.forceFlush(),!Un||13!=n.keyCode||n.ctrlKey||n.altKey||n.metaKey)e.someProp("handleKeyDown",t=>t(e,n))||di(e,n)?n.preventDefault():Oi(e,"key");else{let t=Date.now();e.input.lastIOSEnter=t,e.input.lastIOSEnterFallbackTimeout=setTimeout(()=>{e.input.lastIOSEnter==t&&(e.someProp("handleKeyDown",t=>t(e,Tn(13,"Enter"))),e.input.lastIOSEnter=0)},200)}},Ei.keyup=(e,t)=>{16==t.keyCode&&(e.input.shiftKey=!1)},Ei.keypress=(e,t)=>{let n=t;if(ji(e,n)||!n.charCode||n.ctrlKey&&!n.altKey||Kn&&n.metaKey)return;if(e.someProp("handleKeyPress",t=>t(e,n)))return void n.preventDefault();let r=e.state.selection;if(!(r instanceof et&&r.$from.sameParent(r.$to))){let t=String.fromCharCode(n.charCode),i=()=>e.state.tr.insertText(t).scrollIntoView();/[\r\n]/.test(t)||e.someProp("handleTextInput",n=>n(e,r.$from.pos,r.$to.pos,t,i))||e.dispatch(i()),n.preventDefault()}};const $i=Kn?"metaKey":"ctrlKey";Si.mousedown=(e,t)=>{let n=t;e.input.shiftKey=n.shiftKey;let r=qi(e),i=Date.now(),o="singleClick";i-e.input.lastClick.time<500&&function(e,t){let n=t.x-e.clientX,r=t.y-e.clientY;return n*n+r*r<100}(n,e.input.lastClick)&&!n[$i]&&e.input.lastClick.button==n.button&&("singleClick"==e.input.lastClick.type?o="doubleClick":"doubleClick"==e.input.lastClick.type&&(o="tripleClick")),e.input.lastClick={time:i,x:n.clientX,y:n.clientY,type:o,button:n.button};let s=e.posAtCoords(Ri(n));s&&("singleClick"==o?(e.input.mouseDown&&e.input.mouseDown.done(),e.input.mouseDown=new Vi(e,s,n,!!r)):("doubleClick"==o?Pi:Li)(e,s.pos,s.inside,n)?n.preventDefault():Oi(e,"pointer"))};class Vi{constructor(e,t,n,r){let i,o;if(this.view=e,this.pos=t,this.event=n,this.flushed=r,this.delayedSelectionSync=!1,this.mightDrag=null,this.startDoc=e.state.doc,this.selectNode=!!n[$i],this.allowDefault=n.shiftKey,t.inside>-1)i=e.state.doc.nodeAt(t.inside),o=t.inside;else{let n=e.state.doc.resolve(t.pos);i=n.parent,o=n.depth?n.before():0}const s=r?null:n.target,l=s?e.docView.nearestDesc(s,!0):null;this.target=l&&1==l.nodeDOM.nodeType?l.nodeDOM:null;let{selection:a}=e.state;(0==n.button&&i.type.spec.draggable&&!1!==i.type.spec.selectable||a instanceof nt&&a.from<=o&&a.to>o)&&(this.mightDrag={node:i,pos:o,addAttr:!(!this.target||this.target.draggable),setUneditable:!(!this.target||!Vn||this.target.hasAttribute("contentEditable"))}),this.target&&this.mightDrag&&(this.mightDrag.addAttr||this.mightDrag.setUneditable)&&(this.view.domObserver.stop(),this.mightDrag.addAttr&&(this.target.draggable=!0),this.mightDrag.setUneditable&&setTimeout(()=>{this.view.input.mouseDown==this&&this.target.setAttribute("contentEditable","false")},20),this.view.domObserver.start()),e.root.addEventListener("mouseup",this.up=this.up.bind(this)),e.root.addEventListener("mousemove",this.move=this.move.bind(this)),Oi(e,"pointer")}done(){this.view.root.removeEventListener("mouseup",this.up),this.view.root.removeEventListener("mousemove",this.move),this.mightDrag&&this.target&&(this.view.domObserver.stop(),this.mightDrag.addAttr&&this.target.removeAttribute("draggable"),this.mightDrag.setUneditable&&this.target.removeAttribute("contentEditable"),this.view.domObserver.start()),this.delayedSelectionSync&&setTimeout(()=>Wr(this.view)),this.view.input.mouseDown=null}up(e){if(this.done(),!this.view.dom.contains(e.target))return;let t=this.pos;this.view.state.doc!=this.startDoc&&(t=this.view.posAtCoords(Ri(e))),this.updateAllowDefault(e),this.allowDefault||!t?Oi(this.view,"pointer"):Bi(this.view,t.pos,t.inside,e,this.selectNode)?e.preventDefault():0==e.button&&(this.flushed||Hn&&this.mightDrag&&!this.mightDrag.node.isAtom||Jn&&!this.view.state.selection.visible&&Math.min(Math.abs(t.pos-this.view.state.selection.from),Math.abs(t.pos-this.view.state.selection.to))<=2)?(Ii(this.view,Ge.near(this.view.state.doc.resolve(t.pos))),e.preventDefault()):Oi(this.view,"pointer")}move(e){this.updateAllowDefault(e),Oi(this.view,"pointer"),0==e.buttons&&this.done()}updateAllowDefault(e){!this.allowDefault&&(Math.abs(this.event.x-e.clientX)>4||Math.abs(this.event.y-e.clientY)>4)&&(this.allowDefault=!0)}}function ji(e,t){return!!e.composing||!!(Hn&&Math.abs(t.timeStamp-e.input.compositionEndedAt)<500)&&(e.input.compositionEndedAt=-2e8,!0)}Si.touchstart=e=>{e.input.lastTouch=Date.now(),qi(e),Oi(e,"pointer")},Si.touchmove=e=>{e.input.lastTouch=Date.now(),Oi(e,"pointer")},Si.contextmenu=e=>qi(e);const Ji=Gn?5e3:-1;function Wi(e,t){clearTimeout(e.input.composingTimeout),t>-1&&(e.input.composingTimeout=setTimeout(()=>Ki(e),t))}function Hi(e){for(e.composing&&(e.input.composing=!1,e.input.compositionEndedAt=function(){let e=document.createEvent("Event");return e.initEvent("event",!0,!0),e.timeStamp}());e.input.compositionNodes.length>0;)e.input.compositionNodes.pop().markParentsDirty()}function Ui(e){let t=e.domSelectionRange();if(!t.focusNode)return null;let n=function(e,t){for(;;){if(3==e.nodeType&&t)return e;if(1==e.nodeType&&t>0){if("false"==e.contentEditable)return null;t=On(e=e.childNodes[t-1])}else{if(!e.parentNode||Nn(e))return null;t=Dn(e),e=e.parentNode}}}(t.focusNode,t.focusOffset),r=function(e,t){for(;;){if(3==e.nodeType&&t<e.nodeValue.length)return e;if(1==e.nodeType&&t<e.childNodes.length){if("false"==e.contentEditable)return null;e=e.childNodes[t],t=0}else{if(!e.parentNode||Nn(e))return null;t=Dn(e)+1,e=e.parentNode}}}(t.focusNode,t.focusOffset);if(n&&r&&n!=r){let t=r.pmViewDesc,i=e.domObserver.lastChangedTextNode;if(n==i||r==i)return i;if(!t||!t.isText(r.nodeValue))return r;if(e.input.compositionNode==r){let e=n.pmViewDesc;if(e&&e.isText(n.nodeValue))return r}}return n||r}function Ki(e,t=!1){if(!(Gn&&e.domObserver.flushingSoon>=0)){if(e.domObserver.forceFlush(),Hi(e),t||e.docView&&e.docView.dirty){let n=jr(e),r=e.state.selection;return n&&!n.eq(r)?e.dispatch(e.state.tr.setSelection(n)):!e.markCursor&&!t||r.$from.node(r.$from.sharedDepth(r.to)).inlineContent?e.updateState(e.state):e.dispatch(e.state.tr.deleteSelection()),!0}return!1}}Ei.compositionstart=Ei.compositionupdate=e=>{if(!e.composing){e.domObserver.flush();let{state:t}=e,n=t.selection.$to;if(t.selection instanceof et&&(t.storedMarks||!n.textOffset&&n.parentOffset&&n.nodeBefore.marks.some(e=>!1===e.type.spec.inclusive)||Jn&&Zn&&function(e){let{focusNode:t,focusOffset:n}=e.domSelectionRange();if(!t||1!=t.nodeType||n>=t.childNodes.length)return!1;let r=t.childNodes[n];return 1==r.nodeType&&"false"==r.contentEditable}(e)))e.markCursor=e.state.storedMarks||n.marks(),Ki(e,!0),e.markCursor=null;else if(Ki(e,!t.selection.empty),Vn&&t.selection.empty&&n.parentOffset&&!n.textOffset&&n.nodeBefore.marks.length){let t=e.domSelectionRange();for(let n=t.focusNode,r=t.focusOffset;n&&1==n.nodeType&&0!=r;){let t=r<0?n.lastChild:n.childNodes[r-1];if(!t)break;if(3==t.nodeType){let n=e.domSelection();n&&n.collapse(t,t.nodeValue.length);break}n=t,r=-1}}e.input.composing=!0}Wi(e,Ji)},Ei.compositionend=(e,t)=>{e.composing&&(e.input.composing=!1,e.input.compositionEndedAt=t.timeStamp,e.input.compositionPendingChanges=e.domObserver.pendingRecords().length?e.input.compositionID:0,e.input.compositionNode=null,e.input.compositionPendingChanges&&Promise.resolve().then(()=>e.domObserver.flush()),e.input.compositionID++,Wi(e,20))};const Zi=qn&&$n<15||Un&&Xn<604;function Gi(e,t,n,r,i){let o=mi(e,t,n,r,e.state.selection.$from);if(e.someProp("handlePaste",t=>t(e,i,o||c.empty)))return!0;if(!o)return!1;let s=function(e){return 0==e.openStart&&0==e.openEnd&&1==e.content.childCount?e.content.firstChild:null}(o),l=s?e.state.tr.replaceSelectionWith(s,r):e.state.tr.replaceSelection(o);return e.dispatch(l.scrollIntoView().setMeta("paste",!0).setMeta("uiEvent","paste")),!0}function Qi(e){let t=e.getData("text/plain")||e.getData("Text");if(t)return t;let n=e.getData("text/uri-list");return n?n.replace(/\r?\n/g," "):""}Si.copy=Ei.cut=(e,t)=>{let n=t,r=e.state.selection,i="cut"==n.type;if(r.empty)return;let o=Zi?null:n.clipboardData,s=r.content(),{dom:l,text:a}=fi(e,s);o?(n.preventDefault(),o.clearData(),o.setData("text/html",l.innerHTML),o.setData("text/plain",a)):function(e,t){if(!e.dom.parentNode)return;let n=e.dom.parentNode.appendChild(document.createElement("div"));n.appendChild(t),n.style.cssText="position: fixed; left: -10000px; top: 10px";let r=getSelection(),i=document.createRange();i.selectNodeContents(t),e.dom.blur(),r.removeAllRanges(),r.addRange(i),setTimeout(()=>{n.parentNode&&n.parentNode.removeChild(n),e.focus()},50)}(e,l),i&&e.dispatch(e.state.tr.deleteSelection().scrollIntoView().setMeta("uiEvent","cut"))},Ei.paste=(e,t)=>{let n=t;if(e.composing&&!Gn)return;let r=Zi?null:n.clipboardData,i=e.input.shiftKey&&45!=e.input.lastKeyCode;r&&Gi(e,Qi(r),r.getData("text/html"),i,n)?n.preventDefault():function(e,t){if(!e.dom.parentNode)return;let n=e.input.shiftKey||e.state.selection.$from.parent.type.spec.code,r=e.dom.parentNode.appendChild(document.createElement(n?"textarea":"div"));n||(r.contentEditable="true"),r.style.cssText="position: fixed; left: -10000px; top: 10px",r.focus();let i=e.input.shiftKey&&45!=e.input.lastKeyCode;setTimeout(()=>{e.focus(),r.parentNode&&r.parentNode.removeChild(r),n?Gi(e,r.value,null,i,t):Gi(e,r.textContent,r.innerHTML,i,t)},50)}(e,n)};class Xi{constructor(e,t,n){this.slice=e,this.move=t,this.node=n}}const Yi=Kn?"altKey":"ctrlKey";function eo(e,t){let n=e.someProp("dragCopies",e=>!e(t));return null!=n?n:!t[Yi]}Si.dragstart=(e,t)=>{let n=t,r=e.input.mouseDown;if(r&&r.done(),!n.dataTransfer)return;let i,o=e.state.selection,s=o.empty?null:e.posAtCoords(Ri(n));if(s&&s.pos>=o.from&&s.pos<=(o instanceof nt?o.to-1:o.to));else if(r&&r.mightDrag)i=nt.create(e.state.doc,r.mightDrag.pos);else if(n.target&&1==n.target.nodeType){let t=e.docView.nearestDesc(n.target,!0);t&&t.node.type.spec.draggable&&t!=e.docView&&(i=nt.create(e.state.doc,t.posBefore))}let l=(i||e.state.selection).content(),{dom:a,text:c,slice:h}=fi(e,l);(!n.dataTransfer.files.length||!Jn||Wn>120)&&n.dataTransfer.clearData(),n.dataTransfer.setData(Zi?"Text":"text/html",a.innerHTML),n.dataTransfer.effectAllowed="copyMove",Zi||n.dataTransfer.setData("text/plain",c),e.dragging=new Xi(h,eo(e,n),i)},Si.dragend=e=>{let t=e.dragging;window.setTimeout(()=>{e.dragging==t&&(e.dragging=null)},50)},Ei.dragover=Ei.dragenter=(e,t)=>t.preventDefault(),Ei.drop=(e,t)=>{try{!function(e,t,n){if(!t.dataTransfer)return;let r=e.posAtCoords(Ri(t));if(!r)return;let i=e.state.doc.resolve(r.pos),o=n&&n.slice;o?e.someProp("transformPasted",t=>{o=t(o,e,!1)}):o=mi(e,Qi(t.dataTransfer),Zi?null:t.dataTransfer.getData("text/html"),!1,i);let s=!(!n||!eo(e,t));if(e.someProp("handleDrop",n=>n(e,t,o||c.empty,s)))return void t.preventDefault();if(!o)return;t.preventDefault();let l=o?function(e,t,n){let r=e.resolve(t);if(!n.content.size)return t;let i=n.content;for(let e=0;e<n.openStart;e++)i=i.firstChild.content;for(let e=1;e<=(0==n.openStart&&n.size?2:1);e++)for(let t=r.depth;t>=0;t--){let n=t==r.depth?0:r.pos<=(r.start(t+1)+r.end(t+1))/2?-1:1,o=r.index(t)+(n>0?1:0),s=r.node(t),l=!1;if(1==e)l=s.canReplace(o,o,i);else{let e=s.contentMatchAt(o).findWrapping(i.firstChild.type);l=e&&s.canReplaceWith(o,o,e[0])}if(l)return 0==n?r.pos:n<0?r.before(t+1):r.after(t+1)}return null}(e.state.doc,i.pos,o):i.pos;null==l&&(l=i.pos);let a=e.state.tr;if(s){let{node:e}=n;e?e.replace(a):a.deleteSelection()}let h=a.mapping.map(l),u=0==o.openStart&&0==o.openEnd&&1==o.content.childCount,p=a.doc;u?a.replaceRangeWith(h,h,o.content.firstChild):a.replaceRange(h,h,o);if(a.doc.eq(p))return;let d=a.doc.resolve(h);if(u&&nt.isSelectable(o.content.firstChild)&&d.nodeAfter&&d.nodeAfter.sameMarkup(o.content.firstChild))a.setSelection(new nt(d));else{let t=a.mapping.map(l);a.mapping.maps[a.mapping.maps.length-1].forEach((e,n,r,i)=>t=i),a.setSelection(Xr(e,d,a.doc.resolve(t)))}e.focus(),e.dispatch(a.setMeta("uiEvent","drop"))}(e,t,e.dragging)}finally{e.dragging=null}},Si.focus=e=>{e.input.lastFocus=Date.now(),e.focused||(e.domObserver.stop(),e.dom.classList.add("ProseMirror-focused"),e.domObserver.start(),e.focused=!0,setTimeout(()=>{e.docView&&e.hasFocus()&&!e.domObserver.currentSelection.eq(e.domSelectionRange())&&Wr(e)},20))},Si.blur=(e,t)=>{let n=t;e.focused&&(e.domObserver.stop(),e.dom.classList.remove("ProseMirror-focused"),e.domObserver.start(),n.relatedTarget&&e.dom.contains(n.relatedTarget)&&e.domObserver.currentSelection.clear(),e.focused=!1)},Si.beforeinput=(e,t)=>{if(Jn&&Gn&&"deleteContentBackward"==t.inputType){e.domObserver.flushSoon();let{domChangeCount:t}=e.input;setTimeout(()=>{if(e.input.domChangeCount!=t)return;if(e.dom.blur(),e.focus(),e.someProp("handleKeyDown",t=>t(e,Tn(8,"Backspace"))))return;let{$cursor:n}=e.state.selection;n&&n.pos>0&&e.dispatch(e.state.tr.delete(n.pos-1,n.pos).scrollIntoView())},50)}};for(let e in Ei)Si[e]=Ei[e];function to(e,t){if(e==t)return!0;for(let n in e)if(e[n]!==t[n])return!1;for(let n in t)if(!(n in e))return!1;return!0}class no{constructor(e,t){this.toDOM=e,this.spec=t||lo,this.side=this.spec.side||0}map(e,t,n,r){let{pos:i,deleted:o}=e.mapResult(t.from+r,this.side<0?-1:1);return o?null:new oo(i-n,i-n,this)}valid(){return!0}eq(e){return this==e||e instanceof no&&(this.spec.key&&this.spec.key==e.spec.key||this.toDOM==e.toDOM&&to(this.spec,e.spec))}destroy(e){this.spec.destroy&&this.spec.destroy(e)}}class ro{constructor(e,t){this.attrs=e,this.spec=t||lo}map(e,t,n,r){let i=e.map(t.from+r,this.spec.inclusiveStart?-1:1)-n,o=e.map(t.to+r,this.spec.inclusiveEnd?1:-1)-n;return i>=o?null:new oo(i,o,this)}valid(e,t){return t.from<t.to}eq(e){return this==e||e instanceof ro&&to(this.attrs,e.attrs)&&to(this.spec,e.spec)}static is(e){return e.type instanceof ro}destroy(){}}class io{constructor(e,t){this.attrs=e,this.spec=t||lo}map(e,t,n,r){let i=e.mapResult(t.from+r,1);if(i.deleted)return null;let o=e.mapResult(t.to+r,-1);return o.deleted||o.pos<=i.pos?null:new oo(i.pos-n,o.pos-n,this)}valid(e,t){let n,{index:r,offset:i}=e.content.findIndex(t.from);return i==t.from&&!(n=e.child(r)).isText&&i+n.nodeSize==t.to}eq(e){return this==e||e instanceof io&&to(this.attrs,e.attrs)&&to(this.spec,e.spec)}destroy(){}}class oo{constructor(e,t,n){this.from=e,this.to=t,this.type=n}copy(e,t){return new oo(e,t,this.type)}eq(e,t=0){return this.type.eq(e.type)&&this.from+t==e.from&&this.to+t==e.to}map(e,t,n){return this.type.map(e,this,t,n)}static widget(e,t,n){return new oo(e,e,new no(t,n))}static inline(e,t,n,r){return new oo(e,t,new ro(n,r))}static node(e,t,n,r){return new oo(e,t,new io(n,r))}get spec(){return this.type.spec}get inline(){return this.type instanceof ro}get widget(){return this.type instanceof no}}const so=[],lo={};class ao{constructor(e,t){this.local=e.length?e:so,this.children=t.length?t:so}static create(e,t){return t.length?mo(t,e,0,lo):co}find(e,t,n){let r=[];return this.findInner(null==e?0:e,null==t?1e9:t,r,0,n),r}findInner(e,t,n,r,i){for(let o=0;o<this.local.length;o++){let s=this.local[o];s.from<=t&&s.to>=e&&(!i||i(s.spec))&&n.push(s.copy(s.from+r,s.to+r))}for(let o=0;o<this.children.length;o+=3)if(this.children[o]<t&&this.children[o+1]>e){let s=this.children[o]+1;this.children[o+2].findInner(e-s,t-s,n,r+s,i)}}map(e,t,n){return this==co||0==e.maps.length?this:this.mapInner(e,t,0,0,n||lo)}mapInner(e,t,n,r,i){let o;for(let s=0;s<this.local.length;s++){let l=this.local[s].map(e,n,r);l&&l.type.valid(t,l)?(o||(o=[])).push(l):i.onRemove&&i.onRemove(this.local[s].spec)}return this.children.length?function(e,t,n,r,i,o,s){let l=e.slice();for(let e=0,t=o;e<n.maps.length;e++){let r=0;n.maps[e].forEach((e,n,i,o)=>{let s=o-i-(n-e);for(let i=0;i<l.length;i+=3){let o=l[i+1];if(o<0||e>o+t-r)continue;let a=l[i]+t-r;n>=a?l[i+1]=e<=a?-2:-1:e>=t&&s&&(l[i]+=s,l[i+1]+=s)}r+=s}),t=n.maps[e].map(t,-1)}let a=!1;for(let t=0;t<l.length;t+=3)if(l[t+1]<0){if(-2==l[t+1]){a=!0,l[t+1]=-1;continue}let c=n.map(e[t]+o),h=c-i;if(h<0||h>=r.content.size){a=!0;continue}let u=n.map(e[t+1]+o,-1)-i,{index:p,offset:d}=r.content.findIndex(h),f=r.maybeChild(p);if(f&&d==h&&d+f.nodeSize==u){let r=l[t+2].mapInner(n,f,c+1,e[t]+o+1,s);r!=co?(l[t]=h,l[t+1]=u,l[t+2]=r):(l[t+1]=-2,a=!0)}else a=!0}if(a){let a=function(e,t,n,r,i,o,s){function l(e,t){for(let o=0;o<e.local.length;o++){let l=e.local[o].map(r,i,t);l?n.push(l):s.onRemove&&s.onRemove(e.local[o].spec)}for(let n=0;n<e.children.length;n+=3)l(e.children[n+2],e.children[n]+t+1)}for(let n=0;n<e.length;n+=3)-1==e[n+1]&&l(e[n+2],t[n]+o+1);return n}(l,e,t,n,i,o,s),c=mo(a,r,0,s);t=c.local;for(let e=0;e<l.length;e+=3)l[e+1]<0&&(l.splice(e,3),e-=3);for(let e=0,t=0;e<c.children.length;e+=3){let n=c.children[e];for(;t<l.length&&l[t]<n;)t+=3;l.splice(t,0,c.children[e],c.children[e+1],c.children[e+2])}}return new ao(t.sort(go),l)}(this.children,o||[],e,t,n,r,i):o?new ao(o.sort(go),so):co}add(e,t){return t.length?this==co?ao.create(e,t):this.addInner(e,t,0):this}addInner(e,t,n){let r,i=0;e.forEach((e,o)=>{let s,l=o+n;if(s=po(t,e,l)){for(r||(r=this.children.slice());i<r.length&&r[i]<o;)i+=3;r[i]==o?r[i+2]=r[i+2].addInner(e,s,l+1):r.splice(i,0,o,o+e.nodeSize,mo(s,e,l+1,lo)),i+=3}});let o=uo(i?fo(t):t,-n);for(let t=0;t<o.length;t++)o[t].type.valid(e,o[t])||o.splice(t--,1);return new ao(o.length?this.local.concat(o).sort(go):this.local,r||this.children)}remove(e){return 0==e.length||this==co?this:this.removeInner(e,0)}removeInner(e,t){let n=this.children,r=this.local;for(let r=0;r<n.length;r+=3){let i,o=n[r]+t,s=n[r+1]+t;for(let t,n=0;n<e.length;n++)(t=e[n])&&t.from>o&&t.to<s&&(e[n]=null,(i||(i=[])).push(t));if(!i)continue;n==this.children&&(n=this.children.slice());let l=n[r+2].removeInner(i,o+1);l!=co?n[r+2]=l:(n.splice(r,3),r-=3)}if(r.length)for(let n,i=0;i<e.length;i++)if(n=e[i])for(let e=0;e<r.length;e++)r[e].eq(n,t)&&(r==this.local&&(r=this.local.slice()),r.splice(e--,1));return n==this.children&&r==this.local?this:r.length||n.length?new ao(r,n):co}forChild(e,t){if(this==co)return this;if(t.isLeaf)return ao.empty;let n,r;for(let t=0;t<this.children.length;t+=3)if(this.children[t]>=e){this.children[t]==e&&(n=this.children[t+2]);break}let i=e+1,o=i+t.content.size;for(let e=0;e<this.local.length;e++){let t=this.local[e];if(t.from<o&&t.to>i&&t.type instanceof ro){let e=Math.max(i,t.from)-i,n=Math.min(o,t.to)-i;e<n&&(r||(r=[])).push(t.copy(e,n))}}if(r){let e=new ao(r.sort(go),so);return n?new ho([e,n]):e}return n||co}eq(e){if(this==e)return!0;if(!(e instanceof ao)||this.local.length!=e.local.length||this.children.length!=e.children.length)return!1;for(let t=0;t<this.local.length;t++)if(!this.local[t].eq(e.local[t]))return!1;for(let t=0;t<this.children.length;t+=3)if(this.children[t]!=e.children[t]||this.children[t+1]!=e.children[t+1]||!this.children[t+2].eq(e.children[t+2]))return!1;return!0}locals(e){return yo(this.localsInner(e))}localsInner(e){if(this==co)return so;if(e.inlineContent||!this.local.some(ro.is))return this.local;let t=[];for(let e=0;e<this.local.length;e++)this.local[e].type instanceof ro||t.push(this.local[e]);return t}forEachSet(e){e(this)}}ao.empty=new ao([],[]),ao.removeOverlap=yo;const co=ao.empty;class ho{constructor(e){this.members=e}map(e,t){const n=this.members.map(n=>n.map(e,t,lo));return ho.from(n)}forChild(e,t){if(t.isLeaf)return ao.empty;let n=[];for(let r=0;r<this.members.length;r++){let i=this.members[r].forChild(e,t);i!=co&&(i instanceof ho?n=n.concat(i.members):n.push(i))}return ho.from(n)}eq(e){if(!(e instanceof ho)||e.members.length!=this.members.length)return!1;for(let t=0;t<this.members.length;t++)if(!this.members[t].eq(e.members[t]))return!1;return!0}locals(e){let t,n=!0;for(let r=0;r<this.members.length;r++){let i=this.members[r].localsInner(e);if(i.length)if(t){n&&(t=t.slice(),n=!1);for(let e=0;e<i.length;e++)t.push(i[e])}else t=i}return t?yo(n?t:t.sort(go)):so}static from(e){switch(e.length){case 0:return co;case 1:return e[0];default:return new ho(e.every(e=>e instanceof ao)?e:e.reduce((e,t)=>e.concat(t instanceof ao?t:t.members),[]))}}forEachSet(e){for(let t=0;t<this.members.length;t++)this.members[t].forEachSet(e)}}function uo(e,t){if(!t||!e.length)return e;let n=[];for(let r=0;r<e.length;r++){let i=e[r];n.push(new oo(i.from+t,i.to+t,i.type))}return n}function po(e,t,n){if(t.isLeaf)return null;let r=n+t.nodeSize,i=null;for(let t,o=0;o<e.length;o++)(t=e[o])&&t.from>n&&t.to<r&&((i||(i=[])).push(t),e[o]=null);return i}function fo(e){let t=[];for(let n=0;n<e.length;n++)null!=e[n]&&t.push(e[n]);return t}function mo(e,t,n,r){let i=[],o=!1;t.forEach((t,s)=>{let l=po(e,t,s+n);if(l){o=!0;let e=mo(l,t,n+s+1,r);e!=co&&i.push(s,s+t.nodeSize,e)}});let s=uo(o?fo(e):e,-n).sort(go);for(let e=0;e<s.length;e++)s[e].type.valid(t,s[e])||(r.onRemove&&r.onRemove(s[e].spec),s.splice(e--,1));return s.length||i.length?new ao(s,i):co}function go(e,t){return e.from-t.from||e.to-t.to}function yo(e){let t=e;for(let n=0;n<t.length-1;n++){let r=t[n];if(r.from!=r.to)for(let i=n+1;i<t.length;i++){let o=t[i];if(o.from!=r.from){o.from<r.to&&(t==e&&(t=e.slice()),t[n]=r.copy(r.from,o.from),ko(t,i,r.copy(o.from,r.to)));break}o.to!=r.to&&(t==e&&(t=e.slice()),t[i]=o.copy(o.from,r.to),ko(t,i+1,o.copy(r.to,o.to)))}}return t}function ko(e,t,n){for(;t<e.length&&go(n,e[t])>0;)t++;e.splice(t,0,n)}function bo(e){let t=[];return e.someProp("decorations",n=>{let r=n(e.state);r&&r!=co&&t.push(r)}),e.cursorWrapper&&t.push(ao.create(e.state.doc,[e.cursorWrapper.deco])),ho.from(t)}const wo={childList:!0,characterData:!0,characterDataOldValue:!0,attributes:!0,attributeOldValue:!0,subtree:!0},Co=qn&&$n<=11;class xo{constructor(){this.anchorNode=null,this.anchorOffset=0,this.focusNode=null,this.focusOffset=0}set(e){this.anchorNode=e.anchorNode,this.anchorOffset=e.anchorOffset,this.focusNode=e.focusNode,this.focusOffset=e.focusOffset}clear(){this.anchorNode=this.focusNode=null}eq(e){return e.anchorNode==this.anchorNode&&e.anchorOffset==this.anchorOffset&&e.focusNode==this.focusNode&&e.focusOffset==this.focusOffset}}class Do{constructor(e,t){this.view=e,this.handleDOMChange=t,this.queue=[],this.flushingSoon=-1,this.observer=null,this.currentSelection=new xo,this.onCharData=null,this.suppressingSelectionUpdates=!1,this.lastChangedTextNode=null,this.observer=window.MutationObserver&&new window.MutationObserver(e=>{for(let t=0;t<e.length;t++)this.queue.push(e[t]);qn&&$n<=11&&e.some(e=>"childList"==e.type&&e.removedNodes.length||"characterData"==e.type&&e.oldValue.length>e.target.nodeValue.length)?this.flushSoon():this.flush()}),Co&&(this.onCharData=e=>{this.queue.push({target:e.target,type:"characterData",oldValue:e.prevValue}),this.flushSoon()}),this.onSelectionChange=this.onSelectionChange.bind(this)}flushSoon(){this.flushingSoon<0&&(this.flushingSoon=window.setTimeout(()=>{this.flushingSoon=-1,this.flush()},20))}forceFlush(){this.flushingSoon>-1&&(window.clearTimeout(this.flushingSoon),this.flushingSoon=-1,this.flush())}start(){this.observer&&(this.observer.takeRecords(),this.observer.observe(this.view.dom,wo)),this.onCharData&&this.view.dom.addEventListener("DOMCharacterDataModified",this.onCharData),this.connectSelection()}stop(){if(this.observer){let e=this.observer.takeRecords();if(e.length){for(let t=0;t<e.length;t++)this.queue.push(e[t]);window.setTimeout(()=>this.flush(),20)}this.observer.disconnect()}this.onCharData&&this.view.dom.removeEventListener("DOMCharacterDataModified",this.onCharData),this.disconnectSelection()}connectSelection(){this.view.dom.ownerDocument.addEventListener("selectionchange",this.onSelectionChange)}disconnectSelection(){this.view.dom.ownerDocument.removeEventListener("selectionchange",this.onSelectionChange)}suppressSelectionUpdates(){this.suppressingSelectionUpdates=!0,setTimeout(()=>this.suppressingSelectionUpdates=!1,50)}onSelectionChange(){if(Yr(this.view)){if(this.suppressingSelectionUpdates)return Wr(this.view);if(qn&&$n<=11&&!this.view.state.selection.empty){let e=this.view.domSelectionRange();if(e.focusNode&&En(e.focusNode,e.focusOffset,e.anchorNode,e.anchorOffset))return this.flushSoon()}this.flush()}}setCurSelection(){this.currentSelection.set(this.view.domSelectionRange())}ignoreSelectionChange(e){if(!e.focusNode)return!0;let t,n=new Set;for(let t=e.focusNode;t;t=vn(t))n.add(t);for(let r=e.anchorNode;r;r=vn(r))if(n.has(r)){t=r;break}let r=t&&this.view.docView.nearestDesc(t);return r&&r.ignoreMutation({type:"selection",target:3==t.nodeType?t.parentNode:t})?(this.setCurSelection(),!0):void 0}pendingRecords(){if(this.observer)for(let e of this.observer.takeRecords())this.queue.push(e);return this.queue}flush(){let{view:e}=this;if(!e.docView||this.flushingSoon>-1)return;let t=this.pendingRecords();t.length&&(this.queue=[]);let n=e.domSelectionRange(),r=!this.suppressingSelectionUpdates&&!this.currentSelection.eq(n)&&Yr(e)&&!this.ignoreSelectionChange(n),i=-1,o=-1,s=!1,l=[];if(e.editable)for(let e=0;e<t.length;e++){let n=this.registerMutation(t[e],l);n&&(i=i<0?n.from:Math.min(n.from,i),o=o<0?n.to:Math.max(n.to,o),n.typeOver&&(s=!0))}if(Vn&&l.length){let t=l.filter(e=>"BR"==e.nodeName);if(2==t.length){let[e,n]=t;e.parentNode&&e.parentNode.parentNode==n.parentNode?n.remove():e.remove()}else{let{focusNode:n}=this.currentSelection;for(let r of t){let t=r.parentNode;!t||"LI"!=t.nodeName||n&&Eo(e,n)==t||r.remove()}}}else if((Jn||Hn)&&l.some(e=>"BR"==e.nodeName)&&(8==e.input.lastKeyCode||46==e.input.lastKeyCode))for(let e of l)if("BR"==e.nodeName&&e.parentNode){let t=e.nextSibling;t&&1==t.nodeType&&"false"==t.contentEditable&&e.parentNode.removeChild(e)}let a=null;i<0&&r&&e.input.lastFocus>Date.now()-200&&Math.max(e.input.lastTouch,e.input.lastClick.time)<Date.now()-300&&Fn(n)&&(a=jr(e))&&a.eq(Ge.near(e.state.doc.resolve(0),1))?(e.input.lastFocus=0,Wr(e),this.currentSelection.set(n),e.scrollToSelection()):(i>-1||r)&&(i>-1&&(e.docView.markDirty(i,o),function(e){if(vo.has(e))return;if(vo.set(e,null),-1!==["normal","nowrap","pre-line"].indexOf(getComputedStyle(e.dom).whiteSpace)){if(e.requiresGeckoHackNode=Vn,_o)return;console.warn("ProseMirror expects the CSS white-space property to be set, preferably to 'pre-wrap'. It is recommended to load style/prosemirror.css from the prosemirror-view package."),_o=!0}}(e)),this.handleDOMChange(i,o,s,l),e.docView&&e.docView.dirty?e.updateState(e.state):this.currentSelection.eq(n)||Wr(e),this.currentSelection.set(n))}registerMutation(e,t){if(t.indexOf(e.target)>-1)return null;let n=this.view.docView.nearestDesc(e.target);if("attributes"==e.type&&(n==this.view.docView||"contenteditable"==e.attributeName||"style"==e.attributeName&&!e.oldValue&&!e.target.getAttribute("style")))return null;if(!n||n.ignoreMutation(e))return null;if("childList"==e.type){for(let n=0;n<e.addedNodes.length;n++){let r=e.addedNodes[n];t.push(r),3==r.nodeType&&(this.lastChangedTextNode=r)}if(n.contentDOM&&n.contentDOM!=n.dom&&!n.contentDOM.contains(e.target))return{from:n.posBefore,to:n.posAfter};let r=e.previousSibling,i=e.nextSibling;if(qn&&$n<=11&&e.addedNodes.length)for(let t=0;t<e.addedNodes.length;t++){let{previousSibling:n,nextSibling:o}=e.addedNodes[t];(!n||Array.prototype.indexOf.call(e.addedNodes,n)<0)&&(r=n),(!o||Array.prototype.indexOf.call(e.addedNodes,o)<0)&&(i=o)}let o=r&&r.parentNode==e.target?Dn(r)+1:0,s=n.localPosFromDOM(e.target,o,-1),l=i&&i.parentNode==e.target?Dn(i):e.target.childNodes.length;return{from:s,to:n.localPosFromDOM(e.target,l,1)}}return"attributes"==e.type?{from:n.posAtStart-n.border,to:n.posAtEnd+n.border}:(this.lastChangedTextNode=e.target,{from:n.posAtStart,to:n.posAtEnd,typeOver:e.target.nodeValue==e.oldValue})}}let vo=new WeakMap,_o=!1;function So(e,t){let n=t.startContainer,r=t.startOffset,i=t.endContainer,o=t.endOffset,s=e.domAtPos(e.state.selection.anchor);return En(s.node,s.offset,i,o)&&([n,r,i,o]=[i,o,n,r]),{anchorNode:n,anchorOffset:r,focusNode:i,focusOffset:o}}function Eo(e,t){for(let n=t.parentNode;n&&n!=e.dom;n=n.parentNode){let t=e.docView.nearestDesc(n,!0);if(t&&t.node.isBlock)return n}return null}function Ao(e){let t=e.pmViewDesc;if(t)return t.parseRule();if("BR"==e.nodeName&&e.parentNode){if(Hn&&/^(ul|ol)$/i.test(e.parentNode.nodeName)){let e=document.createElement("div");return e.appendChild(document.createElement("li")),{skip:e}}if(e.parentNode.lastChild==e||Hn&&/^(tr|table)$/i.test(e.parentNode.nodeName))return{ignore:!0}}else if("IMG"==e.nodeName&&e.getAttribute("mark-placeholder"))return{ignore:!0};return null}const Mo=/^(a|abbr|acronym|b|bd[io]|big|br|button|cite|code|data(list)?|del|dfn|em|i|img|ins|kbd|label|map|mark|meter|output|q|ruby|s|samp|small|span|strong|su[bp]|time|u|tt|var)$/i;function Oo(e,t,n,i,o){let s=e.input.compositionPendingChanges||(e.composing?e.input.compositionID:0);if(e.input.compositionPendingChanges=0,t<0){let t=e.input.lastSelectionTime>Date.now()-50?e.input.lastSelectionOrigin:null,n=jr(e,t);if(n&&!e.state.selection.eq(n)){if(Jn&&Gn&&13===e.input.lastKeyCode&&Date.now()-100<e.input.lastKeyCodeTime&&e.someProp("handleKeyDown",t=>t(e,Tn(13,"Enter"))))return;let r=e.state.tr.setSelection(n);"pointer"==t?r.setMeta("pointer",!0):"key"==t&&r.scrollIntoView(),s&&r.setMeta("composition",s),e.dispatch(r)}return}let l=e.state.doc.resolve(t),a=l.sharedDepth(n);t=l.before(a+1),n=e.state.doc.resolve(n).after(a+1);let c,h,u=e.state.selection,p=function(e,t,n){let r,{node:i,fromOffset:o,toOffset:s,from:l,to:a}=e.docView.parseRange(t,n),c=e.domSelectionRange(),h=c.anchorNode;if(h&&e.dom.contains(1==h.nodeType?h:h.parentNode)&&(r=[{node:h,offset:c.anchorOffset}],Fn(c)||r.push({node:c.focusNode,offset:c.focusOffset})),Jn&&8===e.input.lastKeyCode)for(let e=s;e>o;e--){let t=i.childNodes[e-1],n=t.pmViewDesc;if("BR"==t.nodeName&&!n){s=e;break}if(!n||n.size)break}let u=e.state.doc,p=e.someProp("domParser")||K.fromSchema(e.state.schema),d=u.resolve(l),f=null,m=p.parse(i,{topNode:d.parent,topMatch:d.parent.contentMatchAt(d.index()),topOpen:!0,from:o,to:s,preserveWhitespace:"pre"!=d.parent.type.whitespace||"full",findPositions:r,ruleFromNode:Ao,context:d});if(r&&null!=r[0].pos){let e=r[0].pos,t=r[1]&&r[1].pos;null==t&&(t=e),f={anchor:e+l,head:t+l}}return{doc:m,sel:f,from:l,to:a}}(e,t,n),d=e.state.doc,f=d.slice(p.from,p.to);8===e.input.lastKeyCode&&Date.now()-100<e.input.lastKeyCodeTime?(c=e.state.selection.to,h="end"):(c=e.state.selection.from,h="start"),e.input.lastKeyCode=null;let m=function(e,t,n,r,i){let o=e.findDiffStart(t,n);if(null==o)return null;let{a:s,b:l}=e.findDiffEnd(t,n+e.size,n+t.size);if("end"==i){r-=s+Math.max(0,o-Math.min(s,l))-o}if(s<o&&e.size<t.size){let e=r<=o&&r>=s?o-r:0;o-=e,o&&o<t.size&&To(t.textBetween(o-1,o+1))&&(o+=e?1:-1),l=o+(l-s),s=o}else if(l<o){let t=r<=o&&r>=l?o-r:0;o-=t,o&&o<e.size&&To(e.textBetween(o-1,o+1))&&(o+=t?1:-1),s=o+(s-l),l=o}return{start:o,endA:s,endB:l}}(f.content,p.doc.content,p.from,c,h);if(m&&e.input.domChangeCount++,(Un&&e.input.lastIOSEnter>Date.now()-225||Gn)&&o.some(e=>1==e.nodeType&&!Mo.test(e.nodeName))&&(!m||m.endA>=m.endB)&&e.someProp("handleKeyDown",t=>t(e,Tn(13,"Enter"))))return void(e.input.lastIOSEnter=0);if(!m){if(!(i&&u instanceof et&&!u.empty&&u.$head.sameParent(u.$anchor))||e.composing||p.sel&&p.sel.anchor!=p.sel.head){if(p.sel){let t=No(e,e.state.doc,p.sel);if(t&&!t.eq(e.state.selection)){let n=e.state.tr.setSelection(t);s&&n.setMeta("composition",s),e.dispatch(n)}}return}m={start:u.from,endA:u.to,endB:u.to}}e.state.selection.from<e.state.selection.to&&m.start==m.endB&&e.state.selection instanceof et&&(m.start>e.state.selection.from&&m.start<=e.state.selection.from+2&&e.state.selection.from>=p.from?m.start=e.state.selection.from:m.endA<e.state.selection.to&&m.endA>=e.state.selection.to-2&&e.state.selection.to<=p.to&&(m.endB+=e.state.selection.to-m.endA,m.endA=e.state.selection.to)),qn&&$n<=11&&m.endB==m.start+1&&m.endA==m.start&&m.start>p.from&&" "==p.doc.textBetween(m.start-p.from-1,m.start-p.from+1)&&(m.start--,m.endA--,m.endB--);let g=p.doc.resolveNoCache(m.start-p.from),y=p.doc.resolveNoCache(m.endB-p.from),k=d.resolve(m.start),b=g.sameParent(y)&&g.parent.inlineContent&&k.end()>=m.endA;if((Un&&e.input.lastIOSEnter>Date.now()-225&&(!b||o.some(e=>"DIV"==e.nodeName||"P"==e.nodeName))||!b&&g.pos<p.doc.content.size&&(!g.sameParent(y)||!g.parent.inlineContent)&&g.pos<y.pos&&!/\S/.test(p.doc.textBetween(g.pos,y.pos,"","")))&&e.someProp("handleKeyDown",t=>t(e,Tn(13,"Enter"))))return void(e.input.lastIOSEnter=0);if(e.state.selection.anchor>m.start&&function(e,t,n,r,i){if(n-t<=i.pos-r.pos||Fo(r,!0,!1)<i.pos)return!1;let o=e.resolve(t);if(!r.parent.isTextblock){let e=o.nodeAfter;return null!=e&&n==t+e.nodeSize}if(o.parentOffset<o.parent.content.size||!o.parent.isTextblock)return!1;let s=e.resolve(Fo(o,!0,!0));return!(!s.parent.isTextblock||s.pos>n||Fo(s,!0,!1)<n)&&r.parent.content.cut(r.parentOffset).eq(s.parent.content)}(d,m.start,m.endA,g,y)&&e.someProp("handleKeyDown",t=>t(e,Tn(8,"Backspace"))))return void(Gn&&Jn&&e.domObserver.suppressSelectionUpdates());Jn&&m.endB==m.start&&(e.input.lastChromeDelete=Date.now()),Gn&&!b&&g.start()!=y.start()&&0==y.parentOffset&&g.depth==y.depth&&p.sel&&p.sel.anchor==p.sel.head&&p.sel.head==m.endA&&(m.endB-=2,y=p.doc.resolveNoCache(m.endB-p.from),setTimeout(()=>{e.someProp("handleKeyDown",function(t){return t(e,Tn(13,"Enter"))})},20));let w,C=m.start,x=m.endA,D=t=>{let n=t||e.state.tr.replace(C,x,p.doc.slice(m.start-p.from,m.endB-p.from));if(p.sel){let t=No(e,n.doc,p.sel);t&&!(Jn&&e.composing&&t.empty&&(m.start!=m.endB||e.input.lastChromeDelete<Date.now()-100)&&(t.head==C||t.head==n.mapping.map(x)-1)||qn&&t.empty&&t.head==C)&&n.setSelection(t)}return s&&n.setMeta("composition",s),n.scrollIntoView()};if(b)if(g.pos==y.pos){qn&&$n<=11&&0==g.parentOffset&&(e.domObserver.suppressSelectionUpdates(),setTimeout(()=>Wr(e),20));let t=D(e.state.tr.delete(C,x)),n=d.resolve(m.start).marksAcross(d.resolve(m.endA));n&&t.ensureMarks(n),e.dispatch(t)}else if(m.endA==m.endB&&(w=function(e,t){let n,i,o,s=e.firstChild.marks,l=t.firstChild.marks,a=s,c=l;for(let e=0;e<l.length;e++)a=l[e].removeFromSet(a);for(let e=0;e<s.length;e++)c=s[e].removeFromSet(c);if(1==a.length&&0==c.length)i=a[0],n="add",o=e=>e.mark(i.addToSet(e.marks));else{if(0!=a.length||1!=c.length)return null;i=c[0],n="remove",o=e=>e.mark(i.removeFromSet(e.marks))}let h=[];for(let e=0;e<t.childCount;e++)h.push(o(t.child(e)));if(r.from(h).eq(e))return{mark:i,type:n}}(g.parent.content.cut(g.parentOffset,y.parentOffset),k.parent.content.cut(k.parentOffset,m.endA-k.start())))){let t=D(e.state.tr);"add"==w.type?t.addMark(C,x,w.mark):t.removeMark(C,x,w.mark),e.dispatch(t)}else if(g.parent.child(g.index()).isText&&g.index()==y.index()-(y.textOffset?0:1)){let t=g.parent.textBetween(g.parentOffset,y.parentOffset),n=()=>D(e.state.tr.insertText(t,C,x));e.someProp("handleTextInput",r=>r(e,C,x,t,n))||e.dispatch(n())}else e.dispatch(D());else e.dispatch(D())}function No(e,t,n){return Math.max(n.anchor,n.head)>t.content.size?null:Xr(e,t.resolve(n.anchor),t.resolve(n.head))}function Fo(e,t,n){let r=e.depth,i=t?e.end():e.pos;for(;r>0&&(t||e.indexAfter(r)==e.node(r).childCount);)r--,i++,t=!1;if(n){let t=e.node(r).maybeChild(e.indexAfter(r));for(;t&&!t.isLeaf;)t=t.firstChild,i++}return i}function To(e){if(2!=e.length)return!1;let t=e.charCodeAt(0),n=e.charCodeAt(1);return t>=56320&&t<=57343&&n>=55296&&n<=56319}class Ro{constructor(e,t){this._root=null,this.focused=!1,this.trackWrites=null,this.mounted=!1,this.markCursor=null,this.cursorWrapper=null,this.lastSelectedViewDesc=void 0,this.input=new Mi,this.prevDirectPlugins=[],this.pluginViews=[],this.requiresGeckoHackNode=!1,this.dragging=null,this._props=t,this.state=t.state,this.directPlugins=t.plugins||[],this.directPlugins.forEach(Lo),this.dispatch=this.dispatch.bind(this),this.dom=e&&e.mount||document.createElement("div"),e&&(e.appendChild?e.appendChild(this.dom):"function"==typeof e?e(this.dom):e.mount&&(this.mounted=!0)),this.editable=Bo(this),Io(this),this.nodeViews=Po(this),this.docView=Er(this.state.doc,zo(this),bo(this),this.dom,this),this.domObserver=new Do(this,(e,t,n,r)=>Oo(this,e,t,n,r)),this.domObserver.start(),function(e){for(let t in Si){let n=Si[t];e.dom.addEventListener(t,e.input.eventHandlers[t]=t=>{!Ti(e,t)||Fi(e,t)||!e.editable&&t.type in Ei||n(e,t)},Ai[t]?{passive:!0}:void 0)}Hn&&e.dom.addEventListener("input",()=>null),Ni(e)}(this),this.updatePluginViews()}get composing(){return this.input.composing}get props(){if(this._props.state!=this.state){let e=this._props;this._props={};for(let t in e)this._props[t]=e[t];this._props.state=this.state}return this._props}update(e){e.handleDOMEvents!=this._props.handleDOMEvents&&Ni(this);let t=this._props;this._props=e,e.plugins&&(e.plugins.forEach(Lo),this.directPlugins=e.plugins),this.updateStateInner(e.state,t)}setProps(e){let t={};for(let e in this._props)t[e]=this._props[e];t.state=this.state;for(let n in e)t[n]=e[n];this.update(t)}updateState(e){this.updateStateInner(e,this._props)}updateStateInner(e,t){var n;let r=this.state,i=!1,o=!1;e.storedMarks&&this.composing&&(Hi(this),o=!0),this.state=e;let s=r.plugins!=e.plugins||this._props.plugins!=t.plugins;if(s||this._props.plugins!=t.plugins||this._props.nodeViews!=t.nodeViews){let e=Po(this);(function(e,t){let n=0,r=0;for(let r in e){if(e[r]!=t[r])return!0;n++}for(let e in t)r++;return n!=r})(e,this.nodeViews)&&(this.nodeViews=e,i=!0)}(s||t.handleDOMEvents!=this._props.handleDOMEvents)&&Ni(this),this.editable=Bo(this),Io(this);let l=bo(this),a=zo(this),c=r.plugins==e.plugins||r.doc.eq(e.doc)?e.scrollToSelection>r.scrollToSelection?"to selection":"preserve":"reset",h=i||!this.docView.matchesNode(e.doc,a,l);!h&&e.selection.eq(r.selection)||(o=!0);let u="preserve"==c&&o&&null==this.dom.style.overflowAnchor&&function(e){let t,n,r=e.dom.getBoundingClientRect(),i=Math.max(0,r.top);for(let o=(r.left+r.right)/2,s=i+1;s<Math.min(innerHeight,r.bottom);s+=5){let r=e.root.elementFromPoint(o,s);if(!r||r==e.dom||!e.dom.contains(r))continue;let l=r.getBoundingClientRect();if(l.top>=i-20){t=r,n=l.top;break}}return{refDOM:t,refTop:n,stack:rr(e.dom)}}(this);if(o){this.domObserver.stop();let t=h&&(qn||Jn)&&!this.composing&&!r.selection.empty&&!e.selection.empty&&function(e,t){let n=Math.min(e.$anchor.sharedDepth(e.head),t.$anchor.sharedDepth(t.head));return e.$anchor.start(n)!=t.$anchor.start(n)}(r.selection,e.selection);if(h){let n=Jn?this.trackWrites=this.domSelectionRange().focusNode:null;this.composing&&(this.input.compositionNode=Ui(this)),!i&&this.docView.update(e.doc,a,l,this)||(this.docView.updateOuterDeco(a),this.docView.destroy(),this.docView=Er(e.doc,a,l,this.dom,this)),n&&!this.trackWrites&&(t=!0)}t||!(this.input.mouseDown&&this.domObserver.currentSelection.eq(this.domSelectionRange())&&function(e){let t=e.docView.domFromPos(e.state.selection.anchor,0),n=e.domSelectionRange();return En(t.node,t.offset,n.anchorNode,n.anchorOffset)}(this))?Wr(this,t):(Gr(this,e.selection),this.domObserver.setCurSelection()),this.domObserver.start()}this.updatePluginViews(r),(null===(n=this.dragging)||void 0===n?void 0:n.node)&&!r.doc.eq(e.doc)&&this.updateDraggedNode(this.dragging,r),"reset"==c?this.dom.scrollTop=0:"to selection"==c?this.scrollToSelection():u&&function({refDOM:e,refTop:t,stack:n}){let r=e?e.getBoundingClientRect().top:0;ir(n,0==r?0:r-t)}(u)}scrollToSelection(){let e=this.domSelectionRange().focusNode;if(e&&this.dom.contains(1==e.nodeType?e:e.parentNode))if(this.someProp("handleScrollToSelection",e=>e(this)));else if(this.state.selection instanceof nt){let t=this.docView.domAfterPos(this.state.selection.from);1==t.nodeType&&nr(this,t.getBoundingClientRect(),e)}else nr(this,this.coordsAtPos(this.state.selection.head,1),e);else;}destroyPluginViews(){let e;for(;e=this.pluginViews.pop();)e.destroy&&e.destroy()}updatePluginViews(e){if(e&&e.plugins==this.state.plugins&&this.directPlugins==this.prevDirectPlugins)for(let t=0;t<this.pluginViews.length;t++){let n=this.pluginViews[t];n.update&&n.update(this,e)}else{this.prevDirectPlugins=this.directPlugins,this.destroyPluginViews();for(let e=0;e<this.directPlugins.length;e++){let t=this.directPlugins[e];t.spec.view&&this.pluginViews.push(t.spec.view(this))}for(let e=0;e<this.state.plugins.length;e++){let t=this.state.plugins[e];t.spec.view&&this.pluginViews.push(t.spec.view(this))}}}updateDraggedNode(e,t){let n=e.node,r=-1;if(this.state.doc.nodeAt(n.from)==n.node)r=n.from;else{let e=n.from+(this.state.doc.content.size-t.doc.content.size);(e>0&&this.state.doc.nodeAt(e))==n.node&&(r=e)}this.dragging=new Xi(e.slice,e.move,r<0?void 0:nt.create(this.state.doc,r))}someProp(e,t){let n,r=this._props&&this._props[e];if(null!=r&&(n=t?t(r):r))return n;for(let r=0;r<this.directPlugins.length;r++){let i=this.directPlugins[r].props[e];if(null!=i&&(n=t?t(i):i))return n}let i=this.state.plugins;if(i)for(let r=0;r<i.length;r++){let o=i[r].props[e];if(null!=o&&(n=t?t(o):o))return n}}hasFocus(){if(qn){let e=this.root.activeElement;if(e==this.dom)return!0;if(!e||!this.dom.contains(e))return!1;for(;e&&this.dom!=e&&this.dom.contains(e);){if("false"==e.contentEditable)return!1;e=e.parentElement}return!0}return this.root.activeElement==this.dom}focus(){this.domObserver.stop(),this.editable&&function(e){if(e.setActive)return e.setActive();if(or)return e.focus(or);let t=rr(e);e.focus(null==or?{get preventScroll(){return or={preventScroll:!0},!0}}:void 0),or||(or=!1,ir(t,0))}(this.dom),Wr(this),this.domObserver.start()}get root(){let e=this._root;if(null==e)for(let e=this.dom.parentNode;e;e=e.parentNode)if(9==e.nodeType||11==e.nodeType&&e.host)return e.getSelection||(Object.getPrototypeOf(e).getSelection=()=>e.ownerDocument.getSelection()),this._root=e;return e||document}updateRoot(){this._root=null}posAtCoords(e){return cr(this,e)}coordsAtPos(e,t=1){return dr(this,e,t)}domAtPos(e,t=0){return this.docView.domFromPos(e,t)}nodeDOM(e){let t=this.docView.descAt(e);return t?t.nodeDOM:null}posAtDOM(e,t,n=-1){let r=this.docView.posFromDOM(e,t,n);if(null==r)throw new RangeError("DOM position not inside the editor");return r}endOfTextblock(e,t){return Cr(this,t||this.state,e)}pasteHTML(e,t){return Gi(this,"",e,!1,t||new ClipboardEvent("paste"))}pasteText(e,t){return Gi(this,e,null,!0,t||new ClipboardEvent("paste"))}serializeForClipboard(e){return fi(this,e)}destroy(){this.docView&&(!function(e){e.domObserver.stop();for(let t in e.input.eventHandlers)e.dom.removeEventListener(t,e.input.eventHandlers[t]);clearTimeout(e.input.composingTimeout),clearTimeout(e.input.lastIOSEnterFallbackTimeout)}(this),this.destroyPluginViews(),this.mounted?(this.docView.update(this.state.doc,[],bo(this),this),this.dom.textContent=""):this.dom.parentNode&&this.dom.parentNode.removeChild(this.dom),this.docView.destroy(),this.docView=null,_n=null)}get isDestroyed(){return null==this.docView}dispatchEvent(e){return function(e,t){Fi(e,t)||!Si[t.type]||!e.editable&&t.type in Ei||Si[t.type](e,t)}(this,e)}domSelectionRange(){let e=this.domSelection();return e?Hn&&11===this.root.nodeType&&function(e){let t=e.activeElement;for(;t&&t.shadowRoot;)t=t.shadowRoot.activeElement;return t}(this.dom.ownerDocument)==this.dom&&function(e,t){if(t.getComposedRanges){let n=t.getComposedRanges(e.root)[0];if(n)return So(e,n)}let n;function r(e){e.preventDefault(),e.stopImmediatePropagation(),n=e.getTargetRanges()[0]}return e.dom.addEventListener("beforeinput",r,!0),document.execCommand("indent"),e.dom.removeEventListener("beforeinput",r,!0),n?So(e,n):null}(this,e)||e:{focusNode:null,focusOffset:0,anchorNode:null,anchorOffset:0}}domSelection(){return this.root.getSelection()}}function zo(e){let t=Object.create(null);return t.class="ProseMirror",t.contenteditable=String(e.editable),e.someProp("attributes",n=>{if("function"==typeof n&&(n=n(e.state)),n)for(let e in n)"class"==e?t.class+=" "+n[e]:"style"==e?t.style=(t.style?t.style+";":"")+n[e]:t[e]||"contenteditable"==e||"nodeName"==e||(t[e]=String(n[e]))}),t.translate||(t.translate="no"),[oo.node(0,e.state.doc.content.size,t)]}function Io(e){if(e.markCursor){let t=document.createElement("img");t.className="ProseMirror-separator",t.setAttribute("mark-placeholder","true"),t.setAttribute("alt",""),e.cursorWrapper={dom:t,deco:oo.widget(e.state.selection.from,t,{raw:!0,marks:e.markCursor})}}else e.cursorWrapper=null}function Bo(e){return!e.someProp("editable",t=>!1===t(e.state))}function Po(e){let t=Object.create(null);function n(e){for(let n in e)Object.prototype.hasOwnProperty.call(t,n)||(t[n]=e[n])}return e.someProp("nodeViews",n),e.someProp("markViews",n),t}function Lo(e){if(e.spec.state||e.spec.filterTransaction||e.spec.appendTransaction)throw new RangeError("Plugins passed directly to the view must not have a state component")}Ro.prototype.dispatch=function(e){let t=this._props.dispatchTransaction;t?t.call(this,e):this.updateState(this.state.apply(e))};const qo={};function $o(e,t){"string"!=typeof t&&(t=$o.defaultChars);const n=function(e){let t=qo[e];if(t)return t;t=qo[e]=[];for(let e=0;e<128;e++){const n=String.fromCharCode(e);t.push(n)}for(let n=0;n<e.length;n++){const r=e.charCodeAt(n);t[r]="%"+("0"+r.toString(16).toUpperCase()).slice(-2)}return t}(t);return e.replace(/(%[a-f0-9]{2})+/gi,function(e){let t="";for(let r=0,i=e.length;r<i;r+=3){const o=parseInt(e.slice(r+1,r+3),16);if(o<128)t+=n[o];else{if(192==(224&o)&&r+3<i){const n=parseInt(e.slice(r+4,r+6),16);if(128==(192&n)){const e=o<<6&1984|63&n;t+=e<128?"��":String.fromCharCode(e),r+=3;continue}}if(224==(240&o)&&r+6<i){const n=parseInt(e.slice(r+4,r+6),16),i=parseInt(e.slice(r+7,r+9),16);if(128==(192&n)&&128==(192&i)){const e=o<<12&61440|n<<6&4032|63&i;t+=e<2048||e>=55296&&e<=57343?"���":String.fromCharCode(e),r+=6;continue}}if(240==(248&o)&&r+9<i){const n=parseInt(e.slice(r+4,r+6),16),i=parseInt(e.slice(r+7,r+9),16),s=parseInt(e.slice(r+10,r+12),16);if(128==(192&n)&&128==(192&i)&&128==(192&s)){let e=o<<18&1835008|n<<12&258048|i<<6&4032|63&s;e<65536||e>1114111?t+="����":(e-=65536,t+=String.fromCharCode(55296+(e>>10),56320+(1023&e))),r+=9;continue}}t+="�"}}return t})}$o.defaultChars=";/?:@&=+$,#",$o.componentChars="";const Vo={};function jo(e,t,n){"string"!=typeof t&&(n=t,t=jo.defaultChars),void 0===n&&(n=!0);const r=function(e){let t=Vo[e];if(t)return t;t=Vo[e]=[];for(let e=0;e<128;e++){const n=String.fromCharCode(e);/^[0-9a-z]$/i.test(n)?t.push(n):t.push("%"+("0"+e.toString(16).toUpperCase()).slice(-2))}for(let n=0;n<e.length;n++)t[e.charCodeAt(n)]=e[n];return t}(t);let i="";for(let t=0,o=e.length;t<o;t++){const s=e.charCodeAt(t);if(n&&37===s&&t+2<o&&/^[0-9a-f]{2}$/i.test(e.slice(t+1,t+3)))i+=e.slice(t,t+3),t+=2;else if(s<128)i+=r[s];else if(s>=55296&&s<=57343){if(s>=55296&&s<=56319&&t+1<o){const n=e.charCodeAt(t+1);if(n>=56320&&n<=57343){i+=encodeURIComponent(e[t]+e[t+1]),t++;continue}}i+="%EF%BF%BD"}else i+=encodeURIComponent(e[t])}return i}function Jo(e){let t="";return t+=e.protocol||"",t+=e.slashes?"//":"",t+=e.auth?e.auth+"@":"",e.hostname&&-1!==e.hostname.indexOf(":")?t+="["+e.hostname+"]":t+=e.hostname||"",t+=e.port?":"+e.port:"",t+=e.pathname||"",t+=e.search||"",t+=e.hash||"",t}function Wo(){this.protocol=null,this.slashes=null,this.auth=null,this.port=null,this.hostname=null,this.hash=null,this.search=null,this.pathname=null}jo.defaultChars=";/?:@&=+$,-_.!~*'()#",jo.componentChars="-_.!~*'()";const Ho=/^([a-z0-9.+-]+:)/i,Uo=/:[0-9]*$/,Ko=/^(\/\/?(?!\/)[^\?\s]*)(\?[^\s]*)?$/,Zo=["{","}","|","\\","^","`"].concat(["<",">",'"',"`"," ","\r","\n","\t"]),Go=["'"].concat(Zo),Qo=["%","/","?",";","#"].concat(Go),Xo=["/","?","#"],Yo=/^[+a-z0-9A-Z_-]{0,63}$/,es=/^([+a-z0-9A-Z_-]{0,63})(.*)$/,ts={javascript:!0,"javascript:":!0},ns={http:!0,https:!0,ftp:!0,gopher:!0,file:!0,"http:":!0,"https:":!0,"ftp:":!0,"gopher:":!0,"file:":!0};function rs(e,t){if(e&&e instanceof Wo)return e;const n=new Wo;return n.parse(e,t),n}Wo.prototype.parse=function(e,t){let n,r,i,o=e;if(o=o.trim(),!t&&1===e.split("#").length){const e=Ko.exec(o);if(e)return this.pathname=e[1],e[2]&&(this.search=e[2]),this}let s=Ho.exec(o);if(s&&(s=s[0],n=s.toLowerCase(),this.protocol=s,o=o.substr(s.length)),(t||s||o.match(/^\/\/[^@\/]+@[^@\/]+/))&&(i="//"===o.substr(0,2),!i||s&&ts[s]||(o=o.substr(2),this.slashes=!0)),!ts[s]&&(i||s&&!ns[s])){let e,t,n=-1;for(let e=0;e<Xo.length;e++)r=o.indexOf(Xo[e]),-1!==r&&(-1===n||r<n)&&(n=r);t=-1===n?o.lastIndexOf("@"):o.lastIndexOf("@",n),-1!==t&&(e=o.slice(0,t),o=o.slice(t+1),this.auth=e),n=-1;for(let e=0;e<Qo.length;e++)r=o.indexOf(Qo[e]),-1!==r&&(-1===n||r<n)&&(n=r);-1===n&&(n=o.length),":"===o[n-1]&&n--;const i=o.slice(0,n);o=o.slice(n),this.parseHost(i),this.hostname=this.hostname||"";const s="["===this.hostname[0]&&"]"===this.hostname[this.hostname.length-1];if(!s){const e=this.hostname.split(/\./);for(let t=0,n=e.length;t<n;t++){const n=e[t];if(n&&!n.match(Yo)){let r="";for(let e=0,t=n.length;e<t;e++)n.charCodeAt(e)>127?r+="x":r+=n[e];if(!r.match(Yo)){const r=e.slice(0,t),i=e.slice(t+1),s=n.match(es);s&&(r.push(s[1]),i.unshift(s[2])),i.length&&(o=i.join(".")+o),this.hostname=r.join(".");break}}}}this.hostname.length>255&&(this.hostname=""),s&&(this.hostname=this.hostname.substr(1,this.hostname.length-2))}const l=o.indexOf("#");-1!==l&&(this.hash=o.substr(l),o=o.slice(0,l));const a=o.indexOf("?");return-1!==a&&(this.search=o.substr(a),o=o.slice(0,a)),o&&(this.pathname=o),ns[n]&&this.hostname&&!this.pathname&&(this.pathname=""),this},Wo.prototype.parseHost=function(e){let t=Uo.exec(e);t&&(t=t[0],":"!==t&&(this.port=t.substr(1)),e=e.substr(0,e.length-t.length)),e&&(this.hostname=e)};var is,ss=Object.freeze({__proto__:null,decode:$o,encode:jo,format:Jo,parse:rs}),ls=/[\0-\uD7FF\uE000-\uFFFF]|[\uD800-\uDBFF][\uDC00-\uDFFF]|[\uD800-\uDBFF](?![\uDC00-\uDFFF])|(?:[^\uD800-\uDBFF]|^)[\uDC00-\uDFFF]/,as=/[\0-\x1F\x7F-\x9F]/,cs=/[!-#%-\*,-\/:;\?@\[-\]_\{\}\xA1\xA7\xAB\xB6\xB7\xBB\xBF\u037E\u0387\u055A-\u055F\u0589\u058A\u05BE\u05C0\u05C3\u05C6\u05F3\u05F4\u0609\u060A\u060C\u060D\u061B\u061D-\u061F\u066A-\u066D\u06D4\u0700-\u070D\u07F7-\u07F9\u0830-\u083E\u085E\u0964\u0965\u0970\u09FD\u0A76\u0AF0\u0C77\u0C84\u0DF4\u0E4F\u0E5A\u0E5B\u0F04-\u0F12\u0F14\u0F3A-\u0F3D\u0F85\u0FD0-\u0FD4\u0FD9\u0FDA\u104A-\u104F\u10FB\u1360-\u1368\u1400\u166E\u169B\u169C\u16EB-\u16ED\u1735\u1736\u17D4-\u17D6\u17D8-\u17DA\u1800-\u180A\u1944\u1945\u1A1E\u1A1F\u1AA0-\u1AA6\u1AA8-\u1AAD\u1B5A-\u1B60\u1B7D\u1B7E\u1BFC-\u1BFF\u1C3B-\u1C3F\u1C7E\u1C7F\u1CC0-\u1CC7\u1CD3\u2010-\u2027\u2030-\u2043\u2045-\u2051\u2053-\u205E\u207D\u207E\u208D\u208E\u2308-\u230B\u2329\u232A\u2768-\u2775\u27C5\u27C6\u27E6-\u27EF\u2983-\u2998\u29D8-\u29DB\u29FC\u29FD\u2CF9-\u2CFC\u2CFE\u2CFF\u2D70\u2E00-\u2E2E\u2E30-\u2E4F\u2E52-\u2E5D\u3001-\u3003\u3008-\u3011\u3014-\u301F\u3030\u303D\u30A0\u30FB\uA4FE\uA4FF\uA60D-\uA60F\uA673\uA67E\uA6F2-\uA6F7\uA874-\uA877\uA8CE\uA8CF\uA8F8-\uA8FA\uA8FC\uA92E\uA92F\uA95F\uA9C1-\uA9CD\uA9DE\uA9DF\uAA5C-\uAA5F\uAADE\uAADF\uAAF0\uAAF1\uABEB\uFD3E\uFD3F\uFE10-\uFE19\uFE30-\uFE52\uFE54-\uFE61\uFE63\uFE68\uFE6A\uFE6B\uFF01-\uFF03\uFF05-\uFF0A\uFF0C-\uFF0F\uFF1A\uFF1B\uFF1F\uFF20\uFF3B-\uFF3D\uFF3F\uFF5B\uFF5D\uFF5F-\uFF65]|\uD800[\uDD00-\uDD02\uDF9F\uDFD0]|\uD801\uDD6F|\uD802[\uDC57\uDD1F\uDD3F\uDE50-\uDE58\uDE7F\uDEF0-\uDEF6\uDF39-\uDF3F\uDF99-\uDF9C]|\uD803[\uDEAD\uDF55-\uDF59\uDF86-\uDF89]|\uD804[\uDC47-\uDC4D\uDCBB\uDCBC\uDCBE-\uDCC1\uDD40-\uDD43\uDD74\uDD75\uDDC5-\uDDC8\uDDCD\uDDDB\uDDDD-\uDDDF\uDE38-\uDE3D\uDEA9]|\uD805[\uDC4B-\uDC4F\uDC5A\uDC5B\uDC5D\uDCC6\uDDC1-\uDDD7\uDE41-\uDE43\uDE60-\uDE6C\uDEB9\uDF3C-\uDF3E]|\uD806[\uDC3B\uDD44-\uDD46\uDDE2\uDE3F-\uDE46\uDE9A-\uDE9C\uDE9E-\uDEA2\uDF00-\uDF09]|\uD807[\uDC41-\uDC45\uDC70\uDC71\uDEF7\uDEF8\uDF43-\uDF4F\uDFFF]|\uD809[\uDC70-\uDC74]|\uD80B[\uDFF1\uDFF2]|\uD81A[\uDE6E\uDE6F\uDEF5\uDF37-\uDF3B\uDF44]|\uD81B[\uDE97-\uDE9A\uDFE2]|\uD82F\uDC9F|\uD836[\uDE87-\uDE8B]|\uD83A[\uDD5E\uDD5F]/,hs=/[\$\+<->\^`\|~\xA2-\xA6\xA8\xA9\xAC\xAE-\xB1\xB4\xB8\xD7\xF7\u02C2-\u02C5\u02D2-\u02DF\u02E5-\u02EB\u02ED\u02EF-\u02FF\u0375\u0384\u0385\u03F6\u0482\u058D-\u058F\u0606-\u0608\u060B\u060E\u060F\u06DE\u06E9\u06FD\u06FE\u07F6\u07FE\u07FF\u0888\u09F2\u09F3\u09FA\u09FB\u0AF1\u0B70\u0BF3-\u0BFA\u0C7F\u0D4F\u0D79\u0E3F\u0F01-\u0F03\u0F13\u0F15-\u0F17\u0F1A-\u0F1F\u0F34\u0F36\u0F38\u0FBE-\u0FC5\u0FC7-\u0FCC\u0FCE\u0FCF\u0FD5-\u0FD8\u109E\u109F\u1390-\u1399\u166D\u17DB\u1940\u19DE-\u19FF\u1B61-\u1B6A\u1B74-\u1B7C\u1FBD\u1FBF-\u1FC1\u1FCD-\u1FCF\u1FDD-\u1FDF\u1FED-\u1FEF\u1FFD\u1FFE\u2044\u2052\u207A-\u207C\u208A-\u208C\u20A0-\u20C0\u2100\u2101\u2103-\u2106\u2108\u2109\u2114\u2116-\u2118\u211E-\u2123\u2125\u2127\u2129\u212E\u213A\u213B\u2140-\u2144\u214A-\u214D\u214F\u218A\u218B\u2190-\u2307\u230C-\u2328\u232B-\u2426\u2440-\u244A\u249C-\u24E9\u2500-\u2767\u2794-\u27C4\u27C7-\u27E5\u27F0-\u2982\u2999-\u29D7\u29DC-\u29FB\u29FE-\u2B73\u2B76-\u2B95\u2B97-\u2BFF\u2CE5-\u2CEA\u2E50\u2E51\u2E80-\u2E99\u2E9B-\u2EF3\u2F00-\u2FD5\u2FF0-\u2FFF\u3004\u3012\u3013\u3020\u3036\u3037\u303E\u303F\u309B\u309C\u3190\u3191\u3196-\u319F\u31C0-\u31E3\u31EF\u3200-\u321E\u322A-\u3247\u3250\u3260-\u327F\u328A-\u32B0\u32C0-\u33FF\u4DC0-\u4DFF\uA490-\uA4C6\uA700-\uA716\uA720\uA721\uA789\uA78A\uA828-\uA82B\uA836-\uA839\uAA77-\uAA79\uAB5B\uAB6A\uAB6B\uFB29\uFBB2-\uFBC2\uFD40-\uFD4F\uFDCF\uFDFC-\uFDFF\uFE62\uFE64-\uFE66\uFE69\uFF04\uFF0B\uFF1C-\uFF1E\uFF3E\uFF40\uFF5C\uFF5E\uFFE0-\uFFE6\uFFE8-\uFFEE\uFFFC\uFFFD]|\uD800[\uDD37-\uDD3F\uDD79-\uDD89\uDD8C-\uDD8E\uDD90-\uDD9C\uDDA0\uDDD0-\uDDFC]|\uD802[\uDC77\uDC78\uDEC8]|\uD805\uDF3F|\uD807[\uDFD5-\uDFF1]|\uD81A[\uDF3C-\uDF3F\uDF45]|\uD82F\uDC9C|\uD833[\uDF50-\uDFC3]|\uD834[\uDC00-\uDCF5\uDD00-\uDD26\uDD29-\uDD64\uDD6A-\uDD6C\uDD83\uDD84\uDD8C-\uDDA9\uDDAE-\uDDEA\uDE00-\uDE41\uDE45\uDF00-\uDF56]|\uD835[\uDEC1\uDEDB\uDEFB\uDF15\uDF35\uDF4F\uDF6F\uDF89\uDFA9\uDFC3]|\uD836[\uDC00-\uDDFF\uDE37-\uDE3A\uDE6D-\uDE74\uDE76-\uDE83\uDE85\uDE86]|\uD838[\uDD4F\uDEFF]|\uD83B[\uDCAC\uDCB0\uDD2E\uDEF0\uDEF1]|\uD83C[\uDC00-\uDC2B\uDC30-\uDC93\uDCA0-\uDCAE\uDCB1-\uDCBF\uDCC1-\uDCCF\uDCD1-\uDCF5\uDD0D-\uDDAD\uDDE6-\uDE02\uDE10-\uDE3B\uDE40-\uDE48\uDE50\uDE51\uDE60-\uDE65\uDF00-\uDFFF]|\uD83D[\uDC00-\uDED7\uDEDC-\uDEEC\uDEF0-\uDEFC\uDF00-\uDF76\uDF7B-\uDFD9\uDFE0-\uDFEB\uDFF0]|\uD83E[\uDC00-\uDC0B\uDC10-\uDC47\uDC50-\uDC59\uDC60-\uDC87\uDC90-\uDCAD\uDCB0\uDCB1\uDD00-\uDE53\uDE60-\uDE6D\uDE70-\uDE7C\uDE80-\uDE88\uDE90-\uDEBD\uDEBF-\uDEC5\uDECE-\uDEDB\uDEE0-\uDEE8\uDEF0-\uDEF8\uDF00-\uDF92\uDF94-\uDFCA]/,us=/[ \xA0\u1680\u2000-\u200A\u2028\u2029\u202F\u205F\u3000]/,ps=Object.freeze({__proto__:null,Any:ls,Cc:as,Cf:/[\xAD\u0600-\u0605\u061C\u06DD\u070F\u0890\u0891\u08E2\u180E\u200B-\u200F\u202A-\u202E\u2060-\u2064\u2066-\u206F\uFEFF\uFFF9-\uFFFB]|\uD804[\uDCBD\uDCCD]|\uD80D[\uDC30-\uDC3F]|\uD82F[\uDCA0-\uDCA3]|\uD834[\uDD73-\uDD7A]|\uDB40[\uDC01\uDC20-\uDC7F]/,P:cs,S:hs,Z:us}),ds=new Uint16Array('ᵁ<Õıʊҝջאٵ۞ޢߖࠏઑඡ༉༦ረዡᐕᒝᓃᓟᔥ\0\0\0\0\0\0ᕫᛍᦍᰒᷝ↰⊍⏀⏻⑂⠤⤒ⴈ⹈⿎〖㊺㘹㞬㣾㨨㩱㫠㬮ࠀEMabcfglmnoprstu\\bfms¦³¹ÈÏlig耻Æ䃆P耻&䀦cute耻Á䃁reve;䄂Āiyx}rc耻Â䃂;䐐r;쀀𝔄rave耻À䃀pha;䎑acr;䄀d;橓Āgp¡on;䄄f;쀀𝔸plyFunction;恡ing耻Å䃅Ācs¾Ãr;쀀𝒜ign;扔ilde耻Ã䃃ml耻Ä䃄ЀaceforsuåûþėĜĢħĪĀcrêòkslash;或Ŷöø;櫧ed;挆y;䐑ƀcrtąċĔause;戵noullis;愬a;䎒r;쀀𝔅pf;쀀𝔹eve;䋘còēmpeq;扎܀HOacdefhilorsuōőŖƀƞƢƵƷƺǜȕɳɸɾcy;䐧PY耻©䂩ƀcpyŝŢźute;䄆Ā;iŧŨ拒talDifferentialD;慅leys;愭ȀaeioƉƎƔƘron;䄌dil耻Ç䃇rc;䄈nint;戰ot;䄊ĀdnƧƭilla;䂸terDot;䂷òſi;䎧rcleȀDMPTLJNjǑǖot;抙inus;抖lus;投imes;抗oĀcsǢǸkwiseContourIntegral;戲eCurlyĀDQȃȏoubleQuote;思uote;怙ȀlnpuȞȨɇɕonĀ;eȥȦ户;橴ƀgitȯȶȺruent;扡nt;戯ourIntegral;戮ĀfrɌɎ;愂oduct;成nterClockwiseContourIntegral;戳oss;樯cr;쀀𝒞pĀ;Cʄʅ拓ap;才րDJSZacefiosʠʬʰʴʸˋ˗ˡ˦̳ҍĀ;oŹʥtrahd;椑cy;䐂cy;䐅cy;䐏ƀgrsʿ˄ˇger;怡r;憡hv;櫤Āayː˕ron;䄎;䐔lĀ;t˝˞戇a;䎔r;쀀𝔇Āaf˫̧Ācm˰̢riticalȀADGT̖̜̀̆cute;䂴oŴ̋̍;䋙bleAcute;䋝rave;䁠ilde;䋜ond;拄ferentialD;慆Ѱ̽\0\0\0͔͂\0Ѕf;쀀𝔻ƀ;DE͈͉͍䂨ot;惜qual;扐blèCDLRUVͣͲϏϢϸontourIntegraìȹoɴ\0\0ͻ»͉nArrow;懓Āeo·ΤftƀARTΐΖΡrrow;懐ightArrow;懔eåˊngĀLRΫτeftĀARγιrrow;柸ightArrow;柺ightArrow;柹ightĀATϘϞrrow;懒ee;抨pɁϩ\0\0ϯrrow;懑ownArrow;懕erticalBar;戥ǹABLRTaВЪаўѿͼrrowƀ;BUНОТ憓ar;椓pArrow;懵reve;䌑eft˒к\0ц\0ѐightVector;楐eeVector;楞ectorĀ;Bљњ憽ar;楖ightǔѧ\0ѱeeVector;楟ectorĀ;BѺѻ懁ar;楗eeĀ;A҆҇护rrow;憧ĀctҒҗr;쀀𝒟rok;䄐ࠀNTacdfglmopqstuxҽӀӄӋӞӢӧӮӵԡԯԶՒ՝ՠեG;䅊H耻Ð䃐cute耻É䃉ƀaiyӒӗӜron;䄚rc耻Ê䃊;䐭ot;䄖r;쀀𝔈rave耻È䃈ement;戈ĀapӺӾcr;䄒tyɓԆ\0\0ԒmallSquare;旻erySmallSquare;斫ĀgpԦԪon;䄘f;쀀𝔼silon;䎕uĀaiԼՉlĀ;TՂՃ橵ilde;扂librium;懌Āci՚r;愰m;橳a;䎗ml耻Ë䃋Āipժկsts;戃onentialE;慇ʀcfiosօֈ֍ֲy;䐤r;쀀𝔉lledɓ֗\0\0֣mallSquare;旼erySmallSquare;斪Ͱֺ\0ֿ\0\0ׄf;쀀𝔽All;戀riertrf;愱còJTabcdfgorstרׯؒؖ؛؝أ٬ٲcy;䐃耻>䀾mmaĀ;d䎓;䏜reve;䄞ƀeiy؇،ؐdil;䄢rc;䄜;䐓ot;䄠r;쀀𝔊;拙pf;쀀𝔾eater̀EFGLSTصلَٖٛ٦qualĀ;Lؾؿ扥ess;招ullEqual;执reater;檢ess;扷lantEqual;橾ilde;扳cr;쀀𝒢;扫ЀAacfiosuڅڋږڛڞڪھۊRDcy;䐪Āctڐڔek;䋇;䁞irc;䄤r;愌lbertSpace;愋ǰگ\0ڲf;愍izontalLine;攀Āctۃۅòکrok;䄦mpńېۘownHumðįqual;扏܀EJOacdfgmnostuۺ۾܃܇ܚܞܡܨ݄ݸދޏޕcy;䐕lig;䄲cy;䐁cute耻Í䃍Āiyܓܘrc耻Î䃎;䐘ot;䄰r;愑rave耻Ì䃌ƀ;apܠܯܿĀcgܴܷr;䄪inaryI;慈lieóϝǴ݉\0ݢĀ;eݍݎ戬Āgrݓݘral;戫section;拂isibleĀCTݬݲomma;恣imes;恢ƀgptݿރވon;䄮f;쀀𝕀a;䎙cr;愐ilde;䄨ǫޚ\0ޞcy;䐆l耻Ï䃏ʀcfosuެ߂ߐĀiyޱrc;䄴;䐙r;쀀𝔍pf;쀀𝕁ǣ߇\0ߌr;쀀𝒥rcy;䐈kcy;䐄HJacfosߤߨ߽߬߱ࠂࠈcy;䐥cy;䐌ppa;䎚Āey߶dil;䄶;䐚r;쀀𝔎pf;쀀𝕂cr;쀀𝒦րJTaceflmostࠥࠩࠬࡐࡣসে্ੇcy;䐉耻<䀼ʀcmnpr࠷࠼ࡁࡄࡍute;䄹bda;䎛g;柪lacetrf;愒r;憞ƀaeyࡗࡡron;䄽dil;䄻;䐛Āfsࡨ॰tԀACDFRTUVarࡾࢩࢱࣦ࣠ࣼयज़ΐ४ĀnrࢃgleBracket;柨rowƀ;BR࢙࢚࢞憐ar;懤ightArrow;懆eiling;挈oǵࢷ\0ࣃbleBracket;柦nǔࣈ\0࣒eeVector;楡ectorĀ;Bࣛࣜ懃ar;楙loor;挊ightĀAV࣯ࣵrrow;憔ector;楎Āerँगeƀ;AVउऊऐ抣rrow;憤ector;楚iangleƀ;BEतथऩ抲ar;槏qual;抴pƀDTVषूौownVector;楑eeVector;楠ectorĀ;Bॖॗ憿ar;楘ectorĀ;B॥०憼ar;楒ightáΜs̀EFGLSTॾঋকঝঢভqualGreater;拚ullEqual;扦reater;扶ess;檡lantEqual;橽ilde;扲r;쀀𝔏Ā;eঽা拘ftarrow;懚idot;䄿ƀnpwਖਛgȀLRlr৷ਂਐeftĀAR০৬rrow;柵ightArrow;柷ightArrow;柶eftĀarγਊightáοightáϊf;쀀𝕃erĀLRਢਬeftArrow;憙ightArrow;憘ƀchtਾੀੂòࡌ;憰rok;䅁;扪Ѐacefiosuਗ਼અઋp;椅y;䐜Ādl੯iumSpace;恟lintrf;愳r;쀀𝔐nusPlus;戓pf;쀀𝕄cò੶;䎜ҀJacefostuણધભીଔଙඑඞcy;䐊cute;䅃ƀaeyહાron;䅇dil;䅅;䐝ƀgswે૰ativeƀMTV૨ediumSpace;怋hiĀcn૦ëeryThiîtedĀGLଆreaterGreateòٳessLesóੈLine;䀊r;쀀𝔑ȀBnptଢନଷreak;恠BreakingSpace;䂠f;愕ڀ;CDEGHLNPRSTV୕ୖ୪௫ఄ಄ದൡඅ櫬Āoungruent;扢pCap;扭oubleVerticalBar;戦ƀlqxஃஊement;戉ualĀ;Tஒஓ扠ilde;쀀≂̸ists;戄reater;EFGLSTஶஷ扯qual;扱ullEqual;쀀≧̸reater;쀀≫̸ess;批lantEqual;쀀⩾̸ilde;扵umpń௲ownHump;쀀≎̸qual;쀀≏̸eĀfsఊధtTriangleƀ;BEచఛడ拪ar;쀀⧏̸qual;括s̀;EGLSTవశ఼ౄోౘ扮qual;扰reater;扸ess;쀀≪̸lantEqual;쀀⩽̸ilde;扴estedĀGL౨౹reaterGreater;쀀⪢̸essLess;쀀⪡̸recedesƀ;ESಒಓಛ技qual;쀀⪯̸lantEqual;拠ĀeiಫಹverseElement;戌ghtTriangleƀ;BEೋೌ拫ar;쀀⧐̸qual;拭ĀquೝഌuareSuĀbp೨setĀ;Eೳ쀀⊏̸qual;拢ersetĀ;Eഃആ쀀⊐̸qual;拣ƀbcpഓതൎsetĀ;Eഛഞ쀀⊂⃒qual;抈ceedsȀ;ESTലള഻െ抁qual;쀀⪰̸lantEqual;拡ilde;쀀≿̸ersetĀ;E൘൛쀀⊃⃒qual;抉ildeȀ;EFT൮൯൵ൿ扁qual;扄ullEqual;扇ilde;扉erticalBar;戤cr;쀀𝒩ilde耻Ñ䃑;䎝܀Eacdfgmoprstuvලෂෛ෧ขภยา฿ไlig;䅒cute耻Ó䃓Āiyීrc耻Ô䃔;䐞blac;䅐r;쀀𝔒rave耻Ò䃒ƀaei෮ෲcr;䅌ga;䎩cron;䎟pf;쀀𝕆enCurlyĀDQฎบoubleQuote;怜uote;怘;橔Āclวฬr;쀀𝒪ash耻Ø䃘iŬืde耻Õ䃕es;樷ml耻Ö䃖erĀBP๋Āar๐๓r;怾acĀek๚;揞et;掴arenthesis;揜ҀacfhilorsງຊຏຒດຝະrtialD;戂y;䐟r;쀀𝔓i;䎦;䎠usMinus;䂱Āipຢອncareplanåڝf;愙Ȁ;eio຺ູ檻cedesȀ;EST່້扺qual;檯lantEqual;扼ilde;找me;怳Ādpuct;戏ortionĀ;aȥl;戝Āci༁༆r;쀀𝒫;䎨ȀUfos༑༖༛༟OT耻"䀢r;쀀𝔔pf;愚cr;쀀𝒬BEacefhiorsu༾གྷཇའཱིྦྷྪྭ႖ႩႴႾarr;椐G耻®䂮ƀcnrཎནབute;䅔g;柫rĀ;tཛྷཝ憠l;椖ƀaeyཧཬཱron;䅘dil;䅖;䐠Ā;vླྀཹ愜erseĀEUྂྙĀlq྇ྎement;戋uilibrium;懋pEquilibrium;楯r»ཹo;䎡ghtЀACDFTUVa࿁ဢဨၛႇϘĀnr࿆࿒gleBracket;柩rowƀ;BL憒ar;懥eftArrow;懄eiling;按oǵ\0စbleBracket;柧nǔည\0နeeVector;楝ectorĀ;Bဝသ懂ar;楕loor;挋Āerိ၃eƀ;AVဵံြ抢rrow;憦ector;楛iangleƀ;BEၐၑၕ抳ar;槐qual;抵pƀDTVၣၮၸownVector;楏eeVector;楜ectorĀ;Bႂႃ憾ar;楔ectorĀ;B႑႒懀ar;楓Āpuႛ႞f;愝ndImplies;楰ightarrow;懛ĀchႹႼr;愛;憱leDelayed;槴ڀHOacfhimoqstuფჱჷჽᄙᄞᅑᅖᅡᅧᆵᆻᆿĀCcჩხHcy;䐩y;䐨FTcy;䐬cute;䅚ʀ;aeiyᄈᄉᄎᄓᄗ檼ron;䅠dil;䅞rc;䅜;䐡r;쀀𝔖ortȀDLRUᄪᄴᄾᅉownArrow»ОeftArrow»࢚ightArrow»pArrow;憑gma;䎣allCircle;战pf;쀀𝕊ɲᅭ\0\0ᅰt;戚areȀ;ISUᅻᅼᆉᆯ斡ntersection;抓uĀbpᆏᆞsetĀ;Eᆗᆘ抏qual;抑ersetĀ;Eᆨᆩ抐qual;抒nion;抔cr;쀀𝒮ar;拆ȀbcmpᇈᇛሉላĀ;sᇍᇎ拐etĀ;Eᇍᇕqual;抆ĀchᇠህeedsȀ;ESTᇭᇮᇴᇿ扻qual;檰lantEqual;扽ilde;承Tháྌ;我ƀ;esሒሓሣ拑rsetĀ;Eሜም抃qual;抇et»ሓրHRSacfhiorsሾቄቕቱቶኟዂወዑORN耻Þ䃞ADE;愢ĀHcቒcy;䐋y;䐦Ābuቚቜ;䀉;䎤ƀaeyብቪቯron;䅤dil;䅢;䐢r;쀀𝔗ĀeiቻDzኀ\0ኇefore;戴a;䎘ĀcnኘkSpace;쀀 Space;怉ldeȀ;EFTካኬኲኼ戼qual;扃ullEqual;扅ilde;扈pf;쀀𝕋ipleDot;惛Āctዖዛr;쀀𝒯rok;䅦ૡዷጎጚጦ\0ጬጱ\0\0\0\0\0ጸጽ፷ᎅ\0ᐄᐊᐐĀcrዻጁute耻Ú䃚rĀ;oጇገ憟cir;楉rǣጓ\0y;䐎ve;䅬Āiyጞጣrc耻Û䃛;䐣blac;䅰r;쀀𝔘rave耻Ù䃙acr;䅪Ādiፁ፩erĀBPፈ፝Āarፍፐr;䁟acĀekፗፙ;揟et;掵arenthesis;揝onĀ;P፰፱拃lus;抎Āgp፻on;䅲f;쀀𝕌ЀADETadps᎕ᎮᎸᏄϨᏒᏗᏳrrowƀ;BDᅐᎠᎤar;椒ownArrow;懅ownArrow;憕quilibrium;楮eeĀ;AᏋᏌ报rrow;憥ownáϳerĀLRᏞᏨeftArrow;憖ightArrow;憗iĀ;lᏹᏺ䏒on;䎥ing;䅮cr;쀀𝒰ilde;䅨ml耻Ü䃜ҀDbcdefosvᐧᐬᐰᐳᐾᒅᒊᒐᒖash;披ar;櫫y;䐒ashĀ;lᐻᐼ抩;櫦Āerᑃᑅ;拁ƀbtyᑌᑐᑺar;怖Ā;iᑏᑕcalȀBLSTᑡᑥᑪᑴar;戣ine;䁼eparator;杘ilde;所ThinSpace;怊r;쀀𝔙pf;쀀𝕍cr;쀀𝒱dash;抪ʀcefosᒧᒬᒱᒶᒼirc;䅴dge;拀r;쀀𝔚pf;쀀𝕎cr;쀀𝒲Ȁfiosᓋᓐᓒᓘr;쀀𝔛;䎞pf;쀀𝕏cr;쀀𝒳ҀAIUacfosuᓱᓵᓹᓽᔄᔏᔔᔚᔠcy;䐯cy;䐇cy;䐮cute耻Ý䃝Āiyᔉᔍrc;䅶;䐫r;쀀𝔜pf;쀀𝕐cr;쀀𝒴ml;䅸ЀHacdefosᔵᔹᔿᕋᕏᕝᕠᕤcy;䐖cute;䅹Āayᕄᕉron;䅽;䐗ot;䅻Dzᕔ\0ᕛoWidtèa;䎖r;愨pf;愤cr;쀀𝒵ᖃᖊᖐ\0ᖰᖶᖿ\0\0\0\0ᗆᗛᗫᙟ᙭\0ᚕ᚛ᚲᚹ\0ᚾcute耻á䃡reve;䄃̀;Ediuyᖜᖝᖡᖣᖨᖭ戾;쀀∾̳;房rc耻â䃢te肻´̆;䐰lig耻æ䃦Ā;r²ᖺ;쀀𝔞rave耻à䃠ĀepᗊᗖĀfpᗏᗔsym;愵èᗓha;䎱ĀapᗟcĀclᗤᗧr;䄁g;樿ɤᗰ\0\0ᘊʀ;adsvᗺᗻᗿᘁᘇ戧nd;橕;橜lope;橘;橚;elmrszᘘᘙᘛᘞᘿᙏᙙ戠;榤e»ᘙsdĀ;aᘥᘦ戡ѡᘰᘲᘴᘶᘸᘺᘼᘾ;榨;榩;榪;榫;榬;榭;榮;榯tĀ;vᙅᙆ戟bĀ;dᙌᙍ抾;榝Āptᙔᙗh;戢»¹arr;捼Āgpᙣᙧon;䄅f;쀀𝕒;Eaeiopᙻᙽᚂᚄᚇᚊ;橰cir;橯;扊d;手s;䀧roxĀ;eᚒñᚃing耻å䃥ƀctyᚡᚦᚨr;쀀𝒶;䀪mpĀ;eᚯñʈilde耻ã䃣ml耻ä䃤Āciᛂᛈoninôɲnt;樑ࠀNabcdefiklnoprsu᛭ᛱᜰᝃᝈ០៦ᠹᡐᜍ᥈ᥰot;櫭ĀcrᛶkȀcepsᜀᜅᜍᜓong;扌psilon;䏶rime;怵imĀ;e戽q;拍Ŷᜢᜦee;抽edĀ;gᜬᜭ挅e»ᜭrkĀ;tbrk;掶Āoyᜁᝁ;䐱quo;怞ʀcmprtᝓᝡᝤᝨausĀ;eĊĉptyv;榰séᜌnoõēƀahwᝯᝳ;䎲;愶een;扬r;쀀𝔟gcostuvwឍឝឳេ៕៛ƀaiuបពរðݠrc;旯p»፱ƀdptឤឨឭot;樀lus;樁imes;樂ɱឹ\0\0ើcup;樆ar;昅riangleĀdu៍្own;施p;斳plus;樄eåᑄåᒭarow;植ƀakoᠦᠵĀcn៲ᠣkƀlst֫᠂ozenge;槫riangleȀ;dlr᠒᠓᠘斴own;斾eft;旂ight;斸k;搣Ʊᠫ\0ᠳƲᠯ\0ᠱ;斒;斑4;斓ck;斈ĀeoᠾᡍĀ;qᡃᡆ쀀=⃥uiv;쀀≡⃥t;挐Ȁptwxᡙᡞᡧᡬf;쀀𝕓Ā;tᏋᡣom»Ꮜtie;拈DHUVbdhmptuvᢅᢖᢪᢻᣗᣛᣬᤅᤊᤐᤡȀLRlrᢎᢐᢒᢔ;敗;敔;敖;敓ʀ;DUduᢡᢢᢤᢦᢨ敐;敦;敩;敤;敧ȀLRlrᢳᢵᢷᢹ;敝;敚;敜;教;HLRhlrᣊᣋᣍᣏᣑᣓᣕ救;敬;散;敠;敫;敢;敟ox;槉ȀLRlrᣤᣦᣨᣪ;敕;敒;攐;攌ʀ;DUduڽ;敥;敨;攬;攴inus;抟lus;択imes;抠ȀLRlrᤙᤛᤝ;敛;敘;攘;攔;HLRhlrᤰᤱᤳᤵᤷ᤻᤹攂;敪;敡;敞;攼;攤;攜Āevģbar耻¦䂦Ȁceioᥑᥖᥚᥠr;쀀𝒷mi;恏mĀ;elƀ;bhᥨᥩᥫ䁜;槅sub;柈ŬᥴlĀ;e怢t»pƀ;Eeįᦅᦇ;檮Ā;qۜۛೡᦧ\0᧨ᨑᨕᨲ\0ᨷᩐ\0\0᪴\0\0᫁\0\0ᬡᬮ᭒\0᯽\0ᰌƀcprᦲute;䄇̀;abcdsᦿᧀᧄ᧕᧙戩nd;橄rcup;橉Āau᧒p;橋p;橇ot;橀;쀀∩︀Āeo᧢᧥t;恁îړȀaeiu᧰᧻ᨁᨅǰ᧵\0᧸s;橍on;䄍dil耻ç䃧rc;䄉psĀ;sᨌᨍ橌m;橐ot;䄋ƀdmnᨛᨠᨦil肻¸ƭptyv;榲t脀¢;eᨭᨮ䂢räƲr;쀀𝔠ƀceiᨽᩀᩍy;䑇ckĀ;mᩇᩈ朓ark»ᩈ;䏇r;Ecefms᩠ᩢᩫ᪤᪪旋;槃ƀ;elᩩᩪᩭ䋆q;扗eɡᩴ\0\0᪈rrowĀlr᩼᪁eft;憺ight;憻ʀRSacd᪒᪔᪖»ཇ;擈st;抛irc;抚ash;抝nint;樐id;櫯cir;槂ubsĀ;u᪻᪼晣it»᪼ˬ᫇\0ᬊonĀ;eᫍᫎ䀺Ā;qÇÆɭ\0\0aĀ;t䀬;䁀ƀ;fl戁îᅠeĀmxent»eóɍǧ\0ᬇĀ;dኻᬂot;橭nôɆƀfryᬐᬔᬗ;쀀𝕔oäɔ脀©;sŕᬝr;愗Āaoᬥᬩrr;憵ss;朗Ācuᬲᬷr;쀀𝒸Ābpᬼ᭄Ā;eᭁᭂ櫏;櫑Ā;eᭉᭊ櫐;櫒dot;拯delprvw᭠᭬᭷ᮂᮬᯔarrĀlr᭨᭪;椸;椵ɰ᭲\0\0᭵r;拞c;拟arrĀ;pᮀ憶;椽̀;bcdosᮏᮐᮖᮡᮥᮨ截rcap;橈Āauᮛᮞp;橆p;橊ot;抍r;橅;쀀∪︀Ȁalrv᮵ᮿᯞᯣrrĀ;mᮼᮽ憷;椼yƀevwᯇᯔᯘqɰᯎ\0\0ᯒreã᭳uã᭵ee;拎edge;拏en耻¤䂤earrowĀlrᯮ᯳eft»ᮀight»ᮽeäᯝĀciᰁᰇoninôǷnt;戱lcty;挭ঀAHabcdefhijlorstuwz᰻᰿ᱝᱩᱵᲞᲬᲷᴍᵻᶑᶫᶻ᷆᷍ròar;楥Ȁglrs᱈ᱍ᱒᱔ger;怠eth;愸òᄳhĀ;vᱚᱛ怐»ऊūᱡᱧarow;椏aã̕Āayᱮᱳron;䄏;䐴ƀ;ao̲ᱼᲄĀgrʿᲁr;懊tseq;橷ƀglmᲑᲔᲘ耻°䂰ta;䎴ptyv;榱ĀirᲣᲨsht;楿;쀀𝔡arĀlrᲳᲵ»ࣜ»သʀaegsv᳂᳖᳜᳠mƀ;oș᳔ndĀ;ș᳑uit;晦amma;䏝in;拲ƀ;io᳧᳨᳸䃷de脀÷;o᳧ᳰntimes;拇nø᳷cy;䑒cɯᴆ\0\0ᴊrn;挞op;挍ʀlptuwᴘᴝᴢᵉᵕlar;䀤f;쀀𝕕ʀ;emps̋ᴭᴷᴽᵂqĀ;d͒ᴳot;扑inus;戸lus;戔quare;抡blebarwedgåúnƀadhᄮᵝᵧownarrowóᲃarpoonĀlrᵲᵶefôᲴighôᲶŢᵿᶅkaro÷གɯᶊ\0\0ᶎrn;挟op;挌ƀcotᶘᶣᶦĀryᶝᶡ;쀀𝒹;䑕l;槶rok;䄑Ādrᶰᶴot;拱iĀ;fᶺ᠖斿Āah᷀᷃ròЩaòྦangle;榦Āci᷒ᷕy;䑟grarr;柿ऀDacdefglmnopqrstuxḁḉḙḸոḼṉṡṾấắẽỡἪἷὄĀDoḆᴴoôĀcsḎḔute耻é䃩ter;橮ȀaioyḢḧḱḶron;䄛rĀ;cḭḮ扖耻ê䃪lon;払;䑍ot;䄗ĀDrṁṅot;扒;쀀𝔢ƀ;rsṐṑṗ檚ave耻è䃨Ā;dṜṝ檖ot;檘Ȁ;ilsṪṫṲṴ檙nters;揧;愓Ā;dṹṺ檕ot;檗ƀapsẅẉẗcr;䄓tyƀ;svẒẓẕ戅et»ẓpĀ1;ẝẤijạả;怄;怅怃ĀgsẪẬ;䅋p;怂ĀgpẴẸon;䄙f;쀀𝕖ƀalsỄỎỒrĀ;sỊị拕l;槣us;橱iƀ;lvỚớở䎵on»ớ;䏵ȀcsuvỪỳἋἣĀioữḱrc»Ḯɩỹ\0\0ỻíՈantĀglἂἆtr»ṝess»ṺƀaeiἒἚls;䀽st;扟vĀ;DȵἠD;橸parsl;槥ĀDaἯἳot;打rr;楱ƀcdiἾὁỸr;愯oô͒ĀahὉὋ;䎷耻ð䃰Āmrὓὗl耻ë䃫o;悬ƀcipὡὤὧl;䀡sôծĀeoὬὴctatioîՙnentialåչৡᾒ\0ᾞ\0ᾡᾧ\0\0ῆῌ\0ΐ\0ῦῪ \0 ⁚llingdotseñṄy;䑄male;晀ƀilrᾭᾳ῁lig;耀ffiɩᾹ\0\0᾽g;耀ffig;耀ffl;쀀𝔣lig;耀filig;쀀fjƀaltῙῡt;晭ig;耀flns;斱of;䆒ǰ΅\0ῳf;쀀𝕗ĀakֿῷĀ;vῼ´拔;櫙artint;樍Āao⁕Ācs‑⁒ႉ‸⁅⁈\0⁐β•‥‧\0耻½䂽;慓耻¼䂼;慕;慙;慛Ƴ‴\0‶;慔;慖ʴ‾⁁\0\0⁃耻¾䂾;慗;慜5;慘ƶ⁌\0⁎;慚;慝8;慞l;恄wn;挢cr;쀀𝒻ࢀEabcdefgijlnorstv₂₉₥₰₴⃰℃ℒℸ̗ℾ⅒↞Ā;lٍ₇;檌ƀcmpₐₕute;䇵maĀ;dₜ᳚䎳;檆reve;䄟Āiy₪₮rc;䄝;䐳ot;䄡Ȁ;lqsؾق₽ƀ;qsؾٌlanô٥Ȁ;cdl٥⃒⃥⃕c;檩otĀ;o⃜⃝檀Ā;l⃢⃣檂;檄Ā;e⃪⃭쀀⋛︀s;檔r;쀀𝔤Ā;gٳ؛mel;愷cy;䑓Ȁ;Eajٚℌℎℐ;檒;檥;檤ȀEaesℛℝ℩ℴ;扩pĀ;p℣ℤ檊rox»ℤĀ;q℮ℯ檈Ā;q℮ℛim;拧pf;쀀𝕘Āci⅃ⅆr;愊mƀ;el٫ⅎ⅐;檎;檐茀>;cdlqrⅠⅪⅮⅳⅹĀciⅥⅧ;檧r;橺ot;拗Par;榕uest;橼ʀadelsↄⅪ←ٖ↛ǰ↉\0proør;楸qĀlqؿ↖lesó₈ií٫Āen↣↭rtneqq;쀀≩︀Å↪ԀAabcefkosy⇄⇇⇱⇵⇺∘∝∯≨≽ròΠȀilmr⇐⇔⇗⇛rsðᒄf»․ilôکĀdr⇠⇤cy;䑊ƀ;cwࣴ⇫⇯ir;楈;憭ar;意irc;䄥ƀalr∁∎∓rtsĀ;u∉∊晥it»∊lip;怦con;抹r;쀀𝔥sĀew∣∩arow;椥arow;椦ʀamopr∺∾≃≞≣rr;懿tht;戻kĀlr≉≓eftarrow;憩ightarrow;憪f;쀀𝕙bar;怕ƀclt≯≴≸r;쀀𝒽asè⇴rok;䄧Ābp⊂⊇ull;恃hen»ᱛૡ⊣\0⊪\0⊸⋅⋎\0⋕⋳\0\0⋸⌢⍧⍢⍿\0⎆⎪⎴cute耻í䃭ƀ;iyݱ⊰⊵rc耻î䃮;䐸Ācx⊼⊿y;䐵cl耻¡䂡ĀfrΟ⋉;쀀𝔦rave耻ì䃬Ȁ;inoܾ⋝⋩⋮Āin⋢⋦nt;樌t;戭fin;槜ta;愩lig;䄳ƀaop⋾⌚⌝ƀcgt⌅⌈⌗r;䄫ƀelpܟ⌏⌓inåގarôܠh;䄱f;抷ed;䆵ʀ;cfotӴ⌬⌱⌽⍁are;愅inĀ;t⌸⌹戞ie;槝doô⌙ʀ;celpݗ⍌⍐⍛⍡al;抺Āgr⍕⍙eróᕣã⍍arhk;樗rod;樼Ȁcgpt⍯⍲⍶⍻y;䑑on;䄯f;쀀𝕚a;䎹uest耻¿䂿Āci⎊⎏r;쀀𝒾nʀ;EdsvӴ⎛⎝⎡ӳ;拹ot;拵Ā;v⎦⎧拴;拳Ā;iݷ⎮lde;䄩ǫ⎸\0⎼cy;䑖l耻ï䃯̀cfmosu⏌⏗⏜⏡⏧⏵Āiy⏑⏕rc;䄵;䐹r;쀀𝔧ath;䈷pf;쀀𝕛ǣ⏬\0⏱r;쀀𝒿rcy;䑘kcy;䑔Ѐacfghjos␋␖␢ppaĀ;v␓␔䎺;䏰Āey␛␠dil;䄷;䐺r;쀀𝔨reen;䄸cy;䑅cy;䑜pf;쀀𝕜cr;쀀𝓀ABEHabcdefghjlmnoprstuv⑰⒁⒆⒍⒑┎┽╚▀♎♞♥♹♽⚚⚲⛘❝❨➋⟀⠁⠒ƀart⑷⑺⑼ròòΕail;椛arr;椎Ā;gঔ⒋;檋ar;楢ॣ⒥\0⒪\0⒱\0\0\0\0\0⒵Ⓔ\0ⓆⓈⓍ\0⓹ute;䄺mptyv;榴raîࡌbda;䎻gƀ;dlࢎⓁⓃ;榑åࢎ;檅uo耻«䂫rЀ;bfhlpst࢙ⓞⓦⓩ⓫⓮⓱⓵Ā;f࢝ⓣs;椟s;椝ë≒p;憫l;椹im;楳l;憢ƀ;ae⓿─┄檫il;椙Ā;s┉┊檭;쀀⪭︀ƀabr┕┙┝rr;椌rk;杲Āak┢┬cĀek┨┪;䁻;䁛Āes┱┳;榋lĀdu┹┻;榏;榍Ȁaeuy╆╋╖╘ron;䄾Ādi═╔il;䄼ìࢰâ┩;䐻Ȁcqrs╣╦╭╽a;椶uoĀ;rนᝆĀdu╲╷har;楧shar;楋h;憲ʀ;fgqs▋▌উ◳◿扤tʀahlrt▘▤▷◂◨rrowĀ;t࢙□aé⓶arpoonĀdu▯▴own»њp»०eftarrows;懇ightƀahs◍◖◞rrowĀ;sࣴࢧarpoonóquigarro÷⇰hreetimes;拋ƀ;qs▋ও◺lanôবʀ;cdgsব☊☍☝☨c;檨otĀ;o☔☕橿Ā;r☚☛檁;檃Ā;e☢☥쀀⋚︀s;檓ʀadegs☳☹☽♉♋pproøⓆot;拖qĀgq♃♅ôউgtò⒌ôছiíলƀilr♕࣡♚sht;楼;쀀𝔩Ā;Eজ♣;檑š♩♶rĀdu▲♮Ā;l॥♳;楪lk;斄cy;䑙ʀ;achtੈ⚈⚋⚑⚖rò◁orneòᴈard;楫ri;旺Āio⚟⚤dot;䅀ustĀ;a⚬⚭掰che»⚭ȀEaes⚻⚽⛉⛔;扨pĀ;p⛃⛄檉rox»⛄Ā;q⛎⛏檇Ā;q⛎⚻im;拦Ѐabnoptwz⛩⛴⛷✚✯❁❇❐Ānr⛮⛱g;柬r;懽rëࣁgƀlmr⛿✍✔eftĀar০✇ightá৲apsto;柼ightá৽parrowĀlr✥✩efô⓭ight;憬ƀafl✶✹✽r;榅;쀀𝕝us;樭imes;樴š❋❏st;戗áፎƀ;ef❗❘᠀旊nge»❘arĀ;l❤❥䀨t;榓ʀachmt❳❶❼➅➇ròࢨorneòᶌarĀ;d➃;業;怎ri;抿̀achiqt➘➝ੀ➢➮➻quo;怹r;쀀𝓁mƀ;egল➪➬;檍;檏Ābu┪➳oĀ;rฟ➹;怚rok;䅂萀<;cdhilqrࠫ⟒☹⟜⟠⟥⟪⟰Āci⟗⟙;檦r;橹reå◲mes;拉arr;楶uest;橻ĀPi⟵⟹ar;榖ƀ;ef⠀भ旃rĀdu⠇⠍shar;楊har;楦Āen⠗⠡rtneqq;쀀≨︀Å⠞܀Dacdefhilnopsu⡀⡅⢂⢎⢓⢠⢥⢨⣚⣢⣤ઃ⣳⤂Dot;戺Ȁclpr⡎⡒⡣⡽r耻¯䂯Āet⡗⡙;時Ā;e⡞⡟朠se»⡟Ā;sျ⡨toȀ;dluျ⡳⡷⡻owîҌefôएðᏑker;斮Āoy⢇⢌mma;権;䐼ash;怔asuredangle»ᘦr;쀀𝔪o;愧ƀcdn⢯⢴⣉ro耻µ䂵Ȁ;acdᑤ⢽⣀⣄sôᚧir;櫰ot肻·Ƶusƀ;bd⣒ᤃ⣓戒Ā;uᴼ⣘;横ţ⣞⣡p;櫛ò−ðઁĀdp⣩⣮els;抧f;쀀𝕞Āct⣸⣽r;쀀𝓂pos»ᖝƀ;lm⤉⤊⤍䎼timap;抸ఀGLRVabcdefghijlmoprstuvw⥂⥓⥾⦉⦘⧚⧩⨕⨚⩘⩝⪃⪕⪤⪨⬄⬇⭄⭿⮮ⰴⱧⱼ⳩Āgt⥇⥋;쀀⋙̸Ā;v⥐쀀≫⃒ƀelt⥚⥲⥶ftĀar⥡⥧rrow;懍ightarrow;懎;쀀⋘̸Ā;v⥻ే쀀≪⃒ightarrow;懏ĀDd⦎⦓ash;抯ash;抮ʀbcnpt⦣⦧⦬⦱⧌la»˞ute;䅄g;쀀∠⃒ʀ;Eiop⦼⧀⧅⧈;쀀⩰̸d;쀀≋̸s;䅉roøurĀ;a⧓⧔普lĀ;s⧓ସdz⧟\0⧣p肻 ଷmpĀ;e௹ఀʀaeouy⧴⧾⨃⨐⨓ǰ⧹\0⧻;橃on;䅈dil;䅆ngĀ;dൾ⨊ot;쀀⩭̸p;橂;䐽ash;怓;Aadqsxஒ⨩⨭⨻⩁⩅⩐rr;懗rĀhr⨳⨶k;椤Ā;oᏲᏰot;쀀≐̸uiöୣĀei⩊⩎ar;椨íistĀ;sடr;쀀𝔫ȀEest⩦⩹⩼ƀ;qs⩭ƀ;qs⩴lanôií௪Ā;rஶ⪁»ஷƀAap⪊⪍⪑rò⥱rr;憮ar;櫲ƀ;svྍ⪜ྌĀ;d⪡⪢拼;拺cy;䑚AEadest⪷⪺⪾⫂⫅⫶⫹rò⥦;쀀≦̸rr;憚r;急Ȁ;fqs⫎⫣⫯tĀar⫔⫙rro÷⫁ightarro÷⪐ƀ;qs⪺⫪lanôౕĀ;sౕ⫴»శiíౝĀ;rవ⫾iĀ;eచథiäඐĀpt⬌⬑f;쀀𝕟膀¬;in⬙⬚⬶䂬nȀ;Edvஉ⬤⬨⬮;쀀⋹̸ot;쀀⋵̸ǡஉ⬳⬵;拷;拶iĀ;vಸ⬼ǡಸ⭁⭃;拾;拽ƀaor⭋⭣⭩rȀ;ast⭕⭚⭟lleìl;쀀⫽⃥;쀀∂̸lint;樔ƀ;ceಒ⭰⭳uåಥĀ;cಘ⭸Ā;eಒ⭽ñಘȀAait⮈⮋⮝⮧rò⦈rrƀ;cw⮔⮕⮙憛;쀀⤳̸;쀀↝̸ghtarrow»⮕riĀ;eೋೖchimpqu⮽⯍⯙⬄⯤⯯Ȁ;cerല⯆ഷ⯉uå;쀀𝓃ortɭ⬅\0\0⯖ará⭖mĀ;e൮⯟Ā;q൴൳suĀbp⯫⯭ååഋƀbcp⯶ⰑⰙȀ;Ees⯿ⰀഢⰄ抄;쀀⫅̸etĀ;eഛⰋqĀ;qണⰀcĀ;eലⰗñസȀ;EesⰢⰣൟⰧ抅;쀀⫆̸etĀ;e൘ⰮqĀ;qൠⰣȀgilrⰽⰿⱅⱇìௗlde耻ñ䃱çృiangleĀlrⱒⱜeftĀ;eచⱚñదightĀ;eೋⱥñĀ;mⱬⱭ䎽ƀ;esⱴⱵⱹ䀣ro;愖p;怇ҀDHadgilrsⲏⲔⲙⲞⲣⲰⲶⳓⳣash;抭arr;椄p;쀀≍⃒ash;抬ĀetⲨⲬ;쀀≥⃒;쀀>⃒nfin;槞ƀAetⲽⳁⳅrr;椂;쀀≤⃒Ā;rⳊⳍ쀀<⃒ie;쀀⊴⃒ĀAtⳘⳜrr;椃rie;쀀⊵⃒im;쀀∼⃒ƀAan⳰ⴂrr;懖rĀhr⳺⳽k;椣Ā;oᏧᏥear;椧ቓ᪕\0\0\0\0\0\0\0\0\0\0\0\0\0ⴭ\0ⴸⵈⵠⵥⶄᬇ\0\0ⶍⶫ\0ⷈⷎ\0ⷜ⸙⸫⸾⹃Ācsⴱ᪗ute耻ó䃳ĀiyⴼⵅrĀ;cⵂ耻ô䃴;䐾ʀabios᪠ⵒⵗLjⵚlac;䅑v;樸old;榼lig;䅓Ācrir;榿;쀀𝔬ͯ\0\0\0ⶂn;䋛ave耻ò䃲;槁Ābmⶈ෴ar;榵Ȁacitⶕⶥⶨrò᪀Āirⶠr;榾oss;榻nå๒;槀ƀaeiⶱⶵⶹcr;䅍ga;䏉ƀcdnⷀⷅǍron;䎿;榶pf;쀀𝕠ƀaelⷔǒr;榷rp;榹;adiosvⷪⷫⷮ⸈⸍⸐⸖戨rò᪆Ȁ;efmⷷⷸ⸂⸅橝rĀ;oⷾⷿ愴f»ⷿ耻ª䂪耻º䂺gof;抶r;橖lope;橗;橛ƀclo⸟⸡⸧ò⸁ash耻ø䃸l;折iŬⸯ⸴de耻õ䃵esĀ;aǛ⸺s;樶ml耻ö䃶bar;挽ૡ\0\0⺀⺝\0⺢⺹\0\0⻋ຜ\0⼓\0\0⼫⾼\0⿈rȀ;astЃ脀¶;l䂶leìЃɩ\0\0m;櫳;櫽y;䐿rʀcimpt⺋⺏⺓ᡥ⺗nt;䀥od;䀮il;怰enk;怱r;쀀𝔭ƀimo⺨⺰⺴Ā;v⺭⺮䏆;䏕maô੶ne;明ƀ;tv⺿⻀⻈䏀chfork»´;䏖Āau⻏⻟nĀck⻕⻝kĀ;h⇴⻛;愎ö⇴sҀ;abcdemst⻳ᤈ⼄⼆⼊⼎䀫cir;樣ir;樢Āouᵀ⼂;樥;橲n肻±ຝim;樦wo;樧ƀipu⼙⼠⼥ntint;樕f;쀀𝕡nd耻£䂣Ԁ;Eaceinosu່⼿⽁⽄⽇⾁⾉⾒⽾⾶;檳p;檷uå໙Ā;c໎⽌̀;acens່⽙⽟⽦⽨⽾pproø⽃urlyeñ໙ñ໎ƀaes⽯⽶⽺pprox;檹qq;檵im;拨iíໟmeĀ;s⾈ຮ怲ƀEas⽸⾐⽺ð⽵ƀdfp⾙⾯ƀals⾠⾥⾪lar;挮ine;挒urf;挓Ā;t⾴ïrel;抰Āci⿀⿅r;쀀𝓅;䏈ncsp;怈̀fiopsu⋢⿱r;쀀𝔮pf;쀀𝕢rime;恗cr;쀀𝓆ƀaeo⿸〉〓tĀei々rnionóڰnt;樖stĀ;e【】䀿ñἙô༔ABHabcdefhilmnoprstuxけさすムㄎㄫㅇㅢㅲㆎ㈆㈕㈤㈩㉘㉮㉲㊐㊰㊷ƀartぇおがròႳòϝail;検aròᱥar;楤cdenqrtとふへみわゔヌĀeuねぱ;쀀∽̱te;䅕iãᅮmptyv;榳gȀ;del࿑らるろ;榒;榥å࿑uo耻»䂻rր;abcfhlpstwガクシスゼゾダッデナp;極Ā;fゴs;椠;椳s;椞ë≝ð✮l;楅im;楴l;憣;憝Āaiパフil;椚oĀ;nホボ戶aló༞ƀabrョリヮrò៥rk;杳ĀakンヽcĀekヹ・;䁽;䁝Āes;榌lĀduㄊㄌ;榎;榐Ȁaeuyㄗㄜㄧㄩron;䅙Ādiㄡㄥil;䅗ìâヺ;䑀Ȁclqsㄴㄷㄽㅄa;椷dhar;楩uoĀ;rȎȍh;憳ƀacgㅎㅟངlȀ;ipsླྀㅘㅛႜnåႻarôྩt;断ƀilrㅩဣㅮsht;楽;쀀𝔯ĀaoㅷㆆrĀduㅽㅿ»ѻĀ;l႑ㆄ;楬Ā;vㆋㆌ䏁;䏱ƀgns㆕ㇹㇼht̀ahlrstㆤㆰ㇂㇘rrowĀ;tㆭaéトarpoonĀduㆻㆿowîㅾp»႒eftĀah㇊㇐rrowóarpoonóՑightarrows;應quigarro÷ニhreetimes;拌g;䋚ingdotseñἲƀahm㈍㈐㈓ròaòՑ;怏oustĀ;a㈞掱che»mid;櫮Ȁabpt㈲㈽㉀㉒Ānr㈷㈺g;柭r;懾rëဃƀafl㉇㉊㉎r;榆;쀀𝕣us;樮imes;樵Āap㉝㉧rĀ;g㉣㉤䀩t;榔olint;樒arò㇣Ȁachq㉻㊀Ⴜ㊅quo;怺r;쀀𝓇Ābu・㊊oĀ;rȔȓƀhir㊗㊛㊠reåㇸmes;拊iȀ;efl㊪ၙᠡ㊫方tri;槎luhar;楨;愞ൡ㋕㋛㋟㌬㌸㍱\0㍺㎤\0\0㏬㏰\0㐨㑈㑚㒭㒱㓊㓱\0㘖\0\0㘳cute;䅛quï➺Ԁ;Eaceinpsyᇭ㋳㋵㋿㌂㌋㌏㌟㌦㌩;檴ǰ㋺\0㋼;檸on;䅡uåᇾĀ;dᇳ㌇il;䅟rc;䅝ƀEas㌖㌘㌛;檶p;檺im;择olint;樓iíሄ;䑁otƀ;be㌴ᵇ㌵担;橦Aacmstx㍆㍊㍗㍛㍞㍣㍭rr;懘rĀhr㍐㍒ë∨Ā;oਸ਼t耻§䂧i;䀻war;椩mĀin㍩ðnuóñt;朶rĀ;o㍶⁕쀀𝔰Ȁacoy㎂㎆㎑㎠rp;景Āhy㎋㎏cy;䑉;䑈rtɭ㎙\0\0㎜iäᑤaraì耻䂭Āgm㎨㎴maƀ;fv㎱㎲㎲䏃;䏂Ѐ;deglnprካ㏅㏉㏎㏖㏞㏡㏦ot;橪Ā;qኰĀ;E㏓㏔檞;檠Ā;E㏛㏜檝;檟e;扆lus;樤arr;楲aròᄽȀaeit㏸㐈㐏㐗Āls㏽㐄lsetmé㍪hp;樳parsl;槤Ādlᑣ㐔e;挣Ā;e㐜㐝檪Ā;s㐢㐣檬;쀀⪬︀ƀflp㐮㐳㑂tcy;䑌Ā;b㐸㐹䀯Ā;a㐾㐿槄r;挿f;쀀𝕤aĀdr㑍ЂesĀ;u㑔㑕晠it»㑕ƀcsu㑠㑹㒟Āau㑥㑯pĀ;sᆈ㑫;쀀⊓︀pĀ;sᆴ㑵;쀀⊔︀uĀbp㑿㒏ƀ;esᆗᆜ㒆etĀ;eᆗ㒍ñᆝƀ;esᆨᆭ㒖etĀ;eᆨ㒝ñᆮƀ;afᅻ㒦ְrť㒫ֱ»ᅼaròᅈȀcemt㒹㒾㓂㓅r;쀀𝓈tmîñiì㐕aræᆾĀar㓎㓕rĀ;f㓔ឿ昆Āan㓚㓭ightĀep㓣㓪psiloîỠhé⺯s»⡒ʀbcmnp㓻㕞ሉ㖋㖎Ҁ;Edemnprs㔎㔏㔑㔕㔞㔣㔬㔱㔶抂;櫅ot;檽Ā;dᇚ㔚ot;櫃ult;櫁ĀEe㔨㔪;櫋;把lus;檿arr;楹ƀeiu㔽㕒㕕tƀ;en㔎㕅㕋qĀ;qᇚ㔏eqĀ;q㔫㔨m;櫇Ābp㕚㕜;櫕;櫓c̀;acensᇭ㕬㕲㕹㕻㌦pproø㋺urlyeñᇾñᇳƀaes㖂㖈㌛pproø㌚qñ㌗g;晪ڀ123;Edehlmnps㖩㖬㖯ሜ㖲㖴㗀㗉㗕㗚㗟㗨㗭耻¹䂹耻²䂲耻³䂳;櫆Āos㖹㖼t;檾ub;櫘Ā;dሢ㗅ot;櫄sĀou㗏㗒l;柉b;櫗arr;楻ult;櫂ĀEe㗤㗦;櫌;抋lus;櫀ƀeiu㗴㘉㘌tƀ;enሜ㗼㘂qĀ;qሢ㖲eqĀ;q㗧㗤m;櫈Ābp㘑㘓;櫔;櫖ƀAan㘜㘠㘭rr;懙rĀhr㘦㘨ë∮Ā;oਫwar;椪lig耻ß䃟㙑㙝㙠ዎ㙳㙹\0㙾㛂\0\0\0\0\0㛛㜃\0㜉㝬\0\0\0㞇ɲ㙖\0\0㙛get;挖;䏄rëƀaey㙦㙫㙰ron;䅥dil;䅣;䑂lrec;挕r;쀀𝔱Ȁeiko㚆㚝㚵㚼Dz㚋\0㚑eĀ4fኄኁaƀ;sv㚘㚙㚛䎸ym;䏑Ācn㚢㚲kĀas㚨㚮pproøim»ኬsðኞĀas㚺㚮ðrn耻þ䃾Ǭ̟㛆⋧es膀×;bd㛏㛐㛘䃗Ā;aᤏ㛕r;樱;樰ƀeps㛡㛣㜀á⩍Ȁ;bcf҆㛬㛰㛴ot;挶ir;櫱Ā;o㛹㛼쀀𝕥rk;櫚á㍢rime;怴ƀaip㜏㜒㝤dåቈadempst㜡㝍㝀㝑㝗㝜㝟ngleʀ;dlqr㜰㜱㜶㝀㝂斵own»ᶻeftĀ;e⠀㜾ñम;扜ightĀ;e㊪㝋ñၚot;旬inus;樺lus;樹b;槍ime;樻ezium;揢ƀcht㝲㝽㞁Āry㝷㝻;쀀𝓉;䑆cy;䑛rok;䅧Āio㞋㞎xôheadĀlr㞗㞠eftarro÷ࡏightarrow»ཝऀAHabcdfghlmoprstuw㟐㟓㟗㟤㟰㟼㠎㠜㠣㠴㡑㡝㡫㢩㣌㣒㣪㣶ròϭar;楣Ācr㟜㟢ute耻ú䃺òᅐrǣ㟪\0㟭y;䑞ve;䅭Āiy㟵㟺rc耻û䃻;䑃ƀabh㠃㠆㠋ròᎭlac;䅱aòᏃĀir㠓㠘sht;楾;쀀𝔲rave耻ù䃹š㠧㠱rĀlr㠬㠮»ॗ»ႃlk;斀Āct㠹㡍ɯ㠿\0\0㡊rnĀ;e㡅㡆挜r»㡆op;挏ri;旸Āal㡖㡚cr;䅫肻¨͉Āgp㡢㡦on;䅳f;쀀𝕦̀adhlsuᅋ㡸㡽፲㢑㢠ownáᎳarpoonĀlr㢈㢌efô㠭ighô㠯iƀ;hl㢙㢚㢜䏅»ᏺon»㢚parrows;懈ƀcit㢰㣄㣈ɯ㢶\0\0㣁rnĀ;e㢼㢽挝r»㢽op;挎ng;䅯ri;旹cr;쀀𝓊ƀdir㣙㣝㣢ot;拰lde;䅩iĀ;f㜰㣨»᠓Āam㣯㣲rò㢨l耻ü䃼angle;榧ހABDacdeflnoprsz㤜㤟㤩㤭㦵㦸㦽㧟㧤㧨㧳㧹㧽㨁㨠ròϷarĀ;v㤦㤧櫨;櫩asèϡĀnr㤲㤷grt;榜eknprst㓣㥆㥋㥒㥝㥤㦖appá␕othinçẖƀhir㓫⻈㥙opô⾵Ā;hᎷ㥢ïㆍĀiu㥩㥭gmá㎳Ābp㥲㦄setneqĀ;q㥽㦀쀀⊊︀;쀀⫋︀setneqĀ;q㦏㦒쀀⊋︀;쀀⫌︀Āhr㦛㦟etá㚜iangleĀlr㦪㦯eft»थight»ၑy;䐲ash»ံƀelr㧄㧒㧗ƀ;beⷪ㧋㧏ar;抻q;扚lip;拮Ābt㧜ᑨaòᑩr;쀀𝔳tré㦮suĀbp㧯㧱»ജ»൙pf;쀀𝕧roðtré㦴Ācu㨆㨋r;쀀𝓋Ābp㨐㨘nĀEe㦀㨖»㥾nĀEe㦒㨞»㦐igzag;榚cefoprs㨶㨻㩖㩛㩔㩡㩪irc;䅵Ādi㩀㩑Ābg㩅㩉ar;機eĀ;qᗺ㩏;扙erp;愘r;쀀𝔴pf;쀀𝕨Ā;eᑹ㩦atèᑹcr;쀀𝓌ૣណ㪇\0㪋\0㪐㪛\0\0㪝㪨㪫㪯\0\0㫃㫎\0㫘ៜtré៑r;쀀𝔵ĀAa㪔㪗ròσrò৶;䎾ĀAa㪡㪤ròθrò৫að✓is;拻ƀdptឤ㪵㪾Āfl㪺ឩ;쀀𝕩imåឲĀAa㫇㫊ròώròਁĀcq㫒ីr;쀀𝓍Āpt៖㫜ré។Ѐacefiosu㫰㫽㬈㬌㬑㬕㬛㬡cĀuy㫶㫻te耻ý䃽;䑏Āiy㬂㬆rc;䅷;䑋n耻¥䂥r;쀀𝔶cy;䑗pf;쀀𝕪cr;쀀𝓎Ācm㬦㬩y;䑎l耻ÿ䃿Ԁacdefhiosw㭂㭈㭔㭘㭤㭩㭭㭴㭺㮀cute;䅺Āay㭍㭒ron;䅾;䐷ot;䅼Āet㭝㭡træᕟa;䎶r;쀀𝔷cy;䐶grarr;懝pf;쀀𝕫cr;쀀𝓏Ājn㮅㮇;怍j;怌'.split("").map(e=>e.charCodeAt(0))),fs=new Uint16Array("Ȁaglq\tɭ\0\0p;䀦os;䀧t;䀾t;䀼uot;䀢".split("").map(e=>e.charCodeAt(0)));const ms=new Map([[0,65533],[128,8364],[130,8218],[131,402],[132,8222],[133,8230],[134,8224],[135,8225],[136,710],[137,8240],[138,352],[139,8249],[140,338],[142,381],[145,8216],[146,8217],[147,8220],[148,8221],[149,8226],[150,8211],[151,8212],[152,732],[153,8482],[154,353],[155,8250],[156,339],[158,382],[159,376]]),gs=null!==(is=String.fromCodePoint)&&void 0!==is?is:function(e){let t="";return e>65535&&(e-=65536,t+=String.fromCharCode(e>>>10&1023|55296),e=56320|1023&e),t+=String.fromCharCode(e),t};var ys;!function(e){e[e.NUM=35]="NUM",e[e.SEMI=59]="SEMI",e[e.EQUALS=61]="EQUALS",e[e.ZERO=48]="ZERO",e[e.NINE=57]="NINE",e[e.LOWER_A=97]="LOWER_A",e[e.LOWER_F=102]="LOWER_F",e[e.LOWER_X=120]="LOWER_X",e[e.LOWER_Z=122]="LOWER_Z",e[e.UPPER_A=65]="UPPER_A",e[e.UPPER_F=70]="UPPER_F",e[e.UPPER_Z=90]="UPPER_Z"}(ys||(ys={}));var ks,bs,ws;function Cs(e){return e>=ys.ZERO&&e<=ys.NINE}function xs(e){return e>=ys.UPPER_A&&e<=ys.UPPER_F||e>=ys.LOWER_A&&e<=ys.LOWER_F}function Ds(e){return e===ys.EQUALS||function(e){return e>=ys.UPPER_A&&e<=ys.UPPER_Z||e>=ys.LOWER_A&&e<=ys.LOWER_Z||Cs(e)}(e)}!function(e){e[e.VALUE_LENGTH=49152]="VALUE_LENGTH",e[e.BRANCH_LENGTH=16256]="BRANCH_LENGTH",e[e.JUMP_TABLE=127]="JUMP_TABLE"}(ks||(ks={})),function(e){e[e.EntityStart=0]="EntityStart",e[e.NumericStart=1]="NumericStart",e[e.NumericDecimal=2]="NumericDecimal",e[e.NumericHex=3]="NumericHex",e[e.NamedEntity=4]="NamedEntity"}(bs||(bs={})),function(e){e[e.Legacy=0]="Legacy",e[e.Strict=1]="Strict",e[e.Attribute=2]="Attribute"}(ws||(ws={}));class vs{constructor(e,t,n){this.decodeTree=e,this.emitCodePoint=t,this.errors=n,this.state=bs.EntityStart,this.consumed=1,this.result=0,this.treeIndex=0,this.excess=1,this.decodeMode=ws.Strict}startEntity(e){this.decodeMode=e,this.state=bs.EntityStart,this.result=0,this.treeIndex=0,this.excess=1,this.consumed=1}write(e,t){switch(this.state){case bs.EntityStart:return e.charCodeAt(t)===ys.NUM?(this.state=bs.NumericStart,this.consumed+=1,this.stateNumericStart(e,t+1)):(this.state=bs.NamedEntity,this.stateNamedEntity(e,t));case bs.NumericStart:return this.stateNumericStart(e,t);case bs.NumericDecimal:return this.stateNumericDecimal(e,t);case bs.NumericHex:return this.stateNumericHex(e,t);case bs.NamedEntity:return this.stateNamedEntity(e,t)}}stateNumericStart(e,t){return t>=e.length?-1:(32|e.charCodeAt(t))===ys.LOWER_X?(this.state=bs.NumericHex,this.consumed+=1,this.stateNumericHex(e,t+1)):(this.state=bs.NumericDecimal,this.stateNumericDecimal(e,t))}addToNumericResult(e,t,n,r){if(t!==n){const i=n-t;this.result=this.result*Math.pow(r,i)+parseInt(e.substr(t,i),r),this.consumed+=i}}stateNumericHex(e,t){const n=t;for(;t<e.length;){const r=e.charCodeAt(t);if(!Cs(r)&&!xs(r))return this.addToNumericResult(e,n,t,16),this.emitNumericEntity(r,3);t+=1}return this.addToNumericResult(e,n,t,16),-1}stateNumericDecimal(e,t){const n=t;for(;t<e.length;){const r=e.charCodeAt(t);if(!Cs(r))return this.addToNumericResult(e,n,t,10),this.emitNumericEntity(r,2);t+=1}return this.addToNumericResult(e,n,t,10),-1}emitNumericEntity(e,t){var n;if(this.consumed<=t)return null===(n=this.errors)||void 0===n||n.absenceOfDigitsInNumericCharacterReference(this.consumed),0;if(e===ys.SEMI)this.consumed+=1;else if(this.decodeMode===ws.Strict)return 0;return this.emitCodePoint(function(e){var t;return e>=55296&&e<=57343||e>1114111?65533:null!==(t=ms.get(e))&&void 0!==t?t:e}(this.result),this.consumed),this.errors&&(e!==ys.SEMI&&this.errors.missingSemicolonAfterCharacterReference(),this.errors.validateNumericCharacterReference(this.result)),this.consumed}stateNamedEntity(e,t){const{decodeTree:n}=this;let r=n[this.treeIndex],i=(r&ks.VALUE_LENGTH)>>14;for(;t<e.length;t++,this.excess++){const o=e.charCodeAt(t);if(this.treeIndex=Ss(n,r,this.treeIndex+Math.max(1,i),o),this.treeIndex<0)return 0===this.result||this.decodeMode===ws.Attribute&&(0===i||Ds(o))?0:this.emitNotTerminatedNamedEntity();if(r=n[this.treeIndex],i=(r&ks.VALUE_LENGTH)>>14,0!==i){if(o===ys.SEMI)return this.emitNamedEntityData(this.treeIndex,i,this.consumed+this.excess);this.decodeMode!==ws.Strict&&(this.result=this.treeIndex,this.consumed+=this.excess,this.excess=0)}}return-1}emitNotTerminatedNamedEntity(){var e;const{result:t,decodeTree:n}=this,r=(n[t]&ks.VALUE_LENGTH)>>14;return this.emitNamedEntityData(t,r,this.consumed),null===(e=this.errors)||void 0===e||e.missingSemicolonAfterCharacterReference(),this.consumed}emitNamedEntityData(e,t,n){const{decodeTree:r}=this;return this.emitCodePoint(1===t?r[e]&~ks.VALUE_LENGTH:r[e+1],n),3===t&&this.emitCodePoint(r[e+2],n),n}end(){var e;switch(this.state){case bs.NamedEntity:return 0===this.result||this.decodeMode===ws.Attribute&&this.result!==this.treeIndex?0:this.emitNotTerminatedNamedEntity();case bs.NumericDecimal:return this.emitNumericEntity(0,2);case bs.NumericHex:return this.emitNumericEntity(0,3);case bs.NumericStart:return null===(e=this.errors)||void 0===e||e.absenceOfDigitsInNumericCharacterReference(this.consumed),0;case bs.EntityStart:return 0}}}function _s(e){let t="";const n=new vs(e,e=>t+=gs(e));return function(e,r){let i=0,o=0;for(;(o=e.indexOf("&",o))>=0;){t+=e.slice(i,o),n.startEntity(r);const s=n.write(e,o+1);if(s<0){i=o+n.end();break}i=o+s,o=0===s?i+1:i}const s=t+e.slice(i);return t="",s}}function Ss(e,t,n,r){const i=(t&ks.BRANCH_LENGTH)>>7,o=t&ks.JUMP_TABLE;if(0===i)return 0!==o&&r===o?n:-1;if(o){const t=r-o;return t<0||t>=i?-1:e[n+t]-1}let s=n,l=s+i-1;for(;s<=l;){const t=s+l>>>1,n=e[t];if(n<r)s=t+1;else{if(!(n>r))return e[t+i];l=t-1}}return-1}const Es=_s(ds);function As(e,t=ws.Legacy){return Es(e,t)}function Ms(e){return"[object String]"===function(e){return Object.prototype.toString.call(e)}(e)}_s(fs);const Os=Object.prototype.hasOwnProperty;function Ns(e){return Array.prototype.slice.call(arguments,1).forEach(function(t){if(t){if("object"!=typeof t)throw new TypeError(t+"must be object");Object.keys(t).forEach(function(n){e[n]=t[n]})}}),e}function Fs(e,t,n){return[].concat(e.slice(0,t),n,e.slice(t+1))}function Ts(e){return!(e>=55296&&e<=57343)&&(!(e>=64976&&e<=65007)&&(!!(65535&~e&&65534!=(65535&e))&&(!(e>=0&&e<=8)&&(11!==e&&(!(e>=14&&e<=31)&&(!(e>=127&&e<=159)&&!(e>1114111)))))))}function Rs(e){if(e>65535){const t=55296+((e-=65536)>>10),n=56320+(1023&e);return String.fromCharCode(t,n)}return String.fromCharCode(e)}const zs=/\\([!"#$%&'()*+,\-./:;<=>?@[\\\]^_`{|}~])/g,Is=new RegExp(zs.source+"|"+/&([a-z#][a-z0-9]{1,31});/gi.source,"gi"),Bs=/^#((?:x[a-f0-9]{1,8}|[0-9]{1,8}))$/i;function Ps(e){return e.indexOf("\\")<0&&e.indexOf("&")<0?e:e.replace(Is,function(e,t,n){return t||function(e,t){if(35===t.charCodeAt(0)&&Bs.test(t)){const n="x"===t[1].toLowerCase()?parseInt(t.slice(2),16):parseInt(t.slice(1),10);return Ts(n)?Rs(n):e}const n=As(e);return n!==e?n:e}(e,n)})}const Ls=/[&<>"]/,qs=/[&<>"]/g,$s={"&":"&","<":"<",">":">",'"':"""};function Vs(e){return $s[e]}function js(e){return Ls.test(e)?e.replace(qs,Vs):e}const Js=/[.?*+^$[\]\\(){}|-]/g;function Ws(e){switch(e){case 9:case 32:return!0}return!1}function Hs(e){if(e>=8192&&e<=8202)return!0;switch(e){case 9:case 10:case 11:case 12:case 13:case 32:case 160:case 5760:case 8239:case 8287:case 12288:return!0}return!1}function Us(e){return cs.test(e)||hs.test(e)}function Ks(e){switch(e){case 33:case 34:case 35:case 36:case 37:case 38:case 39:case 40:case 41:case 42:case 43:case 44:case 45:case 46:case 47:case 58:case 59:case 60:case 61:case 62:case 63:case 64:case 91:case 92:case 93:case 94:case 95:case 96:case 123:case 124:case 125:case 126:return!0;default:return!1}}function Zs(e){return e=e.trim().replace(/\s+/g," "),"Ṿ"==="ẞ".toLowerCase()&&(e=e.replace(/ẞ/g,"ß")),e.toLowerCase().toUpperCase()}const Gs={mdurl:ss,ucmicro:ps};var Qs=Object.freeze({__proto__:null,arrayReplaceAt:Fs,assign:Ns,escapeHtml:js,escapeRE:function(e){return e.replace(Js,"\\$&")},fromCodePoint:Rs,has:function(e,t){return Os.call(e,t)},isMdAsciiPunct:Ks,isPunctChar:Us,isSpace:Ws,isString:Ms,isValidEntityCode:Ts,isWhiteSpace:Hs,lib:Gs,normalizeReference:Zs,unescapeAll:Ps,unescapeMd:function(e){return e.indexOf("\\")<0?e:e.replace(zs,"$1")}});var Xs=Object.freeze({__proto__:null,parseLinkDestination:function(e,t,n){let r,i=t;const o={ok:!1,pos:0,str:""};if(60===e.charCodeAt(i)){for(i++;i<n;){if(r=e.charCodeAt(i),10===r)return o;if(60===r)return o;if(62===r)return o.pos=i+1,o.str=Ps(e.slice(t+1,i)),o.ok=!0,o;92===r&&i+1<n?i+=2:i++}return o}let s=0;for(;i<n&&(r=e.charCodeAt(i),32!==r)&&!(r<32||127===r);)if(92===r&&i+1<n){if(32===e.charCodeAt(i+1))break;i+=2}else{if(40===r&&(s++,s>32))return o;if(41===r){if(0===s)break;s--}i++}return t===i||0!==s||(o.str=Ps(e.slice(t,i)),o.pos=i,o.ok=!0),o},parseLinkLabel:function(e,t,n){let r,i,o,s;const l=e.posMax,a=e.pos;for(e.pos=t+1,r=1;e.pos<l;){if(o=e.src.charCodeAt(e.pos),93===o&&(r--,0===r)){i=!0;break}if(s=e.pos,e.md.inline.skipToken(e),91===o)if(s===e.pos-1)r++;else if(n)return e.pos=a,-1}let c=-1;return i&&(c=e.pos),e.pos=a,c},parseLinkTitle:function(e,t,n,r){let i,o=t;const s={ok:!1,can_continue:!1,pos:0,str:"",marker:0};if(r)s.str=r.str,s.marker=r.marker;else{if(o>=n)return s;let r=e.charCodeAt(o);if(34!==r&&39!==r&&40!==r)return s;t++,o++,40===r&&(r=41),s.marker=r}for(;o<n;){if(i=e.charCodeAt(o),i===s.marker)return s.pos=o+1,s.str+=Ps(e.slice(t,o)),s.ok=!0,s;if(40===i&&41===s.marker)return s;92===i&&o+1<n&&o++,o++}return s.can_continue=!0,s.str+=Ps(e.slice(t,o)),s}});const Ys={};function el(){this.rules=Ns({},Ys)}function tl(){this.__rules__=[],this.__cache__=null}function nl(e,t,n){this.type=e,this.tag=t,this.attrs=null,this.map=null,this.nesting=n,this.level=0,this.children=null,this.content="",this.markup="",this.info="",this.meta=null,this.block=!1,this.hidden=!1}function rl(e,t,n){this.src=e,this.env=n,this.tokens=[],this.inlineMode=!1,this.md=t}Ys.code_inline=function(e,t,n,r,i){const o=e[t];return"<code"+i.renderAttrs(o)+">"+js(o.content)+"</code>"},Ys.code_block=function(e,t,n,r,i){const o=e[t];return"<pre"+i.renderAttrs(o)+"><code>"+js(e[t].content)+"</code></pre>\n"},Ys.fence=function(e,t,n,r,i){const o=e[t],s=o.info?Ps(o.info).trim():"";let l,a="",c="";if(s){const e=s.split(/(\s+)/g);a=e[0],c=e.slice(2).join("")}if(l=n.highlight&&n.highlight(o.content,a,c)||js(o.content),0===l.indexOf("<pre"))return l+"\n";if(s){const e=o.attrIndex("class"),t=o.attrs?o.attrs.slice():[];e<0?t.push(["class",n.langPrefix+a]):(t[e]=t[e].slice(),t[e][1]+=" "+n.langPrefix+a);const r={attrs:t};return`<pre><code${i.renderAttrs(r)}>${l}</code></pre>\n`}return`<pre><code${i.renderAttrs(o)}>${l}</code></pre>\n`},Ys.image=function(e,t,n,r,i){const o=e[t];return o.attrs[o.attrIndex("alt")][1]=i.renderInlineAsText(o.children,n,r),i.renderToken(e,t,n)},Ys.hardbreak=function(e,t,n){return n.xhtmlOut?"<br />\n":"<br>\n"},Ys.softbreak=function(e,t,n){return n.breaks?n.xhtmlOut?"<br />\n":"<br>\n":"\n"},Ys.text=function(e,t){return js(e[t].content)},Ys.html_block=function(e,t){return e[t].content},Ys.html_inline=function(e,t){return e[t].content},el.prototype.renderAttrs=function(e){let t,n,r;if(!e.attrs)return"";for(r="",t=0,n=e.attrs.length;t<n;t++)r+=" "+js(e.attrs[t][0])+'="'+js(e.attrs[t][1])+'"';return r},el.prototype.renderToken=function(e,t,n){const r=e[t];let i="";if(r.hidden)return"";r.block&&-1!==r.nesting&&t&&e[t-1].hidden&&(i+="\n"),i+=(-1===r.nesting?"</":"<")+r.tag,i+=this.renderAttrs(r),0===r.nesting&&n.xhtmlOut&&(i+=" /");let o=!1;if(r.block&&(o=!0,1===r.nesting&&t+1<e.length)){const n=e[t+1];("inline"===n.type||n.hidden||-1===n.nesting&&n.tag===r.tag)&&(o=!1)}return i+=o?">\n":">",i},el.prototype.renderInline=function(e,t,n){let r="";const i=this.rules;for(let o=0,s=e.length;o<s;o++){const s=e[o].type;void 0!==i[s]?r+=i[s](e,o,t,n,this):r+=this.renderToken(e,o,t)}return r},el.prototype.renderInlineAsText=function(e,t,n){let r="";for(let i=0,o=e.length;i<o;i++)switch(e[i].type){case"text":case"html_inline":case"html_block":r+=e[i].content;break;case"image":r+=this.renderInlineAsText(e[i].children,t,n);break;case"softbreak":case"hardbreak":r+="\n"}return r},el.prototype.render=function(e,t,n){let r="";const i=this.rules;for(let o=0,s=e.length;o<s;o++){const s=e[o].type;"inline"===s?r+=this.renderInline(e[o].children,t,n):void 0!==i[s]?r+=i[s](e,o,t,n,this):r+=this.renderToken(e,o,t,n)}return r},tl.prototype.__find__=function(e){for(let t=0;t<this.__rules__.length;t++)if(this.__rules__[t].name===e)return t;return-1},tl.prototype.__compile__=function(){const e=this,t=[""];e.__rules__.forEach(function(e){e.enabled&&e.alt.forEach(function(e){t.indexOf(e)<0&&t.push(e)})}),e.__cache__={},t.forEach(function(t){e.__cache__[t]=[],e.__rules__.forEach(function(n){n.enabled&&(t&&n.alt.indexOf(t)<0||e.__cache__[t].push(n.fn))})})},tl.prototype.at=function(e,t,n){const r=this.__find__(e),i=n||{};if(-1===r)throw new Error("Parser rule not found: "+e);this.__rules__[r].fn=t,this.__rules__[r].alt=i.alt||[],this.__cache__=null},tl.prototype.before=function(e,t,n,r){const i=this.__find__(e),o=r||{};if(-1===i)throw new Error("Parser rule not found: "+e);this.__rules__.splice(i,0,{name:t,enabled:!0,fn:n,alt:o.alt||[]}),this.__cache__=null},tl.prototype.after=function(e,t,n,r){const i=this.__find__(e),o=r||{};if(-1===i)throw new Error("Parser rule not found: "+e);this.__rules__.splice(i+1,0,{name:t,enabled:!0,fn:n,alt:o.alt||[]}),this.__cache__=null},tl.prototype.push=function(e,t,n){const r=n||{};this.__rules__.push({name:e,enabled:!0,fn:t,alt:r.alt||[]}),this.__cache__=null},tl.prototype.enable=function(e,t){Array.isArray(e)||(e=[e]);const n=[];return e.forEach(function(e){const r=this.__find__(e);if(r<0){if(t)return;throw new Error("Rules manager: invalid rule name "+e)}this.__rules__[r].enabled=!0,n.push(e)},this),this.__cache__=null,n},tl.prototype.enableOnly=function(e,t){Array.isArray(e)||(e=[e]),this.__rules__.forEach(function(e){e.enabled=!1}),this.enable(e,t)},tl.prototype.disable=function(e,t){Array.isArray(e)||(e=[e]);const n=[];return e.forEach(function(e){const r=this.__find__(e);if(r<0){if(t)return;throw new Error("Rules manager: invalid rule name "+e)}this.__rules__[r].enabled=!1,n.push(e)},this),this.__cache__=null,n},tl.prototype.getRules=function(e){return null===this.__cache__&&this.__compile__(),this.__cache__[e]||[]},nl.prototype.attrIndex=function(e){if(!this.attrs)return-1;const t=this.attrs;for(let n=0,r=t.length;n<r;n++)if(t[n][0]===e)return n;return-1},nl.prototype.attrPush=function(e){this.attrs?this.attrs.push(e):this.attrs=[e]},nl.prototype.attrSet=function(e,t){const n=this.attrIndex(e),r=[e,t];n<0?this.attrPush(r):this.attrs[n]=r},nl.prototype.attrGet=function(e){const t=this.attrIndex(e);let n=null;return t>=0&&(n=this.attrs[t][1]),n},nl.prototype.attrJoin=function(e,t){const n=this.attrIndex(e);n<0?this.attrPush([e,t]):this.attrs[n][1]=this.attrs[n][1]+" "+t},rl.prototype.Token=nl;const il=/\r\n?|\n/g,ol=/\0/g;function sl(e){return/^<a[>\s]/i.test(e)}function ll(e){return/^<\/a\s*>/i.test(e)}const al=/\+-|\.\.|\?\?\?\?|!!!!|,,|--/,cl=/\((c|tm|r)\)/i,hl=/\((c|tm|r)\)/gi,ul={c:"©",r:"®",tm:"™"};function pl(e,t){return ul[t.toLowerCase()]}function dl(e){let t=0;for(let n=e.length-1;n>=0;n--){const r=e[n];"text"!==r.type||t||(r.content=r.content.replace(hl,pl)),"link_open"===r.type&&"auto"===r.info&&t--,"link_close"===r.type&&"auto"===r.info&&t++}}function fl(e){let t=0;for(let n=e.length-1;n>=0;n--){const r=e[n];"text"!==r.type||t||al.test(r.content)&&(r.content=r.content.replace(/\+-/g,"±").replace(/\.{2,}/g,"…").replace(/([?!])…/g,"$1..").replace(/([?!]){4,}/g,"$1$1$1").replace(/,{2,}/g,",").replace(/(^|[^-])---(?=[^-]|$)/gm,"$1—").replace(/(^|\s)--(?=\s|$)/gm,"$1–").replace(/(^|[^-\s])--(?=[^-\s]|$)/gm,"$1–")),"link_open"===r.type&&"auto"===r.info&&t--,"link_close"===r.type&&"auto"===r.info&&t++}}const ml=/['"]/,gl=/['"]/g;function yl(e,t,n){return e.slice(0,t)+n+e.slice(t+1)}function kl(e,t){let n;const r=[];for(let i=0;i<e.length;i++){const o=e[i],s=e[i].level;for(n=r.length-1;n>=0&&!(r[n].level<=s);n--);if(r.length=n+1,"text"!==o.type)continue;let l=o.content,a=0,c=l.length;e:for(;a<c;){gl.lastIndex=a;const h=gl.exec(l);if(!h)break;let u=!0,p=!0;a=h.index+1;const d="'"===h[0];let f=32;if(h.index-1>=0)f=l.charCodeAt(h.index-1);else for(n=i-1;n>=0&&("softbreak"!==e[n].type&&"hardbreak"!==e[n].type);n--)if(e[n].content){f=e[n].content.charCodeAt(e[n].content.length-1);break}let m=32;if(a<c)m=l.charCodeAt(a);else for(n=i+1;n<e.length&&("softbreak"!==e[n].type&&"hardbreak"!==e[n].type);n++)if(e[n].content){m=e[n].content.charCodeAt(0);break}const g=Ks(f)||Us(String.fromCharCode(f)),y=Ks(m)||Us(String.fromCharCode(m)),k=Hs(f),b=Hs(m);if(b?u=!1:y&&(k||g||(u=!1)),k?p=!1:g&&(b||y||(p=!1)),34===m&&'"'===h[0]&&f>=48&&f<=57&&(p=u=!1),u&&p&&(u=g,p=y),u||p){if(p)for(n=r.length-1;n>=0;n--){let u=r[n];if(r[n].level<s)break;if(u.single===d&&r[n].level===s){let s,p;u=r[n],d?(s=t.md.options.quotes[2],p=t.md.options.quotes[3]):(s=t.md.options.quotes[0],p=t.md.options.quotes[1]),o.content=yl(o.content,h.index,p),e[u.token].content=yl(e[u.token].content,u.pos,s),a+=p.length-1,u.token===i&&(a+=s.length-1),l=o.content,c=l.length,r.length=n;continue e}}u?r.push({token:i,pos:h.index,single:d,level:s}):p&&d&&(o.content=yl(o.content,h.index,"’"))}else d&&(o.content=yl(o.content,h.index,"’"))}}}const bl=[["normalize",function(e){let t;t=e.src.replace(il,"\n"),t=t.replace(ol,"�"),e.src=t}],["block",function(e){let t;e.inlineMode?(t=new e.Token("inline","",0),t.content=e.src,t.map=[0,1],t.children=[],e.tokens.push(t)):e.md.block.parse(e.src,e.md,e.env,e.tokens)}],["inline",function(e){const t=e.tokens;for(let n=0,r=t.length;n<r;n++){const r=t[n];"inline"===r.type&&e.md.inline.parse(r.content,e.md,e.env,r.children)}}],["linkify",function(e){const t=e.tokens;if(e.md.options.linkify)for(let n=0,r=t.length;n<r;n++){if("inline"!==t[n].type||!e.md.linkify.pretest(t[n].content))continue;let r=t[n].children,i=0;for(let o=r.length-1;o>=0;o--){const s=r[o];if("link_close"!==s.type){if("html_inline"===s.type&&(sl(s.content)&&i>0&&i--,ll(s.content)&&i++),!(i>0)&&"text"===s.type&&e.md.linkify.test(s.content)){const i=s.content;let l=e.md.linkify.match(i);const a=[];let c=s.level,h=0;l.length>0&&0===l[0].index&&o>0&&"text_special"===r[o-1].type&&(l=l.slice(1));for(let t=0;t<l.length;t++){const n=l[t].url,r=e.md.normalizeLink(n);if(!e.md.validateLink(r))continue;let o=l[t].text;o=l[t].schema?"mailto:"!==l[t].schema||/^mailto:/i.test(o)?e.md.normalizeLinkText(o):e.md.normalizeLinkText("mailto:"+o).replace(/^mailto:/,""):e.md.normalizeLinkText("http://"+o).replace(/^http:\/\//,"");const s=l[t].index;if(s>h){const t=new e.Token("text","",0);t.content=i.slice(h,s),t.level=c,a.push(t)}const u=new e.Token("link_open","a",1);u.attrs=[["href",r]],u.level=c++,u.markup="linkify",u.info="auto",a.push(u);const p=new e.Token("text","",0);p.content=o,p.level=c,a.push(p);const d=new e.Token("link_close","a",-1);d.level=--c,d.markup="linkify",d.info="auto",a.push(d),h=l[t].lastIndex}if(h<i.length){const t=new e.Token("text","",0);t.content=i.slice(h),t.level=c,a.push(t)}t[n].children=r=Fs(r,o,a)}}else for(o--;r[o].level!==s.level&&"link_open"!==r[o].type;)o--}}}],["replacements",function(e){let t;if(e.md.options.typographer)for(t=e.tokens.length-1;t>=0;t--)"inline"===e.tokens[t].type&&(cl.test(e.tokens[t].content)&&dl(e.tokens[t].children),al.test(e.tokens[t].content)&&fl(e.tokens[t].children))}],["smartquotes",function(e){if(e.md.options.typographer)for(let t=e.tokens.length-1;t>=0;t--)"inline"===e.tokens[t].type&&ml.test(e.tokens[t].content)&&kl(e.tokens[t].children,e)}],["text_join",function(e){let t,n;const r=e.tokens,i=r.length;for(let e=0;e<i;e++){if("inline"!==r[e].type)continue;const i=r[e].children,o=i.length;for(t=0;t<o;t++)"text_special"===i[t].type&&(i[t].type="text");for(t=n=0;t<o;t++)"text"===i[t].type&&t+1<o&&"text"===i[t+1].type?i[t+1].content=i[t].content+i[t+1].content:(t!==n&&(i[n]=i[t]),n++);t!==n&&(i.length=n)}}]];function wl(){this.ruler=new tl;for(let e=0;e<bl.length;e++)this.ruler.push(bl[e][0],bl[e][1])}function Cl(e,t,n,r){this.src=e,this.md=t,this.env=n,this.tokens=r,this.bMarks=[],this.eMarks=[],this.tShift=[],this.sCount=[],this.bsCount=[],this.blkIndent=0,this.line=0,this.lineMax=0,this.tight=!1,this.ddIndent=-1,this.listIndent=-1,this.parentType="root",this.level=0;const i=this.src;for(let e=0,t=0,n=0,r=0,o=i.length,s=!1;t<o;t++){const l=i.charCodeAt(t);if(!s){if(Ws(l)){n++,9===l?r+=4-r%4:r++;continue}s=!0}10!==l&&t!==o-1||(10!==l&&t++,this.bMarks.push(e),this.eMarks.push(t),this.tShift.push(n),this.sCount.push(r),this.bsCount.push(0),s=!1,n=0,r=0,e=t+1)}this.bMarks.push(i.length),this.eMarks.push(i.length),this.tShift.push(0),this.sCount.push(0),this.bsCount.push(0),this.lineMax=this.bMarks.length-1}wl.prototype.process=function(e){const t=this.ruler.getRules("");for(let n=0,r=t.length;n<r;n++)t[n](e)},wl.prototype.State=rl,Cl.prototype.push=function(e,t,n){const r=new nl(e,t,n);return r.block=!0,n<0&&this.level--,r.level=this.level,n>0&&this.level++,this.tokens.push(r),r},Cl.prototype.isEmpty=function(e){return this.bMarks[e]+this.tShift[e]>=this.eMarks[e]},Cl.prototype.skipEmptyLines=function(e){for(let t=this.lineMax;e<t&&!(this.bMarks[e]+this.tShift[e]<this.eMarks[e]);e++);return e},Cl.prototype.skipSpaces=function(e){for(let t=this.src.length;e<t;e++){if(!Ws(this.src.charCodeAt(e)))break}return e},Cl.prototype.skipSpacesBack=function(e,t){if(e<=t)return e;for(;e>t;)if(!Ws(this.src.charCodeAt(--e)))return e+1;return e},Cl.prototype.skipChars=function(e,t){for(let n=this.src.length;e<n&&this.src.charCodeAt(e)===t;e++);return e},Cl.prototype.skipCharsBack=function(e,t,n){if(e<=n)return e;for(;e>n;)if(t!==this.src.charCodeAt(--e))return e+1;return e},Cl.prototype.getLines=function(e,t,n,r){if(e>=t)return"";const i=new Array(t-e);for(let o=0,s=e;s<t;s++,o++){let e=0;const l=this.bMarks[s];let a,c=l;for(a=s+1<t||r?this.eMarks[s]+1:this.eMarks[s];c<a&&e<n;){const t=this.src.charCodeAt(c);if(Ws(t))9===t?e+=4-(e+this.bsCount[s])%4:e++;else{if(!(c-l<this.tShift[s]))break;e++}c++}i[o]=e>n?new Array(e-n+1).join(" ")+this.src.slice(c,a):this.src.slice(c,a)}return i.join("")},Cl.prototype.Token=nl;function xl(e,t){const n=e.bMarks[t]+e.tShift[t],r=e.eMarks[t];return e.src.slice(n,r)}function Dl(e){const t=[],n=e.length;let r=0,i=e.charCodeAt(r),o=!1,s=0,l="";for(;r<n;)124===i&&(o?(l+=e.substring(s,r-1),s=r):(t.push(l+e.substring(s,r)),l="",s=r+1)),o=92===i,r++,i=e.charCodeAt(r);return t.push(l+e.substring(s)),t}function vl(e,t){const n=e.eMarks[t];let r=e.bMarks[t]+e.tShift[t];const i=e.src.charCodeAt(r++);if(42!==i&&45!==i&&43!==i)return-1;if(r<n){if(!Ws(e.src.charCodeAt(r)))return-1}return r}function _l(e,t){const n=e.bMarks[t]+e.tShift[t],r=e.eMarks[t];let i=n;if(i+1>=r)return-1;let o=e.src.charCodeAt(i++);if(o<48||o>57)return-1;for(;;){if(i>=r)return-1;if(o=e.src.charCodeAt(i++),!(o>=48&&o<=57)){if(41===o||46===o)break;return-1}if(i-n>=10)return-1}return i<r&&(o=e.src.charCodeAt(i),!Ws(o))?-1:i}const Sl="<[A-Za-z][A-Za-z0-9\\-]*(?:\\s+[a-zA-Z_:][a-zA-Z0-9:._-]*(?:\\s*=\\s*(?:[^\"'=<>`\\x00-\\x20]+|'[^']*'|\"[^\"]*\"))?)*\\s*\\/?>",El="<\\/[A-Za-z][A-Za-z0-9\\-]*\\s*>",Al=new RegExp("^(?:"+Sl+"|"+El+"|\x3c!---?>|\x3c!--(?:[^-]|-[^-]|--[^>])*--\x3e|<[?][\\s\\S]*?[?]>|<![A-Za-z][^>]*>|<!\\[CDATA\\[[\\s\\S]*?\\]\\]>)"),Ml=new RegExp("^(?:"+Sl+"|"+El+")"),Ol=[[/^<(script|pre|style|textarea)(?=(\s|>|$))/i,/<\/(script|pre|style|textarea)>/i,!0],[/^<!--/,/-->/,!0],[/^<\?/,/\?>/,!0],[/^<![A-Z]/,/>/,!0],[/^<!\[CDATA\[/,/\]\]>/,!0],[new RegExp("^</?("+["address","article","aside","base","basefont","blockquote","body","caption","center","col","colgroup","dd","details","dialog","dir","div","dl","dt","fieldset","figcaption","figure","footer","form","frame","frameset","h1","h2","h3","h4","h5","h6","head","header","hr","html","iframe","legend","li","link","main","menu","menuitem","nav","noframes","ol","optgroup","option","p","param","search","section","summary","table","tbody","td","tfoot","th","thead","title","tr","track","ul"].join("|")+")(?=(\\s|/?>|$))","i"),/^$/,!0],[new RegExp(Ml.source+"\\s*$"),/^$/,!1]];const Nl=[["table",function(e,t,n,r){if(t+2>n)return!1;let i=t+1;if(e.sCount[i]<e.blkIndent)return!1;if(e.sCount[i]-e.blkIndent>=4)return!1;let o=e.bMarks[i]+e.tShift[i];if(o>=e.eMarks[i])return!1;const s=e.src.charCodeAt(o++);if(124!==s&&45!==s&&58!==s)return!1;if(o>=e.eMarks[i])return!1;const l=e.src.charCodeAt(o++);if(124!==l&&45!==l&&58!==l&&!Ws(l))return!1;if(45===s&&Ws(l))return!1;for(;o<e.eMarks[i];){const t=e.src.charCodeAt(o);if(124!==t&&45!==t&&58!==t&&!Ws(t))return!1;o++}let a=xl(e,t+1),c=a.split("|");const h=[];for(let e=0;e<c.length;e++){const t=c[e].trim();if(!t){if(0===e||e===c.length-1)continue;return!1}if(!/^:?-+:?$/.test(t))return!1;58===t.charCodeAt(t.length-1)?h.push(58===t.charCodeAt(0)?"center":"right"):58===t.charCodeAt(0)?h.push("left"):h.push("")}if(a=xl(e,t).trim(),-1===a.indexOf("|"))return!1;if(e.sCount[t]-e.blkIndent>=4)return!1;c=Dl(a),c.length&&""===c[0]&&c.shift(),c.length&&""===c[c.length-1]&&c.pop();const u=c.length;if(0===u||u!==h.length)return!1;if(r)return!0;const p=e.parentType;e.parentType="table";const d=e.md.block.ruler.getRules("blockquote"),f=[t,0];e.push("table_open","table",1).map=f,e.push("thead_open","thead",1).map=[t,t+1],e.push("tr_open","tr",1).map=[t,t+1];for(let t=0;t<c.length;t++){const n=e.push("th_open","th",1);h[t]&&(n.attrs=[["style","text-align:"+h[t]]]);const r=e.push("inline","",0);r.content=c[t].trim(),r.children=[],e.push("th_close","th",-1)}let m;e.push("tr_close","tr",-1),e.push("thead_close","thead",-1);let g=0;for(i=t+2;i<n&&!(e.sCount[i]<e.blkIndent);i++){let r=!1;for(let t=0,o=d.length;t<o;t++)if(d[t](e,i,n,!0)){r=!0;break}if(r)break;if(a=xl(e,i).trim(),!a)break;if(e.sCount[i]-e.blkIndent>=4)break;if(c=Dl(a),c.length&&""===c[0]&&c.shift(),c.length&&""===c[c.length-1]&&c.pop(),g+=u-c.length,g>65536)break;if(i===t+2){e.push("tbody_open","tbody",1).map=m=[t+2,0]}e.push("tr_open","tr",1).map=[i,i+1];for(let t=0;t<u;t++){const n=e.push("td_open","td",1);h[t]&&(n.attrs=[["style","text-align:"+h[t]]]);const r=e.push("inline","",0);r.content=c[t]?c[t].trim():"",r.children=[],e.push("td_close","td",-1)}e.push("tr_close","tr",-1)}return m&&(e.push("tbody_close","tbody",-1),m[1]=i),e.push("table_close","table",-1),f[1]=i,e.parentType=p,e.line=i,!0},["paragraph","reference"]],["code",function(e,t,n){if(e.sCount[t]-e.blkIndent<4)return!1;let r=t+1,i=r;for(;r<n;)if(e.isEmpty(r))r++;else{if(!(e.sCount[r]-e.blkIndent>=4))break;r++,i=r}e.line=i;const o=e.push("code_block","code",0);return o.content=e.getLines(t,i,4+e.blkIndent,!1)+"\n",o.map=[t,e.line],!0}],["fence",function(e,t,n,r){let i=e.bMarks[t]+e.tShift[t],o=e.eMarks[t];if(e.sCount[t]-e.blkIndent>=4)return!1;if(i+3>o)return!1;const s=e.src.charCodeAt(i);if(126!==s&&96!==s)return!1;let l=i;i=e.skipChars(i,s);let a=i-l;if(a<3)return!1;const c=e.src.slice(l,i),h=e.src.slice(i,o);if(96===s&&h.indexOf(String.fromCharCode(s))>=0)return!1;if(r)return!0;let u=t,p=!1;for(;(u++,!(u>=n))&&(i=l=e.bMarks[u]+e.tShift[u],o=e.eMarks[u],!(i<o&&e.sCount[u]<e.blkIndent));)if(e.src.charCodeAt(i)===s&&!(e.sCount[u]-e.blkIndent>=4||(i=e.skipChars(i,s),i-l<a||(i=e.skipSpaces(i),i<o)))){p=!0;break}a=e.sCount[t],e.line=u+(p?1:0);const d=e.push("fence","code",0);return d.info=h,d.content=e.getLines(t+1,u,a,!0),d.markup=c,d.map=[t,e.line],!0},["paragraph","reference","blockquote","list"]],["blockquote",function(e,t,n,r){let i=e.bMarks[t]+e.tShift[t],o=e.eMarks[t];const s=e.lineMax;if(e.sCount[t]-e.blkIndent>=4)return!1;if(62!==e.src.charCodeAt(i))return!1;if(r)return!0;const l=[],a=[],c=[],h=[],u=e.md.block.ruler.getRules("blockquote"),p=e.parentType;e.parentType="blockquote";let d,f=!1;for(d=t;d<n;d++){const t=e.sCount[d]<e.blkIndent;if(i=e.bMarks[d]+e.tShift[d],o=e.eMarks[d],i>=o)break;if(62===e.src.charCodeAt(i++)&&!t){let t,n,r=e.sCount[d]+1;32===e.src.charCodeAt(i)?(i++,r++,n=!1,t=!0):9===e.src.charCodeAt(i)?(t=!0,(e.bsCount[d]+r)%4==3?(i++,r++,n=!1):n=!0):t=!1;let s=r;for(l.push(e.bMarks[d]),e.bMarks[d]=i;i<o;){const t=e.src.charCodeAt(i);if(!Ws(t))break;9===t?s+=4-(s+e.bsCount[d]+(n?1:0))%4:s++,i++}f=i>=o,a.push(e.bsCount[d]),e.bsCount[d]=e.sCount[d]+1+(t?1:0),c.push(e.sCount[d]),e.sCount[d]=s-r,h.push(e.tShift[d]),e.tShift[d]=i-e.bMarks[d];continue}if(f)break;let r=!1;for(let t=0,i=u.length;t<i;t++)if(u[t](e,d,n,!0)){r=!0;break}if(r){e.lineMax=d,0!==e.blkIndent&&(l.push(e.bMarks[d]),a.push(e.bsCount[d]),h.push(e.tShift[d]),c.push(e.sCount[d]),e.sCount[d]-=e.blkIndent);break}l.push(e.bMarks[d]),a.push(e.bsCount[d]),h.push(e.tShift[d]),c.push(e.sCount[d]),e.sCount[d]=-1}const m=e.blkIndent;e.blkIndent=0;const g=e.push("blockquote_open","blockquote",1);g.markup=">";const y=[t,0];g.map=y,e.md.block.tokenize(e,t,d),e.push("blockquote_close","blockquote",-1).markup=">",e.lineMax=s,e.parentType=p,y[1]=e.line;for(let n=0;n<h.length;n++)e.bMarks[n+t]=l[n],e.tShift[n+t]=h[n],e.sCount[n+t]=c[n],e.bsCount[n+t]=a[n];return e.blkIndent=m,!0},["paragraph","reference","blockquote","list"]],["hr",function(e,t,n,r){const i=e.eMarks[t];if(e.sCount[t]-e.blkIndent>=4)return!1;let o=e.bMarks[t]+e.tShift[t];const s=e.src.charCodeAt(o++);if(42!==s&&45!==s&&95!==s)return!1;let l=1;for(;o<i;){const t=e.src.charCodeAt(o++);if(t!==s&&!Ws(t))return!1;t===s&&l++}if(l<3)return!1;if(r)return!0;e.line=t+1;const a=e.push("hr","hr",0);return a.map=[t,e.line],a.markup=Array(l+1).join(String.fromCharCode(s)),!0},["paragraph","reference","blockquote","list"]],["list",function(e,t,n,r){let i,o,s,l,a=t,c=!0;if(e.sCount[a]-e.blkIndent>=4)return!1;if(e.listIndent>=0&&e.sCount[a]-e.listIndent>=4&&e.sCount[a]<e.blkIndent)return!1;let h,u,p,d=!1;if(r&&"paragraph"===e.parentType&&e.sCount[a]>=e.blkIndent&&(d=!0),(p=_l(e,a))>=0){if(h=!0,s=e.bMarks[a]+e.tShift[a],u=Number(e.src.slice(s,p-1)),d&&1!==u)return!1}else{if(!((p=vl(e,a))>=0))return!1;h=!1}if(d&&e.skipSpaces(p)>=e.eMarks[a])return!1;if(r)return!0;const f=e.src.charCodeAt(p-1),m=e.tokens.length;h?(l=e.push("ordered_list_open","ol",1),1!==u&&(l.attrs=[["start",u]])):l=e.push("bullet_list_open","ul",1);const g=[a,0];l.map=g,l.markup=String.fromCharCode(f);let y=!1;const k=e.md.block.ruler.getRules("list"),b=e.parentType;for(e.parentType="list";a<n;){o=p,i=e.eMarks[a];const t=e.sCount[a]+p-(e.bMarks[a]+e.tShift[a]);let r=t;for(;o<i;){const t=e.src.charCodeAt(o);if(9===t)r+=4-(r+e.bsCount[a])%4;else{if(32!==t)break;r++}o++}const u=o;let d;d=u>=i?1:r-t,d>4&&(d=1);const m=t+d;l=e.push("list_item_open","li",1),l.markup=String.fromCharCode(f);const g=[a,0];l.map=g,h&&(l.info=e.src.slice(s,p-1));const b=e.tight,w=e.tShift[a],C=e.sCount[a],x=e.listIndent;if(e.listIndent=e.blkIndent,e.blkIndent=m,e.tight=!0,e.tShift[a]=u-e.bMarks[a],e.sCount[a]=r,u>=i&&e.isEmpty(a+1)?e.line=Math.min(e.line+2,n):e.md.block.tokenize(e,a,n,!0),e.tight&&!y||(c=!1),y=e.line-a>1&&e.isEmpty(e.line-1),e.blkIndent=e.listIndent,e.listIndent=x,e.tShift[a]=w,e.sCount[a]=C,e.tight=b,l=e.push("list_item_close","li",-1),l.markup=String.fromCharCode(f),a=e.line,g[1]=a,a>=n)break;if(e.sCount[a]<e.blkIndent)break;if(e.sCount[a]-e.blkIndent>=4)break;let D=!1;for(let t=0,r=k.length;t<r;t++)if(k[t](e,a,n,!0)){D=!0;break}if(D)break;if(h){if(p=_l(e,a),p<0)break;s=e.bMarks[a]+e.tShift[a]}else if(p=vl(e,a),p<0)break;if(f!==e.src.charCodeAt(p-1))break}return l=h?e.push("ordered_list_close","ol",-1):e.push("bullet_list_close","ul",-1),l.markup=String.fromCharCode(f),g[1]=a,e.line=a,e.parentType=b,c&&function(e,t){const n=e.level+2;for(let r=t+2,i=e.tokens.length-2;r<i;r++)e.tokens[r].level===n&&"paragraph_open"===e.tokens[r].type&&(e.tokens[r+2].hidden=!0,e.tokens[r].hidden=!0,r+=2)}(e,m),!0},["paragraph","reference","blockquote"]],["reference",function(e,t,n,r){let i=e.bMarks[t]+e.tShift[t],o=e.eMarks[t],s=t+1;if(e.sCount[t]-e.blkIndent>=4)return!1;if(91!==e.src.charCodeAt(i))return!1;function l(t){const n=e.lineMax;if(t>=n||e.isEmpty(t))return null;let r=!1;if(e.sCount[t]-e.blkIndent>3&&(r=!0),e.sCount[t]<0&&(r=!0),!r){const r=e.md.block.ruler.getRules("reference"),i=e.parentType;e.parentType="reference";let o=!1;for(let i=0,s=r.length;i<s;i++)if(r[i](e,t,n,!0)){o=!0;break}if(e.parentType=i,o)return null}const i=e.bMarks[t]+e.tShift[t],o=e.eMarks[t];return e.src.slice(i,o+1)}let a=e.src.slice(i,o+1);o=a.length;let c=-1;for(i=1;i<o;i++){const e=a.charCodeAt(i);if(91===e)return!1;if(93===e){c=i;break}if(10===e){const e=l(s);null!==e&&(a+=e,o=a.length,s++)}else if(92===e&&(i++,i<o&&10===a.charCodeAt(i))){const e=l(s);null!==e&&(a+=e,o=a.length,s++)}}if(c<0||58!==a.charCodeAt(c+1))return!1;for(i=c+2;i<o;i++){const e=a.charCodeAt(i);if(10===e){const e=l(s);null!==e&&(a+=e,o=a.length,s++)}else if(!Ws(e))break}const h=e.md.helpers.parseLinkDestination(a,i,o);if(!h.ok)return!1;const u=e.md.normalizeLink(h.str);if(!e.md.validateLink(u))return!1;i=h.pos;const p=i,d=s,f=i;for(;i<o;i++){const e=a.charCodeAt(i);if(10===e){const e=l(s);null!==e&&(a+=e,o=a.length,s++)}else if(!Ws(e))break}let m,g=e.md.helpers.parseLinkTitle(a,i,o);for(;g.can_continue;){const t=l(s);if(null===t)break;a+=t,i=o,o=a.length,s++,g=e.md.helpers.parseLinkTitle(a,i,o,g)}for(i<o&&f!==i&&g.ok?(m=g.str,i=g.pos):(m="",i=p,s=d);i<o;){if(!Ws(a.charCodeAt(i)))break;i++}if(i<o&&10!==a.charCodeAt(i)&&m)for(m="",i=p,s=d;i<o;){if(!Ws(a.charCodeAt(i)))break;i++}if(i<o&&10!==a.charCodeAt(i))return!1;const y=Zs(a.slice(1,c));return!!y&&(r||(void 0===e.env.references&&(e.env.references={}),void 0===e.env.references[y]&&(e.env.references[y]={title:m,href:u}),e.line=s),!0)}],["html_block",function(e,t,n,r){let i=e.bMarks[t]+e.tShift[t],o=e.eMarks[t];if(e.sCount[t]-e.blkIndent>=4)return!1;if(!e.md.options.html)return!1;if(60!==e.src.charCodeAt(i))return!1;let s=e.src.slice(i,o),l=0;for(;l<Ol.length&&!Ol[l][0].test(s);l++);if(l===Ol.length)return!1;if(r)return Ol[l][2];let a=t+1;if(!Ol[l][1].test(s))for(;a<n&&!(e.sCount[a]<e.blkIndent);a++)if(i=e.bMarks[a]+e.tShift[a],o=e.eMarks[a],s=e.src.slice(i,o),Ol[l][1].test(s)){0!==s.length&&a++;break}e.line=a;const c=e.push("html_block","",0);return c.map=[t,a],c.content=e.getLines(t,a,e.blkIndent,!0),!0},["paragraph","reference","blockquote"]],["heading",function(e,t,n,r){let i=e.bMarks[t]+e.tShift[t],o=e.eMarks[t];if(e.sCount[t]-e.blkIndent>=4)return!1;let s=e.src.charCodeAt(i);if(35!==s||i>=o)return!1;let l=1;for(s=e.src.charCodeAt(++i);35===s&&i<o&&l<=6;)l++,s=e.src.charCodeAt(++i);if(l>6||i<o&&!Ws(s))return!1;if(r)return!0;o=e.skipSpacesBack(o,i);const a=e.skipCharsBack(o,35,i);a>i&&Ws(e.src.charCodeAt(a-1))&&(o=a),e.line=t+1;const c=e.push("heading_open","h"+String(l),1);c.markup="########".slice(0,l),c.map=[t,e.line];const h=e.push("inline","",0);return h.content=e.src.slice(i,o).trim(),h.map=[t,e.line],h.children=[],e.push("heading_close","h"+String(l),-1).markup="########".slice(0,l),!0},["paragraph","reference","blockquote"]],["lheading",function(e,t,n){const r=e.md.block.ruler.getRules("paragraph");if(e.sCount[t]-e.blkIndent>=4)return!1;const i=e.parentType;e.parentType="paragraph";let o,s=0,l=t+1;for(;l<n&&!e.isEmpty(l);l++){if(e.sCount[l]-e.blkIndent>3)continue;if(e.sCount[l]>=e.blkIndent){let t=e.bMarks[l]+e.tShift[l];const n=e.eMarks[l];if(t<n&&(o=e.src.charCodeAt(t),(45===o||61===o)&&(t=e.skipChars(t,o),t=e.skipSpaces(t),t>=n))){s=61===o?1:2;break}}if(e.sCount[l]<0)continue;let t=!1;for(let i=0,o=r.length;i<o;i++)if(r[i](e,l,n,!0)){t=!0;break}if(t)break}if(!s)return!1;const a=e.getLines(t,l,e.blkIndent,!1).trim();e.line=l+1;const c=e.push("heading_open","h"+String(s),1);c.markup=String.fromCharCode(o),c.map=[t,e.line];const h=e.push("inline","",0);return h.content=a,h.map=[t,e.line-1],h.children=[],e.push("heading_close","h"+String(s),-1).markup=String.fromCharCode(o),e.parentType=i,!0}],["paragraph",function(e,t,n){const r=e.md.block.ruler.getRules("paragraph"),i=e.parentType;let o=t+1;for(e.parentType="paragraph";o<n&&!e.isEmpty(o);o++){if(e.sCount[o]-e.blkIndent>3)continue;if(e.sCount[o]<0)continue;let t=!1;for(let i=0,s=r.length;i<s;i++)if(r[i](e,o,n,!0)){t=!0;break}if(t)break}const s=e.getLines(t,o,e.blkIndent,!1).trim();e.line=o,e.push("paragraph_open","p",1).map=[t,e.line];const l=e.push("inline","",0);return l.content=s,l.map=[t,e.line],l.children=[],e.push("paragraph_close","p",-1),e.parentType=i,!0}]];function Fl(){this.ruler=new tl;for(let e=0;e<Nl.length;e++)this.ruler.push(Nl[e][0],Nl[e][1],{alt:(Nl[e][2]||[]).slice()})}function Tl(e,t,n,r){this.src=e,this.env=n,this.md=t,this.tokens=r,this.tokens_meta=Array(r.length),this.pos=0,this.posMax=this.src.length,this.level=0,this.pending="",this.pendingLevel=0,this.cache={},this.delimiters=[],this._prev_delimiters=[],this.backticks={},this.backticksScanned=!1,this.linkLevel=0}function Rl(e){switch(e){case 10:case 33:case 35:case 36:case 37:case 38:case 42:case 43:case 45:case 58:case 60:case 61:case 62:case 64:case 91:case 92:case 93:case 94:case 95:case 96:case 123:case 125:case 126:return!0;default:return!1}}Fl.prototype.tokenize=function(e,t,n){const r=this.ruler.getRules(""),i=r.length,o=e.md.options.maxNesting;let s=t,l=!1;for(;s<n&&(e.line=s=e.skipEmptyLines(s),!(s>=n))&&!(e.sCount[s]<e.blkIndent);){if(e.level>=o){e.line=n;break}const t=e.line;let a=!1;for(let o=0;o<i;o++)if(a=r[o](e,s,n,!1),a){if(t>=e.line)throw new Error("block rule didn't increment state.line");break}if(!a)throw new Error("none of the block rules matched");e.tight=!l,e.isEmpty(e.line-1)&&(l=!0),s=e.line,s<n&&e.isEmpty(s)&&(l=!0,s++,e.line=s)}},Fl.prototype.parse=function(e,t,n,r){if(!e)return;const i=new this.State(e,t,n,r);this.tokenize(i,i.line,i.lineMax)},Fl.prototype.State=Cl,Tl.prototype.pushPending=function(){const e=new nl("text","",0);return e.content=this.pending,e.level=this.pendingLevel,this.tokens.push(e),this.pending="",e},Tl.prototype.push=function(e,t,n){this.pending&&this.pushPending();const r=new nl(e,t,n);let i=null;return n<0&&(this.level--,this.delimiters=this._prev_delimiters.pop()),r.level=this.level,n>0&&(this.level++,this._prev_delimiters.push(this.delimiters),this.delimiters=[],i={delimiters:this.delimiters}),this.pendingLevel=this.level,this.tokens.push(r),this.tokens_meta.push(i),r},Tl.prototype.scanDelims=function(e,t){const n=this.posMax,r=this.src.charCodeAt(e),i=e>0?this.src.charCodeAt(e-1):32;let o=e;for(;o<n&&this.src.charCodeAt(o)===r;)o++;const s=o-e,l=o<n?this.src.charCodeAt(o):32,a=Ks(i)||Us(String.fromCharCode(i)),c=Ks(l)||Us(String.fromCharCode(l)),h=Hs(i),u=Hs(l),p=!u&&(!c||h||a),d=!h&&(!a||u||c);return{can_open:p&&(t||!d||a),can_close:d&&(t||!p||c),length:s}},Tl.prototype.Token=nl;const zl=/(?:^|[^a-z0-9.+-])([a-z][a-z0-9.+-]*)$/i;const Il=[];for(let e=0;e<256;e++)Il.push(0);function Bl(e,t){let n;const r=[],i=t.length;for(let o=0;o<i;o++){const i=t[o];if(126!==i.marker)continue;if(-1===i.end)continue;const s=t[i.end];n=e.tokens[i.token],n.type="s_open",n.tag="s",n.nesting=1,n.markup="~~",n.content="",n=e.tokens[s.token],n.type="s_close",n.tag="s",n.nesting=-1,n.markup="~~",n.content="","text"===e.tokens[s.token-1].type&&"~"===e.tokens[s.token-1].content&&r.push(s.token-1)}for(;r.length;){const t=r.pop();let i=t+1;for(;i<e.tokens.length&&"s_close"===e.tokens[i].type;)i++;i--,t!==i&&(n=e.tokens[i],e.tokens[i]=e.tokens[t],e.tokens[t]=n)}}"\\!\"#$%&'()*+,./:;<=>?@[]^_`{|}~-".split("").forEach(function(e){Il[e.charCodeAt(0)]=1});var Pl={tokenize:function(e,t){const n=e.pos,r=e.src.charCodeAt(n);if(t)return!1;if(126!==r)return!1;const i=e.scanDelims(e.pos,!0);let o=i.length;const s=String.fromCharCode(r);if(o<2)return!1;let l;o%2&&(l=e.push("text","",0),l.content=s,o--);for(let t=0;t<o;t+=2)l=e.push("text","",0),l.content=s+s,e.delimiters.push({marker:r,length:0,token:e.tokens.length-1,end:-1,open:i.can_open,close:i.can_close});return e.pos+=i.length,!0},postProcess:function(e){const t=e.tokens_meta,n=e.tokens_meta.length;Bl(e,e.delimiters);for(let r=0;r<n;r++)t[r]&&t[r].delimiters&&Bl(e,t[r].delimiters)}};function Ll(e,t){for(let n=t.length-1;n>=0;n--){const r=t[n];if(95!==r.marker&&42!==r.marker)continue;if(-1===r.end)continue;const i=t[r.end],o=n>0&&t[n-1].end===r.end+1&&t[n-1].marker===r.marker&&t[n-1].token===r.token-1&&t[r.end+1].token===i.token+1,s=String.fromCharCode(r.marker),l=e.tokens[r.token];l.type=o?"strong_open":"em_open",l.tag=o?"strong":"em",l.nesting=1,l.markup=o?s+s:s,l.content="";const a=e.tokens[i.token];a.type=o?"strong_close":"em_close",a.tag=o?"strong":"em",a.nesting=-1,a.markup=o?s+s:s,a.content="",o&&(e.tokens[t[n-1].token].content="",e.tokens[t[r.end+1].token].content="",n--)}}var ql={tokenize:function(e,t){const n=e.pos,r=e.src.charCodeAt(n);if(t)return!1;if(95!==r&&42!==r)return!1;const i=e.scanDelims(e.pos,42===r);for(let t=0;t<i.length;t++){e.push("text","",0).content=String.fromCharCode(r),e.delimiters.push({marker:r,length:i.length,token:e.tokens.length-1,end:-1,open:i.can_open,close:i.can_close})}return e.pos+=i.length,!0},postProcess:function(e){const t=e.tokens_meta,n=e.tokens_meta.length;Ll(e,e.delimiters);for(let r=0;r<n;r++)t[r]&&t[r].delimiters&&Ll(e,t[r].delimiters)}};const $l=/^([a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*)$/,Vl=/^([a-zA-Z][a-zA-Z0-9+.-]{1,31}):([^<>\x00-\x20]*)$/;const jl=/^&#((?:x[a-f0-9]{1,6}|[0-9]{1,7}));/i,Jl=/^&([a-z][a-z0-9]{1,31});/i;function Wl(e){const t={},n=e.length;if(!n)return;let r=0,i=-2;const o=[];for(let s=0;s<n;s++){const n=e[s];if(o.push(0),e[r].marker===n.marker&&i===n.token-1||(r=s),i=n.token,n.length=n.length||0,!n.close)continue;t.hasOwnProperty(n.marker)||(t[n.marker]=[-1,-1,-1,-1,-1,-1]);const l=t[n.marker][(n.open?3:0)+n.length%3];let a=r-o[r]-1,c=a;for(;a>l;a-=o[a]+1){const t=e[a];if(t.marker===n.marker&&(t.open&&t.end<0)){let r=!1;if((t.close||n.open)&&(t.length+n.length)%3==0&&(t.length%3==0&&n.length%3==0||(r=!0)),!r){const r=a>0&&!e[a-1].open?o[a-1]+1:0;o[s]=s-a+r,o[a]=r,n.open=!1,t.end=s,t.close=!1,c=-1,i=-2;break}}}-1!==c&&(t[n.marker][(n.open?3:0)+(n.length||0)%3]=c)}}const Hl=[["text",function(e,t){let n=e.pos;for(;n<e.posMax&&!Rl(e.src.charCodeAt(n));)n++;return n!==e.pos&&(t||(e.pending+=e.src.slice(e.pos,n)),e.pos=n,!0)}],["linkify",function(e,t){if(!e.md.options.linkify)return!1;if(e.linkLevel>0)return!1;const n=e.pos;if(n+3>e.posMax)return!1;if(58!==e.src.charCodeAt(n))return!1;if(47!==e.src.charCodeAt(n+1))return!1;if(47!==e.src.charCodeAt(n+2))return!1;const r=e.pending.match(zl);if(!r)return!1;const i=r[1],o=e.md.linkify.matchAtStart(e.src.slice(n-i.length));if(!o)return!1;let s=o.url;if(s.length<=i.length)return!1;s=s.replace(/\*+$/,"");const l=e.md.normalizeLink(s);if(!e.md.validateLink(l))return!1;if(!t){e.pending=e.pending.slice(0,-i.length);const t=e.push("link_open","a",1);t.attrs=[["href",l]],t.markup="linkify",t.info="auto";e.push("text","",0).content=e.md.normalizeLinkText(s);const n=e.push("link_close","a",-1);n.markup="linkify",n.info="auto"}return e.pos+=s.length-i.length,!0}],["newline",function(e,t){let n=e.pos;if(10!==e.src.charCodeAt(n))return!1;const r=e.pending.length-1,i=e.posMax;if(!t)if(r>=0&&32===e.pending.charCodeAt(r))if(r>=1&&32===e.pending.charCodeAt(r-1)){let t=r-1;for(;t>=1&&32===e.pending.charCodeAt(t-1);)t--;e.pending=e.pending.slice(0,t),e.push("hardbreak","br",0)}else e.pending=e.pending.slice(0,-1),e.push("softbreak","br",0);else e.push("softbreak","br",0);for(n++;n<i&&Ws(e.src.charCodeAt(n));)n++;return e.pos=n,!0}],["escape",function(e,t){let n=e.pos;const r=e.posMax;if(92!==e.src.charCodeAt(n))return!1;if(n++,n>=r)return!1;let i=e.src.charCodeAt(n);if(10===i){for(t||e.push("hardbreak","br",0),n++;n<r&&(i=e.src.charCodeAt(n),Ws(i));)n++;return e.pos=n,!0}let o=e.src[n];if(i>=55296&&i<=56319&&n+1<r){const t=e.src.charCodeAt(n+1);t>=56320&&t<=57343&&(o+=e.src[n+1],n++)}const s="\\"+o;if(!t){const t=e.push("text_special","",0);i<256&&0!==Il[i]?t.content=o:t.content=s,t.markup=s,t.info="escape"}return e.pos=n+1,!0}],["backticks",function(e,t){let n=e.pos;if(96!==e.src.charCodeAt(n))return!1;const r=n;n++;const i=e.posMax;for(;n<i&&96===e.src.charCodeAt(n);)n++;const o=e.src.slice(r,n),s=o.length;if(e.backticksScanned&&(e.backticks[s]||0)<=r)return t||(e.pending+=o),e.pos+=s,!0;let l,a=n;for(;-1!==(l=e.src.indexOf("`",a));){for(a=l+1;a<i&&96===e.src.charCodeAt(a);)a++;const r=a-l;if(r===s){if(!t){const t=e.push("code_inline","code",0);t.markup=o,t.content=e.src.slice(n,l).replace(/\n/g," ").replace(/^ (.+) $/,"$1")}return e.pos=a,!0}e.backticks[r]=l}return e.backticksScanned=!0,t||(e.pending+=o),e.pos+=s,!0}],["strikethrough",Pl.tokenize],["emphasis",ql.tokenize],["link",function(e,t){let n,r,i,o,s="",l="",a=e.pos,c=!0;if(91!==e.src.charCodeAt(e.pos))return!1;const h=e.pos,u=e.posMax,p=e.pos+1,d=e.md.helpers.parseLinkLabel(e,e.pos,!0);if(d<0)return!1;let f=d+1;if(f<u&&40===e.src.charCodeAt(f)){for(c=!1,f++;f<u&&(n=e.src.charCodeAt(f),Ws(n)||10===n);f++);if(f>=u)return!1;if(a=f,i=e.md.helpers.parseLinkDestination(e.src,f,e.posMax),i.ok){for(s=e.md.normalizeLink(i.str),e.md.validateLink(s)?f=i.pos:s="",a=f;f<u&&(n=e.src.charCodeAt(f),Ws(n)||10===n);f++);if(i=e.md.helpers.parseLinkTitle(e.src,f,e.posMax),f<u&&a!==f&&i.ok)for(l=i.str,f=i.pos;f<u&&(n=e.src.charCodeAt(f),Ws(n)||10===n);f++);}(f>=u||41!==e.src.charCodeAt(f))&&(c=!0),f++}if(c){if(void 0===e.env.references)return!1;if(f<u&&91===e.src.charCodeAt(f)?(a=f+1,f=e.md.helpers.parseLinkLabel(e,f),f>=0?r=e.src.slice(a,f++):f=d+1):f=d+1,r||(r=e.src.slice(p,d)),o=e.env.references[Zs(r)],!o)return e.pos=h,!1;s=o.href,l=o.title}if(!t){e.pos=p,e.posMax=d;const t=[["href",s]];e.push("link_open","a",1).attrs=t,l&&t.push(["title",l]),e.linkLevel++,e.md.inline.tokenize(e),e.linkLevel--,e.push("link_close","a",-1)}return e.pos=f,e.posMax=u,!0}],["image",function(e,t){let n,r,i,o,s,l,a,c,h="";const u=e.pos,p=e.posMax;if(33!==e.src.charCodeAt(e.pos))return!1;if(91!==e.src.charCodeAt(e.pos+1))return!1;const d=e.pos+2,f=e.md.helpers.parseLinkLabel(e,e.pos+1,!1);if(f<0)return!1;if(o=f+1,o<p&&40===e.src.charCodeAt(o)){for(o++;o<p&&(n=e.src.charCodeAt(o),Ws(n)||10===n);o++);if(o>=p)return!1;for(c=o,l=e.md.helpers.parseLinkDestination(e.src,o,e.posMax),l.ok&&(h=e.md.normalizeLink(l.str),e.md.validateLink(h)?o=l.pos:h=""),c=o;o<p&&(n=e.src.charCodeAt(o),Ws(n)||10===n);o++);if(l=e.md.helpers.parseLinkTitle(e.src,o,e.posMax),o<p&&c!==o&&l.ok)for(a=l.str,o=l.pos;o<p&&(n=e.src.charCodeAt(o),Ws(n)||10===n);o++);else a="";if(o>=p||41!==e.src.charCodeAt(o))return e.pos=u,!1;o++}else{if(void 0===e.env.references)return!1;if(o<p&&91===e.src.charCodeAt(o)?(c=o+1,o=e.md.helpers.parseLinkLabel(e,o),o>=0?i=e.src.slice(c,o++):o=f+1):o=f+1,i||(i=e.src.slice(d,f)),s=e.env.references[Zs(i)],!s)return e.pos=u,!1;h=s.href,a=s.title}if(!t){r=e.src.slice(d,f);const t=[];e.md.inline.parse(r,e.md,e.env,t);const n=e.push("image","img",0),i=[["src",h],["alt",""]];n.attrs=i,n.children=t,n.content=r,a&&i.push(["title",a])}return e.pos=o,e.posMax=p,!0}],["autolink",function(e,t){let n=e.pos;if(60!==e.src.charCodeAt(n))return!1;const r=e.pos,i=e.posMax;for(;;){if(++n>=i)return!1;const t=e.src.charCodeAt(n);if(60===t)return!1;if(62===t)break}const o=e.src.slice(r+1,n);if(Vl.test(o)){const n=e.md.normalizeLink(o);if(!e.md.validateLink(n))return!1;if(!t){const t=e.push("link_open","a",1);t.attrs=[["href",n]],t.markup="autolink",t.info="auto";e.push("text","",0).content=e.md.normalizeLinkText(o);const r=e.push("link_close","a",-1);r.markup="autolink",r.info="auto"}return e.pos+=o.length+2,!0}if($l.test(o)){const n=e.md.normalizeLink("mailto:"+o);if(!e.md.validateLink(n))return!1;if(!t){const t=e.push("link_open","a",1);t.attrs=[["href",n]],t.markup="autolink",t.info="auto";e.push("text","",0).content=e.md.normalizeLinkText(o);const r=e.push("link_close","a",-1);r.markup="autolink",r.info="auto"}return e.pos+=o.length+2,!0}return!1}],["html_inline",function(e,t){if(!e.md.options.html)return!1;const n=e.posMax,r=e.pos;if(60!==e.src.charCodeAt(r)||r+2>=n)return!1;const i=e.src.charCodeAt(r+1);if(33!==i&&63!==i&&47!==i&&!function(e){const t=32|e;return t>=97&&t<=122}(i))return!1;const o=e.src.slice(r).match(Al);if(!o)return!1;if(!t){const t=e.push("html_inline","",0);t.content=o[0],s=t.content,/^<a[>\s]/i.test(s)&&e.linkLevel++,function(e){return/^<\/a\s*>/i.test(e)}(t.content)&&e.linkLevel--}var s;return e.pos+=o[0].length,!0}],["entity",function(e,t){const n=e.pos,r=e.posMax;if(38!==e.src.charCodeAt(n))return!1;if(n+1>=r)return!1;if(35===e.src.charCodeAt(n+1)){const r=e.src.slice(n).match(jl);if(r){if(!t){const t="x"===r[1][0].toLowerCase()?parseInt(r[1].slice(1),16):parseInt(r[1],10),n=e.push("text_special","",0);n.content=Ts(t)?Rs(t):Rs(65533),n.markup=r[0],n.info="entity"}return e.pos+=r[0].length,!0}}else{const r=e.src.slice(n).match(Jl);if(r){const n=As(r[0]);if(n!==r[0]){if(!t){const t=e.push("text_special","",0);t.content=n,t.markup=r[0],t.info="entity"}return e.pos+=r[0].length,!0}}}return!1}]],Ul=[["balance_pairs",function(e){const t=e.tokens_meta,n=e.tokens_meta.length;Wl(e.delimiters);for(let e=0;e<n;e++)t[e]&&t[e].delimiters&&Wl(t[e].delimiters)}],["strikethrough",Pl.postProcess],["emphasis",ql.postProcess],["fragments_join",function(e){let t,n,r=0;const i=e.tokens,o=e.tokens.length;for(t=n=0;t<o;t++)i[t].nesting<0&&r--,i[t].level=r,i[t].nesting>0&&r++,"text"===i[t].type&&t+1<o&&"text"===i[t+1].type?i[t+1].content=i[t].content+i[t+1].content:(t!==n&&(i[n]=i[t]),n++);t!==n&&(i.length=n)}]];function Kl(){this.ruler=new tl;for(let e=0;e<Hl.length;e++)this.ruler.push(Hl[e][0],Hl[e][1]);this.ruler2=new tl;for(let e=0;e<Ul.length;e++)this.ruler2.push(Ul[e][0],Ul[e][1])}function Zl(e){return Array.prototype.slice.call(arguments,1).forEach(function(t){t&&Object.keys(t).forEach(function(n){e[n]=t[n]})}),e}function Gl(e){return Object.prototype.toString.call(e)}function Ql(e){return"[object Function]"===Gl(e)}function Xl(e){return e.replace(/[.?*+^$[\]\\(){}|-]/g,"\\$&")}Kl.prototype.skipToken=function(e){const t=e.pos,n=this.ruler.getRules(""),r=n.length,i=e.md.options.maxNesting,o=e.cache;if(void 0!==o[t])return void(e.pos=o[t]);let s=!1;if(e.level<i){for(let i=0;i<r;i++)if(e.level++,s=n[i](e,!0),e.level--,s){if(t>=e.pos)throw new Error("inline rule didn't increment state.pos");break}}else e.pos=e.posMax;s||e.pos++,o[t]=e.pos},Kl.prototype.tokenize=function(e){const t=this.ruler.getRules(""),n=t.length,r=e.posMax,i=e.md.options.maxNesting;for(;e.pos<r;){const o=e.pos;let s=!1;if(e.level<i)for(let r=0;r<n;r++)if(s=t[r](e,!1),s){if(o>=e.pos)throw new Error("inline rule didn't increment state.pos");break}if(s){if(e.pos>=r)break}else e.pending+=e.src[e.pos++]}e.pending&&e.pushPending()},Kl.prototype.parse=function(e,t,n,r){const i=new this.State(e,t,n,r);this.tokenize(i);const o=this.ruler2.getRules(""),s=o.length;for(let e=0;e<s;e++)o[e](i)},Kl.prototype.State=Tl;const Yl={fuzzyLink:!0,fuzzyEmail:!0,fuzzyIP:!1};const ea={"http:":{validate:function(e,t,n){const r=e.slice(t);return n.re.http||(n.re.http=new RegExp("^\\/\\/"+n.re.src_auth+n.re.src_host_port_strict+n.re.src_path,"i")),n.re.http.test(r)?r.match(n.re.http)[0].length:0}},"https:":"http:","ftp:":"http:","//":{validate:function(e,t,n){const r=e.slice(t);return n.re.no_http||(n.re.no_http=new RegExp("^"+n.re.src_auth+"(?:localhost|(?:(?:"+n.re.src_domain+")\\.)+"+n.re.src_domain_root+")"+n.re.src_port+n.re.src_host_terminator+n.re.src_path,"i")),n.re.no_http.test(r)?t>=3&&":"===e[t-3]||t>=3&&"/"===e[t-3]?0:r.match(n.re.no_http)[0].length:0}},"mailto:":{validate:function(e,t,n){const r=e.slice(t);return n.re.mailto||(n.re.mailto=new RegExp("^"+n.re.src_email_name+"@"+n.re.src_host_strict,"i")),n.re.mailto.test(r)?r.match(n.re.mailto)[0].length:0}}},ta="biz|com|edu|gov|net|org|pro|web|xxx|aero|asia|coop|info|museum|name|shop|рф".split("|");function na(e){const t=e.re=function(e){const t={};e=e||{},t.src_Any=ls.source,t.src_Cc=as.source,t.src_Z=us.source,t.src_P=cs.source,t.src_ZPCc=[t.src_Z,t.src_P,t.src_Cc].join("|"),t.src_ZCc=[t.src_Z,t.src_Cc].join("|");const n="[><|]";return t.src_pseudo_letter="(?:(?![><|]|"+t.src_ZPCc+")"+t.src_Any+")",t.src_ip4="(?:(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\.){3}(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)",t.src_auth="(?:(?:(?!"+t.src_ZCc+"|[@/\\[\\]()]).)+@)?",t.src_port="(?::(?:6(?:[0-4]\\d{3}|5(?:[0-4]\\d{2}|5(?:[0-2]\\d|3[0-5])))|[1-5]?\\d{1,4}))?",t.src_host_terminator="(?=$|[><|]|"+t.src_ZPCc+")(?!"+(e["---"]?"-(?!--)|":"-|")+"_|:\\d|\\.-|\\.(?!$|"+t.src_ZPCc+"))",t.src_path="(?:[/?#](?:(?!"+t.src_ZCc+"|"+n+"|[()[\\]{}.,\"'?!\\-;]).|\\[(?:(?!"+t.src_ZCc+"|\\]).)*\\]|\\((?:(?!"+t.src_ZCc+"|[)]).)*\\)|\\{(?:(?!"+t.src_ZCc+'|[}]).)*\\}|\\"(?:(?!'+t.src_ZCc+'|["]).)+\\"|\\\'(?:(?!'+t.src_ZCc+"|[']).)+\\'|\\'(?="+t.src_pseudo_letter+"|[-])|\\.{2,}[a-zA-Z0-9%/&]|\\.(?!"+t.src_ZCc+"|[.]|$)|"+(e["---"]?"\\-(?!--(?:[^-]|$))(?:-*)|":"\\-+|")+",(?!"+t.src_ZCc+"|$)|;(?!"+t.src_ZCc+"|$)|\\!+(?!"+t.src_ZCc+"|[!]|$)|\\?(?!"+t.src_ZCc+"|[?]|$))+|\\/)?",t.src_email_name='[\\-;:&=\\+\\$,\\.a-zA-Z0-9_][\\-;:&=\\+\\$,\\"\\.a-zA-Z0-9_]*',t.src_xn="xn--[a-z0-9\\-]{1,59}",t.src_domain_root="(?:"+t.src_xn+"|"+t.src_pseudo_letter+"{1,63})",t.src_domain="(?:"+t.src_xn+"|(?:"+t.src_pseudo_letter+")|(?:"+t.src_pseudo_letter+"(?:-|"+t.src_pseudo_letter+"){0,61}"+t.src_pseudo_letter+"))",t.src_host="(?:(?:(?:(?:"+t.src_domain+")\\.)*"+t.src_domain+"))",t.tpl_host_fuzzy="(?:"+t.src_ip4+"|(?:(?:(?:"+t.src_domain+")\\.)+(?:%TLDS%)))",t.tpl_host_no_ip_fuzzy="(?:(?:(?:"+t.src_domain+")\\.)+(?:%TLDS%))",t.src_host_strict=t.src_host+t.src_host_terminator,t.tpl_host_fuzzy_strict=t.tpl_host_fuzzy+t.src_host_terminator,t.src_host_port_strict=t.src_host+t.src_port+t.src_host_terminator,t.tpl_host_port_fuzzy_strict=t.tpl_host_fuzzy+t.src_port+t.src_host_terminator,t.tpl_host_port_no_ip_fuzzy_strict=t.tpl_host_no_ip_fuzzy+t.src_port+t.src_host_terminator,t.tpl_host_fuzzy_test="localhost|www\\.|\\.\\d{1,3}\\.|(?:\\.(?:%TLDS%)(?:"+t.src_ZPCc+"|>|$))",t.tpl_email_fuzzy='(^|[><|]|"|\\(|'+t.src_ZCc+")("+t.src_email_name+"@"+t.tpl_host_fuzzy_strict+")",t.tpl_link_fuzzy="(^|(?![.:/\\-_@])(?:[$+<=>^`||]|"+t.src_ZPCc+"))((?![$+<=>^`||])"+t.tpl_host_port_fuzzy_strict+t.src_path+")",t.tpl_link_no_ip_fuzzy="(^|(?![.:/\\-_@])(?:[$+<=>^`||]|"+t.src_ZPCc+"))((?![$+<=>^`||])"+t.tpl_host_port_no_ip_fuzzy_strict+t.src_path+")",t}(e.__opts__),n=e.__tlds__.slice();function r(e){return e.replace("%TLDS%",t.src_tlds)}e.onCompile(),e.__tlds_replaced__||n.push("a[cdefgilmnoqrstuwxz]|b[abdefghijmnorstvwyz]|c[acdfghiklmnoruvwxyz]|d[ejkmoz]|e[cegrstu]|f[ijkmor]|g[abdefghilmnpqrstuwy]|h[kmnrtu]|i[delmnoqrst]|j[emop]|k[eghimnprwyz]|l[abcikrstuvy]|m[acdeghklmnopqrstuvwxyz]|n[acefgilopruz]|om|p[aefghklmnrstwy]|qa|r[eosuw]|s[abcdeghijklmnortuvxyz]|t[cdfghjklmnortvwz]|u[agksyz]|v[aceginu]|w[fs]|y[et]|z[amw]"),n.push(t.src_xn),t.src_tlds=n.join("|"),t.email_fuzzy=RegExp(r(t.tpl_email_fuzzy),"i"),t.link_fuzzy=RegExp(r(t.tpl_link_fuzzy),"i"),t.link_no_ip_fuzzy=RegExp(r(t.tpl_link_no_ip_fuzzy),"i"),t.host_fuzzy_test=RegExp(r(t.tpl_host_fuzzy_test),"i");const i=[];function o(e,t){throw new Error('(LinkifyIt) Invalid schema "'+e+'": '+t)}e.__compiled__={},Object.keys(e.__schemas__).forEach(function(t){const n=e.__schemas__[t];if(null===n)return;const r={validate:null,link:null};if(e.__compiled__[t]=r,"[object Object]"===Gl(n))return!function(e){return"[object RegExp]"===Gl(e)}(n.validate)?Ql(n.validate)?r.validate=n.validate:o(t,n):r.validate=function(e){return function(t,n){const r=t.slice(n);return e.test(r)?r.match(e)[0].length:0}}(n.validate),void(Ql(n.normalize)?r.normalize=n.normalize:n.normalize?o(t,n):r.normalize=function(e,t){t.normalize(e)});!function(e){return"[object String]"===Gl(e)}(n)?o(t,n):i.push(t)}),i.forEach(function(t){e.__compiled__[e.__schemas__[t]]&&(e.__compiled__[t].validate=e.__compiled__[e.__schemas__[t]].validate,e.__compiled__[t].normalize=e.__compiled__[e.__schemas__[t]].normalize)}),e.__compiled__[""]={validate:null,normalize:function(e,t){t.normalize(e)}};const s=Object.keys(e.__compiled__).filter(function(t){return t.length>0&&e.__compiled__[t]}).map(Xl).join("|");e.re.schema_test=RegExp("(^|(?!_)(?:[><|]|"+t.src_ZPCc+"))("+s+")","i"),e.re.schema_search=RegExp("(^|(?!_)(?:[><|]|"+t.src_ZPCc+"))("+s+")","ig"),e.re.schema_at_start=RegExp("^"+e.re.schema_search.source,"i"),e.re.pretest=RegExp("("+e.re.schema_test.source+")|("+e.re.host_fuzzy_test.source+")|@","i"),function(e){e.__index__=-1,e.__text_cache__=""}(e)}function ra(e,t){const n=e.__index__,r=e.__last_index__,i=e.__text_cache__.slice(n,r);this.schema=e.__schema__.toLowerCase(),this.index=n+t,this.lastIndex=r+t,this.raw=i,this.text=i,this.url=i}function ia(e,t){const n=new ra(e,t);return e.__compiled__[n.schema].normalize(n,e),n}function oa(e,t){if(!(this instanceof oa))return new oa(e,t);var n;t||(n=e,Object.keys(n||{}).reduce(function(e,t){return e||Yl.hasOwnProperty(t)},!1)&&(t=e,e={})),this.__opts__=Zl({},Yl,t),this.__index__=-1,this.__last_index__=-1,this.__schema__="",this.__text_cache__="",this.__schemas__=Zl({},ea,e),this.__compiled__={},this.__tlds__=ta,this.__tlds_replaced__=!1,this.re={},na(this)}oa.prototype.add=function(e,t){return this.__schemas__[e]=t,na(this),this},oa.prototype.set=function(e){return this.__opts__=Zl(this.__opts__,e),this},oa.prototype.test=function(e){if(this.__text_cache__=e,this.__index__=-1,!e.length)return!1;let t,n,r,i,o,s,l,a,c;if(this.re.schema_test.test(e))for(l=this.re.schema_search,l.lastIndex=0;null!==(t=l.exec(e));)if(i=this.testSchemaAt(e,t[2],l.lastIndex),i){this.__schema__=t[2],this.__index__=t.index+t[1].length,this.__last_index__=t.index+t[0].length+i;break}return this.__opts__.fuzzyLink&&this.__compiled__["http:"]&&(a=e.search(this.re.host_fuzzy_test),a>=0&&(this.__index__<0||a<this.__index__)&&null!==(n=e.match(this.__opts__.fuzzyIP?this.re.link_fuzzy:this.re.link_no_ip_fuzzy))&&(o=n.index+n[1].length,(this.__index__<0||o<this.__index__)&&(this.__schema__="",this.__index__=o,this.__last_index__=n.index+n[0].length))),this.__opts__.fuzzyEmail&&this.__compiled__["mailto:"]&&(c=e.indexOf("@"),c>=0&&null!==(r=e.match(this.re.email_fuzzy))&&(o=r.index+r[1].length,s=r.index+r[0].length,(this.__index__<0||o<this.__index__||o===this.__index__&&s>this.__last_index__)&&(this.__schema__="mailto:",this.__index__=o,this.__last_index__=s))),this.__index__>=0},oa.prototype.pretest=function(e){return this.re.pretest.test(e)},oa.prototype.testSchemaAt=function(e,t,n){return this.__compiled__[t.toLowerCase()]?this.__compiled__[t.toLowerCase()].validate(e,n,this):0},oa.prototype.match=function(e){const t=[];let n=0;this.__index__>=0&&this.__text_cache__===e&&(t.push(ia(this,n)),n=this.__last_index__);let r=n?e.slice(n):e;for(;this.test(r);)t.push(ia(this,n)),r=r.slice(this.__last_index__),n+=this.__last_index__;return t.length?t:null},oa.prototype.matchAtStart=function(e){if(this.__text_cache__=e,this.__index__=-1,!e.length)return null;const t=this.re.schema_at_start.exec(e);if(!t)return null;const n=this.testSchemaAt(e,t[2],t[0].length);return n?(this.__schema__=t[2],this.__index__=t.index+t[1].length,this.__last_index__=t.index+t[0].length+n,ia(this,0)):null},oa.prototype.tlds=function(e,t){return e=Array.isArray(e)?e:[e],t?(this.__tlds__=this.__tlds__.concat(e).sort().filter(function(e,t,n){return e!==n[t-1]}).reverse(),na(this),this):(this.__tlds__=e.slice(),this.__tlds_replaced__=!0,na(this),this)},oa.prototype.normalize=function(e){e.schema||(e.url="http://"+e.url),"mailto:"!==e.schema||/^mailto:/i.test(e.url)||(e.url="mailto:"+e.url)},oa.prototype.onCompile=function(){};const sa=2147483647,la=36,aa=/^xn--/,ca=/[^\0-\x7F]/,ha=/[\x2E\u3002\uFF0E\uFF61]/g,ua={overflow:"Overflow: input needs wider integers to process","not-basic":"Illegal input >= 0x80 (not a basic code point)","invalid-input":"Invalid input"},pa=Math.floor,da=String.fromCharCode;function fa(e){throw new RangeError(ua[e])}function ma(e,t){const n=e.split("@");let r="";n.length>1&&(r=n[0]+"@",e=n[1]);const i=function(e,t){const n=[];let r=e.length;for(;r--;)n[r]=t(e[r]);return n}((e=e.replace(ha,".")).split("."),t).join(".");return r+i}function ga(e){const t=[];let n=0;const r=e.length;for(;n<r;){const i=e.charCodeAt(n++);if(i>=55296&&i<=56319&&n<r){const r=e.charCodeAt(n++);56320==(64512&r)?t.push(((1023&i)<<10)+(1023&r)+65536):(t.push(i),n--)}else t.push(i)}return t}const ya=function(e){return e>=48&&e<58?e-48+26:e>=65&&e<91?e-65:e>=97&&e<123?e-97:la},ka=function(e,t){return e+22+75*(e<26)-((0!=t)<<5)},ba=function(e,t,n){let r=0;for(e=n?pa(e/700):e>>1,e+=pa(e/t);e>455;r+=la)e=pa(e/35);return pa(r+36*e/(e+38))},wa=function(e){const t=[],n=e.length;let r=0,i=128,o=72,s=e.lastIndexOf("-");s<0&&(s=0);for(let n=0;n<s;++n)e.charCodeAt(n)>=128&&fa("not-basic"),t.push(e.charCodeAt(n));for(let l=s>0?s+1:0;l<n;){const s=r;for(let t=1,i=la;;i+=la){l>=n&&fa("invalid-input");const s=ya(e.charCodeAt(l++));s>=la&&fa("invalid-input"),s>pa((sa-r)/t)&&fa("overflow"),r+=s*t;const a=i<=o?1:i>=o+26?26:i-o;if(s<a)break;const c=la-a;t>pa(sa/c)&&fa("overflow"),t*=c}const a=t.length+1;o=ba(r-s,a,0==s),pa(r/a)>sa-i&&fa("overflow"),i+=pa(r/a),r%=a,t.splice(r++,0,i)}return String.fromCodePoint(...t)},Ca=function(e){const t=[],n=(e=ga(e)).length;let r=128,i=0,o=72;for(const n of e)n<128&&t.push(da(n));const s=t.length;let l=s;for(s&&t.push("-");l<n;){let n=sa;for(const t of e)t>=r&&t<n&&(n=t);const a=l+1;n-r>pa((sa-i)/a)&&fa("overflow"),i+=(n-r)*a,r=n;for(const n of e)if(n<r&&++i>sa&&fa("overflow"),n===r){let e=i;for(let n=la;;n+=la){const r=n<=o?1:n>=o+26?26:n-o;if(e<r)break;const i=e-r,s=la-r;t.push(da(ka(r+i%s,0))),e=pa(i/s)}t.push(da(ka(e,0))),o=ba(i,a,l===s),i=0,++l}++i,++r}return t.join("")},xa=function(e){return ma(e,function(e){return ca.test(e)?"xn--"+Ca(e):e})},Da=function(e){return ma(e,function(e){return aa.test(e)?wa(e.slice(4).toLowerCase()):e})};const va={default:{options:{html:!1,xhtmlOut:!1,breaks:!1,langPrefix:"language-",linkify:!1,typographer:!1,quotes:"“”‘’",highlight:null,maxNesting:100},components:{core:{},block:{},inline:{}}},zero:{options:{html:!1,xhtmlOut:!1,breaks:!1,langPrefix:"language-",linkify:!1,typographer:!1,quotes:"“”‘’",highlight:null,maxNesting:20},components:{core:{rules:["normalize","block","inline","text_join"]},block:{rules:["paragraph"]},inline:{rules:["text"],rules2:["balance_pairs","fragments_join"]}}},commonmark:{options:{html:!0,xhtmlOut:!0,breaks:!1,langPrefix:"language-",linkify:!1,typographer:!1,quotes:"“”‘’",highlight:null,maxNesting:20},components:{core:{rules:["normalize","block","inline","text_join"]},block:{rules:["blockquote","code","fence","heading","hr","html_block","lheading","list","reference","paragraph"]},inline:{rules:["autolink","backticks","emphasis","entity","escape","html_inline","image","link","newline","text"],rules2:["balance_pairs","emphasis","fragments_join"]}}}},_a=/^(vbscript|javascript|file|data):/,Sa=/^data:image\/(gif|png|jpeg|webp);/;function Ea(e){const t=e.trim().toLowerCase();return!_a.test(t)||Sa.test(t)}const Aa=["http:","https:","mailto:"];function Ma(e){const t=rs(e,!0);if(t.hostname&&(!t.protocol||Aa.indexOf(t.protocol)>=0))try{t.hostname=xa(t.hostname)}catch(e){}return jo(Jo(t))}function Oa(e){const t=rs(e,!0);if(t.hostname&&(!t.protocol||Aa.indexOf(t.protocol)>=0))try{t.hostname=Da(t.hostname)}catch(e){}return $o(Jo(t),$o.defaultChars+"%")}function Na(e,t){if(!(this instanceof Na))return new Na(e,t);t||Ms(e)||(t=e||{},e="default"),this.inline=new Kl,this.block=new Fl,this.core=new wl,this.renderer=new el,this.linkify=new oa,this.validateLink=Ea,this.normalizeLink=Ma,this.normalizeLinkText=Oa,this.utils=Qs,this.helpers=Ns({},Xs),this.options={},this.configure(e),t&&this.set(t)}Na.prototype.set=function(e){return Ns(this.options,e),this},Na.prototype.configure=function(e){const t=this;if(Ms(e)){const t=e;if(!(e=va[t]))throw new Error('Wrong `markdown-it` preset "'+t+'", check name')}if(!e)throw new Error("Wrong `markdown-it` preset, can't be empty");return e.options&&t.set(e.options),e.components&&Object.keys(e.components).forEach(function(n){e.components[n].rules&&t[n].ruler.enableOnly(e.components[n].rules),e.components[n].rules2&&t[n].ruler2.enableOnly(e.components[n].rules2)}),this},Na.prototype.enable=function(e,t){let n=[];Array.isArray(e)||(e=[e]),["core","block","inline"].forEach(function(t){n=n.concat(this[t].ruler.enable(e,!0))},this),n=n.concat(this.inline.ruler2.enable(e,!0));const r=e.filter(function(e){return n.indexOf(e)<0});if(r.length&&!t)throw new Error("MarkdownIt. Failed to enable unknown rule(s): "+r);return this},Na.prototype.disable=function(e,t){let n=[];Array.isArray(e)||(e=[e]),["core","block","inline"].forEach(function(t){n=n.concat(this[t].ruler.disable(e,!0))},this),n=n.concat(this.inline.ruler2.disable(e,!0));const r=e.filter(function(e){return n.indexOf(e)<0});if(r.length&&!t)throw new Error("MarkdownIt. Failed to disable unknown rule(s): "+r);return this},Na.prototype.use=function(e){const t=[this].concat(Array.prototype.slice.call(arguments,1));return e.apply(e,t),this},Na.prototype.parse=function(e,t){if("string"!=typeof e)throw new Error("Input data should be a String");const n=new this.core.State(e,this,t);return this.core.process(n),n.tokens},Na.prototype.render=function(e,t){return t=t||{},this.renderer.render(this.parse(e,t),this.options,t)},Na.prototype.parseInline=function(e,t){const n=new this.core.State(e,this,t);return n.inlineMode=!0,this.core.process(n),n.tokens},Na.prototype.renderInline=function(e,t){return t=t||{},this.renderer.render(this.parseInline(e,t),this.options,t)};const Fa=new H({nodes:{doc:{content:"block+"},paragraph:{content:"inline*",group:"block",parseDOM:[{tag:"p"}],toDOM:()=>["p",0]},blockquote:{content:"block+",group:"block",parseDOM:[{tag:"blockquote"}],toDOM:()=>["blockquote",0]},horizontal_rule:{group:"block",parseDOM:[{tag:"hr"}],toDOM:()=>["div",["hr"]]},heading:{attrs:{level:{default:1}},content:"(text | image)*",group:"block",defining:!0,parseDOM:[{tag:"h1",attrs:{level:1}},{tag:"h2",attrs:{level:2}},{tag:"h3",attrs:{level:3}},{tag:"h4",attrs:{level:4}},{tag:"h5",attrs:{level:5}},{tag:"h6",attrs:{level:6}}],toDOM:e=>["h"+e.attrs.level,0]},code_block:{content:"text*",group:"block",code:!0,defining:!0,marks:"",attrs:{params:{default:""}},parseDOM:[{tag:"pre",preserveWhitespace:"full",getAttrs:e=>({params:e.getAttribute("data-params")||""})}],toDOM:e=>["pre",e.attrs.params?{"data-params":e.attrs.params}:{},["code",0]]},ordered_list:{content:"list_item+",group:"block",attrs:{order:{default:1},tight:{default:!1}},parseDOM:[{tag:"ol",getAttrs:e=>({order:e.hasAttribute("start")?+e.getAttribute("start"):1,tight:e.hasAttribute("data-tight")})}],toDOM:e=>["ol",{start:1==e.attrs.order?null:e.attrs.order,"data-tight":e.attrs.tight?"true":null},0]},bullet_list:{content:"list_item+",group:"block",attrs:{tight:{default:!1}},parseDOM:[{tag:"ul",getAttrs:e=>({tight:e.hasAttribute("data-tight")})}],toDOM:e=>["ul",{"data-tight":e.attrs.tight?"true":null},0]},list_item:{content:"block+",defining:!0,parseDOM:[{tag:"li"}],toDOM:()=>["li",0]},text:{group:"inline"},image:{inline:!0,attrs:{src:{},alt:{default:null},title:{default:null}},group:"inline",draggable:!0,parseDOM:[{tag:"img[src]",getAttrs:e=>({src:e.getAttribute("src"),title:e.getAttribute("title"),alt:e.getAttribute("alt")})}],toDOM:e=>["img",e.attrs]},hard_break:{inline:!0,group:"inline",selectable:!1,parseDOM:[{tag:"br"}],toDOM:()=>["br"]}},marks:{em:{parseDOM:[{tag:"i"},{tag:"em"},{style:"font-style=italic"},{style:"font-style=normal",clearMark:e=>"em"==e.type.name}],toDOM:()=>["em"]},strong:{parseDOM:[{tag:"strong"},{tag:"b",getAttrs:e=>"normal"!=e.style.fontWeight&&null},{style:"font-weight=400",clearMark:e=>"strong"==e.type.name},{style:"font-weight",getAttrs:e=>/^(bold(er)?|[5-9]\d{2,})$/.test(e)&&null}],toDOM:()=>["strong"]},link:{attrs:{href:{},title:{default:null}},inclusive:!1,parseDOM:[{tag:"a[href]",getAttrs:e=>({href:e.getAttribute("href"),title:e.getAttribute("title")})}],toDOM:e=>["a",e.attrs]},code:{code:!0,parseDOM:[{tag:"code"}],toDOM:()=>["code"]}}});class Ta{constructor(e,t){this.schema=e,this.tokenHandlers=t,this.stack=[{type:e.topNodeType,attrs:null,content:[],marks:l.none}]}top(){return this.stack[this.stack.length-1]}push(e){this.stack.length&&this.top().content.push(e)}addText(e){if(!e)return;let t,n=this.top(),r=n.content,i=r[r.length-1],o=this.schema.text(e,n.marks);i&&(t=function(e,t){if(e.isText&&t.isText&&l.sameSet(e.marks,t.marks))return e.withText(e.text+t.text)}(i,o))?r[r.length-1]=t:r.push(o)}openMark(e){let t=this.top();t.marks=e.addToSet(t.marks)}closeMark(e){let t=this.top();t.marks=e.removeFromSet(t.marks)}parseTokens(e){for(let t=0;t<e.length;t++){let n=e[t],r=this.tokenHandlers[n.type];if(!r)throw new Error("Token type `"+n.type+"` not supported by Markdown parser");r(this,n,e,t)}}addNode(e,t,n){let r=this.top(),i=e.createAndFill(t,n,r?r.marks:[]);return i?(this.push(i),i):null}openNode(e,t){this.stack.push({type:e,attrs:t,content:[],marks:l.none})}closeNode(){let e=this.stack.pop();return this.addNode(e.type,e.attrs,e.content)}}function Ra(e,t,n,r){return e.getAttrs?e.getAttrs(t,n,r):e.attrs instanceof Function?e.attrs(t):e.attrs}function za(e,t){return e.noCloseToken||"code_inline"==t||"code_block"==t||"fence"==t}function Ia(e){return"\n"==e[e.length-1]?e.slice(0,e.length-1):e}function Ba(){}function Pa(e,t){for(;++t<e.length;)if("list_item_open"!=e[t].type)return e[t].hidden;return!1}const La=new class{constructor(e,t,n){this.schema=e,this.tokenizer=t,this.tokens=n,this.tokenHandlers=function(e,t){let n=Object.create(null);for(let r in t){let i=t[r];if(i.block){let t=e.nodeType(i.block);za(i,r)?n[r]=(e,n,r,o)=>{e.openNode(t,Ra(i,n,r,o)),e.addText(Ia(n.content)),e.closeNode()}:(n[r+"_open"]=(e,n,r,o)=>e.openNode(t,Ra(i,n,r,o)),n[r+"_close"]=e=>e.closeNode())}else if(i.node){let t=e.nodeType(i.node);n[r]=(e,n,r,o)=>e.addNode(t,Ra(i,n,r,o))}else if(i.mark){let t=e.marks[i.mark];za(i,r)?n[r]=(e,n,r,o)=>{e.openMark(t.create(Ra(i,n,r,o))),e.addText(Ia(n.content)),e.closeMark(t)}:(n[r+"_open"]=(e,n,r,o)=>e.openMark(t.create(Ra(i,n,r,o))),n[r+"_close"]=e=>e.closeMark(t))}else{if(!i.ignore)throw new RangeError("Unrecognized parsing spec "+JSON.stringify(i));za(i,r)?n[r]=Ba:(n[r+"_open"]=Ba,n[r+"_close"]=Ba)}}return n.text=(e,t)=>e.addText(t.content),n.inline=(e,t)=>e.parseTokens(t.children),n.softbreak=n.softbreak||(e=>e.addText(" ")),n}(e,n)}parse(e,t={}){let n,r=new Ta(this.schema,this.tokenHandlers);r.parseTokens(this.tokenizer.parse(e,t));do{n=r.closeNode()}while(r.stack.length);return n||this.schema.topNodeType.createAndFill()}}(Fa,Na("commonmark",{html:!1}),{blockquote:{block:"blockquote"},paragraph:{block:"paragraph"},list_item:{block:"list_item"},bullet_list:{block:"bullet_list",getAttrs:(e,t,n)=>({tight:Pa(t,n)})},ordered_list:{block:"ordered_list",getAttrs:(e,t,n)=>({order:+e.attrGet("start")||1,tight:Pa(t,n)})},heading:{block:"heading",getAttrs:e=>({level:+e.tag.slice(1)})},code_block:{block:"code_block",noCloseToken:!0},fence:{block:"code_block",getAttrs:e=>({params:e.info||""}),noCloseToken:!0},hr:{node:"horizontal_rule"},image:{node:"image",getAttrs:e=>({src:e.attrGet("src"),title:e.attrGet("title")||null,alt:e.children[0]&&e.children[0].content||null})},hardbreak:{node:"hard_break"},em:{mark:"em"},strong:{mark:"strong"},link:{mark:"link",getAttrs:e=>({href:e.attrGet("href"),title:e.attrGet("title")||null})},code_inline:{mark:"code",noCloseToken:!0}});function qa(e,{allowSpaces:t=!1}){return n=>{const r=new RegExp(`\\s${e}$`),i=t?new RegExp(`${e}.*?(?=\\s${e}|$)`,"g"):new RegExp(`(?:^)?${e}[^\\s${e}]*`,"g"),o=n.before(),s=n.end(),l=n.doc.textBetween(o,s,"\0","\0");let a;for(;a=i.exec(l);){const e=a.input.slice(Math.max(0,a.index-1),a.index);if(!/^[\s\0]?$/.test(e))continue;const i=a.index+n.start();let o=i+a[0].length;if(t&&r.test(l.slice(o-1,o+1))&&(a[0]+=" ",o++),i<n.pos&&o>=n.pos)return{range:{from:i,to:o},text:a[0]}}}}function $a({matcher:e=qa("#"),suggestionClass:t="ProseMirror-suggestion",onEnter:n=()=>!1,onChange:r=()=>!1,onExit:i=()=>!1,onKeyDown:o=()=>!1,debug:s=!1}){return new mt({key:new kt("suggestions"),view(){return{update:(e,t)=>{const o=this.key.getState(t),s=this.key.getState(e.state),l=o.active&&s.active&&o.range.from!==s.range.from,a=!o.active&&s.active,c=o.active&&!s.active,h=!a&&!c&&o.text!==s.text;(c||l)&&i({view:e,range:o.range,text:o.text}),h&&!l&&r({view:e,range:s.range,text:s.text}),(a||l)&&n({view:e,range:s.range,text:s.text})}}},state:{init:()=>({active:!1,range:{},text:null}),apply(t,n){const{selection:r}=t,i={...n};if(r.from===r.to){(r.from<n.range.from||r.from>n.range.to)&&(i.active=!1);const t=r.$from,o=e(t);o?(i.active=!0,i.range=o.range,i.text=o.text):i.active=!1}else i.active=!1;return i.active||(i.range={},i.text=null),i}},props:{handleKeyDown(e,t){const{active:n}=this.getState(e.state);return!!n&&o({view:e,event:t})},decorations(e){const{active:n,range:r}=this.getState(e);return n?ao.create(e.doc,[oo.inline(r.from,r.to,{nodeName:"span",class:t,style:s?"background: rgba(0, 0, 255, 0.05); color: blue; border: 2px solid blue;":null})]):null}}})}export{K as DOMParser,ie as DOMSerializer,oo as Decoration,ao as DecorationSet,dt as EditorState,Ro as EditorView,mt as Plugin,kt as PluginKey,H as Schema,et as TextSelection,zt as baseKeymap,xn as basicSchema,La as defaultMarkdownParser,Qt as history,pn as keymap,en as redo,$a as suggestionsPlugin,qa as triggerCharacter,Yt as undo};
diff --git a/third_party/js/prosemirror/rollup.config.mjs b/third_party/js/prosemirror/rollup.config.mjs
@@ -0,0 +1,44 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+import terser from "@rollup/plugin-terser";
+import nodeResolve from "@rollup/plugin-node-resolve";
+import commonjs from "@rollup/plugin-commonjs";
+import json from "@rollup/plugin-json";
+
+export default function (commandLineArgs = {}) {
+ const plugins = [
+ nodeResolve({
+ browser: true,
+ dedupe: [
+ "prosemirror-commands",
+ "prosemirror-history",
+ "prosemirror-keymap",
+ "prosemirror-markdown",
+ "prosemirror-model",
+ "prosemirror-schema-basic",
+ "prosemirror-state",
+ "prosemirror-transform",
+ "prosemirror-view",
+ ],
+ }),
+ commonjs(),
+ json(),
+ ];
+
+ if (commandLineArgs.configMinify) {
+ plugins.push(terser());
+ }
+
+ return {
+ input: "bundle_entry.mjs",
+ output: {
+ file: "prosemirror.bundle.mjs",
+ format: "esm",
+ sourcemap: false,
+ },
+ plugins,
+ treeshake: true,
+ };
+}
diff --git a/toolkit/content/license.html b/toolkit/content/license.html
@@ -129,6 +129,7 @@
#endif
<li><a href="about:license#praton">praton License</a></li>
<li><a href="about:license#praton1">praton and inet_ntop License</a></li>
+ <li><a href="about:license#prosemirror">ProseMirror License</a></li>
<li><a href="about:license#qcms">qcms License</a></li>
<li><a href="about:license#qrcode-generator">QR Code Generator License</a></li>
<li><a href="about:license#react">React License</a></li>
@@ -4738,6 +4739,79 @@ product.
<tr>
<td>
+ <h1><a id="prosemirror"></a>ProseMirror Licenses</h1>
+ </td>
+ <td>
+ <p>This entry applies to all files in <code>third_party/js/prosemirror/</code> and its subdirectories. Most ProseMirror packages are licensed under the MIT license, except where noted below.</p>
+ <ul>
+ <li>
+ MIT License:
+ <ul>
+ <li><code>third_party/js/prosemirror/prosemirror-commands</code></li>
+ <li><code>third_party/js/prosemirror/prosemirror-history</code></li>
+ <li><code>third_party/js/prosemirror/prosemirror-keymap</code></li>
+ <li><code>third_party/js/prosemirror/prosemirror-model</code></li>
+ <li><code>third_party/js/prosemirror/prosemirror-schema-basic</code></li>
+ <li><code>third_party/js/prosemirror/prosemirror-state</code></li>
+ <li><code>third_party/js/prosemirror/prosemirror-transform</code></li>
+ <li><code>third_party/js/prosemirror/prosemirror-view</code></li>
+ </ul>
+ </li>
+ <li>
+ Apache License 2.0:
+ <ul>
+ <li><code>third_party/js/prosemirror/prosemirror-suggestions</code></li>
+ </ul>
+ </li>
+ </ul>
+ </td>
+ <td>
+ <pre>
+ MIT License (applies to core and non-core ProseMirror packages)
+
+ Copyright (C) 2015-2017 by Marijn Haverbeke <marijn@haverbeke.berlin> and others
+
+ Permission is hereby granted, free of charge, to any person obtaining a copy
+ of this software and associated documentation files (the "Software"), to deal
+ in the Software without restriction, including without limitation the rights
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ copies of the Software, and to permit persons to whom the Software is
+ furnished to do so, subject to the following conditions:
+
+ The above copyright notice and this permission notice shall be included in
+ all copies or substantial portions of the Software.
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ THE SOFTWARE.
+
+ ---
+
+ Apache License 2.0 (applies to third-party package <code>third_party/js/prosemirror/prosemirror-suggestions</code>):
+
+ Copyright 2017 Quartzy, Inc.
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ </pre>
+ </td>
+ </tr>
+
+ <tr>
+ <td>
<h1><a id="qrcode-generator"></a>QR Code Generator License</h1>
</td>
<td>