@aicayzer/inkkit 0.1.5 → 0.1.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -22734,7 +22734,7 @@ var SerializerState = class extends Stack {
22734
22734
  #closeMark;
22735
22735
  };
22736
22736
  //#endregion
22737
- //#region node_modules/.pnpm/prosemirror-view@1.42.6_patch_hash=a3414754d6cb6cd32517617a4affee5f7895ca0b3b976186951d43106adf18b3/node_modules/prosemirror-view/dist/index.js
22737
+ //#region node_modules/.pnpm/prosemirror-view@1.42.6_patch_hash=808819d35255b79fc131ebc4873ab2fbcd47cd8f5bc05cd11e8caab02ecee548/node_modules/prosemirror-view/dist/index.js
22738
22738
  var domIndex = function(node) {
22739
22739
  for (var index = 0;; index++) {
22740
22740
  node = node.previousSibling;
@@ -25232,6 +25232,17 @@ editHandlers.keydown = (view, _event) => {
25232
25232
  view.input.lastKeyCodeTime = Date.now();
25233
25233
  if (android && chrome && event.keyCode == 13) return;
25234
25234
  if (event.keyCode != 229) view.domObserver.forceFlush();
25235
+ if (event.keyCode != 229 && ![
25236
+ 16,
25237
+ 17,
25238
+ 18,
25239
+ 91,
25240
+ 92,
25241
+ 93
25242
+ ].includes(event.keyCode)) {
25243
+ view.domObserver.flush();
25244
+ view.input.lastFocus = 0;
25245
+ }
25235
25246
  if (ios && event.keyCode == 13 && !event.ctrlKey && !event.altKey && !event.metaKey) {
25236
25247
  let now = Date.now();
25237
25248
  view.input.lastIOSEnter = now;
@@ -28391,8 +28402,8 @@ function editorLabels(ctx) {
28391
28402
  return ctx.isInjected(labelsCtx.key) ? ctx.get(labelsCtx.key) : defaultLabels;
28392
28403
  }
28393
28404
  //#endregion
28394
- //#region node_modules/.pnpm/dompurify@3.4.16/node_modules/dompurify/dist/purify.es.mjs
28395
- /*! @license DOMPurify 3.4.16 | (c) Cure53 and other contributors | Released under the Apache license 2.0 and Mozilla Public License 2.0 | github.com/cure53/DOMPurify/blob/3.4.16/LICENSE */
28405
+ //#region node_modules/.pnpm/dompurify@3.4.17/node_modules/dompurify/dist/purify.es.mjs
28406
+ /*! @license DOMPurify 3.4.17 | (c) Cure53 and other contributors | Released under the Apache license 2.0 and Mozilla Public License 2.0 | github.com/cure53/DOMPurify/blob/3.4.17/LICENSE */
28396
28407
  function _OverloadYield(e, d) {
28397
28408
  this.v = e, this.k = d;
28398
28409
  }
@@ -28516,9 +28527,12 @@ Array.prototype.indexOf;
28516
28527
  var arrayLastIndexOf = unapply(Array.prototype.lastIndexOf);
28517
28528
  var arrayPop = unapply(Array.prototype.pop);
28518
28529
  var arrayPush = unapply(Array.prototype.push);
28519
- Array.prototype.slice;
28530
+ var arraySlice = unapply(Array.prototype.slice);
28520
28531
  var arraySplice = unapply(Array.prototype.splice);
28521
28532
  var arrayIsArray = Array.isArray;
28533
+ var setCreate = unconstruct(Set);
28534
+ var setAdd = unapply(Set.prototype.add);
28535
+ var setHas = unapply(Set.prototype.has);
28522
28536
  var stringToLowerCase = unapply(String.prototype.toLowerCase);
28523
28537
  var stringToString = unapply(String.prototype.toString);
28524
28538
  var stringMatch = unapply(String.prototype.match);
@@ -28663,6 +28677,25 @@ function lookupGetter(object, prop) {
28663
28677
  }
28664
28678
  return fallbackValue;
28665
28679
  }
28680
+ /**
28681
+ * Like lookupGetter, but for accessor properties only, and returns null
28682
+ * instead of a null-returning fallback when no getter exists on the chain.
28683
+ * Lets a caller fall back to a direct property read in environments that
28684
+ * lack the getter, where a fallback returning null would be indistinguishable
28685
+ * from a legitimate null value (e.g. namespaceURI of a no-namespace element).
28686
+ *
28687
+ * @param object - The object (usually a prototype) to start the lookup at.
28688
+ * @param prop - The accessor property name.
28689
+ * @returns The unapplied getter, or null if none was found.
28690
+ */
28691
+ function lookupAccessor(object, prop) {
28692
+ while (object !== null) {
28693
+ const desc = getOwnPropertyDescriptor(object, prop);
28694
+ if (desc) return desc.get ? unapply(desc.get) : null;
28695
+ object = getPrototypeOf(object);
28696
+ }
28697
+ return null;
28698
+ }
28666
28699
  function isRegex(value) {
28667
28700
  try {
28668
28701
  regExpTest(value, "");
@@ -29347,7 +29380,7 @@ var NODE_TYPE = {
29347
29380
  documentFragment: 11,
29348
29381
  notation: 12
29349
29382
  };
29350
- var LITERAL_TEXT_ELEMENT_NAMES = [
29383
+ var LITERAL_TEXT_ELEMENTS = freeze(addToSet({}, [
29351
29384
  "style",
29352
29385
  "script",
29353
29386
  "xmp",
@@ -29356,15 +29389,7 @@ var LITERAL_TEXT_ELEMENT_NAMES = [
29356
29389
  "noframes",
29357
29390
  "plaintext",
29358
29391
  "noscript"
29359
- ];
29360
- var LITERAL_TEXT_ELEMENTS = freeze(addToSet({}, LITERAL_TEXT_ELEMENT_NAMES));
29361
- var LITERAL_TEXT_CLOSE = function() {
29362
- const map = {};
29363
- arrayForEach(LITERAL_TEXT_ELEMENT_NAMES, (name) => {
29364
- map[name] = seal(new RegExp("</" + name + "(?=[\\t\\n\\f\\r />])", "i"));
29365
- });
29366
- return freeze(map);
29367
- }();
29392
+ ]));
29368
29393
  var getGlobal = function getGlobal() {
29369
29394
  return typeof window === "undefined" ? null : window;
29370
29395
  };
@@ -29442,7 +29467,7 @@ var _resolveObjectOption = function _resolveObjectOption(cfg, key, makeFallback)
29442
29467
  function createDOMPurify() {
29443
29468
  let window = arguments.length > 0 && arguments[0] !== void 0 ? arguments[0] : getGlobal();
29444
29469
  const DOMPurify = (root) => createDOMPurify(root);
29445
- DOMPurify.version = "3.4.16";
29470
+ DOMPurify.version = "3.4.17";
29446
29471
  DOMPurify.removed = [];
29447
29472
  if (!window || !window.document || window.document.nodeType !== NODE_TYPE.document || !window.Element) {
29448
29473
  DOMPurify.isSupported = false;
@@ -29474,6 +29499,10 @@ function createDOMPurify() {
29474
29499
  const _readNodeName = function _readNodeName(node) {
29475
29500
  return getNodeName ? getNodeName(node) : node.nodeName;
29476
29501
  };
29502
+ const getNamespaceURI = lookupAccessor(ElementPrototype, "namespaceURI");
29503
+ const _readNamespaceURI = function _readNamespaceURI(element) {
29504
+ return getNamespaceURI ? getNamespaceURI(element) : element.namespaceURI;
29505
+ };
29477
29506
  if (typeof HTMLTemplateElement === "function") {
29478
29507
  const template = document.createElement("template");
29479
29508
  if (template.content && template.content.ownerDocument) document = template.content.ownerDocument;
@@ -29594,6 +29623,7 @@ function createDOMPurify() {
29594
29623
  const SANITIZE_NAMED_PROPS_PREFIX = "user-content-";
29595
29624
  let KEEP_CONTENT = true;
29596
29625
  let IN_PLACE = false;
29626
+ let IN_PLACE_VISITED = null;
29597
29627
  let USE_PROFILES = {};
29598
29628
  let FORBID_CONTENTS = null;
29599
29629
  const DEFAULT_FORBID_CONTENTS = addToSet({}, [
@@ -29867,18 +29897,23 @@ function createDOMPurify() {
29867
29897
  * return. Return true otherwise.
29868
29898
  */
29869
29899
  const _checkValidNamespace = function _checkValidNamespace(element) {
29870
- let parent = getParentNode(element);
29871
- if (!parent || !parent.tagName) parent = {
29872
- namespaceURI: NAMESPACE,
29873
- tagName: "template"
29874
- };
29875
- const tagName = stringToLowerCase(element.tagName);
29876
- const parentTagName = stringToLowerCase(parent.tagName);
29877
- if (!ALLOWED_NAMESPACES[element.namespaceURI]) return false;
29878
- if (element.namespaceURI === SVG_NAMESPACE) return _checkSvgNamespace(tagName, parent, parentTagName);
29879
- if (element.namespaceURI === MATHML_NAMESPACE) return _checkMathMlNamespace(tagName, parent, parentTagName);
29880
- if (element.namespaceURI === HTML_NAMESPACE) return _checkHtmlNamespace(tagName, parent, parentTagName);
29881
- if (PARSER_MEDIA_TYPE === "application/xhtml+xml" && ALLOWED_NAMESPACES[element.namespaceURI]) return true;
29900
+ const elementNamespace = _readNamespaceURI(element);
29901
+ const tagName = stringToLowerCase(_readNodeName(element));
29902
+ const parentNode = getParentNode(element);
29903
+ let parent;
29904
+ let parentTagName;
29905
+ if (parentNode && _readNodeType(parentNode) === NODE_TYPE.element) {
29906
+ parent = { namespaceURI: _readNamespaceURI(parentNode) };
29907
+ parentTagName = stringToLowerCase(_readNodeName(parentNode));
29908
+ } else {
29909
+ parent = { namespaceURI: NAMESPACE };
29910
+ parentTagName = "template";
29911
+ }
29912
+ if (!ALLOWED_NAMESPACES[elementNamespace]) return false;
29913
+ if (elementNamespace === SVG_NAMESPACE) return _checkSvgNamespace(tagName, parent, parentTagName);
29914
+ if (elementNamespace === MATHML_NAMESPACE) return _checkMathMlNamespace(tagName, parent, parentTagName);
29915
+ if (elementNamespace === HTML_NAMESPACE) return _checkHtmlNamespace(tagName, parent, parentTagName);
29916
+ if (PARSER_MEDIA_TYPE === "application/xhtml+xml" && ALLOWED_NAMESPACES[elementNamespace]) return true;
29882
29917
  return false;
29883
29918
  };
29884
29919
  /**
@@ -29922,36 +29957,21 @@ function createDOMPurify() {
29922
29957
  }
29923
29958
  };
29924
29959
  /**
29925
- * _neutralizeRoot
29926
- *
29927
- * Fail-closed teardown of an in-place root after the sanitize walk aborts
29928
- * (campaign-3 F2). An internal throw mid-walk — e.g. a page-registered
29929
- * custom element's reaction detaches a node so `_forceRemove`'s deliberate
29930
- * parentless guard throws, or any other re-entrant engine mutation — would
29931
- * otherwise leave the caller's *live* tree half-sanitized, with everything
29932
- * after the abort point still carrying its handlers. There is no safe way
29933
- * to resume the walk (the tree mutated under us), so we strip the root bare:
29934
- * remove every child and every attribute, then let the caller's catch see
29935
- * the original error. Clobber-safe (cached `remove`/`childNodes`/`attributes`
29936
- * getters; the root was already clobber-pre-flighted at the IN_PLACE entry).
29960
+ * Fail-closed teardown of an in-place root: strip every disallowed attribute
29961
+ * in the subtree first, so nothing armed is left behind, then remove all
29962
+ * children and the root's own attributes.
29937
29963
  *
29938
29964
  * @param root the in-place root to empty
29939
29965
  */
29940
29966
  const _neutralizeRoot = function _neutralizeRoot(root) {
29941
29967
  _neutralizeSubtree(root);
29942
29968
  const childNodes = getChildNodes(root);
29943
- if (childNodes) {
29944
- const snapshot = [];
29945
- arrayForEach(childNodes, (child) => {
29946
- arrayPush(snapshot, child);
29947
- });
29948
- arrayForEach(snapshot, (child) => {
29949
- try {
29950
- remove(child);
29951
- } catch (_) {}
29952
- });
29953
- }
29954
- const attributes = getAttributes(root);
29969
+ if (childNodes) arrayForEach(arraySlice(childNodes), (child) => {
29970
+ try {
29971
+ remove(child);
29972
+ } catch (_) {}
29973
+ });
29974
+ const attributes = _readNodeType(root) === NODE_TYPE.element ? getAttributes(root) : null;
29955
29975
  if (attributes) for (let i = attributes.length - 1; i >= 0; --i) {
29956
29976
  const attribute = attributes[i];
29957
29977
  const name = attribute && attribute.name;
@@ -29959,18 +29979,9 @@ function createDOMPurify() {
29959
29979
  }
29960
29980
  };
29961
29981
  /**
29962
- * _removeAttribute
29963
- *
29964
- * Name-based getAttributeNode()/removeAttribute() ASCII-lowercase their
29965
- * lookup key for HTML elements in an HTML document, so they silently miss an
29966
- * attribute whose stored qualified name still contains uppercase ASCII
29967
- * letters. That happens when the node came from a case-preserving source
29968
- * (an XML/XHTML document imported via importNode(), or createAttributeNS()),
29969
- * where e.g. `ONERROR` survives the walk: the policy check lowercases to
29970
- * `onerror` and rejects it, but `removeAttribute('ONERROR')` looks up
29971
- * `onerror` and finds nothing. Remove the exact Attr node instead, which is
29972
- * case- and namespace-exact, and fall back to name-based removal only when
29973
- * the caller could not supply the node.
29982
+ * Removes an attribute, preferably as its exact Attr node: name-based
29983
+ * removal lowercases the name in HTML documents and misses case-preserved
29984
+ * names, e.g. from imported XHTML.
29974
29985
  *
29975
29986
  * @param name an Attribute name
29976
29987
  * @param element a DOM node
@@ -30004,12 +30015,8 @@ function createDOMPurify() {
30004
30015
  }
30005
30016
  };
30006
30017
  /**
30007
- * _stripDisallowedAttributes
30008
- *
30009
- * Removes every attribute the active configuration does not allow from a
30010
- * single element, using the same allowlist as the main attribute pass (so
30011
- * `on*` handlers go, but no `/^on/` blocklist is introduced). Used only to
30012
- * neutralise nodes that are being discarded from an in-place tree.
30018
+ * Removes every attribute the configuration does not allow. Used to
30019
+ * neutralize nodes discarded from an in-place tree.
30013
30020
  *
30014
30021
  * @param element the element to strip
30015
30022
  */
@@ -30024,77 +30031,158 @@ function createDOMPurify() {
30024
30031
  }
30025
30032
  };
30026
30033
  /**
30027
- * _neutralizeSubtree
30034
+ * Iterative depth-first walk, so deep trees cannot overflow the call stack,
30035
+ * reading through the cached getters, so clobbering cannot hide children.
30036
+ * When `visit` returns false, the node's children are skipped. With
30037
+ * `intoShadow`, open shadow roots are walked after their host's children.
30028
30038
  *
30029
- * Completes the audit-5 F1 fix across every removal path. The KEEP_CONTENT
30030
- * move-hoist neutralises only disallowed-tag removals; clobber, mXSS-canary,
30031
- * namespace, comment, processing-instruction and KEEP_CONTENT:false removals
30032
- * all drop their subtree wholesale via `_forceRemove`. On the IN_PLACE path
30033
- * those dropped nodes are detached from the caller's LIVE tree but a
30034
- * handler-bearing original among them (an `<img onerror>`/`<video>` that was
30035
- * loading) keeps its queued resource event, which fires in page scope after
30036
- * sanitize returns. This walks a removed subtree and strips every attribute
30037
- * the active configuration does not allow — so `on*` handlers are cancelled
30038
- * through the SAME allowlist that governs kept nodes, not a separate `/^on/`
30039
- * blocklist. Run synchronously before sanitize returns, i.e. before any
30040
- * queued event can fire. Hook-free by design: these nodes leave the output,
30041
- * so firing attribute hooks for them would be surprising. Clobber-safe reads;
30042
- * a doomed clobbered node may shadow `removeAttribute` (its own attributes are
30043
- * irrelevant — it is discarded — while its non-clobbered descendants, e.g.
30044
- * the `<img>`, are reached and scrubbed).
30045
- *
30046
- * @param root the root of a removed subtree to neutralise
30039
+ * @param root where to start
30040
+ * @param visit called for every node
30041
+ * @param intoShadow whether to enter open shadow roots
30047
30042
  */
30048
- const _neutralizeSubtree = function _neutralizeSubtree(root) {
30043
+ const _walkTree = function _walkTree(root, visit) {
30044
+ let intoShadow = arguments.length > 2 && arguments[2] !== void 0 ? arguments[2] : false;
30049
30045
  const stack = [root];
30050
30046
  while (stack.length > 0) {
30051
30047
  const node = stack.pop();
30052
- if (_readNodeType(node) === NODE_TYPE.element) _stripDisallowedAttributes(node);
30048
+ if (visit(node) === false) continue;
30049
+ if (intoShadow && _readNodeType(node) === NODE_TYPE.element) {
30050
+ const shadowRoot = getShadowRoot(node);
30051
+ if (_isDocumentFragment(shadowRoot)) stack.push(shadowRoot);
30052
+ }
30053
30053
  const childNodes = getChildNodes(node);
30054
30054
  if (childNodes) for (let i = childNodes.length - 1; i >= 0; --i) stack.push(childNodes[i]);
30055
30055
  }
30056
30056
  };
30057
30057
  /**
30058
- * _neutralizePatchLinkage
30058
+ * Strips disallowed attributes from every element of a discarded subtree,
30059
+ * so a handler that is already queued (a loading image) cannot fire.
30059
30060
  *
30060
- * IN_PLACE entry pre-pass (declarative-partial-updates / streaming
30061
- * hardening, https://github.com/WICG/declarative-partial-updates).
30061
+ * @param root the root of a removed subtree
30062
+ */
30063
+ const _neutralizeSubtree = function _neutralizeSubtree(root) {
30064
+ _walkTree(root, (node) => {
30065
+ if (_readNodeType(node) === NODE_TYPE.element) _stripDisallowedAttributes(node);
30066
+ });
30067
+ };
30068
+ /**
30069
+ * All nodes under `root`, root first, including open shadow roots. Template
30070
+ * content is not entered, it is inert.
30062
30071
  *
30063
- * The main walk strips patch linkage (`for`/`patchsrc`) and removes range
30064
- * markers (PIs / markup comments) node-by-node, in document order, AS it
30065
- * reaches each node. On a live in-place root that leaves a window: from the
30066
- * moment the root is connected until the walk arrives at a given node, that
30067
- * node's linkage is live. A patch applied on connection/stream can fire as
30068
- * a microtask during the walk and inject or teleport an unsanitized DOM
30069
- * range into a region the iterator has already passed and will not revisit,
30070
- * so the post-return "tree is sanitized" contract is violated. Sweep the
30071
- * whole tree once up front and sever every linkage before the walk begins,
30072
- * closing that window.
30072
+ * @param root the in-place root
30073
+ * @return the nodes
30074
+ */
30075
+ const _collectLiveNodes = function _collectLiveNodes(root) {
30076
+ const nodes = [];
30077
+ _walkTree(root, (node) => {
30078
+ nodes.push(node);
30079
+ }, true);
30080
+ return nodes;
30081
+ };
30082
+ /**
30083
+ * Strips disallowed attributes from every element that was under the
30084
+ * in-place root when the call started and no longer is, whatever removed
30085
+ * or moved it.
30073
30086
  *
30074
- * This CANNOT undo a patch that already fired before sanitize ran — that is
30075
- * the irreducible "do not IN_PLACE a live-connected attacker tree" caveat —
30076
- * but it closes everything from sanitize-start onward. Gated on SAFE_FOR_XML
30077
- * to group with the rest of the declarative-partial-updates handling and
30078
- * stay overridable, consistent with the codebase.
30087
+ * @param root the in-place root
30088
+ * @param before the root's nodes, collected before the walk
30089
+ */
30090
+ const _neutralizeEscapedElements = function _neutralizeEscapedElements(root, before) {
30091
+ const reachable = setCreate();
30092
+ arrayForEach(_collectLiveNodes(root), (node) => {
30093
+ if (_readNodeType(node) === NODE_TYPE.element) setAdd(reachable, node);
30094
+ });
30095
+ arrayForEach(before, (node) => {
30096
+ if (_readNodeType(node) === NODE_TYPE.element && !setHas(reachable, node)) _stripDisallowedAttributes(node);
30097
+ });
30098
+ };
30099
+ /**
30100
+ * Neutralizes everything that left the in-place tree during the call: what
30101
+ * DOMPurify removed, and what hooks or custom elements removed or moved out.
30079
30102
  *
30080
- * Clobber-safe traversal (cached childNodes getter); per-node try/catch so a
30081
- * clobbered root cannot defeat the sweep of its non-clobbered descendants.
30103
+ * @param root the in-place root
30104
+ * @param before the root's nodes, collected before the walk
30105
+ * @return true if the root itself was removed
30106
+ */
30107
+ const _neutralizeDiscarded = function _neutralizeDiscarded(root, before) {
30108
+ let rootRemoved = false;
30109
+ arrayForEach(DOMPurify.removed, (entry) => {
30110
+ if (entry.element) {
30111
+ rootRemoved = rootRemoved || entry.element === root;
30112
+ _neutralizeSubtree(entry.element);
30113
+ }
30114
+ });
30115
+ _neutralizeEscapedElements(root, before);
30116
+ return rootRemoved;
30117
+ };
30118
+ const MAX_UNVISITED_ROUNDS = 8;
30119
+ /**
30120
+ * The walk only moves forward, so a node moved to a position it already
30121
+ * passed is never visited. Give every original node that is still in the
30122
+ * tree but unvisited the normal treatment, hooks included. Nodes created
30123
+ * during the call are left alone. Fails closed if the tree keeps changing.
30124
+ *
30125
+ * @param root the in-place root
30126
+ * @param before the root's nodes, collected before the walk
30127
+ */
30128
+ const _sanitizeUnvisitedNodes = function _sanitizeUnvisitedNodes(root, before) {
30129
+ const visited = IN_PLACE_VISITED;
30130
+ let original = null;
30131
+ const getOriginal = () => {
30132
+ if (!original) {
30133
+ original = setCreate();
30134
+ arrayForEach(before, (beforeNode) => {
30135
+ setAdd(original, beforeNode);
30136
+ });
30137
+ }
30138
+ return original;
30139
+ };
30140
+ for (let round = 0;; round++) {
30141
+ const live = _collectLiveNodes(root);
30142
+ const containers = [root];
30143
+ let pending = false;
30144
+ arrayForEach(live, (node) => {
30145
+ if (_readNodeType(node) === NODE_TYPE.documentFragment) arrayPush(containers, node);
30146
+ else if (!pending && !setHas(visited, node)) pending = setHas(getOriginal(), node);
30147
+ });
30148
+ if (!pending) return;
30149
+ if (round >= MAX_UNVISITED_ROUNDS) throw typeErrorCreate("the tree kept changing during in-place sanitization; refusing to return it");
30150
+ arrayForEach(containers, (container, index) => {
30151
+ _sanitizeUnvisitedIn(container, index > 0, root, getOriginal());
30152
+ });
30153
+ }
30154
+ };
30155
+ /**
30156
+ * One _sanitizeUnvisitedNodes pass over the root or an open shadow root.
30082
30157
  *
30083
- * NOTE (pending real-Chrome confirmation, see test/declarative-patch-probe
30084
- * .html Q1): this mirrors the existing policy of keeping `for` on
30085
- * <label>/<output>. If the shipping feature can drive a patch through a
30086
- * surviving `for`-on-label/output + `id` pair, this pre-pass and the
30087
- * attribute check at _isBasicCustomElement's caller must additionally drop
30088
- * that pair on the IN_PLACE path. Left as-is until the taxonomy is verified.
30158
+ * @param container the root or an open shadow root
30159
+ * @param inShadow whether container is a shadow root
30160
+ * @param root the in-place root
30161
+ * @param original the root's nodes collected before the walk, as a set
30162
+ */
30163
+ const _sanitizeUnvisitedIn = function _sanitizeUnvisitedIn(container, inShadow, root, original) {
30164
+ const iterator = _createNodeIterator(container);
30165
+ let node;
30166
+ while (node = iterator.nextNode()) {
30167
+ if (!setHas(original, node) || setHas(IN_PLACE_VISITED, node)) continue;
30168
+ if (inShadow) _executeHooks(hooks.uponSanitizeShadowNode, node, null);
30169
+ _sanitizeElements(node, root);
30170
+ _sanitizeAttributes(node, root);
30171
+ if (_isDocumentFragment(node.content)) _sanitizeShadowDOM2(node.content);
30172
+ }
30173
+ };
30174
+ /**
30175
+ * Records that _sanitizeElements processed `node`. No-op outside IN_PLACE.
30089
30176
  *
30090
- * @param root the in-place root to sweep
30177
+ * @param node the node being processed
30091
30178
  */
30179
+ const _markVisited = function _markVisited(node) {
30180
+ if (IN_PLACE_VISITED) setAdd(IN_PLACE_VISITED, node);
30181
+ };
30092
30182
  /**
30093
- * Central policy for declarative-partial-updates patch-linkage attributes,
30094
- * shared by the _neutralizePatchLinkage pre-pass and _isValidAttribute so
30095
- * the two sites cannot drift: `patchsrc` always links, `for` links
30096
- * everywhere except on <label>/<output>, and the whole policy is gated on
30097
- * SAFE_FOR_XML (see the rationale block in _isValidAttribute).
30183
+ * Patch-linkage attributes (declarative partial updates): `patchsrc`
30184
+ * always, `for` everywhere except on <label> and <output>. Gated on
30185
+ * SAFE_FOR_XML. Shared by the IN_PLACE pre-pass and _isValidAttribute.
30098
30186
  *
30099
30187
  * @param lcName the transformCaseFunc'd attribute name
30100
30188
  * @param lcTag the transformCaseFunc'd tag name of the carrying element
@@ -30105,17 +30193,26 @@ function createDOMPurify() {
30105
30193
  if (lcName === "patchsrc") return true;
30106
30194
  return lcName === "for" && lcTag !== "label" && lcTag !== "output";
30107
30195
  };
30196
+ /**
30197
+ * IN_PLACE pre-pass: sever all patch linkage before the walk, so no patch
30198
+ * can fire mid-walk into a part of the tree the walker already passed.
30199
+ * Removes range markers (processing instructions, comments with markup)
30200
+ * and strips linkage attributes. It cannot undo a patch that already fired.
30201
+ *
30202
+ * TODO: if the shipped feature can patch through `for` on <label>/<output>,
30203
+ * drop that pair here too (see test/declarative-patch-probe.html).
30204
+ *
30205
+ * @param root the in-place root to sweep
30206
+ */
30108
30207
  const _neutralizePatchLinkage = function _neutralizePatchLinkage(root) {
30109
30208
  if (!SAFE_FOR_XML) return;
30110
- const stack = [root];
30111
- while (stack.length > 0) {
30112
- const node = stack.pop();
30209
+ _walkTree(root, (node) => {
30113
30210
  const nodeType = _readNodeType(node);
30114
30211
  if (nodeType === NODE_TYPE.processingInstruction || nodeType === NODE_TYPE.comment && regExpTest(COMMENT_MARKUP_PROBE, node.data)) {
30115
30212
  try {
30116
30213
  remove(node);
30117
30214
  } catch (_) {}
30118
- continue;
30215
+ return false;
30119
30216
  }
30120
30217
  if (nodeType === NODE_TYPE.element) {
30121
30218
  const element = node;
@@ -30125,9 +30222,8 @@ function createDOMPurify() {
30125
30222
  if (element.hasAttribute && element.hasAttribute("for") && _isPatchLinkageAttribute("for", lcTag)) element.removeAttribute("for");
30126
30223
  } catch (_) {}
30127
30224
  }
30128
- const childNodes = getChildNodes(node);
30129
- if (childNodes) for (let i = childNodes.length - 1; i >= 0; --i) stack.push(childNodes[i]);
30130
- }
30225
+ return true;
30226
+ });
30131
30227
  };
30132
30228
  /**
30133
30229
  * _initDocument
@@ -30204,6 +30300,27 @@ function createDOMPurify() {
30204
30300
  */
30205
30301
  const _scrubTemplateExpressions2 = function _scrubTemplateExpressions(node) {
30206
30302
  var _node$querySelectorAl;
30303
+ _scrubOwnCharacterData(node);
30304
+ const templates = (_node$querySelectorAl = node.querySelectorAll) === null || _node$querySelectorAl === void 0 ? void 0 : _node$querySelectorAl.call(node, "template");
30305
+ if (templates) arrayForEach(templates, (tmpl) => {
30306
+ if (_isDocumentFragment(tmpl.content)) _scrubTemplateExpressions2(tmpl.content);
30307
+ });
30308
+ };
30309
+ /**
30310
+ * _scrubOwnCharacterData
30311
+ *
30312
+ * Non-recursive core of _scrubTemplateExpressions: normalize `node` and
30313
+ * strip template expressions from its own character data only. Does not
30314
+ * enter <template>.content or attached shadow roots (NodeIterator never
30315
+ * does). Used by _sanitizeShadowDOM, where every nested template content
30316
+ * and shadow root is already walked - and therefore scrubbed - by its own
30317
+ * recursive _sanitizeShadowDOM call; recursing here as well would rescrub
30318
+ * each nested fragment once per ancestor, which is quadratic in nesting
30319
+ * depth.
30320
+ *
30321
+ * @param node The root whose own character data should be scrubbed.
30322
+ */
30323
+ const _scrubOwnCharacterData = function _scrubOwnCharacterData(node) {
30207
30324
  node.normalize();
30208
30325
  const doc = getOwnerDocument ? getOwnerDocument(node) : node.ownerDocument;
30209
30326
  const walker = createNodeIterator.call(doc || node, node, NodeFilter.SHOW_TEXT | NodeFilter.SHOW_COMMENT | NodeFilter.SHOW_CDATA_SECTION | NodeFilter.SHOW_PROCESSING_INSTRUCTION, null);
@@ -30212,10 +30329,6 @@ function createDOMPurify() {
30212
30329
  currentNode.data = _stripTemplateExpressions(currentNode.data);
30213
30330
  currentNode = walker.nextNode();
30214
30331
  }
30215
- const templates = (_node$querySelectorAl = node.querySelectorAll) === null || _node$querySelectorAl === void 0 ? void 0 : _node$querySelectorAl.call(node, "template");
30216
- if (templates) arrayForEach(templates, (tmpl) => {
30217
- if (_isDocumentFragment(tmpl.content)) _scrubTemplateExpressions2(tmpl.content);
30218
- });
30219
30332
  };
30220
30333
  /**
30221
30334
  * _isClobbered
@@ -30291,7 +30404,7 @@ function createDOMPurify() {
30291
30404
  */
30292
30405
  const _isUnsafeNode = function _isUnsafeNode(currentNode, tagName) {
30293
30406
  if (SAFE_FOR_XML && currentNode.hasChildNodes() && !_isNode(currentNode.firstElementChild) && regExpTest(ELEMENT_MARKUP_PROBE, currentNode.textContent) && regExpTest(ELEMENT_MARKUP_PROBE, currentNode.innerHTML)) return true;
30294
- if (SAFE_FOR_XML && currentNode.namespaceURI === HTML_NAMESPACE && LITERAL_TEXT_ELEMENTS[tagName] && (_isNode(currentNode.firstElementChild) || typeof currentNode.textContent === "string" && regExpTest(LITERAL_TEXT_CLOSE[tagName], currentNode.textContent))) return true;
30407
+ if (SAFE_FOR_XML && LITERAL_TEXT_ELEMENTS[tagName] && _readNodeType(currentNode) === NODE_TYPE.element && _readNamespaceURI(currentNode) === HTML_NAMESPACE && (_isNode(currentNode.firstElementChild) || typeof currentNode.textContent === "string" && regExpTest(ELEMENT_MARKUP_PROBE, currentNode.textContent))) return true;
30295
30408
  if (currentNode.nodeType === NODE_TYPE.processingInstruction) return true;
30296
30409
  if (SAFE_FOR_XML && currentNode.nodeType === NODE_TYPE.comment && regExpTest(COMMENT_MARKUP_PROBE, currentNode.data)) return true;
30297
30410
  return false;
@@ -30335,7 +30448,7 @@ function createDOMPurify() {
30335
30448
  */
30336
30449
  const _sanitizeDisallowedNode = function _sanitizeDisallowedNode(currentNode, tagName, root) {
30337
30450
  if (!FORBID_TAGS[tagName] && _isBasicCustomElement(tagName) && _matchesNameCheck(CUSTOM_ELEMENT_HANDLING.tagNameCheck, tagName)) return false;
30338
- if (KEEP_CONTENT && !FORBID_CONTENTS[tagName]) {
30451
+ if (KEEP_CONTENT && !FORBID_CONTENTS[tagName] && !FORBID_CONTENTS[stringToLowerCase(tagName)]) {
30339
30452
  const parentNode = getParentNode(currentNode);
30340
30453
  const childNodes = getChildNodes(currentNode);
30341
30454
  if (childNodes && parentNode) {
@@ -30368,35 +30481,13 @@ function createDOMPurify() {
30368
30481
  return set === defaultSet || set === setConfigSet ? clone(set) : set;
30369
30482
  };
30370
30483
  /**
30371
- * Shared guard for a node that a hook has detached from the walk tree,
30372
- * used after each element-hook site in _sanitizeElements. Detaching is a
30373
- * long-standing user pattern (issue #469; draw.io-style foreignObject
30374
- * filtering). Per the cached, unclobberable parentNode getter the node is
30375
- * genuinely out of the tree, so it can reach neither the serialized
30376
- * output nor an IN_PLACE live tree; treat it as removed and stop
30377
- * processing it. Without this guard, the unsafe-node / namespace checks
30378
- * would call _forceRemove on a parentless node and hit the REPORT-3
30379
- * fail-closed throw — which exists for nodes DOMPurify wants gone but
30380
- * *cannot* detach (clobbered / parentless roots), the opposite of a node
30381
- * that is already safely gone. The walk root is exempt: a detached
30382
- * IN_PLACE root is legitimate input and must still be fully sanitized,
30383
- * and a kill-decision on it must keep hitting the REPORT-3 throw.
30384
- *
30385
- * Nodes detached by hooks stay the hook's responsibility for placement:
30386
- * they are not recorded in DOMPurify.removed, so the post-walk IN_PLACE
30387
- * pass (which iterates DOMPurify.removed) does not reach them. But a
30388
- * hook-detached subtree can still hold a queued resource-event handler -
30389
- * e.g. an <img onload> that began loading when the caller built the live
30390
- * tree - which fires in page scope after sanitize returns even though the
30391
- * handler never reached the returned tree. That is the audit-5 F1 hazard,
30392
- * and the documented node.remove() hook pattern walks straight into it.
30393
- * So on the IN_PLACE path we neutralize the detached subtree inline,
30394
- * stripping its non-allow-listed attributes before returning, exactly as
30395
- * the post-walk pass does for _forceRemove'd subtrees.
30484
+ * A hook may have detached the node it was called for. If so, treat it as
30485
+ * removed and stop processing it; the walk root is exempt. On the IN_PLACE
30486
+ * path the detached subtree is neutralized right away.
30396
30487
  *
30397
30488
  * @param currentNode the node a hook may have detached
30398
30489
  * @param root the current walk root
30399
- * @return true if the node is detached and now handled, false otherwise
30490
+ * @return true if the node was detached
30400
30491
  */
30401
30492
  const _handleHookDetachedNode = function _handleHookDetachedNode(currentNode, root) {
30402
30493
  if (currentNode === root || getParentNode(currentNode) !== null) return false;
@@ -30413,6 +30504,7 @@ function createDOMPurify() {
30413
30504
  * @return true if node was killed, false if left alive
30414
30505
  */
30415
30506
  const _sanitizeElements = function _sanitizeElements(currentNode, root) {
30507
+ _markVisited(currentNode);
30416
30508
  _executeHooks(hooks.beforeSanitizeElements, currentNode, null);
30417
30509
  if (_handleHookDetachedNode(currentNode, root)) return true;
30418
30510
  if (_isClobbered(currentNode)) {
@@ -30556,6 +30648,22 @@ function createDOMPurify() {
30556
30648
  }
30557
30649
  };
30558
30650
  /**
30651
+ * _isUnsafeAttributeValue
30652
+ *
30653
+ * Value-based attribute checks that the attribute walk applies before any
30654
+ * hook can force-keep an attribute, shared with the public
30655
+ * isValidAttribute() so that it never approves a value sanitize() would
30656
+ * strip.
30657
+ *
30658
+ * @param name the attribute name as found on the node (case preserved)
30659
+ * @param value the attribute value
30660
+ * @returns true if the attribute must be removed because of its value
30661
+ */
30662
+ const _isUnsafeAttributeValue = function _isUnsafeAttributeValue(name, value) {
30663
+ if (SAFE_FOR_XML && regExpTest(/((--!?|])>)|<\/(style|script|title|xmp|textarea|noscript|iframe|noembed|noframes)/i, value)) return true;
30664
+ return stringToLowerCase(name) === "attributename" && stringMatch(value, "href") !== null;
30665
+ };
30666
+ /**
30559
30667
  * _sanitizeAttributes
30560
30668
  *
30561
30669
  * @protect attributes
@@ -30599,11 +30707,7 @@ function createDOMPurify() {
30599
30707
  value = SANITIZE_NAMED_PROPS_PREFIX + value;
30600
30708
  recreatedNamedProp = true;
30601
30709
  }
30602
- if (SAFE_FOR_XML && regExpTest(/((--!?|])>)|<\/(style|script|title|xmp|textarea|noscript|iframe|noembed|noframes)/i, value)) {
30603
- _removeAttribute(name, currentNode, attr);
30604
- continue;
30605
- }
30606
- if (lcName === "attributename" && stringMatch(value, "href")) {
30710
+ if (_isUnsafeAttributeValue(name, value)) {
30607
30711
  _removeAttribute(name, currentNode, attr);
30608
30712
  continue;
30609
30713
  }
@@ -30651,24 +30755,13 @@ function createDOMPurify() {
30651
30755
  }
30652
30756
  }
30653
30757
  }
30758
+ if (SAFE_FOR_TEMPLATES) _scrubOwnCharacterData(fragment);
30654
30759
  _executeHooks(hooks.afterSanitizeShadowDOM, fragment, null);
30655
30760
  };
30656
30761
  /**
30657
- * _sanitizeAttachedShadowRoots
30658
- *
30659
- * Walks `root` and feeds every attached shadow root we encounter into
30660
- * the existing _sanitizeShadowDOM pipeline. The default node iterator
30661
- * does not descend into shadow trees, so nodes inside an attached
30662
- * shadow root would otherwise be skipped entirely.
30663
- *
30664
- * Two real input paths put attached shadow roots in front of us:
30665
- * 1. IN_PLACE on a DOM node that already has shadow roots attached.
30666
- * 2. DOM-node input where importNode(dirty, true) deep-clones the
30667
- * shadow root because it was created with `clonable: true`.
30668
- *
30669
- * This pass runs once, up front, so the main iteration loop (and the
30670
- * existing _sanitizeShadowDOM template-content recursion) stay
30671
- * untouched — string-input paths are not affected.
30762
+ * Sanitizes every attached shadow root under `root`, since the node
30763
+ * iterator does not enter shadow trees. Needed for IN_PLACE input and for
30764
+ * DOM input whose clonable shadow roots were copied by importNode().
30672
30765
  *
30673
30766
  * @param root the subtree root to walk for attached shadow roots
30674
30767
  */
@@ -30733,6 +30826,8 @@ function createDOMPurify() {
30733
30826
  if (hooks.uponSanitizeAttribute.length > 0) ALLOWED_ATTR = clone(ALLOWED_ATTR);
30734
30827
  DOMPurify.removed = [];
30735
30828
  const inPlace = IN_PLACE && typeof dirty !== "string" && _isNode(dirty);
30829
+ let inPlaceNodes = [];
30830
+ const outerVisited = IN_PLACE_VISITED;
30736
30831
  if (inPlace) {
30737
30832
  _neutralizePatchLinkage(dirty);
30738
30833
  const nn = _readNodeName(dirty);
@@ -30747,12 +30842,8 @@ function createDOMPurify() {
30747
30842
  _neutralizeRoot(dirty);
30748
30843
  throw typeErrorCreate("root node is clobbered and cannot be sanitized in-place");
30749
30844
  }
30750
- try {
30751
- _sanitizeAttachedShadowRoots(dirty);
30752
- } catch (error) {
30753
- _neutralizeRoot(dirty);
30754
- throw error;
30755
- }
30845
+ inPlaceNodes = _collectLiveNodes(dirty);
30846
+ IN_PLACE_VISITED = setCreate();
30756
30847
  } else if (_isNode(dirty)) {
30757
30848
  body = _initDocument("<!---->");
30758
30849
  importedNode = body.ownerDocument.importNode(dirty, true);
@@ -30768,30 +30859,25 @@ function createDOMPurify() {
30768
30859
  if (body && FORCE_BODY) _forceRemove(body.firstChild);
30769
30860
  const walkRoot = inPlace ? dirty : body;
30770
30861
  try {
30862
+ if (inPlace) _sanitizeAttachedShadowRoots(dirty);
30771
30863
  const nodeIterator = _createNodeIterator(walkRoot);
30772
30864
  while (currentNode = nodeIterator.nextNode()) {
30773
30865
  _sanitizeElements(currentNode, walkRoot);
30774
30866
  _sanitizeAttributes(currentNode, walkRoot);
30775
30867
  if (_isDocumentFragment(currentNode.content)) _sanitizeShadowDOM2(currentNode.content);
30776
30868
  }
30869
+ if (inPlace) _sanitizeUnvisitedNodes(dirty, inPlaceNodes);
30777
30870
  } catch (error) {
30778
30871
  if (inPlace) {
30779
30872
  _neutralizeRoot(dirty);
30780
- arrayForEach(DOMPurify.removed, (entry) => {
30781
- if (entry.element) _neutralizeSubtree(entry.element);
30782
- });
30873
+ _neutralizeDiscarded(dirty, inPlaceNodes);
30783
30874
  }
30784
30875
  throw error;
30876
+ } finally {
30877
+ IN_PLACE_VISITED = outerVisited;
30785
30878
  }
30786
30879
  if (inPlace) {
30787
- let rootWasRemoved = false;
30788
- arrayForEach(DOMPurify.removed, (entry) => {
30789
- if (entry.element) {
30790
- if (entry.element === dirty) rootWasRemoved = true;
30791
- _neutralizeSubtree(entry.element);
30792
- }
30793
- });
30794
- if (rootWasRemoved) throw typeErrorCreate("a node selected for removal could not be safely returned; refusing to sanitize in place");
30880
+ if (_neutralizeDiscarded(dirty, inPlaceNodes)) throw typeErrorCreate("a node selected for removal could not be safely returned; refusing to sanitize in place");
30795
30881
  if (SAFE_FOR_TEMPLATES) _scrubTemplateExpressions2(dirty);
30796
30882
  return dirty;
30797
30883
  }
@@ -30828,6 +30914,7 @@ function createDOMPurify() {
30828
30914
  if (!CONFIG) _parseConfig({});
30829
30915
  const lcTag = transformCaseFunc(tag);
30830
30916
  const lcName = transformCaseFunc(attr);
30917
+ if (typeof value === "string" && (_isUnsafeAttributeValue(attr, value) || !ALLOW_SELF_CLOSE_IN_ATTR && regExpTest(SELF_CLOSING_TAG, value))) return false;
30831
30918
  return _isValidAttribute(lcTag, lcName, value);
30832
30919
  };
30833
30920
  DOMPurify.addHook = function(entryPoint, hookFunction) {
@@ -31167,6 +31254,18 @@ function mermaidPreview(onError) {
31167
31254
  }));
31168
31255
  }
31169
31256
  //#endregion
31257
+ //#region src/literal-text.ts
31258
+ /** Literal lines stay in their textblock, including headings and table cells. */
31259
+ function literalTextNodes(schema, text, marks, isHTML) {
31260
+ const nodes = [];
31261
+ const lines = text.replace(/\r\n?/g, "\n").split("\n");
31262
+ lines.forEach((line, index) => {
31263
+ if (index) nodes.push(schema.nodes.hardbreak.create({ isHTML }, null, index === lines.length - 1 && line === "" ? [] : marks));
31264
+ if (line) nodes.push(schema.text(line, marks));
31265
+ });
31266
+ return nodes;
31267
+ }
31268
+ //#endregion
31170
31269
  //#region node_modules/.pnpm/rope-sequence@1.3.4/node_modules/rope-sequence/dist/index.js
31171
31270
  var GOOD_LEAF_SIZE = 200;
31172
31271
  var RopeSequence = function RopeSequence() {};
@@ -31621,6 +31720,13 @@ A command function that redoes the last undone change, if any.
31621
31720
  */
31622
31721
  var redo = buildCommand(true, true);
31623
31722
  /**
31723
+ The amount of redoable events available in a given editor state.
31724
+ */
31725
+ function redoDepth(state) {
31726
+ let hist = historyKey.getState(state);
31727
+ return hist ? hist.undone.eventCount : 0;
31728
+ }
31729
+ /**
31624
31730
  Returns true if the given transaction was generated by the history
31625
31731
  plugin.
31626
31732
  */
@@ -44042,7 +44148,7 @@ function nodeText(node, missingImages, fileDescriptions) {
44042
44148
  }
44043
44149
  return clipboardText(node.content, missingImages, fileDescriptions);
44044
44150
  }
44045
- function clipboardContent(content, schema) {
44151
+ function clipboardDOM(content, schema) {
44046
44152
  content = shareableFragment(content);
44047
44153
  const container = document.createElement("div");
44048
44154
  const serializer = DOMSerializer.fromSchema(schema);
@@ -44072,14 +44178,30 @@ function clipboardContent(content, schema) {
44072
44178
  label.textContent = `[${definition.getAttribute("data-inkkit-label")}] `;
44073
44179
  definition.prepend(label);
44074
44180
  }
44181
+ return container;
44182
+ }
44183
+ function clipboardContent(content, schema) {
44075
44184
  return {
44076
44185
  text: clipboardText(content),
44077
- html: container.innerHTML
44186
+ html: clipboardDOM(content, schema).innerHTML
44078
44187
  };
44079
44188
  }
44080
- var visibleClipboard = $prose(() => new Plugin({
44189
+ function clipboardSerializer(schema) {
44190
+ const serializer = DOMSerializer.fromSchema(schema);
44191
+ const safe = new DOMSerializer(serializer.nodes, serializer.marks);
44192
+ safe.serializeFragment = (content, options = {}, target) => {
44193
+ const output = target ?? (options.document ?? document).createDocumentFragment();
44194
+ output.append(...clipboardDOM(content, schema).childNodes);
44195
+ return output;
44196
+ };
44197
+ return safe;
44198
+ }
44199
+ var visibleClipboard = $prose((ctx) => new Plugin({
44081
44200
  key: new PluginKey("visibleClipboard"),
44082
- props: { clipboardTextSerializer: (slice) => clipboardText(slice.content) }
44201
+ props: {
44202
+ clipboardSerializer: clipboardSerializer(ctx.get(schemaCtx)),
44203
+ clipboardTextSerializer: (slice) => clipboardText(slice.content)
44204
+ }
44083
44205
  }));
44084
44206
  /** Export a frozen document fragment; presentation URLs never enter the clipboard. */
44085
44207
  async function portableClipboard(content, schema, markdown, adapter, context, completeDiagrams, files, signal = new AbortController().signal) {
@@ -45023,13 +45145,16 @@ function lineBreakHTML(value) {
45023
45145
  "<br/>"
45024
45146
  ].includes(value.trim());
45025
45147
  }
45026
- function restoreInlineBreaks(node) {
45027
- if (node.type === "paragraph" && node.children.length > 1 || node.type === "heading" || node.type === "tableCell") node.children = node.children.map((child) => child.type === "html" && lineBreakHTML(child.value) ? {
45028
- type: "break",
45029
- data: { inkkitHTMLBreak: true },
45030
- position: child.position
45031
- } : child);
45032
- if ("children" in node) node.children.forEach(restoreInlineBreaks);
45148
+ function restoreInlineBreaks(node, inline = false) {
45149
+ const restore = node.type === "paragraph" && node.children.length > 1 || node.type === "heading" || node.type === "tableCell" || inline;
45150
+ if ("children" in node) {
45151
+ node.children = node.children.map((child) => restore && child.type === "html" && lineBreakHTML(child.value) ? {
45152
+ type: "break",
45153
+ data: { inkkitHTMLBreak: true },
45154
+ position: child.position
45155
+ } : child);
45156
+ node.children.forEach((child) => restoreInlineBreaks(child, restore));
45157
+ }
45033
45158
  }
45034
45159
  function containsUnsupported(node, images, inline = false) {
45035
45160
  if (!inline && node.type === "html" && lineBreakHTML(node.value)) return false;
@@ -45934,6 +46059,12 @@ function retainSource(before, after, source, canonical, lineEnding, inTable = fa
45934
46059
  }, source, canonical, lineEnding, inTable);
45935
46060
  const first = before.children[0];
45936
46061
  if (spacer(first)) return retained;
46062
+ const gap = source.slice(first.position.end.offset, before.children[1].position.start.offset);
46063
+ if (!/\r?\n[ \t]*\r?\n/.test(gap)) {
46064
+ const from = first.position.start.offset - start;
46065
+ const to = before.children[1].position.start.offset - start;
46066
+ return retained.startsWith(source.slice(start, start + to)) ? retained.slice(0, from) + retained.slice(to) : retained;
46067
+ }
45937
46068
  const from = first.position.start.offset - start;
45938
46069
  const to = first.position.end.offset - start;
45939
46070
  return retained.startsWith(source.slice(start, before.children[1].position.start.offset)) ? retained.slice(0, from) + "<br />" + retained.slice(to) : retained;
@@ -50114,7 +50245,23 @@ function padShortRows(node) {
50114
50245
  if ("children" in node) node.children.forEach(padShortRows);
50115
50246
  }
50116
50247
  var remarkDialectPlugin = $remark("remarkDialect", () => remarkDialect);
50117
- var commonmarkWithLiteralReferences = commonmark.filter((plugin) => !remarkInlineLinkPlugin.includes(plugin) && plugin !== hardbreakClearMarkPlugin && !hardbreakKeymap.some((part) => part === plugin) && plugin !== syncHeadingIdPlugin && plugin !== createCodeBlockInputRule);
50248
+ var preservingSoftBreaks = $remark("preservingSoftBreaks", () => () => (tree) => {
50249
+ const walk = (parent) => {
50250
+ parent.children = parent.children.flatMap((child) => {
50251
+ if ("children" in child) walk(child);
50252
+ if (child.type !== "text" || !/[\r\n]/.test(child.value)) return [child];
50253
+ return child.value.split(/(\r?\n|\r)/).flatMap((value) => !value ? [] : /^(?:\r?\n|\r)$/.test(value) ? [{
50254
+ type: "break",
50255
+ data: { isInline: true }
50256
+ }] : [{
50257
+ type: "text",
50258
+ value
50259
+ }]);
50260
+ });
50261
+ };
50262
+ walk(tree);
50263
+ });
50264
+ var commonmarkWithLiteralReferences = commonmark.filter((plugin) => !remarkInlineLinkPlugin.includes(plugin) && !remarkLineBreak.includes(plugin) && plugin !== hardbreakClearMarkPlugin && !hardbreakKeymap.some((part) => part === plugin) && plugin !== syncHeadingIdPlugin && plugin !== createCodeBlockInputRule);
50118
50265
  var breaksWithoutMarks = $prose((ctx) => new Plugin({
50119
50266
  key: new PluginKey("inkkitBreaksWithoutMarks"),
50120
50267
  appendTransaction(transactions, oldState, newState) {
@@ -50124,9 +50271,24 @@ var breaksWithoutMarks = $prose((ctx) => new Plugin({
50124
50271
  const end = oldState.doc.content.findDiffEnd(newState.doc.content).b;
50125
50272
  const type = hardbreakSchema.type(ctx);
50126
50273
  const tr = newState.tr;
50274
+ newState.doc.nodesBetween(start, Math.max(start, end), (node, pos) => {
50275
+ if (!node.isTextblock || !node.firstChild) return;
50276
+ let count = 0;
50277
+ for (let index = 0; index < node.childCount; index++) {
50278
+ const child = node.child(index);
50279
+ if (child.type !== type || !child.attrs.isInline || child.attrs.isHTML) break;
50280
+ let previous = pos + 1 + count;
50281
+ for (let transaction = transactions.length - 1; transaction >= 0; transaction--) previous = transactions[transaction].mapping.invert().map(previous, 1);
50282
+ const before = oldState.doc.nodeAt(previous);
50283
+ if (before?.type !== type || !before.attrs.isInline || before.attrs.isHTML || oldState.doc.resolve(previous).parentOffset === 0) break;
50284
+ count += child.nodeSize;
50285
+ }
50286
+ if (count) tr.delete(tr.mapping.map(pos + 1), tr.mapping.map(pos + 1 + count));
50287
+ return false;
50288
+ });
50127
50289
  newState.doc.nodesBetween(start, Math.max(start, end), (node, pos) => {
50128
50290
  if (node.type !== type) return;
50129
- for (const mark of node.marks) if (mark.type.spec.code) tr.removeMark(pos, pos + 1, mark.type);
50291
+ for (const mark of node.marks) if (mark.type.spec.code) tr.removeMark(tr.mapping.map(pos), tr.mapping.map(pos + 1), mark.type);
50130
50292
  });
50131
50293
  return tr.steps.length ? tr : null;
50132
50294
  }
@@ -50388,6 +50550,7 @@ function createDialect(images, wikiLinks = false, files = false) {
50388
50550
  wikiLinks || files ? linkedSyntax(wikiLinks, files) : [],
50389
50551
  createLiteralPreservation(images || files),
50390
50552
  commonmarkWithLiteralReferences,
50553
+ preservingSoftBreaks,
50391
50554
  $node("doc", () => ({
50392
50555
  ...docSchema.schema,
50393
50556
  attrs: {
@@ -51343,7 +51506,8 @@ var ImageView = class {
51343
51506
  return;
51344
51507
  }
51345
51508
  const presentation = state.presentation;
51346
- if (presentation.kind === "missing" || presentation.kind === "error") this.card(label, presentation.kind === "missing" ? this.runtime.labels.fileMissing : this.runtime.labels.fileError, true);
51509
+ if (presentation.kind === "missing") this.card(label, this.runtime.labels.fileMissing, true);
51510
+ else if (presentation.kind === "error") this.card(label, presentation.message || this.runtime.labels.fileError, true);
51347
51511
  else if (presentation.kind === "image") {
51348
51512
  this.renderImage(presentation.url, label, width);
51349
51513
  this.card(label);
@@ -53987,6 +54151,7 @@ var PasteController = class {
53987
54151
  async run(input, files = [], target) {
53988
54152
  if (this.dead) throw new InkKitError("destroyed", "The editor was destroyed.");
53989
54153
  if (this.pending) throw new InkKitError("operation-pending", "An image paste is still pending.");
54154
+ input = this.options.restoreCopy?.(input) ?? input;
53990
54155
  const context = { ...this.options.context() };
53991
54156
  const ctx = this.options.ctx();
53992
54157
  const view = ctx.get(editorViewCtx);
@@ -54027,8 +54192,12 @@ var PasteController = class {
54027
54192
  this.dispatch(view, caretAfterInsertion(view.state.tr.insertText(input.text, anchor.from, anchor.to)).scrollIntoView());
54028
54193
  return;
54029
54194
  }
54030
- const paragraphs = input.text.replace(/\r\n?/g, "\n").split("\n").map((line) => schema.nodes.paragraph.create(null, line ? schema.text(line) : void 0));
54031
- slice = new Slice(Fragment.fromArray(paragraphs), 1, 1);
54195
+ const anchor = this.anchor(operation);
54196
+ const start = view.state.doc.resolve(anchor.from);
54197
+ const end = view.state.doc.resolve(anchor.to);
54198
+ const marks = view.state.storedMarks ?? (anchor.from === anchor.to ? start.marks() : start.marksAcross(end)) ?? [];
54199
+ const nodes = literalTextNodes(schema, input.text, marks, htmlBreakAt(start));
54200
+ slice = new Slice(Fragment.from(schema.nodes.paragraph.create(null, nodes)), 1, 1);
54032
54201
  } else if (input.html) {
54033
54202
  const container = visibleContent(ctx, input.html);
54034
54203
  const imported = /* @__PURE__ */ new Map();
@@ -55019,14 +55188,7 @@ function replacementSlice(state, from, to, value) {
55019
55188
  const nodes = [];
55020
55189
  if (start.parent.type.spec.code) {
55021
55190
  if (value) nodes.push(state.schema.text(value, marks));
55022
- } else {
55023
- const isHTML = htmlBreakAt(start);
55024
- const lines = value.split("\n");
55025
- lines.forEach((line, index) => {
55026
- if (index) nodes.push(state.schema.nodes.hardbreak.create({ isHTML }, null, index === lines.length - 1 && line === "" ? [] : marks));
55027
- if (line) nodes.push(state.schema.text(line, marks));
55028
- });
55029
- }
55191
+ } else nodes.push(...literalTextNodes(state.schema, value, marks, htmlBreakAt(start)));
55030
55192
  return new Slice(Fragment.fromArray(nodes), 0, 0);
55031
55193
  }
55032
55194
  function replaceFormatted(view, search, replacement, preflight, all = false) {
@@ -55853,6 +56015,15 @@ function caretState(state) {
55853
56015
  quoted: quoted($from, $to)
55854
56016
  };
55855
56017
  }
56018
+ function terminalSelection(doc) {
56019
+ const $end = doc.resolve(doc.content.size);
56020
+ const gap = GapCursor;
56021
+ if (isImageBlock(doc.lastChild) && gap.valid($end)) return new GapCursor($end);
56022
+ const text = Selection.findFrom($end, -1, true);
56023
+ if (text) return text;
56024
+ if (gap.valid($end)) return new GapCursor($end);
56025
+ throw new InkKitError("preservation", "Document has no safe writing position");
56026
+ }
55856
56027
  function quoted($from, $to) {
55857
56028
  return $from.blockRange($to, (node) => node.type.name === "blockquote") != null;
55858
56029
  }
@@ -56065,6 +56236,8 @@ var InkKitEditor = class InkKitEditor {
56065
56236
  typedSteps = [];
56066
56237
  undoneSteps = [];
56067
56238
  formattedUndone = 0;
56239
+ formattedRedoLimit;
56240
+ historyDirection;
56068
56241
  lastSourceInput = 0;
56069
56242
  syncingSource = false;
56070
56243
  mode = "formatted";
@@ -56082,6 +56255,52 @@ var InkKitEditor = class InkKitEditor {
56082
56255
  auxClickHandler;
56083
56256
  untrackLinkModifier;
56084
56257
  footnoteKeyHandler;
56258
+ localCopies = /* @__PURE__ */ new Map();
56259
+ rememberCopy(output, markdown, content, privateSource = false, actualCopy = false) {
56260
+ let comments = privateSource;
56261
+ content.descendants((node) => {
56262
+ const labelled = (label) => typeof label === "string" && literalCommentSpans(label).length > 0;
56263
+ if (isComment(node) || /^(?:reference|footnote)_/.test(node.type.name) && labelled(node.attrs.label) || node.marks.some((mark) => mark.type.name === "link" && (labelled(mark.attrs.label) || commentProvenance(mark.attrs.referenceContent)))) comments = true;
56264
+ });
56265
+ if (!comments) {
56266
+ if (actualCopy) this.localCopies.clear();
56267
+ return output;
56268
+ }
56269
+ const key = JSON.stringify([
56270
+ output.text,
56271
+ output.html,
56272
+ markdown
56273
+ ]);
56274
+ const previous = this.localCopies.get(key);
56275
+ if (previous) {
56276
+ output.html = previous.html;
56277
+ if (actualCopy) this.localCopies.clear();
56278
+ this.localCopies.set(key, previous);
56279
+ return output;
56280
+ }
56281
+ const wrapper = document.createElement("div");
56282
+ const token = Array.from(crypto.getRandomValues(/* @__PURE__ */ new Uint8Array(16)), (byte) => byte.toString(16).padStart(2, "0")).join("");
56283
+ wrapper.setAttribute("data-inkkit-local-copy", token);
56284
+ wrapper.innerHTML = output.html;
56285
+ output.html = wrapper.outerHTML;
56286
+ if (actualCopy) this.localCopies.clear();
56287
+ this.localCopies.set(key, {
56288
+ text: output.text,
56289
+ html: output.html,
56290
+ markdown
56291
+ });
56292
+ return output;
56293
+ }
56294
+ retainCopy(html) {
56295
+ for (const [key, copy] of this.localCopies) if (copy.html !== html) this.localCopies.delete(key);
56296
+ }
56297
+ restoreCopy(input) {
56298
+ const copy = [...this.localCopies.values()].find((copy) => copy.text === input.text && copy.html === input.html);
56299
+ return copy && input.markdown == null && !input.plainText && copy.text === input.text && copy.html === input.html ? {
56300
+ ...input,
56301
+ markdown: copy.markdown
56302
+ } : input;
56303
+ }
56085
56304
  copyHandler;
56086
56305
  originalSource = "";
56087
56306
  reportingSuppressed = false;
@@ -56297,7 +56516,9 @@ var InkKitEditor = class InkKitEditor {
56297
56516
  revision: this.session.mark(),
56298
56517
  lastMarkdown: this.lastMarkdown,
56299
56518
  literalMatch: this.literalMatch,
56300
- textScope: this.textScope
56519
+ textScope: this.textScope,
56520
+ formattedRedoLimit: this.formattedRedoLimit,
56521
+ formattedUndone: this.formattedUndone
56301
56522
  };
56302
56523
  }
56303
56524
  finish(call, before, failure) {
@@ -56319,6 +56540,8 @@ var InkKitEditor = class InkKitEditor {
56319
56540
  this.lastMarkdown = before.lastMarkdown;
56320
56541
  this.literalMatch = before.literalMatch;
56321
56542
  this.textScope = before.textScope;
56543
+ this.formattedRedoLimit = before.formattedRedoLimit;
56544
+ this.formattedUndone = before.formattedUndone;
56322
56545
  }
56323
56546
  /** Reports a refused edit to the facade call that made it, or else to `events.error`. */
56324
56547
  refuse(error) {
@@ -56459,7 +56682,7 @@ var InkKitEditor = class InkKitEditor {
56459
56682
  table: this.literalSurface ? void 0 : tableContext(state),
56460
56683
  commands: {
56461
56684
  undo: mutable && (!!this.pendingSource || undo(state)),
56462
- redo: mutable && (this.undoneSteps.length > 0 || !this.pendingSource && redo(state)),
56685
+ redo: mutable && (this.undoneSteps.length > 0 || !this.pendingSource && this.formattedRedoLimit !== 0 && redo(state)),
56463
56686
  insertText: mutable,
56464
56687
  paste: mutable,
56465
56688
  replace: mutable,
@@ -56629,7 +56852,9 @@ var InkKitEditor = class InkKitEditor {
56629
56852
  this.assertAlive();
56630
56853
  expectString(text, "text");
56631
56854
  this.assertMutation(expectedGeneration);
56632
- return this.applySource(text, true);
56855
+ const changed = this.applySource(text, true);
56856
+ if (changed) this.localCopies.clear();
56857
+ return changed;
56633
56858
  }
56634
56859
  applySource(text, isolated, from, to, native = false) {
56635
56860
  if (!this.writable) throw new InkKitError("read-only", "The editor is read-only");
@@ -56648,6 +56873,7 @@ var InkKitEditor = class InkKitEditor {
56648
56873
  const previous = this.pendingSource;
56649
56874
  const continuing = previous && !previous.closed && !closed && now - this.lastSourceInput <= sourceGroupDelay;
56650
56875
  if (previous && !continuing) this.rememberTypedStep(previous);
56876
+ this.formattedRedoLimit = 0;
56651
56877
  this.lastSourceInput = now;
56652
56878
  this.undoneSteps = [];
56653
56879
  this.formattedUndone = 0;
@@ -56775,7 +57001,7 @@ var InkKitEditor = class InkKitEditor {
56775
57001
  }
56776
57002
  }
56777
57003
  let tr = view.state.tr.replaceWith(0, view.state.doc.content.size, parsed.content).setDocAttribute(sourceAttribute, provenance).setMeta(sourceReplacement, true);
56778
- tr.setSelection(Selection.atEnd(tr.doc));
57004
+ tr.setSelection(terminalSelection(tr.doc));
56779
57005
  if (isolated) tr = closeHistory(tr);
56780
57006
  view.dispatch(tr);
56781
57007
  if (isolated) view.dispatch(closeHistory(view.state.tr).setMeta("addToHistory", false));
@@ -56786,7 +57012,7 @@ var InkKitEditor = class InkKitEditor {
56786
57012
  this.assertMutation(expectedGeneration);
56787
57013
  if (this.literalSurface && this.stepTypedSource("undo")) return true;
56788
57014
  const view = this.editor.ctx.get(editorViewCtx);
56789
- const undone = !this.literalSurface && undoSmartPunctuation(view.state, view.dispatch) || undo(view.state, view.dispatch);
57015
+ const undone = !this.literalSurface && undoSmartPunctuation(view.state, view.dispatch) || this.stepFormattedHistory("undo");
56790
57016
  if (undone) {
56791
57017
  if (this.literalSurface && this.undoneSteps.length) this.formattedUndone++;
56792
57018
  else this.undoneSteps = [];
@@ -56799,14 +57025,30 @@ var InkKitEditor = class InkKitEditor {
56799
57025
  const view = this.editor.ctx.get(editorViewCtx);
56800
57026
  if (this.literalSurface) {
56801
57027
  if (this.formattedUndone > 0) {
56802
- this.formattedUndone--;
56803
- if (redo(view.state, view.dispatch)) return true;
57028
+ if (this.formattedRedoLimit !== 0 && redo(view.state)) {
57029
+ const redone = this.stepFormattedHistory("redo");
57030
+ if (redone) this.formattedUndone--;
57031
+ return redone;
57032
+ }
56804
57033
  this.formattedUndone = 0;
56805
57034
  }
56806
57035
  if (this.stepTypedSource("redo")) return true;
56807
57036
  if (this.pendingSource) return false;
56808
57037
  }
56809
- return redo(view.state, view.dispatch);
57038
+ return this.stepFormattedHistory("redo");
57039
+ }
57040
+ stepFormattedHistory(direction) {
57041
+ if (direction === "redo" && this.formattedRedoLimit === 0) return false;
57042
+ const view = this.editor.ctx.get(editorViewCtx);
57043
+ const before = view.state;
57044
+ const previous = this.historyDirection;
57045
+ this.historyDirection = direction;
57046
+ try {
57047
+ (direction === "undo" ? undo : redo)(before, view.dispatch);
57048
+ return view.state !== before;
57049
+ } finally {
57050
+ this.historyDirection = previous;
57051
+ }
56810
57052
  }
56811
57053
  replaceLiteralSelection(text, isolated = true, retainEndings = false) {
56812
57054
  const value = this.plain.value;
@@ -56909,6 +57151,7 @@ var InkKitEditor = class InkKitEditor {
56909
57151
  const metadata = referenceMetadata(this.editor.ctx, selectedDoc ?? doc, content);
56910
57152
  if (metadata != null) result.html = withReferenceMetadata(result.html, metadata);
56911
57153
  this.assertCaptured(captured);
57154
+ this.rememberCopy(result, markdown, content, selectedSource != null && selectedSource.markdown !== selectedSource.shareable);
56912
57155
  return {
56913
57156
  ...version,
56914
57157
  ...result
@@ -56918,7 +57161,7 @@ var InkKitEditor = class InkKitEditor {
56918
57161
  * clipboard again while this runs. */
56919
57162
  async paste(input, expectedGeneration) {
56920
57163
  this.assertAlive();
56921
- input = clipboardInput(input);
57164
+ input = this.restoreCopy(clipboardInput(input));
56922
57165
  this.assertMutation(expectedGeneration);
56923
57166
  if (this.literalSurface) {
56924
57167
  this.pasteAsPlainText(this.mode === "source" && this.formatType === "md" ? input.markdown ?? input.text : input.text);
@@ -56951,14 +57194,9 @@ var InkKitEditor = class InkKitEditor {
56951
57194
  else {
56952
57195
  const { schema } = view.state;
56953
57196
  const marks = view.state.storedMarks ?? (view.state.selection.empty ? view.state.selection.$from.marks() : view.state.selection.$from.marksAcross(view.state.selection.$to)) ?? [];
56954
- const children = [];
56955
57197
  const isHTML = !inHiddenComment && htmlBreakAt(view.state.selection.$from);
56956
57198
  const lines = text.replace(/\r\n?/g, "\n").split("\n");
56957
- lines.forEach((line, index) => {
56958
- const finalBreak = index === lines.length - 1 && line === "";
56959
- if (index) children.push(schema.nodes.hardbreak.create({ isHTML }, null, finalBreak ? [] : marks));
56960
- if (line) children.push(schema.text(line, marks));
56961
- });
57199
+ const children = literalTextNodes(schema, text, marks, isHTML);
56962
57200
  if (!children.length) {
56963
57201
  view.dispatch(view.state.tr.deleteSelection());
56964
57202
  return;
@@ -57001,12 +57239,14 @@ var InkKitEditor = class InkKitEditor {
57001
57239
  async destroy() {
57002
57240
  if (this.destroyed) return;
57003
57241
  this.destroyed = true;
57242
+ this.localCopies.clear();
57004
57243
  this.ready = false;
57005
57244
  this.session.close();
57006
57245
  this.pendingSource = void 0;
57007
57246
  this.typedSteps = [];
57008
57247
  this.undoneSteps = [];
57009
57248
  this.formattedUndone = 0;
57249
+ this.formattedRedoLimit = void 0;
57010
57250
  this.cancelExports();
57011
57251
  this.mediaPolicy();
57012
57252
  this.pasteController.destroy();
@@ -57046,6 +57286,7 @@ var InkKitEditor = class InkKitEditor {
57046
57286
  if (portable && this.events.clipboard) this.clipboardSnapshot("selection").then(async (content) => {
57047
57287
  if (cut) refuseUnportableCut(content);
57048
57288
  await writeClipboard(this.events, content);
57289
+ this.retainCopy(content.html);
57049
57290
  if (cut) {
57050
57291
  this.assertCut(captured);
57051
57292
  this.plain.setSelectionRange(from, to);
@@ -57055,6 +57296,7 @@ var InkKitEditor = class InkKitEditor {
57055
57296
  else {
57056
57297
  const output = clipboardContent(parsed.content, parsed.type.schema);
57057
57298
  if (!event.clipboardData) return;
57299
+ this.rememberCopy(output, selected.markdown, parsed.content, selected.markdown !== selected.shareable, true);
57058
57300
  event.clipboardData.setData("text/plain", output.text);
57059
57301
  event.clipboardData.setData("text/html", output.html);
57060
57302
  if (portable) {
@@ -57079,6 +57321,7 @@ var InkKitEditor = class InkKitEditor {
57079
57321
  instance.ownsRootClass = !root.classList.contains("inkkit-root");
57080
57322
  root.classList.add("inkkit-root");
57081
57323
  instance.pasteController = new PasteController({
57324
+ restoreCopy: (input) => instance.restoreCopy(input),
57082
57325
  ctx: () => instance.editor.ctx,
57083
57326
  context: () => instance.context(),
57084
57327
  adapter: options.images,
@@ -57182,6 +57425,14 @@ var InkKitEditor = class InkKitEditor {
57182
57425
  return;
57183
57426
  }
57184
57427
  }
57428
+ if (instance.formattedRedoLimit !== void 0) {
57429
+ const depth = redoDepth(next);
57430
+ if (depth === 0) instance.formattedRedoLimit = void 0;
57431
+ else if (next !== this.state) {
57432
+ if (instance.historyDirection === "undo") instance.formattedRedoLimit = Math.min(depth, instance.formattedRedoLimit + 1);
57433
+ else if (instance.historyDirection === "redo") instance.formattedRedoLimit = Math.max(0, instance.formattedRedoLimit - 1);
57434
+ }
57435
+ }
57185
57436
  this.updateState(next);
57186
57437
  },
57187
57438
  handleScrollToSelection: () => {
@@ -57189,7 +57440,7 @@ var InkKitEditor = class InkKitEditor {
57189
57440
  return true;
57190
57441
  }
57191
57442
  }));
57192
- }).use(labelsCtx).use(instance.pasteController.plugin).use(visibleClipboard).use($prose(() => instance.suggestions.plugin())).use(instance.keymapPlugin).use(createDialect(Boolean(options.images || options.files), Boolean(options.wikiLinks), Boolean(options.files))).use(codeControlCtx).use(codeControlsPlugin(() => instance.viewport().rect)).use(tablePlugins).use(tableBlock).use(instance.changePlugin).use(instance.historyInputPlugin).use(history).use(clipboard).use(cursor).use(taskListPlugin).use(quoteBackspace).use(calloutEnter).use(literalEnter).use(literalReread).use(listItemBackspace).use(listItemShiftTab).use(nestedItemEnter).use(rejoinLists).use(movedListItems).use(textInputPolicy).use(events.copy ? codeCopyPlugin((text) => events.copy?.(text)) : []).use(placeholderPlugin).use(options.rendering?.codeHighlighting === false ? [] : (await import("./highlight-BIasnL-b.js")).highlightPlugin).use(options.rendering?.diagramPreview === false || !diagramsAvailable() ? [] : mermaidPreview((error) => instance.report(error))).use(selectionPlugin).use($prose(() => search())).use(instance.policyPlugin).use(options.wikiLinks ? wikiLinkView(options.wikiLinks, {
57443
+ }).use(labelsCtx).use(instance.pasteController.plugin).use(visibleClipboard).use($prose(() => instance.suggestions.plugin())).use(instance.keymapPlugin).use(createDialect(Boolean(options.images || options.files), Boolean(options.wikiLinks), Boolean(options.files))).use(codeControlCtx).use(codeControlsPlugin(() => instance.viewport().rect)).use(tablePlugins).use(tableBlock).use(instance.changePlugin).use(instance.historyInputPlugin).use(history).use(clipboard).use(cursor).use(taskListPlugin).use(quoteBackspace).use(calloutEnter).use(literalEnter).use(literalReread).use(listItemBackspace).use(listItemShiftTab).use(nestedItemEnter).use(rejoinLists).use(movedListItems).use(textInputPolicy).use(events.copy ? codeCopyPlugin((text) => events.copy?.(text)) : []).use(placeholderPlugin).use(options.rendering?.codeHighlighting === false ? [] : (await import("./highlight-TSUdUlMi.js")).highlightPlugin).use(options.rendering?.diagramPreview === false || !diagramsAvailable() ? [] : mermaidPreview((error) => instance.report(error))).use(selectionPlugin).use($prose(() => search())).use(instance.policyPlugin).use(options.wikiLinks ? wikiLinkView(options.wikiLinks, {
57193
57444
  capture: () => instance.adapterCapture(),
57194
57445
  active: () => instance.ready && !instance.destroyed && !instance.literalSurface,
57195
57446
  labels: {
@@ -57269,6 +57520,7 @@ var InkKitEditor = class InkKitEditor {
57269
57520
  event.preventDefault();
57270
57521
  instance.paste({
57271
57522
  text: event.clipboardData.getData("text/plain"),
57523
+ html: event.clipboardData.getData("text/html"),
57272
57524
  markdown: event.clipboardData.getData("text/markdown") || void 0
57273
57525
  }).catch((error) => instance.report(error));
57274
57526
  });
@@ -57338,11 +57590,13 @@ var InkKitEditor = class InkKitEditor {
57338
57590
  instance.snapshot();
57339
57591
  const content = expanded;
57340
57592
  const valid = fragment.firstChild?.isInline ? Fragment.from(view.state.schema.nodes.paragraph.create(null, fragment)) : fragment;
57341
- selectionMarkdown(instance.editor.ctx, view.state.doc, valid);
57593
+ const markdown = selectionMarkdown(instance.editor.ctx, view.state.doc, valid);
57342
57594
  const output = clipboardContent(content, view.state.schema);
57343
- event.clipboardData.setData("text/plain", output.text);
57344
57595
  const metadata = referenceMetadata(instance.editor.ctx, view.state.doc, valid);
57345
- event.clipboardData.setData("text/html", metadata != null ? withReferenceMetadata(output.html, metadata) : output.html);
57596
+ if (metadata != null) output.html = withReferenceMetadata(output.html, metadata);
57597
+ instance.rememberCopy(output, markdown, fragment, false, true);
57598
+ event.clipboardData.setData("text/plain", output.text);
57599
+ event.clipboardData.setData("text/html", output.html);
57346
57600
  if (event.type === "cut" && instance.writable) view.dispatch(view.state.tr.deleteSelection().scrollIntoView());
57347
57601
  } catch (error) {
57348
57602
  instance.report(error);
@@ -57358,6 +57612,7 @@ var InkKitEditor = class InkKitEditor {
57358
57612
  instance.clipboardSnapshot("selection").then(async (content) => {
57359
57613
  if (cut) refuseUnportableCut(content);
57360
57614
  await writeClipboard(events, content);
57615
+ instance.retainCopy(content.html);
57361
57616
  if (cut) {
57362
57617
  instance.assertCut(captured);
57363
57618
  view.dispatch(view.state.tr.deleteRange(from, to).scrollIntoView());
@@ -57405,6 +57660,7 @@ var InkKitEditor = class InkKitEditor {
57405
57660
  this.typedSteps = [];
57406
57661
  this.undoneSteps = [];
57407
57662
  this.formattedUndone = 0;
57663
+ this.formattedRedoLimit = void 0;
57408
57664
  this.mode = "formatted";
57409
57665
  this.plainComposing = false;
57410
57666
  this.beforePlainInput = void 0;
@@ -57784,6 +58040,7 @@ var InkKitEditor = class InkKitEditor {
57784
58040
  }])[0]);
57785
58041
  }
57786
58042
  load(markdown, preservation) {
58043
+ this.localCopies.clear();
57787
58044
  this.originalSource = markdown;
57788
58045
  const { ctx } = this.editor;
57789
58046
  const view = ctx.get(editorViewCtx);
@@ -57800,9 +58057,7 @@ var InkKitEditor = class InkKitEditor {
57800
58057
  this.preservation = preservation;
57801
58058
  this.carried = void 0;
57802
58059
  this.lastMarkdown = markdown;
57803
- const { doc } = view.state;
57804
- const $end = doc.resolve(doc.content.size);
57805
- const end = isImageBlock(doc.lastChild) ? new GapCursor($end) : Selection.findFrom($end, -1, true) ?? Selection.atEnd(doc);
58060
+ const end = terminalSelection(view.state.doc);
57806
58061
  view.dispatch(setSearchState(view.state.tr.setSelection(end), new SearchQuery({ search: "" })));
57807
58062
  }
57808
58063
  /** Selects the next match and returns whether there was one. */