@aicayzer/inkkit 0.3.0 → 0.3.2

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.
@@ -2214,7 +2214,7 @@ function tokenizeCharacterEscape(effects, ok, nok) {
2214
2214
  * } from 'micromark-util-types'
2215
2215
  */
2216
2216
  /** @type {Construct} */
2217
- var characterReference = {
2217
+ var characterReference$1 = {
2218
2218
  name: "characterReference",
2219
2219
  tokenize: tokenizeCharacterReference
2220
2220
  };
@@ -7349,7 +7349,7 @@ var flow = {
7349
7349
  };
7350
7350
  /** @satisfies {Extension['string']} */
7351
7351
  var string = {
7352
- [38]: characterReference,
7352
+ [38]: characterReference$1,
7353
7353
  [92]: characterEscape
7354
7354
  };
7355
7355
  /** @satisfies {Extension['text']} */
@@ -7358,7 +7358,7 @@ var text$4 = {
7358
7358
  [-4]: lineEnding$1,
7359
7359
  [-3]: lineEnding$1,
7360
7360
  [33]: labelStartImage,
7361
- [38]: characterReference,
7361
+ [38]: characterReference$1,
7362
7362
  [42]: attention$2,
7363
7363
  [60]: [autolink$1, htmlText],
7364
7364
  [91]: labelStartLink,
@@ -7916,7 +7916,7 @@ function postprocess(events) {
7916
7916
  * @returns {Array<Chunk>}
7917
7917
  * Chunks.
7918
7918
  */
7919
- var search$1 = /[\0\t\n\r]/g;
7919
+ var search$2 = /[\0\t\n\r]/g;
7920
7920
  /**
7921
7921
  * @returns {Preprocessor}
7922
7922
  * Preprocess a value.
@@ -7941,8 +7941,8 @@ function preprocess() {
7941
7941
  start = void 0;
7942
7942
  }
7943
7943
  while (startPosition < value.length) {
7944
- search$1.lastIndex = startPosition;
7945
- const match = search$1.exec(value);
7944
+ search$2.lastIndex = startPosition;
7945
+ const match = search$2.exec(value);
7946
7946
  const endPosition = match && match.index !== void 0 ? match.index : value.length;
7947
7947
  const code = value.charCodeAt(endPosition);
7948
7948
  if (!match) {
@@ -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=808819d35255b79fc131ebc4873ab2fbcd47cd8f5bc05cd11e8caab02ecee548/node_modules/prosemirror-view/dist/index.js
22737
+ //#region node_modules/.pnpm/prosemirror-view@1.42.6_patch_hash=f2853b430244e6a0ce28d0ee793854c199c3f40279a807cb6690af21fae2d25e/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;
@@ -23907,7 +23907,7 @@ var NodeViewDesc = class NodeViewDesc extends ViewDesc {
23907
23907
  this.children = replaceNodes(this.children, pos, pos + text.length, view, desc);
23908
23908
  }
23909
23909
  update(node, outerDeco, innerDeco, view) {
23910
- if (this.dirty == NODE_DIRTY || !node.sameMarkup(this.node)) return false;
23910
+ if (this.dirty == NODE_DIRTY || (this.parent ? !node.sameMarkup(this.node) : node.type != this.node.type)) return false;
23911
23911
  this.updateInner(node, outerDeco, innerDeco, view);
23912
23912
  return true;
23913
23913
  }
@@ -31817,21 +31817,51 @@ var linkSchemes = /* @__PURE__ */ new Set([
31817
31817
  "mailto",
31818
31818
  "tel"
31819
31819
  ]);
31820
+ /** Schemes a host may never add: each runs code or reads local data. */
31821
+ var refusedSchemes = /* @__PURE__ */ new Set([
31822
+ "javascript",
31823
+ "data",
31824
+ "vbscript",
31825
+ "file"
31826
+ ]);
31827
+ var noSchemes = /* @__PURE__ */ new Set();
31820
31828
  /**
31821
31829
  * Whether a link address may be kept. Relative paths, queries and fragments
31822
31830
  * pass, and so does an address whose scheme is listed, in any letter case.
31823
- * Spaces, tabs and line breaks at the ends are ignored, as in a browser. Any
31824
- * other control character makes the address unsafe, because a browser may skip
31825
- * it while it reads the scheme. The scheme is the text before the first `:` in
31826
- * the part before the first `/`, `?`, `#` or `\`, so an unknown or malformed
31827
- * scheme is refused.
31828
- */
31829
- function isSafeLinkTarget(value) {
31831
+ * `extra` holds the lower-case schemes one editor's host added. Spaces, tabs
31832
+ * and line breaks at the ends are ignored, as in a browser. Any other control
31833
+ * character makes the address unsafe, because a browser may skip it while it
31834
+ * reads the scheme. The scheme is the text before the first `:` in the part
31835
+ * before the first `/`, `?`, `#` or `\`, so an unknown or malformed scheme is
31836
+ * refused.
31837
+ */
31838
+ function isSafeLinkTarget(value, extra = noSchemes) {
31830
31839
  const target = value.replace(/^[ \t\n\f\r]+|[ \t\n\f\r]+$/g, "");
31831
31840
  if (/[\u0000-\u001f\u007f]/.test(target)) return false;
31832
31841
  const head = target.slice(0, target.search(/[/?#\\]|$/));
31833
31842
  const colon = head.indexOf(":");
31834
- return colon < 0 || linkSchemes.has(head.slice(0, colon).toLowerCase());
31843
+ if (colon < 0) return true;
31844
+ const scheme = head.slice(0, colon).toLowerCase();
31845
+ return linkSchemes.has(scheme) || extra.has(scheme);
31846
+ }
31847
+ /**
31848
+ * Checks the host's `linkSchemes` option and returns its entries in lower
31849
+ * case. An entry is a scheme name, and `javascript`, `data`, `vbscript` and
31850
+ * `file` are never accepted.
31851
+ */
31852
+ function extraLinkSchemes(value, name) {
31853
+ if (!Array.isArray(value)) throw invalidArgument(`${name} must be a list`);
31854
+ return value.map((entry, index) => {
31855
+ if (typeof entry !== "string" || !/^[a-z][a-z0-9+.-]*$/i.test(entry)) throw invalidArgument(`${name}[${index}] must be a scheme name of letters, digits, +, - and ., starting with a letter`);
31856
+ const scheme = entry.toLowerCase();
31857
+ if (refusedSchemes.has(scheme)) throw invalidArgument(`${name}[${index}] cannot be ${scheme}`);
31858
+ return scheme;
31859
+ });
31860
+ }
31861
+ /** The extra schemes of one editor, so that two editors on a page keep their own. */
31862
+ var linkSchemesCtx = $ctx(noSchemes, "inkkitLinkSchemes");
31863
+ function editorLinkSchemes(ctx) {
31864
+ return ctx.isInjected(linkSchemesCtx.key) ? ctx.get(linkSchemesCtx.key) : noSchemes;
31835
31865
  }
31836
31866
  /**
31837
31867
  * Whether an image source would run script if a renderer treated it as an
@@ -34914,6 +34944,7 @@ var referenceLinks = linkSchema.extendSchema((base) => (ctx) => {
34914
34944
  const schema = base(ctx);
34915
34945
  return {
34916
34946
  ...schema,
34947
+ inclusive: false,
34917
34948
  priority: 40,
34918
34949
  attrs: {
34919
34950
  ...schema.attrs,
@@ -34942,7 +34973,9 @@ var referenceLinks = linkSchema.extendSchema((base) => (ctx) => {
34942
34973
  }],
34943
34974
  toDOM(mark) {
34944
34975
  const attrs = { ...schema.toDOM(mark, false)[1] };
34945
- if (!isSafeLinkTarget(String(mark.attrs.href ?? ""))) attrs.href = "";
34976
+ const href = String(mark.attrs.href ?? "");
34977
+ if (!isSafeLinkTarget(href, editorLinkSchemes(ctx))) attrs.href = "";
34978
+ else if (!attrs.href) attrs.href = href.trim();
34946
34979
  delete attrs.label;
34947
34980
  delete attrs.identifier;
34948
34981
  delete attrs.referenceType;
@@ -35387,6 +35420,10 @@ var undoSmartPunctuation = (state, dispatch) => {
35387
35420
  //#region src/linked-syntax.ts
35388
35421
  var fromTable = (raw) => raw.replace(/(\\*)\|/g, (_match, escapes) => `${"\\".repeat(Math.floor(escapes.length / 2))}|`);
35389
35422
  var toTable = (raw) => raw.replace(/(\\*)\|/g, (_match, escapes) => `${"\\".repeat(escapes.length * 2 + 1)}|`);
35423
+ function pathTableForm(raw, inTable) {
35424
+ if (!inTable) return raw;
35425
+ return raw.replace(/(\\*)\|/g, (_match, escapes) => escapes.length % 2 ? escapes + "|" : escapes + "\\|");
35426
+ }
35390
35427
  function parseLinkedSyntax(raw, inTable = false) {
35391
35428
  const source = inTable ? fromTable(raw) : raw;
35392
35429
  const embed = source.startsWith("!");
@@ -35473,7 +35510,25 @@ function tableForm(raw, authoredInTable, outputInTable) {
35473
35510
  if (authoredInTable === outputInTable) return raw;
35474
35511
  return outputInTable ? toTable(raw) : fromTable(raw);
35475
35512
  }
35513
+ function wikiMarkdown(node, inTable = false) {
35514
+ return tableForm(String(node.attrs.raw), Boolean(node.attrs.inTable), inTable);
35515
+ }
35516
+ function wikiReference(node) {
35517
+ return {
35518
+ raw: wikiMarkdown(node, Boolean(node.attrs.inTable)),
35519
+ target: String(node.attrs.target),
35520
+ ...node.attrs.fragment != null ? { fragment: String(node.attrs.fragment) } : {},
35521
+ label: String(node.attrs.label)
35522
+ };
35523
+ }
35476
35524
  function fileReference(node) {
35525
+ return {
35526
+ ...fileFields(node),
35527
+ raw: fileSource(node)
35528
+ };
35529
+ }
35530
+ /** `fileReference` without the embed's text, which takes writing it. */
35531
+ function fileFields(node) {
35477
35532
  const reference = String(node.attrs.src ?? "");
35478
35533
  const named = node.attrs.inkkitFileRaw != null;
35479
35534
  const alt = String(node.attrs.alt ?? "");
@@ -35492,11 +35547,49 @@ function fileReference(node) {
35492
35547
  ...width != null ? { width } : {}
35493
35548
  };
35494
35549
  }
35550
+ var writers = /* @__PURE__ */ new WeakMap();
35551
+ /** Sets how an image of `schema` is written when it has no authored text. */
35552
+ function setImageWriter(schema, write) {
35553
+ writers.set(schema, write);
35554
+ }
35555
+ /**
35556
+ * A file embed's Markdown as the document holds it: its authored text while
35557
+ * the embed is unchanged, otherwise the text InkKit writes for it.
35558
+ */
35559
+ function fileSource(node) {
35560
+ if (node.attrs.inkkitFileRaw != null) return namedFileMarkdown(node, Boolean(node.attrs.inkkitFileInTable)) ?? "";
35561
+ const authored = authoredFileSource(node);
35562
+ if (authored != null) return authored;
35563
+ const write = writers.get(node.type.schema);
35564
+ if (!write) throw new InkKitError("internal", "The image writer is not set");
35565
+ return pathTableForm(write(node), Boolean(node.attrs.inkkitFileInTable));
35566
+ }
35567
+ /** Only reuse authored spelling while it still describes the image. */
35568
+ function authoredFileSource(node) {
35569
+ const authored = node.attrs.inkkitFileSource;
35570
+ return authored && authored.src === node.attrs.src && authored.alt === node.attrs.alt && authored.title === node.attrs.title ? authored.text : null;
35571
+ }
35572
+ /** Records each path image's Markdown as written, for `FileReference.raw`. */
35573
+ function remarkFileSources() {
35574
+ return (tree, file) => {
35575
+ const source = String(file.value ?? "").replace(/^\uFEFF/, "");
35576
+ const visit = (node, inTable = false) => {
35577
+ if (node.type === "image" && node.inkkitFileRaw == null) {
35578
+ const start = node.position?.start.offset;
35579
+ const end = node.position?.end.offset;
35580
+ if (start != null && end != null) node.inkkitFileSource = source.slice(start, end);
35581
+ node.inkkitFileInTable = inTable;
35582
+ }
35583
+ if ("children" in node) node.children.forEach((child) => visit(child, inTable || node.type === "tableCell"));
35584
+ };
35585
+ visit(tree);
35586
+ };
35587
+ }
35495
35588
  function namedFileMarkdown(node, inTable = false) {
35496
35589
  if (node.attrs.inkkitFileRaw == null) return void 0;
35497
35590
  let raw = String(node.attrs.inkkitFileRaw);
35498
35591
  const authoredInTable = Boolean(node.attrs.inkkitFileInTable);
35499
- const width = fileReference(node).width ?? null;
35592
+ const width = fileFields(node).width ?? null;
35500
35593
  const original = node.attrs.inkkitFileWidth ?? null;
35501
35594
  if (width !== original) {
35502
35595
  if (authoredInTable) raw = fromTable(raw);
@@ -35608,6 +35701,54 @@ var wikiLinkComposition = $prose(() => new Plugin({
35608
35701
  } } }
35609
35702
  }));
35610
35703
  /**
35704
+ * A pasted or moved atom retains its original attributes. Stores the spelling
35705
+ * its new context saves before preservation and node views observe it, so
35706
+ * adapter references and clipboard metadata agree.
35707
+ */
35708
+ var referenceTableContext = $prose(() => new Plugin({
35709
+ key: new PluginKey("inkkitReferenceTableContext"),
35710
+ appendTransaction(transactions, _before, state) {
35711
+ if (!transactions.some((tr) => tr.docChanged)) return null;
35712
+ let ranges = [];
35713
+ for (const transaction of transactions) for (const map of transaction.mapping.maps) {
35714
+ ranges = ranges.map(([from, to]) => [map.map(from, -1), map.map(to)]);
35715
+ map.forEach((_from, _to, from, to) => ranges.push([from, to]));
35716
+ }
35717
+ const tr = state.tr;
35718
+ const seen = /* @__PURE__ */ new Set();
35719
+ for (const [from, to] of ranges) state.doc.nodesBetween(from, to, (node, pos) => {
35720
+ if (![
35721
+ "inkkit_wiki_link",
35722
+ "image",
35723
+ "image_block"
35724
+ ].includes(node.type.name) || seen.has(pos)) return;
35725
+ seen.add(pos);
35726
+ const $pos = state.doc.resolve(pos);
35727
+ let inTable = false;
35728
+ for (let depth = $pos.depth; depth > 0; depth--) if (["table_cell", "table_header"].includes($pos.node(depth).type.name)) inTable = true;
35729
+ if (node.type.name === "inkkit_wiki_link") {
35730
+ if (inTable !== Boolean(node.attrs.inTable)) tr.setNodeMarkup(pos, void 0, {
35731
+ ...node.attrs,
35732
+ raw: wikiMarkdown(node, inTable),
35733
+ inTable
35734
+ });
35735
+ } else if (inTable !== Boolean(node.attrs.inkkitFileInTable)) {
35736
+ const authored = node.attrs.inkkitFileSource;
35737
+ tr.setNodeMarkup(pos, void 0, {
35738
+ ...node.attrs,
35739
+ inkkitFileRaw: namedFileMarkdown(node, inTable) ?? null,
35740
+ inkkitFileSource: authored ? {
35741
+ ...authored,
35742
+ text: pathTableForm(authored.text, inTable)
35743
+ } : null,
35744
+ inkkitFileInTable: inTable
35745
+ });
35746
+ }
35747
+ });
35748
+ return tr.steps.length ? tr : null;
35749
+ }
35750
+ }));
35751
+ /**
35611
35752
  * Turns `[[target]]`, as the person types its last bracket, into the wiki link
35612
35753
  * it reads as when the note is opened, so the note keeps the characters that
35613
35754
  * were typed. Left as text, the brackets save as `\[\[target]]`, which no
@@ -35695,6 +35836,7 @@ function linkedSyntax(wikiLinks, files) {
35695
35836
  const wiki = (node, _parent, state) => tableForm(String(node.raw), Boolean(node.inTable), state.stack.includes("tableCell"));
35696
35837
  const named = (node, parent, state, info) => {
35697
35838
  if (node.inkkitFileRaw != null) return tableForm(String(node.inkkitFileRaw), Boolean(node.inkkitFileInTable), state.stack.includes("tableCell"));
35839
+ if (node.inkkitFileSource != null) return pathTableForm(node.inkkitFileSource, state.stack.includes("tableCell"));
35698
35840
  return handle.image(node, parent, state, info);
35699
35841
  };
35700
35842
  return $remark("inkkitLinkedSyntax", () => function() {
@@ -35719,13 +35861,22 @@ function validName(value) {
35719
35861
  }
35720
35862
  function wikiSuggestionNode(state, name) {
35721
35863
  if (!validName(name)) throw invalidArgument("A note suggestion must be a supported note name");
35722
- return state.schema.nodes.inkkit_wiki_link.create({
35864
+ const { $from } = state.selection;
35865
+ let inTable = false;
35866
+ for (let depth = $from.depth; depth > 0; depth--) if (["table_cell", "table_header"].includes($from.node(depth).type.name)) inTable = true;
35867
+ const node = state.schema.nodes.inkkit_wiki_link.create({
35723
35868
  raw: rawName(name),
35724
35869
  target: name,
35725
35870
  label: name,
35726
35871
  fragment: null,
35727
35872
  inTable: false
35728
- }, void 0, state.selection.$from.marks());
35873
+ }, void 0, $from.marks());
35874
+ if (!inTable) return node;
35875
+ return node.type.create({
35876
+ ...node.attrs,
35877
+ raw: wikiMarkdown(node, true),
35878
+ inTable
35879
+ }, void 0, node.marks);
35729
35880
  }
35730
35881
  function wikiSuggestionTransaction(view, session, name) {
35731
35882
  const { state } = view;
@@ -43197,17 +43348,28 @@ var imageAttrs = {
43197
43348
  validate: "string"
43198
43349
  },
43199
43350
  inkkitFileRaw: { default: null },
43351
+ inkkitFileSource: { default: null },
43200
43352
  inkkitFileTarget: { default: null },
43201
43353
  inkkitFileFragment: { default: null },
43202
43354
  inkkitFileWidth: { default: null },
43203
43355
  inkkitFileInTable: { default: false }
43204
43356
  };
43205
43357
  function markdownImageAttrs(node) {
43358
+ const src = String(node.url ?? "");
43359
+ const alt = String(node.alt ?? "");
43360
+ const title = String(node.title ?? "");
43361
+ const source = typeof node.inkkitFileSource === "string" ? {
43362
+ text: node.inkkitFileSource,
43363
+ src,
43364
+ alt,
43365
+ title
43366
+ } : null;
43206
43367
  return {
43207
- src: String(node.url ?? ""),
43208
- alt: String(node.alt ?? ""),
43209
- title: String(node.title ?? ""),
43368
+ src,
43369
+ alt,
43370
+ title,
43210
43371
  inkkitFileRaw: node.inkkitFileRaw ?? null,
43372
+ inkkitFileSource: source,
43211
43373
  inkkitFileTarget: node.inkkitFileTarget ?? null,
43212
43374
  inkkitFileFragment: node.inkkitFileFragment ?? null,
43213
43375
  inkkitFileWidth: node.inkkitFileWidth ?? null,
@@ -43603,14 +43765,15 @@ var from = {
43603
43765
  }
43604
43766
  }
43605
43767
  };
43768
+ var onThreeLines = (isBlock, block, value) => isBlock ? block !== false || /[\r\n]/.test(value) : Boolean(block);
43606
43769
  function commentSource(node) {
43607
43770
  const body = node.textContent;
43608
- return node.attrs.syntax === "html" ? `<!--${body}-->` : node.type.name === "comment_block" || node.attrs.block ? `%%\n${body}\n%%` : `%%${body}%%`;
43771
+ return node.attrs.syntax === "html" ? `<!--${body}-->` : onThreeLines(node.type.name === "comment_block", node.attrs.block, body) ? `%%\n${body}\n%%` : `%%${body}%%`;
43609
43772
  }
43610
43773
  var handler = (node) => {
43611
43774
  const comment = node;
43612
43775
  const value = comment.value ?? "";
43613
- return comment.syntax === "html" ? `<!--${value}-->` : comment.type === "inkkitCommentBlock" || comment.block ? `%%\n${value}\n%%` : `%%${value}%%`;
43776
+ return comment.syntax === "html" ? `<!--${value}-->` : onThreeLines(comment.type === "inkkitCommentBlock", comment.block, value) ? `%%\n${value}\n%%` : `%%${value}%%`;
43614
43777
  };
43615
43778
  function htmlSpans(value) {
43616
43779
  const spans = [];
@@ -43700,6 +43863,7 @@ function remarkComments() {
43700
43863
  if (node.type === "paragraph" && parentType !== "listItem" && node.children.length === 1 && node.children[0]?.type === "inkkitCommentInline") return {
43701
43864
  ...node.children[0],
43702
43865
  type: "inkkitCommentBlock",
43866
+ block: false,
43703
43867
  position: node.position
43704
43868
  };
43705
43869
  return node;
@@ -43739,7 +43903,7 @@ function commentNode(inline) {
43739
43903
  runner: (state, node, type) => {
43740
43904
  state.addNode(type, {
43741
43905
  syntax: node.syntax,
43742
- block: inline ? Boolean(node.block) : true
43906
+ block: inline ? Boolean(node.block) : node.block !== false
43743
43907
  }, node.value ? [state.schema.text(String(node.value))] : void 0);
43744
43908
  }
43745
43909
  },
@@ -43963,50 +44127,116 @@ async function resolveFile(adapter, reference, context) {
43963
44127
  ].includes(presentation.kind)) throw new TypeError("File presentation needs a resource URL");
43964
44128
  return Object.freeze({ ...presentation });
43965
44129
  }
44130
+ /** The event every reference view hears when the editor's media policy or references change. */
44131
+ var mediaPolicyEvent = "inkkit-media-policy";
44132
+ function requestRefresh(dom, refresh) {
44133
+ dom.dispatchEvent(new CustomEvent(mediaPolicyEvent, { detail: { refresh } }));
44134
+ }
44135
+ /** The refresh a policy event carries. A plain event, such as a host's, carries none. */
44136
+ function refreshOf(event) {
44137
+ const detail = event instanceof CustomEvent ? event.detail : null;
44138
+ return detail && typeof detail === "object" && "refresh" in detail ? detail.refresh : void 0;
44139
+ }
44140
+ function refreshes(refresh, target) {
44141
+ return !refresh.targets || refresh.targets.has(target);
44142
+ }
44143
+ function samePresentation(a, b) {
44144
+ const left = a;
44145
+ const right = b;
44146
+ const keys = Object.keys(left);
44147
+ return keys.length === Object.keys(right).length && keys.every((key) => left[key] === right[key]);
44148
+ }
43966
44149
  var FileResolution = class {
43967
44150
  runtime;
43968
44151
  changed;
44152
+ cache;
43969
44153
  state = { status: "idle" };
43970
44154
  reference;
43971
44155
  key = "";
43972
44156
  request;
43973
44157
  dead = false;
43974
- constructor(runtime, changed) {
44158
+ constructor(runtime, changed, cache) {
43975
44159
  this.runtime = runtime;
43976
44160
  this.changed = changed;
44161
+ this.cache = cache;
43977
44162
  }
43978
44163
  update(reference) {
43979
44164
  const key = JSON.stringify([
44165
+ reference.raw,
43980
44166
  reference.reference,
43981
44167
  reference.kind,
43982
44168
  reference.fragment
43983
44169
  ]);
43984
44170
  this.reference = Object.freeze({ ...reference });
43985
44171
  if (this.key === key && this.request?.isCurrent()) return;
44172
+ if (this.key !== key) this.cache?.release(this, this.key);
43986
44173
  this.key = key;
44174
+ this.cache?.retain(this, key);
44175
+ const cached = this.cache?.get(key);
44176
+ if (cached) {
44177
+ if (this.request?.pending) this.request.controller.abort();
44178
+ this.request = {
44179
+ controller: cached.controller,
44180
+ pending: false,
44181
+ isCurrent: cached.isCurrent
44182
+ };
44183
+ this.state = {
44184
+ status: "resolved",
44185
+ presentation: cached.presentation
44186
+ };
44187
+ this.changed();
44188
+ return;
44189
+ }
43987
44190
  this.retry();
43988
44191
  }
44192
+ /** Resolves again from a blank loading card. */
43989
44193
  retry() {
44194
+ this.cache?.delete(this.key);
43990
44195
  this.cancel();
44196
+ this.start(true);
44197
+ }
44198
+ /**
44199
+ * Resolves again behind the current card, which stays until the answer differs, so a card that
44200
+ * the host's change doesn't affect neither blanks nor moves.
44201
+ */
44202
+ refresh() {
44203
+ this.cache?.delete(this.key);
44204
+ if (this.dead || !this.runtime.active()) return;
44205
+ if (this.state.status === "idle") this.retry();
44206
+ else this.start(false);
44207
+ }
44208
+ start(blank) {
44209
+ if (!this.cache || this.request?.pending) this.request?.controller.abort();
44210
+ this.request = void 0;
43991
44211
  if (this.dead || !this.reference || !this.runtime.active()) return;
43992
44212
  const captured = this.runtime.capture();
43993
44213
  const request = {
43994
44214
  controller: new AbortController(),
44215
+ pending: true,
43995
44216
  isCurrent: captured.isCurrent
43996
44217
  };
43997
44218
  this.request = request;
43998
- this.state = { status: "loading" };
43999
- this.changed();
44219
+ if (blank) {
44220
+ this.state = { status: "loading" };
44221
+ this.changed();
44222
+ }
44000
44223
  resolveFile(this.runtime.adapter, this.reference, {
44001
44224
  ...captured.context,
44002
44225
  signal: request.controller.signal
44003
44226
  }).then((presentation) => {
44004
44227
  if (!this.current(request)) return;
44228
+ const before = this.state;
44229
+ request.pending = false;
44005
44230
  this.state = {
44006
44231
  status: "resolved",
44007
44232
  presentation
44008
44233
  };
44009
- this.changed();
44234
+ this.cache?.set(this.key, {
44235
+ presentation,
44236
+ isCurrent: request.isCurrent,
44237
+ controller: request.controller
44238
+ });
44239
+ if (before.status !== "resolved" || !samePresentation(before.presentation, presentation)) this.changed();
44010
44240
  }, (failure) => {
44011
44241
  if (!this.current(request)) return;
44012
44242
  const error = coded(failure, "file-unavailable");
@@ -44026,9 +44256,63 @@ var FileResolution = class {
44026
44256
  this.request = void 0;
44027
44257
  this.state = { status: "idle" };
44028
44258
  }
44259
+ /** Hidden views keep an answer for this document, but never accept a late answer. */
44260
+ suspend() {
44261
+ if (this.request?.pending || this.state.status === "idle") this.cancel();
44262
+ }
44263
+ invalidate() {
44264
+ this.cache?.delete(this.key);
44265
+ this.cancel();
44266
+ }
44029
44267
  destroy() {
44030
44268
  this.dead = true;
44031
- this.cancel();
44269
+ this.cache?.release(this, this.key);
44270
+ if (!this.cache || this.request?.pending || this.state.status !== "resolved") this.cancel();
44271
+ }
44272
+ };
44273
+ /** A source commit may replace a node view without changing its authored reference. */
44274
+ var FilePresentationCache = class {
44275
+ entries = /* @__PURE__ */ new Map();
44276
+ retain(owner, key) {
44277
+ let entry = this.entries.get(key);
44278
+ if (!entry) this.entries.set(key, entry = {
44279
+ owners: /* @__PURE__ */ new Set(),
44280
+ controllers: /* @__PURE__ */ new Set()
44281
+ });
44282
+ entry.owners.add(owner);
44283
+ }
44284
+ release(owner, key) {
44285
+ const entry = this.entries.get(key);
44286
+ if (!entry) return;
44287
+ entry.owners.delete(owner);
44288
+ queueMicrotask(() => {
44289
+ if (this.entries.get(key) === entry && !entry.owners.size) {
44290
+ for (const controller of entry.controllers) controller.abort();
44291
+ this.entries.delete(key);
44292
+ }
44293
+ });
44294
+ }
44295
+ get(key) {
44296
+ const value = this.entries.get(key)?.value;
44297
+ if (value && !value.isCurrent()) {
44298
+ this.delete(key);
44299
+ return;
44300
+ }
44301
+ return value;
44302
+ }
44303
+ set(key, value) {
44304
+ const entry = this.entries.get(key);
44305
+ if (!entry) return;
44306
+ entry.value = value;
44307
+ entry.controllers.add(value.controller);
44308
+ }
44309
+ delete(key) {
44310
+ const entry = this.entries.get(key);
44311
+ if (entry) {
44312
+ for (const controller of entry.controllers) controller.abort();
44313
+ entry.controllers.clear();
44314
+ entry.value = void 0;
44315
+ }
44032
44316
  }
44033
44317
  };
44034
44318
  //#endregion
@@ -44117,7 +44401,7 @@ function nodeText(node, missingImages, fileDescriptions) {
44117
44401
  if (node.type.name === "image") {
44118
44402
  const description = fileDescriptions.get(node.attrs);
44119
44403
  if (description != null) return description;
44120
- const alt = fileReference(node).label || "Image";
44404
+ const alt = fileFields(node).label || "Image";
44121
44405
  return missingImages.has(String(node.attrs.src ?? "")) ? `[${alt}: image unavailable]` : alt;
44122
44406
  }
44123
44407
  if (node.type.name === "table") {
@@ -44131,7 +44415,9 @@ function nodeText(node, missingImages, fileDescriptions) {
44131
44415
  }
44132
44416
  return clipboardText(node.content, missingImages, fileDescriptions);
44133
44417
  }
44134
- function clipboardDOM(content, schema) {
44418
+ /** `linkSchemes` are the schemes beyond the built-in ones that a copied link keeps. Print and paste
44419
+ * leave it out, so they keep only the built-in ones. */
44420
+ function clipboardDOM(content, schema, linkSchemes) {
44135
44421
  content = shareableFragment(content);
44136
44422
  const container = document.createElement("div");
44137
44423
  const serializer = DOMSerializer.fromSchema(schema);
@@ -44144,7 +44430,7 @@ function clipboardDOM(content, schema) {
44144
44430
  }]
44145
44431
  }, {
44146
44432
  ...serializer.marks,
44147
- link: (mark, inline) => isSafeLinkTarget(String(mark.attrs.href ?? "")) ? serializer.marks.link(mark, inline) : ["span", 0]
44433
+ link: (mark, inline) => isSafeLinkTarget(String(mark.attrs.href ?? ""), linkSchemes) ? serializer.marks.link(mark, inline) : ["span", 0]
44148
44434
  });
44149
44435
  container.append(safe.serializeFragment(content));
44150
44436
  if (!onlyDefinitions(content)) for (const definition of container.querySelectorAll("[data-inkkit-reference-definition]")) definition.remove();
@@ -44163,18 +44449,18 @@ function clipboardDOM(content, schema) {
44163
44449
  }
44164
44450
  return container;
44165
44451
  }
44166
- function clipboardContent(content, schema) {
44452
+ function clipboardContent(content, schema, linkSchemes) {
44167
44453
  return {
44168
44454
  text: clipboardText(content),
44169
- html: clipboardDOM(content, schema).innerHTML
44455
+ html: clipboardDOM(content, schema, linkSchemes).innerHTML
44170
44456
  };
44171
44457
  }
44172
- function clipboardSerializer(schema) {
44458
+ function clipboardSerializer(schema, linkSchemes) {
44173
44459
  const serializer = DOMSerializer.fromSchema(schema);
44174
44460
  const safe = new DOMSerializer(serializer.nodes, serializer.marks);
44175
44461
  safe.serializeFragment = (content, options = {}, target) => {
44176
44462
  const output = target ?? (options.document ?? document).createDocumentFragment();
44177
- output.append(...clipboardDOM(content, schema).childNodes);
44463
+ output.append(...clipboardDOM(content, schema, linkSchemes).childNodes);
44178
44464
  return output;
44179
44465
  };
44180
44466
  return safe;
@@ -44182,15 +44468,15 @@ function clipboardSerializer(schema) {
44182
44468
  var visibleClipboard = $prose((ctx) => new Plugin({
44183
44469
  key: new PluginKey("visibleClipboard"),
44184
44470
  props: {
44185
- clipboardSerializer: clipboardSerializer(ctx.get(schemaCtx)),
44471
+ clipboardSerializer: clipboardSerializer(ctx.get(schemaCtx), editorLinkSchemes(ctx)),
44186
44472
  clipboardTextSerializer: (slice) => clipboardText(slice.content)
44187
44473
  }
44188
44474
  }));
44189
44475
  /** Export a frozen document fragment; presentation URLs never enter the clipboard. */
44190
- async function portableClipboard(content, schema, markdown, adapter, context, completeDiagrams, files, signal = new AbortController().signal) {
44476
+ async function portableClipboard(content, schema, markdown, adapter, context, completeDiagrams, files, signal = new AbortController().signal, linkSchemes) {
44191
44477
  signal.throwIfAborted();
44192
44478
  content = shareableFragment(content);
44193
- const base = clipboardContent(content, schema);
44479
+ const base = clipboardContent(content, schema, linkSchemes);
44194
44480
  const container = document.createElement("div");
44195
44481
  container.innerHTML = base.html;
44196
44482
  const images = [];
@@ -44202,7 +44488,7 @@ async function portableClipboard(content, schema, markdown, adapter, context, co
44202
44488
  const captured = [...container.querySelectorAll("img")].map((element, index) => {
44203
44489
  const node = references[index];
44204
44490
  const reference = String(node?.attrs.src ?? "");
44205
- const { label: authoredAlt, width } = fileReference(node);
44491
+ const { label: authoredAlt, width } = fileFields(node);
44206
44492
  const alt = authoredAlt || "Image";
44207
44493
  if (width != null && width > 0) element.setAttribute("width", String(Math.min(4096, width)));
44208
44494
  element.removeAttribute("src");
@@ -44805,10 +45091,82 @@ var cursor = [
44805
45091
  //#endregion
44806
45092
  //#region src/text-input.ts
44807
45093
  var isInputRules = (plugin) => plugin.spec.isInputRules === true;
45094
+ function heldLink(doc, from, to) {
45095
+ const type = doc.type.schema.marks.link;
45096
+ if (!type) return void 0;
45097
+ let link;
45098
+ let whole = true;
45099
+ doc.nodesBetween(from, to, (node) => {
45100
+ if (!node.isInline) return true;
45101
+ const own = type.isInSet(node.marks);
45102
+ if (!own || link && !own.eq(link)) whole = false;
45103
+ link ??= own;
45104
+ return false;
45105
+ });
45106
+ return whole ? link : void 0;
45107
+ }
44808
45108
  /**
44809
- * Inserts text that arrives in one piece, such as buffered native typing,
44810
- * dictation or a text replacement, at `from` to `to`, and returns whether it
44811
- * did.
45109
+ * The marks that text put in place of `from` to `to` takes. Links are not
45110
+ * inclusive, so text typed at either end of a link stays outside it, but text
45111
+ * that replaces part or all of one link's text stays in that link. Marks
45112
+ * stored for the selection, such as a link started at the caret, come first.
45113
+ */
45114
+ function replacementMarks(state, from, to) {
45115
+ const { doc, selection, storedMarks } = state;
45116
+ if (storedMarks && selection.from === from && selection.to === to) return storedMarks;
45117
+ const $from = doc.resolve(from);
45118
+ if (from === to) return $from.marks();
45119
+ const $to = doc.resolve(to);
45120
+ const marks = $from.marksAcross($to) ?? $from.marks();
45121
+ const link = $from.sameParent($to) ? heldLink(doc, from, to) : void 0;
45122
+ return link && !link.isInSet(marks) ? link.addToSet(marks) : marks;
45123
+ }
45124
+ /**
45125
+ * The link that typing over its end continues, after typing replaced the end
45126
+ * or all of its text. Its stored marks keep it for the next keystrokes until
45127
+ * the caret moves or another edit clears them.
45128
+ */
45129
+ var typingKey = new PluginKey("inkkitTextInput");
45130
+ var typingState = {
45131
+ init: () => null,
45132
+ apply(tr, link, _old, state) {
45133
+ const meta = tr.getMeta(typingKey);
45134
+ if (meta !== void 0) return meta;
45135
+ return link && link.isInSet(state.storedMarks ?? []) ? link : null;
45136
+ }
45137
+ };
45138
+ function linkTyping(state, from, to, text) {
45139
+ const type = state.schema.marks.link;
45140
+ if (!type) return void 0;
45141
+ const marks = replacementMarks(state, from, to);
45142
+ const link = type.isInSet(marks);
45143
+ if (!link) return void 0;
45144
+ const stored = type.isInSet(state.storedMarks ?? []);
45145
+ const continued = typingKey.getState(state);
45146
+ const retyped = stored !== void 0 && continued?.eq(stored) || from < to && !type.isInSet(state.doc.resolve(from).marksAcross(state.doc.resolve(to)) ?? []);
45147
+ if (!retyped && !stored) return void 0;
45148
+ const space = retyped ? -1 : text.search(/\s/);
45149
+ const linked = space < 0 ? text.length : space;
45150
+ const address = link.attrs.form != null;
45151
+ return {
45152
+ marks,
45153
+ link,
45154
+ linked,
45155
+ stored: address ? null : linked < text.length ? link.removeFromSet(marks) : marks,
45156
+ retyped: retyped && !address
45157
+ };
45158
+ }
45159
+ function continueLink(tr, from, text, typing) {
45160
+ const linked = Math.min(typing.linked, text.length);
45161
+ tr.removeMark(from, from + text.length, typing.link.type);
45162
+ if (linked) tr.addMark(from, from + linked, typing.link);
45163
+ if (typing.stored) tr.setStoredMarks(typing.stored);
45164
+ return tr.setMeta(typingKey, typing.retyped ? typing.link : null);
45165
+ }
45166
+ /**
45167
+ * Inserts typed text at `from` to `to`, and returns whether it did. It handles
45168
+ * text that arrives in one piece, such as buffered native typing, dictation or
45169
+ * a text replacement, and typing that continues a link.
44812
45170
  *
44813
45171
  * Input rules are written for one typed character: Milkdown's take their
44814
45172
  * positions from the document before the text goes in, so given more than
@@ -44816,19 +45174,25 @@ var isInputRules = (plugin) => plugin.spec.isInputRules === true;
44816
45174
  * in except for its last character, and the input rules see that character
44817
45175
  * typed after the rest, all in one transaction.
44818
45176
  */
44819
- function insertTextChunk(view, from, to, text, deflt) {
45177
+ function insertTypedText(view, from, to, text, deflt) {
44820
45178
  const characters = Array.from(text);
44821
- if (characters.length < 2 || view.composing || /[\r\n]/.test(text)) return false;
45179
+ if (!characters.length || view.composing || /[\r\n]/.test(text)) return false;
44822
45180
  const { state } = view;
44823
- const $from = state.doc.resolve(from);
44824
- const $to = state.doc.resolve(to);
45181
+ const typing = linkTyping(state, from, to, text);
45182
+ if (characters.length < 2 && !typing) return false;
45183
+ const fallback = () => {
45184
+ const plain = deflt();
45185
+ return typing ? continueLink(plain, from, text, typing) : plain;
45186
+ };
45187
+ const marks = typing?.marks ?? replacementMarks(state, from, to);
44825
45188
  const last = characters.at(-1);
44826
45189
  const rest = text.slice(0, -last.length);
44827
- const tr = state.tr.insertText(rest, from, to);
45190
+ const tr = rest ? state.tr.setStoredMarks(marks).insertText(rest, from, to) : state.tr.delete(from, to);
45191
+ if (typing) continueLink(tr, from, rest, typing);
44828
45192
  const $at = Selection.near(tr.doc.resolve(tr.mapping.map(to)), -1).$head;
44829
45193
  const at = $at.pos;
44830
45194
  if (!$at.parent.inlineContent || $at.parent.type.spec.code || $at.parentOffset < rest.length || $at.parent.textBetween($at.parentOffset - rest.length, $at.parentOffset) !== rest) {
44831
- view.dispatch(deflt().scrollIntoView());
45195
+ view.dispatch(fallback().scrollIntoView());
44832
45196
  return true;
44833
45197
  }
44834
45198
  const rules = state.plugins.filter(isInputRules);
@@ -44836,7 +45200,7 @@ function insertTextChunk(view, from, to, text, deflt) {
44836
45200
  schema: state.schema,
44837
45201
  doc: tr.doc,
44838
45202
  selection: TextSelection.create(tr.doc, at),
44839
- storedMarks: state.storedMarks ?? (from === to ? $from.marks() : $from.marksAcross($to))
45203
+ storedMarks: typing && typing.linked < text.length ? typing.link.removeFromSet(marks) : marks
44840
45204
  }, rest);
44841
45205
  let ruled;
44842
45206
  const preparedView = Object.create(view, {
@@ -44846,7 +45210,7 @@ function insertTextChunk(view, from, to, text, deflt) {
44846
45210
  } }
44847
45211
  });
44848
45212
  if (!rules.some((rule) => rule.props.handleTextInput?.call(rule, preparedView, at, at, last, () => prepared.tr.insertText(last, at)))) {
44849
- const plain = deflt().scrollIntoView();
45213
+ const plain = fallback().scrollIntoView();
44850
45214
  view.dispatch(last === "-" ? insertedHyphen(plain) : plain);
44851
45215
  return true;
44852
45216
  }
@@ -44854,6 +45218,7 @@ function insertTextChunk(view, from, to, text, deflt) {
44854
45218
  for (const step of ruled.steps) tr.step(step);
44855
45219
  tr.setSelection(ruled.selection.map(tr.doc, StepMap.empty));
44856
45220
  if (ruled.storedMarksSet) tr.setStoredMarks(ruled.storedMarks);
45221
+ else if (typing?.stored) tr.setStoredMarks(typing.stored);
44857
45222
  for (const rule of rules) {
44858
45223
  const meta = ruled.getMeta(rule);
44859
45224
  if (meta !== void 0) tr.setMeta(rule, meta);
@@ -44862,6 +45227,65 @@ function insertTextChunk(view, from, to, text, deflt) {
44862
45227
  view.dispatch(tr.scrollIntoView());
44863
45228
  return true;
44864
45229
  }
45230
+ function finishComposition(view, composing) {
45231
+ const { state } = view;
45232
+ const { start, link, retyped } = composing;
45233
+ const end = state.selection.head;
45234
+ if (view.isDestroyed || view.composing || state.doc !== composing.doc || !state.selection.empty || end <= start || !state.doc.resolve(start).sameParent(state.selection.$head)) return;
45235
+ const text = state.doc.textBetween(start, end);
45236
+ if (text.length !== end - start) return;
45237
+ const space = retyped ? -1 : text.search(/\s/);
45238
+ const linked = start + (space < 0 ? text.length : space);
45239
+ const tr = state.tr;
45240
+ if (linked > start) tr.addMark(start, linked, link);
45241
+ const marks = link.type.removeFromSet(tr.doc.resolve(end).marks());
45242
+ tr.setStoredMarks(linked === end ? link.addToSet(marks) : marks).setMeta(typingKey, retyped && linked === end ? link : null).setMeta("composition", composing.composition);
45243
+ view.dispatch(tr);
45244
+ }
45245
+ var controlCharacters = /^[\u0000-\u0008\u000B\u000C\u000E-\u001F\u007F]+$/;
45246
+ /** Typed text: chunks, control characters, and the link rule for typing and composition. */
45247
+ function textInputPlugin() {
45248
+ let composing;
45249
+ return new Plugin({
45250
+ key: typingKey,
45251
+ state: typingState,
45252
+ props: {
45253
+ handleTextInput: (view, from, to, text, deflt) => controlCharacters.test(text) || insertTypedText(view, from, to, text, deflt),
45254
+ handleDOMEvents: {
45255
+ compositionstart(view) {
45256
+ if (view.composing) return false;
45257
+ const { state } = view;
45258
+ const { from, to } = state.selection;
45259
+ const typing = linkTyping(state, from, to, "");
45260
+ composing = typing?.stored && !view.isDestroyed ? {
45261
+ link: typing.link,
45262
+ retyped: typing.retyped,
45263
+ start: from,
45264
+ doc: state.doc
45265
+ } : void 0;
45266
+ return false;
45267
+ },
45268
+ compositionend(view) {
45269
+ const ended = composing;
45270
+ if (ended) setTimeout(() => {
45271
+ if (composing !== ended) return;
45272
+ composing = void 0;
45273
+ finishComposition(view, ended);
45274
+ });
45275
+ return false;
45276
+ }
45277
+ }
45278
+ },
45279
+ appendTransaction(transactions, _old, state) {
45280
+ if (composing && transactions.some((tr) => tr.docChanged)) for (const tr of transactions) {
45281
+ composing.start = tr.mapping.map(composing.start, -1);
45282
+ composing.composition ??= tr.getMeta("composition");
45283
+ }
45284
+ if (composing) composing.doc = state.doc;
45285
+ return null;
45286
+ }
45287
+ });
45288
+ }
44865
45289
  //#endregion
44866
45290
  //#region src/drop-placement.ts
44867
45291
  /**
@@ -44977,6 +45401,95 @@ function caretAfterInsertion(tr, view) {
44977
45401
  return tr.setSelection($end.parent.inlineContent ? TextSelection.create(tr.doc, end) : caretAfter(tr.doc, end, view && calloutFolds(view, tr)));
44978
45402
  }
44979
45403
  //#endregion
45404
+ //#region src/parser.ts
45405
+ /**
45406
+ * Milkdown's parser state, except that it throws where Milkdown would drop content. Milkdown
45407
+ * catches the failure to build a node (`createNodeInParserFail`), logs it and carries on without
45408
+ * the node and everything inside it, so the document it returns can silently lack part of its
45409
+ * source.
45410
+ */
45411
+ var CheckedParserState = class extends ParserState {
45412
+ constructor(schema) {
45413
+ super(schema);
45414
+ const closeNode = this.closeNode;
45415
+ const addNode = this.addNode;
45416
+ const unbuildable = (type, attrs, content) => {
45417
+ try {
45418
+ return !type.createAndFill(attrs, content);
45419
+ } catch {
45420
+ return true;
45421
+ }
45422
+ };
45423
+ this.closeNode = () => {
45424
+ const element = this.top();
45425
+ if (!element || this.size() < 2 || unbuildable(element.type, element.attrs, element.content)) throw new Error(`Cannot build ${element?.type.name ?? "a node"}`);
45426
+ return closeNode();
45427
+ };
45428
+ this.addNode = (type, attrs, content) => {
45429
+ if (!this.top() || unbuildable(type, attrs, content)) throw new Error(`Cannot build ${type.name}`);
45430
+ return addNode(type, attrs, content);
45431
+ };
45432
+ }
45433
+ };
45434
+ /** The document, or undefined when Milkdown cannot build all of it. */
45435
+ function build(schema, tree) {
45436
+ try {
45437
+ return new CheckedParserState(schema).next(tree).toDoc();
45438
+ } catch {
45439
+ return;
45440
+ }
45441
+ }
45442
+ /**
45443
+ * Parses Markdown as Milkdown does, but never returns a document that lacks part of its source.
45444
+ * A top-level block that cannot be built is kept as its literal source, as unsupported syntax is,
45445
+ * so the rest of the document stays editable; when that is not enough, it throws `preservation`.
45446
+ */
45447
+ function parseMarkdown(schema, remark, markdown, parsedTree) {
45448
+ const tree = parsedTree ?? remark.runSync(remark.parse(markdown), markdown);
45449
+ const doc = build(schema, tree);
45450
+ if (doc) return doc;
45451
+ const source = markdown.replace(/^\uFEFF/, "");
45452
+ tree.children = tree.children.map((node) => {
45453
+ if (build(schema, {
45454
+ type: "root",
45455
+ children: [node]
45456
+ })) return node;
45457
+ const start = node.position?.start.offset;
45458
+ const end = node.position?.end.offset;
45459
+ if (start == null || end == null) return node;
45460
+ const value = source.slice(start, end);
45461
+ return {
45462
+ type: "inkkitLiteral",
45463
+ value,
45464
+ comments: literalCommentSpans(value),
45465
+ position: node.position
45466
+ };
45467
+ });
45468
+ const kept = build(schema, tree);
45469
+ if (kept) return kept;
45470
+ throw new InkKitError("preservation", "The Markdown cannot be shown without losing part of it");
45471
+ }
45472
+ /** Replaces Milkdown's parser with `parseMarkdown`, for every caller of `parserCtx`. */
45473
+ var checkedParser = (ctx) => async () => {
45474
+ await ctx.wait(ParserReady);
45475
+ const schema = ctx.get(schemaCtx);
45476
+ ctx.set(parserCtx, (markdown) => parseMarkdown(schema, ctx.get(remarkCtx), markdown));
45477
+ };
45478
+ //#endregion
45479
+ //#region src/gutter.ts
45480
+ var listMarker = /(?:[-*+]|\d{1,9}[.)])(?=[ \t])/g;
45481
+ /**
45482
+ * What the lines of a block repeat before it: the quote markers and
45483
+ * indentation that open the line `offset` is on, up to `offset`, with a list
45484
+ * marker as the spaces it takes, which is how the lines after the item's first
45485
+ * are indented. Anything else before `offset` on its line is content, and ends
45486
+ * the gutter.
45487
+ */
45488
+ function gutterAt(text, offset) {
45489
+ const line = text.slice(text.lastIndexOf("\n", offset - 1) + 1, offset);
45490
+ return /^(?:[ \t]|>|(?:[-*+]|\d{1,9}[.)])(?=[ \t]))*/.exec(line)[0].replace(listMarker, (marker) => " ".repeat(marker.length));
45491
+ }
45492
+ //#endregion
44980
45493
  //#region src/code-fences.ts
44981
45494
  function readFence(value) {
44982
45495
  if (typeof value !== "object" || value == null) return null;
@@ -45368,10 +45881,11 @@ var formatting = /* @__PURE__ */ new Set([
45368
45881
  "delete",
45369
45882
  "highlight"
45370
45883
  ]);
45884
+ var linkTypes = /* @__PURE__ */ new Set(["link", "linkReference"]);
45371
45885
  function ordered(node) {
45372
45886
  let current = node;
45373
45887
  const inner = current.children?.[0];
45374
- if (current.type === "link" && current.children.length === 1 && formatting.has(inner.type)) current = {
45888
+ if (linkTypes.has(current.type) && current.children.length === 1 && formatting.has(inner.type)) current = {
45375
45889
  ...inner,
45376
45890
  children: [{
45377
45891
  ...current,
@@ -45383,6 +45897,32 @@ function ordered(node) {
45383
45897
  children: current.children.map(ordered)
45384
45898
  } : current;
45385
45899
  }
45900
+ function wrappers$1(node) {
45901
+ const chain = [];
45902
+ for (let at = node; at && (linkTypes.has(at.type) || formatting.has(at.type)); at = at.children?.length === 1 ? at.children[0] : void 0) chain.push(at);
45903
+ return chain;
45904
+ }
45905
+ function authoredNesting(before, after) {
45906
+ if (before.type === after.type || !formatting.has(before.type)) return after;
45907
+ const authored = wrappers$1(before);
45908
+ const written = wrappers$1(after);
45909
+ const types = (chain) => chain.map((node) => node.type).sort().join();
45910
+ const linksIn = (chain) => chain.filter((node) => linkTypes.has(node.type));
45911
+ const shape = (node) => ownShape({
45912
+ ...node,
45913
+ children: []
45914
+ });
45915
+ const [authoredLink] = linksIn(authored);
45916
+ const [writtenLink] = linksIn(written);
45917
+ if (linksIn(authored).length !== 1 || linksIn(written).length !== 1 || types(authored) !== types(written) || shape(authoredLink) !== shape(writtenLink) || authored.at(-1).children?.length !== written.at(-1).children?.length) return after;
45918
+ const inner = written.at(-1).children;
45919
+ const byType = new Map(written.map((node) => [node.type, node]));
45920
+ return authored.reduceRight((child, node, level) => ({
45921
+ ...byType.get(node.type),
45922
+ ...level === 0 && { position: after.position },
45923
+ children: child ? [child] : inner
45924
+ }), void 0);
45925
+ }
45386
45926
  /** Where the delimiter row ends, given where the header row before it ends. */
45387
45927
  function delimiterRowEnd(text, headerEnd) {
45388
45928
  const next = text.indexOf("\n", headerEnd);
@@ -45391,15 +45931,17 @@ function delimiterRowEnd(text, headerEnd) {
45391
45931
  ending.lastIndex = next + 1;
45392
45932
  return ending.exec(text)?.index ?? text.length;
45393
45933
  }
45394
- function ownShape(node) {
45934
+ function ownShape(node, spreadless = false) {
45395
45935
  return JSON.stringify(ordered(node), (key, value) => [
45396
45936
  "position",
45397
45937
  "data",
45398
45938
  "children",
45399
45939
  "value",
45400
45940
  "marker"
45401
- ].includes(key) ? void 0 : value);
45941
+ ].includes(key) || spreadless && key === "spread" ? void 0 : value);
45402
45942
  }
45943
+ var holdsLiteralBlock = (node) => (node.type === "listItem" || node.type === "list") && !!node.children?.some((child) => child.type === "inkkitLiteral" || literals$1.has(child) || holdsLiteralBlock(child));
45944
+ var sameShape = (before, after) => ownShape(before) === ownShape(after) || (holdsLiteralBlock(before) || holdsLiteralBlock(after)) && ownShape(before, true) === ownShape(after, true);
45403
45945
  var matchShape = (node) => ownShape({
45404
45946
  ...node,
45405
45947
  checked: void 0
@@ -45456,10 +45998,25 @@ function authoredMarker(list, source) {
45456
45998
  return at == null ? void 0 : markerAt(source, at)?.marker;
45457
45999
  }
45458
46000
  var sameList = (before, after) => before.type === "list" && after.type === "list" && before.ordered === after.ordered;
45459
- function canonicalSlice(marker, children, canonical, from, to, previous, next) {
46001
+ function childGutters(before, after, source, canonical, fromStart = false) {
46002
+ if (inlineParents.has(before.type) || before.type === "table") return void 0;
46003
+ const [first, now] = [before, after].map((node) => fromStart ? node.position?.start.offset : node.children?.[0]?.position?.start.offset);
46004
+ return first == null || now == null ? void 0 : [gutterAt(source, first), gutterAt(canonical, now)];
46005
+ }
46006
+ function regutter(text, gutters, open = false) {
46007
+ if (!gutters || gutters[0] === gutters[1]) return text;
46008
+ const [authored, canonical] = gutters;
46009
+ const bare = canonical.trimEnd();
46010
+ return text.replace(/(\r?\n)([^\r\n]*)/g, (match, ending, line, at) => {
46011
+ if (open && at + match.length === text.length && (line === bare || line === canonical)) return ending + authored;
46012
+ if (line === bare) return ending + authored.trimEnd();
46013
+ return line.startsWith(canonical) ? ending + authored + line.slice(canonical.length) : match;
46014
+ });
46015
+ }
46016
+ function canonicalSlice(marker, children, canonical, from, to, previous, next, gutters, open = false, inherited) {
45460
46017
  let text = canonical.slice(from, to);
45461
46018
  const offsets = (list) => (list.children ?? []).map((item) => item.position.start.offset - from);
45462
- if (marker) return relabel(text, children.map((item) => item.position.start.offset - from), marker);
46019
+ if (marker) return regutter(relabel(text, children.map((item) => item.position.start.offset - from), marker), gutters, open);
45463
46020
  children.forEach((child, index) => {
45464
46021
  if (child.type !== "list") {
45465
46022
  previous = void 0;
@@ -45467,19 +46024,24 @@ function canonicalSlice(marker, children, canonical, from, to, previous, next) {
45467
46024
  }
45468
46025
  const avoid = [previous, index === children.length - 1 ? next : void 0].filter((neighbour) => neighbour?.ordered === child.ordered).map((neighbour) => neighbour.marker);
45469
46026
  const own = markerAt(canonical, child.children[0].position.start.offset)?.marker;
45470
- const choice = [own, ...child.ordered ? [".", ")"] : [
45471
- "-",
45472
- "*",
45473
- "+"
45474
- ]].find((candidate) => candidate && !avoid.includes(candidate));
46027
+ const choice = [
46028
+ inherited && /[.)]/.test(inherited) === !!child.ordered ? inherited : void 0,
46029
+ own,
46030
+ ...child.ordered ? [".", ")"] : [
46031
+ "-",
46032
+ "*",
46033
+ "+"
46034
+ ]
46035
+ ].find((candidate) => candidate && !avoid.includes(candidate));
45475
46036
  if (choice) text = relabel(text, offsets(child), choice);
45476
46037
  previous = {
45477
46038
  ordered: child.ordered,
45478
46039
  marker: choice ?? own ?? ""
45479
46040
  };
45480
46041
  });
45481
- return text;
46042
+ return regutter(text, gutters, open);
45482
46043
  }
46044
+ var itemMarker = (item, source) => item.type === "listItem" ? markerAt(source, item.position.start.offset)?.marker : void 0;
45483
46045
  function authoredNeighbour(before, after, source) {
45484
46046
  if (!before || !after || !sameList(before, after)) return void 0;
45485
46047
  const marker = authoredMarker(before, source);
@@ -45579,35 +46141,90 @@ function retainSplit(candidates, after, source, canonical, lineEnding, taken) {
45579
46141
  text: retainSource(part, after, source, canonical, lineEnding)
45580
46142
  };
45581
46143
  }
45582
- function retainParts(marker, before, after, source, canonical, from, to, lineEnding, previous, next) {
45583
- let items;
46144
+ function liftedSource(blocks, after, source) {
46145
+ if (after.type === "list" || after.type === "blockquote") return void 0;
46146
+ const wanted = signature(after);
46147
+ const found = [];
46148
+ const visit = (node) => {
46149
+ for (const child of node.children ?? []) if (child.type === "list" || child.type === "listItem" || child.type === "blockquote") visit(child);
46150
+ else if ((node.type === "blockquote" || node.type === "listItem" && node.checked == null) && signature(child) === wanted) found.push(child);
46151
+ };
46152
+ blocks.forEach(visit);
46153
+ if (found.length !== 1) return void 0;
46154
+ const start = found[0].position?.start.offset;
46155
+ const end = found[0].position?.end.offset;
46156
+ if (start == null || end == null) return void 0;
46157
+ return regutter(source.slice(start, end), ["", gutterAt(source, start)]);
46158
+ }
46159
+ function strayRun(strays, wanted, source, opening) {
46160
+ const keys = wanted.map(opening);
46161
+ for (const [at, stray] of strays.entries()) {
46162
+ if (opening(stray) !== keys[0]) continue;
46163
+ const run = [stray];
46164
+ for (const next of strays.slice(at + 1)) {
46165
+ if (opening(next) !== keys[run.length] || !/^[ \t>]*(\r?\n[ \t>]*)+$/.test(source.slice(run.at(-1).position.end.offset, next.position.start.offset))) break;
46166
+ run.push(next);
46167
+ }
46168
+ return run;
46169
+ }
46170
+ return [];
46171
+ }
46172
+ function retainParts(marker, before, after, source, canonical, from, to, lineEnding, previous, next, gutters, open = false, inherited) {
45584
46173
  const taken = /* @__PURE__ */ new Set();
45585
46174
  const opening = (item) => item.children?.length ? matchShape(item) + signature(item.children[0]) : void 0;
45586
- const pieces = after.map((child) => {
46175
+ const items = /* @__PURE__ */ new Map();
46176
+ for (let row = before; row.length;) {
46177
+ const deeper = [];
46178
+ for (const node of row) {
46179
+ if (node.type !== "listItem") continue;
46180
+ const key = opening(node);
46181
+ if (key) {
46182
+ const same = items.get(key);
46183
+ if (same) same.push(node);
46184
+ else items.set(key, [node]);
46185
+ }
46186
+ for (const child of node.children ?? []) if (child.type === "list") deeper.push(...child.children ?? []);
46187
+ }
46188
+ row = deeper;
46189
+ }
46190
+ const isNumbered = (item) => /[.)]/.test(itemMarker(item, source) ?? "");
46191
+ const originals = after.map((child) => {
46192
+ const original = child.type === "listItem" ? items.get(opening(child) ?? "")?.shift() : void 0;
46193
+ return original && marker && isNumbered(original) !== /[.)]/.test(marker) ? void 0 : original;
46194
+ });
46195
+ const ownLevel = new Set(before);
46196
+ const strays = [...items.values()].flat().sort((left, right) => left.position.start.offset - right.position.start.offset);
46197
+ const pieces = after.map((child, index) => {
45587
46198
  const end = child.position.end.offset;
45588
46199
  if (child.type === "list" || child.type === "blockquote") {
45589
46200
  const part = retainSplit(before, child, source, canonical, lineEnding, taken);
45590
46201
  if (part) return part.text + canonical.slice(child.children.at(-1).position.end.offset, end);
45591
46202
  const lists = before.filter((node) => node.type === "list" && node.ordered === child.ordered);
45592
- return child.type === "list" && lists.length ? retainParts(authoredMarker(lists[0], source), lists.flatMap((list) => list.children ?? []), child.children, source, canonical, child.position.start.offset, end, lineEnding) : void 0;
45593
- }
45594
- if (child.type !== "listItem") return void 0;
45595
- if (!items) {
45596
- items = /* @__PURE__ */ new Map();
45597
- for (const node of before) {
45598
- const key = node.type === "listItem" ? opening(node) : void 0;
45599
- if (!key) continue;
45600
- const same = items.get(key);
45601
- if (same) same.push(node);
45602
- else items.set(key, [node]);
46203
+ if (child.type === "list" && !lists.length) {
46204
+ const run = strayRun(strays, child.children, source, opening);
46205
+ if (!run.length) return void 0;
46206
+ const first = run[0].position.start.offset;
46207
+ return regutter(retainSource({
46208
+ type: "list",
46209
+ ordered: child.ordered,
46210
+ start: child.ordered ? Number(/^\d+/.exec(source.slice(first))[0]) : null,
46211
+ spread: false,
46212
+ children: run,
46213
+ position: {
46214
+ start: { offset: first },
46215
+ end: { offset: run.at(-1).position.end.offset }
46216
+ }
46217
+ }, child, source, canonical, lineEnding), gutters && [gutters[0], gutterAt(source, first)]) + canonical.slice(child.children.at(-1).position.end.offset, end);
45603
46218
  }
46219
+ return child.type === "list" && lists.length ? retainParts(authoredMarker(lists[0], source), lists.flatMap((list) => list.children ?? []), child.children, source, canonical, child.position.start.offset, end, lineEnding, void 0, void 0, childGutters(lists[0], child, source, canonical)) : void 0;
45604
46220
  }
45605
- const original = items.get(opening(child) ?? "")?.shift();
45606
- const text = original ? retainSource(original, child, source, canonical, lineEnding) : child.children?.some((node) => node.type === "list" || node.type === "blockquote") ? retainParts(void 0, before.flatMap((node) => node.type === "listItem" ? node.children ?? [] : []), child.children, source, canonical, child.position.start.offset, end, lineEnding) : void 0;
46221
+ if (child.type !== "listItem") return void 0;
46222
+ const original = originals[index];
46223
+ const text = original ? regutter(retainSource(original, child, source, canonical, lineEnding, false, void 0, void 0, strays), gutters && [gutters[0], gutterAt(source, original.position.start.offset)]) : child.children?.some((node) => node.type === "list" || node.type === "blockquote") ? retainParts(void 0, before.flatMap((node) => node.type === "listItem" ? node.children ?? [] : []), child.children, source, canonical, child.position.start.offset, end, lineEnding, void 0, void 0, childGutters(before.find((node) => node.type === "listItem"), child, source, canonical)) : void 0;
45607
46224
  return text !== void 0 && marker ? relabel(text, [0], marker) : text;
45608
46225
  });
45609
46226
  if (pieces.every((piece) => piece === void 0)) return void 0;
45610
- let text = "", at = from;
46227
+ let text = "", at = from, lastNumber;
45611
46228
  after.forEach((child, index) => {
45612
46229
  const childStart = child.position.start.offset;
45613
46230
  const childEnd = child.position.end.offset;
@@ -45615,15 +46232,18 @@ function retainParts(marker, before, after, source, canonical, from, to, lineEnd
45615
46232
  let piece = pieces[index];
45616
46233
  const own = piece && markerAt(piece, 0)?.marker;
45617
46234
  if (child.type === "list" && [previous, following].some((neighbour) => neighbour?.ordered === child.ordered && neighbour?.marker === own)) piece = void 0;
45618
- piece ??= canonicalSlice(marker, [child], canonical, childStart, childEnd, previous, following);
46235
+ const number = /^\d+/.exec(piece ?? "")?.[0];
46236
+ if (piece && number && marker && lastNumber !== void 0 && originals[index] && !ownLevel.has(originals[index]) && String(lastNumber + 1).length === number.length) piece = String(lastNumber + 1) + piece.slice(number.length);
46237
+ piece ??= canonicalSlice(marker, [child], canonical, childStart, childEnd, previous, following, gutters, false, inherited);
46238
+ lastNumber = child.type === "listItem" && /^\d/.test(piece) ? Number(/^\d+/.exec(piece)[0]) : void 0;
45619
46239
  previous = child.type === "list" ? {
45620
46240
  ordered: child.ordered,
45621
46241
  marker: markerAt(piece, /^[ \t]*/.exec(piece)[0].length)?.marker ?? ""
45622
46242
  } : void 0;
45623
- text += canonical.slice(at, childStart) + piece;
46243
+ text += regutter(canonical.slice(at, childStart), gutters, true) + piece;
45624
46244
  at = childEnd;
45625
46245
  });
45626
- return text + canonical.slice(at, to);
46246
+ return text + regutter(canonical.slice(at, to), gutters, open);
45627
46247
  }
45628
46248
  function decodedOffsets(raw, lines = false) {
45629
46249
  const offsets = [0];
@@ -45631,7 +46251,7 @@ function decodedOffsets(raw, lines = false) {
45631
46251
  for (let index = 0; index < raw.length;) {
45632
46252
  const rest = raw.slice(index);
45633
46253
  const line = /[ \t]/.test(raw[index - 1] ?? "") && /[ \t]/.test(raw[index]) ? void 0 : /^[ \t]*(?:\r\n?|\n)(?:[ \t]*>)*[ \t]*/.exec(rest)?.[0];
45634
- const token = line ?? /^\\[!"#$%&'()*+,\-./:;<=>?@[\]\\^_`{|}~]/.exec(rest)?.[0] ?? /^&(?:#[xX][\da-fA-F]+|#\d+|[A-Za-z][A-Za-z\d]+);/.exec(rest)?.[0];
46254
+ let token = line ?? /^\\[!"#$%&'()*+,\-./:;<=>?@[\]\\^_`{|}~]/.exec(rest)?.[0] ?? /^&(?:#[xX][\da-fA-F]+|#\d+|[A-Za-z][A-Za-z\d]+);/.exec(rest)?.[0];
45635
46255
  let value = raw[index];
45636
46256
  if (line) {
45637
46257
  if (!lines) return void 0;
@@ -45640,7 +46260,8 @@ function decodedOffsets(raw, lines = false) {
45640
46260
  else if (token) {
45641
46261
  const element = document.createElement("textarea");
45642
46262
  element.innerHTML = token;
45643
- value = element.value;
46263
+ if (element.value === token) token = void 0;
46264
+ else value = element.value;
45644
46265
  }
45645
46266
  index += token?.length ?? 1;
45646
46267
  decoded += value;
@@ -45757,7 +46378,10 @@ function retainRun(before, after, source, canonical, [from, to], [newFrom, newTo
45757
46378
  }
45758
46379
  return source.slice(from, old.offsets[prefix]) + canonical.slice(now.offsets[prefix], now.offsets[b.length - suffix]) + (now.breaks.has(b.length - suffix - 1) ? retainBoundaryText(source.slice(old.offsets[a.length - suffix], to), canonical.slice(now.offsets[b.length - suffix], newTo), true) : source.slice(old.offsets[a.length - suffix], to));
45759
46380
  }
45760
- function retainText(raw, before, after, inTable = false, written, beside = [false, false]) {
46381
+ function retainText(raw, before, after, inTable = false, written, beside = [false, false], readGutters) {
46382
+ let found;
46383
+ const gutters = () => found ??= readGutters?.();
46384
+ const guttered = (text) => text.includes("\n") ? regutter(text, gutters()) : text;
45761
46385
  before = lf(before);
45762
46386
  after = lf(after);
45763
46387
  let prefix = 0;
@@ -45775,19 +46399,34 @@ function retainText(raw, before, after, inTable = false, written, beside = [fals
45775
46399
  const marker = /^(?:\d+)?\\[#+.\->)]/.exec(opening)?.[0];
45776
46400
  if (marker && prefix - lineStart <= decodedOffsets(marker).decoded.length) {
45777
46401
  prefix = lineStart;
45778
- return raw.slice(0, offsets[prefix]) + written.slice(canonical.offsets[prefix], canonical.offsets[after.length - suffix]) + raw.slice(offsets[before.length - suffix]);
46402
+ return raw.slice(0, offsets[prefix]) + guttered(written.slice(canonical.offsets[prefix], canonical.offsets[after.length - suffix])) + raw.slice(offsets[before.length - suffix]);
45779
46403
  }
45780
46404
  }
45781
46405
  }
45782
46406
  if (written !== void 0 && (beside[0] && prefix === 0 || beside[1] && suffix === 0)) {
45783
46407
  const writtenOffsets = decodedOffsets(written);
45784
- if (writtenOffsets && writtenOffsets.decoded === after) return raw.slice(0, offsets[prefix]) + written.slice(writtenOffsets.offsets[prefix], writtenOffsets.offsets[after.length - suffix]) + raw.slice(offsets[before.length - suffix]);
46408
+ if (writtenOffsets && writtenOffsets.decoded === after) return raw.slice(0, offsets[prefix]) + guttered(written.slice(writtenOffsets.offsets[prefix], writtenOffsets.offsets[after.length - suffix])) + raw.slice(offsets[before.length - suffix]);
45785
46409
  }
45786
46410
  const inserted = after.slice(prefix, after.length - suffix).split("").map((character, index) => {
45787
46411
  const at = prefix + index;
45788
- return (character === "%" || character === "=") && (after[at - 1] === character || after[at + 1] === character) || /[\\`*_[\]<>]/.test(character) || inTable && character === "|" ? `\\${character}` : character;
46412
+ return (character === "%" || character === "=") && (after[at - 1] === character || after[at + 1] === character) || /[\\`*_[\]<>]/.test(character) || inTable && character === "|" ? `\\${character}` : character === "\n" ? character + (gutters()?.[0] ?? "") : character;
45789
46413
  }).join("");
45790
- return raw.slice(0, offsets[prefix]) + inserted + raw.slice(offsets[before.length - suffix]);
46414
+ return escapeReferences(raw.slice(0, offsets[prefix]), inserted, raw.slice(offsets[before.length - suffix]));
46415
+ }
46416
+ var characterReference = /&(?:#[xX][\da-fA-F]+|#\d+|[A-Za-z][A-Za-z\d]*);/g;
46417
+ function escapeReferences(head, inserted, tail) {
46418
+ const text = head + inserted + tail;
46419
+ const from = head.length;
46420
+ const to = from + inserted.length;
46421
+ let output = "", at = 0;
46422
+ for (const match of text.matchAll(characterReference)) {
46423
+ const start = match.index;
46424
+ const end = start + match[0].length;
46425
+ if (start >= to || end <= from || (/\\*$/.exec(text.slice(0, start))[0].length & 1) === 1) continue;
46426
+ output += text.slice(at, end - 1) + "\\";
46427
+ at = end - 1;
46428
+ }
46429
+ return output + text.slice(at);
45791
46430
  }
45792
46431
  function retainBoundaryText(raw, canonical, afterBreak = false) {
45793
46432
  if (afterBreak) {
@@ -45984,6 +46623,7 @@ function markLiterals(raw, transformed, blocks = []) {
45984
46623
  raw.children.forEach(mark);
45985
46624
  }
45986
46625
  function retainSource(before, after, source, canonical, lineEnding, inTable = false, width, beside = [false, false], joined = [], reorder, afterBreak = false) {
46626
+ after = authoredNesting(before, after);
45987
46627
  if (literals$1.has(after)) return withEnding(retainLiteral(source.slice(before.position.start.offset, before.position.end.offset), literalForms.get(before), lf(canonical.slice(after.position.start.offset, after.position.end.offset))), lineEnding);
45988
46628
  if (before.type === "list" && after.type === "list" && before.ordered && after.ordered && !numbered.has(before) && before.position?.start.offset != null) {
45989
46629
  const own = {
@@ -46080,15 +46720,20 @@ function retainSource(before, after, source, canonical, lineEnding, inTable = fa
46080
46720
  const to = first.position.end.offset - start;
46081
46721
  return retained.startsWith(source.slice(start, before.children[1].position.start.offset)) ? retained.slice(0, from) + "<br />" + retained.slice(to) : retained;
46082
46722
  }
46083
- if (before.type === "text" && after.type === "text") return retainBoundaryText(withEnding(retainText(raw, before.value, after.value, inTable, canonical.slice(newStart, newEnd), beside), lineEnding), canonical.slice(newStart, newEnd), afterBreak);
46723
+ if (before.type === "text" && after.type === "text") return retainBoundaryText(withEnding(retainText(raw, before.value, after.value, inTable, canonical.slice(newStart, newEnd), beside, () => [gutterAt(source, start), gutterAt(canonical, newStart)]), lineEnding), canonical.slice(newStart, newEnd), afterBreak);
46084
46724
  if (signature(before) === signature(after) && ownShape(before) === ownShape(after) && !holdsLiteral(after) && !reorderedWithin(before, reorder)) return raw;
46725
+ if (before.type !== after.type) {
46726
+ const lifted = liftedSource([before], after, source);
46727
+ if (lifted !== void 0) return lifted;
46728
+ }
46085
46729
  const marker = sameList(before, after) ? authoredMarker(before, source) : void 0;
46086
46730
  const located = (nodes) => nodes.every((node) => node.position?.start.offset != null);
46087
- if (before.type === after.type && ownShape(before) === ownShape(after) && before.children && after.children && before.children.length === after.children.length && located(before.children) && located(after.children)) {
46731
+ if (before.type === after.type && sameShape(before, after) && before.children && after.children && before.children.length === after.children.length && located(before.children) && located(after.children)) {
46088
46732
  let output = "", at = start;
46089
46733
  let previous;
46090
46734
  const same = (candidate, incoming) => signature(candidate) === signature(incoming) && ownShape(candidate) === ownShape(incoming);
46091
46735
  const reorderable = before.type === "table" || before.type === "tableRow";
46736
+ const gutters = childGutters(before, after, source, canonical);
46092
46737
  const moves = reorder?.get(parentKey(before));
46093
46738
  const retained = new Set(reorderable ? before.children.flatMap((child, index) => same(child, after.children[index]) ? [index] : []) : []);
46094
46739
  before.children.forEach((child, index) => {
@@ -46099,7 +46744,7 @@ function retainSource(before, after, source, canonical, lineEnding, inTable = fa
46099
46744
  if (movedIndex >= 0) retained.add(movedIndex);
46100
46745
  const childStart = child.position?.start.offset, childEnd = child.position?.end.offset;
46101
46746
  if (childStart == null || childEnd == null) throw new PreservationError("A source token has no location");
46102
- let retainedSource = rewrittenAddress(before.type, before.children, movedIndex < 0 ? index : movedIndex, after.children, index, source, canonical) ? canonical.slice(incoming.position.start.offset, incoming.position.end.offset) : incoming.type === "list" && !sameList(original, incoming) ? withEnding(canonicalSlice(void 0, [incoming], canonical, incoming.position.start.offset, incoming.position.end.offset, previous, authoredNeighbour(before.children[index + 1], after.children[index + 1], source)), lineEnding) : retainSource(original, incoming, source, canonical, lineEnding, inTable || before.type === "tableCell", before.type === "table" ? before.children[0]?.children?.length : void 0, [after.children[index - 1]?.type === "link", after.children[index + 1]?.type === "link"], [], reorder, after.children[index - 1]?.type === "break");
46747
+ let retainedSource = rewrittenAddress(before.type, before.children, movedIndex < 0 ? index : movedIndex, after.children, index, source, canonical) ? regutter(canonical.slice(incoming.position.start.offset, incoming.position.end.offset), gutters) : incoming.type === "list" && !sameList(original, incoming) ? withEnding(canonicalSlice(void 0, [incoming], canonical, incoming.position.start.offset, incoming.position.end.offset, previous, authoredNeighbour(before.children[index + 1], after.children[index + 1], source), gutters), lineEnding) : retainSource(original, incoming, source, canonical, lineEnding, inTable || before.type === "tableCell", before.type === "table" ? before.children[0]?.children?.length : void 0, [after.children[index - 1]?.type === "link", after.children[index + 1]?.type === "link"], joined, reorder, after.children[index - 1]?.type === "break");
46103
46748
  if (before.type === "tableRow" && movedIndex >= 0 && movedIndex !== index) {
46104
46749
  const originalFrom = original.children?.[0]?.position?.start.offset;
46105
46750
  const originalTo = original.children?.at(-1)?.position?.end.offset;
@@ -46118,7 +46763,7 @@ function retainSource(before, after, source, canonical, lineEnding, inTable = fa
46118
46763
  });
46119
46764
  return output + source.slice(at, end);
46120
46765
  }
46121
- if (before.type === after.type && ownShape(before) === ownShape(after) && before.children && after.children) {
46766
+ if (before.type === after.type && sameShape(before, after) && before.children && after.children) {
46122
46767
  let prefix = 0, suffix = 0;
46123
46768
  while (prefix < before.children.length && prefix < after.children.length && signature(before.children[prefix]) === signature(after.children[prefix])) prefix++;
46124
46769
  while (suffix < before.children.length - prefix && suffix < after.children.length - prefix && signature(before.children[before.children.length - 1 - suffix]) === signature(after.children[after.children.length - 1 - suffix])) suffix++;
@@ -46143,13 +46788,15 @@ function retainSource(before, after, source, canonical, lineEnding, inTable = fa
46143
46788
  const changes = after.children.slice(prefix, after.children.length - suffix);
46144
46789
  const previous = authoredNeighbour(before.children[prefix - 1], after.children[prefix - 1], source);
46145
46790
  const next = authoredNeighbour(before.children[before.children.length - suffix], after.children[after.children.length - suffix], source);
46146
- return source.slice(start, first) + retainBoundaryText(source.slice(first, from) + withEnding((inlineParents.has(before.type) ? retainRun(changed, changes, source, canonical, [from, to], [newFrom, newTo]) : retainParts(marker, [...changed, ...joined], changes, source, canonical, newFrom, newTo, lineEnding, previous, next)) ?? canonicalSlice(marker, changes, canonical, newFrom, newTo, previous, next), lineEnding) + (inlineParents.has(before.type) ? retainBoundaryText(source.slice(to, last), canonical.slice(newTo, newLast), after.children[after.children.length - suffix - 1]?.type === "break") : source.slice(to, last)), canonical.slice(newFirst, newLast)) + source.slice(last, end);
46791
+ const gutters = childGutters(before, after, source, canonical, !prefix);
46792
+ const inherited = itemMarker(before, source);
46793
+ return source.slice(start, first) + retainBoundaryText(source.slice(first, from) + withEnding((inlineParents.has(before.type) ? retainRun(changed, changes, source, canonical, [from, to], [newFrom, newTo]) : retainParts(marker, [...changed, ...joined], changes, source, canonical, newFrom, newTo, lineEnding, previous, next, gutters, suffix > 0, inherited)) ?? canonicalSlice(marker, changes, canonical, newFrom, newTo, previous, next, gutters, suffix > 0, inherited), lineEnding) + (inlineParents.has(before.type) ? retainBoundaryText(source.slice(to, last), canonical.slice(newTo, newLast), after.children[after.children.length - suffix - 1]?.type === "break") : source.slice(to, last)), canonical.slice(newFirst, newLast)) + source.slice(last, end);
46147
46794
  }
46148
46795
  }
46149
46796
  const lastAfter = after.children?.at(-1)?.position?.end.offset;
46150
46797
  const lastBefore = before.children?.at(-1)?.position?.end.offset;
46151
46798
  const trailing = marker != null && lastAfter != null && lastBefore != null && loose(before, source) === loose(after, canonical);
46152
- return withEnding(canonicalSlice(marker, after.children ?? [], canonical, newStart, trailing ? lastAfter : newEnd), lineEnding) + (trailing ? source.slice(lastBefore, end) : "");
46799
+ return withEnding(canonicalSlice(marker, after.children ?? [], canonical, newStart, trailing ? lastAfter : newEnd, void 0, void 0, childGutters(before, after, source, canonical, true), false, itemMarker(before, source)), lineEnding) + (trailing ? source.slice(lastBefore, end) : "");
46153
46800
  }
46154
46801
  function loose(list, text) {
46155
46802
  const blank = (from, to) => from != null && to != null && /\n[ \t>]*\r?\n/.test(text.slice(from, to));
@@ -46246,6 +46893,13 @@ function semanticSignature(doc) {
46246
46893
  attrs
46247
46894
  };
46248
46895
  }
46896
+ if (value.type === "comment_block" && value.attrs) {
46897
+ const { block: _block, ...attrs } = value.attrs;
46898
+ value = {
46899
+ ...value,
46900
+ attrs
46901
+ };
46902
+ }
46249
46903
  const content = value.content;
46250
46904
  if (!content) return value;
46251
46905
  const tokens = [];
@@ -46288,6 +46942,7 @@ function semanticSignature(doc) {
46288
46942
  "authoredFence",
46289
46943
  "form",
46290
46944
  "inkkitFileRaw",
46945
+ "inkkitFileSource",
46291
46946
  "inkkitFileWidth",
46292
46947
  "inkkitFileInTable"
46293
46948
  ].includes(key) ? void 0 : value));
@@ -46476,6 +47131,18 @@ var nothingCarried = {
46476
47131
  var Preservation = class Preservation {
46477
47132
  ctx;
46478
47133
  source;
47134
+ static recent = /* @__PURE__ */ new WeakMap();
47135
+ processor;
47136
+ rawTree;
47137
+ sourceTree;
47138
+ static read(ctx, source) {
47139
+ const cached = this.recent.get(ctx);
47140
+ const previous = cached?.processor === ctx.get(remarkCtx) ? cached : void 0;
47141
+ if (previous?.source === source) return previous;
47142
+ const next = new Preservation(ctx, source, previous);
47143
+ this.recent.set(ctx, next);
47144
+ return next;
47145
+ }
46479
47146
  /** The document as loaded from `source`. */
46480
47147
  baseline;
46481
47148
  blocks = [];
@@ -46493,18 +47160,22 @@ var Preservation = class Preservation {
46493
47160
  nestedLiterals = /* @__PURE__ */ new Set();
46494
47161
  literalsRead = /* @__PURE__ */ new WeakSet();
46495
47162
  definitionSources;
46496
- constructor(ctx, source) {
47163
+ constructor(ctx, source, previous) {
46497
47164
  this.ctx = ctx;
46498
47165
  this.source = source;
46499
47166
  try {
46500
47167
  const parse = (text) => parseDoc(ctx, text);
46501
- this.baseline = parse(source);
46502
- this.lineEnding = source.includes("\r\n") && !source.replaceAll("\r\n", "").includes("\n") ? "\r\n" : "\n";
46503
- const processor = ctx.get(remarkCtx);
47168
+ this.lineEnding = !source.includes("\n") ? source.includes("\r") ? "\r" : "\n" : source.includes("\r\n") && !source.replaceAll("\r\n", "").includes("\n") ? "\r\n" : "\n";
47169
+ const processor = this.processor = ctx.get(remarkCtx);
46504
47170
  const offset = source.startsWith("") ? 1 : 0;
46505
47171
  const body = source.slice(offset);
46506
- const rawTree = parseTree(ctx, source);
46507
- const tree = processor.runSync(structuredClone(rawTree), { value: body });
47172
+ const incremental = previous?.readEditedSource(source);
47173
+ const rawTree = incremental?.raw ?? parseTree(ctx, source);
47174
+ const tree = incremental?.tree ?? processor.runSync(structuredClone(rawTree), { value: body });
47175
+ this.rawTree = rawTree;
47176
+ this.sourceTree = tree;
47177
+ this.baseline = incremental?.doc ?? parseMarkdown(ctx.get(schemaCtx), processor, source, structuredClone(tree));
47178
+ const positions = new Map(rawTree.children.map((node) => [`${node.position?.start.offset}:${node.position?.end.offset}`, node]));
46508
47179
  for (const [index, node] of tree.children.entries()) {
46509
47180
  let start = node.position?.start.offset;
46510
47181
  const end = node.position?.end.offset;
@@ -46516,9 +47187,9 @@ var Preservation = class Preservation {
46516
47187
  const text = source.slice(start, sourceEnd);
46517
47188
  const own = this.baseline.childCount === tree.children.length;
46518
47189
  const parsed = own ? this.baseline.type.create(null, [this.baseline.child(index)]) : parse(text);
46519
- const blockText = parsed.childCount === 1 ? blockForm(ctx, parsed.firstChild) : form(write(ctx, parsed));
47190
+ const blockText = previous?.forms.get(parsed.firstChild) ?? (parsed.childCount === 1 ? blockForm(ctx, parsed.firstChild) : form(write(ctx, parsed)));
46520
47191
  if (own) this.forms.set(this.baseline.child(index), blockText);
46521
- const ast = positionalNode(rawTree, node);
47192
+ const ast = positions.get(`${node.position?.start.offset}:${node.position?.end.offset}`) ?? node;
46522
47193
  if (parsed.childCount === 1 && parsed.firstChild.type.name === "literal_markdown") literalForms.set(ast, blockText);
46523
47194
  this.blocks.push({
46524
47195
  start,
@@ -46555,6 +47226,66 @@ var Preservation = class Preservation {
46555
47226
  throw new PreservationError("Cannot read Markdown for preservation", { cause });
46556
47227
  }
46557
47228
  }
47229
+ readEditedSource(source) {
47230
+ if (this.source.startsWith("") || source.startsWith("") || /[\[\]<>%]/.test(this.source) || /[\[\]<>%]/.test(source) || this.blocks.length !== this.baseline.childCount || this.blocks.some((_, index) => !["paragraph", "heading"].includes(this.baseline.child(index).type.name))) return;
47231
+ let prefix = 0, suffix = 0;
47232
+ while (prefix < source.length && prefix < this.source.length && source[prefix] === this.source[prefix]) prefix++;
47233
+ while (suffix < source.length - prefix && suffix < this.source.length - prefix && source[source.length - suffix - 1] === this.source[this.source.length - suffix - 1]) suffix++;
47234
+ const first = Math.max(0, this.blocks.findIndex((block) => block.end >= prefix) - 1);
47235
+ let last = this.blocks.findIndex((block) => block.start > this.source.length - suffix);
47236
+ if (last < 0) last = this.blocks.length - 1;
47237
+ else last = Math.min(this.blocks.length - 1, last + 1);
47238
+ if (!this.blocks[first] || !this.blocks[last]) return;
47239
+ const start = this.blocks[first].start;
47240
+ const end = this.blocks[last].end;
47241
+ const delta = source.length - this.source.length;
47242
+ const nextEnd = end + delta;
47243
+ if (nextEnd < start || prefix < start || this.source.length - suffix > end) return;
47244
+ const part = new Preservation(this.ctx, source.slice(start, nextEnd));
47245
+ if (part.blocks.length !== part.baseline.childCount || part.blocks.some((_, index) => !["paragraph", "heading"].includes(part.baseline.child(index).type.name))) return;
47246
+ const lines = [0];
47247
+ for (const match of source.matchAll(/\r\n|\r|\n/g)) lines.push(match.index + match[0].length);
47248
+ const point = (offset) => {
47249
+ let low = 0, high = lines.length;
47250
+ while (low + 1 < high) {
47251
+ const middle = low + high >> 1;
47252
+ if (lines[middle] <= offset) low = middle;
47253
+ else high = middle;
47254
+ }
47255
+ return {
47256
+ offset,
47257
+ line: low + 1,
47258
+ column: offset - lines[low] + 1
47259
+ };
47260
+ };
47261
+ const shifted = (node, amount) => {
47262
+ const visit = (value) => {
47263
+ if (Array.isArray(value)) return value.map(visit);
47264
+ if (!value || typeof value !== "object") return value;
47265
+ if ("offset" in value && typeof value.offset === "number") return point(value.offset + amount);
47266
+ return Object.fromEntries(Object.entries(value).map(([key, child]) => [key, visit(child)]));
47267
+ };
47268
+ return visit(node);
47269
+ };
47270
+ const splice = (old, fresh) => ({
47271
+ type: "root",
47272
+ children: [
47273
+ ...old.children.slice(0, first),
47274
+ ...fresh.children.map((node) => shifted(node, start)),
47275
+ ...old.children.slice(last + 1).map((node) => shifted(node, delta))
47276
+ ]
47277
+ });
47278
+ const children = [];
47279
+ this.baseline.forEach((node, _offset, index) => {
47280
+ if (index === first) part.baseline.forEach((child) => children.push(child));
47281
+ if (index < first || index > last) children.push(node);
47282
+ });
47283
+ return {
47284
+ raw: splice(this.rawTree, part.rawTree),
47285
+ tree: splice(this.sourceTree, part.sourceTree),
47286
+ doc: this.baseline.copy(Fragment.fromArray(children))
47287
+ };
47288
+ }
46558
47289
  retainBlock(block, after, canonical, joined = [], reorder) {
46559
47290
  const offset = this.source.startsWith("") ? 1 : 0;
46560
47291
  const start = block.ast.position?.start.offset;
@@ -46594,6 +47325,9 @@ var Preservation = class Preservation {
46594
47325
  const indent = source.slice(source.lastIndexOf("\n", part.from - 1) + 1, part.from);
46595
47326
  return (/^[ \t]*$/.test(indent) ? indent : "") + part.text;
46596
47327
  }
47328
+ retainLifted(positions, index, after) {
47329
+ return liftedSource(this.around(positions, index).map((block) => block.ast), after, this.source.slice(this.source.startsWith("") ? 1 : 0));
47330
+ }
46597
47331
  localEdits(doc) {
46598
47332
  if (this.baseline.childCount !== this.blocks.length || doc.childCount !== this.blocks.length || !doc.sameMarkup(this.baseline)) return void 0;
46599
47333
  let output = "", previous = 0;
@@ -46688,7 +47422,7 @@ var Preservation = class Preservation {
46688
47422
  static plainInline(node) {
46689
47423
  let plain = true;
46690
47424
  node.descendants((child) => {
46691
- if (child.isInline && !child.isText && child.type.name !== "hardbreak") plain = false;
47425
+ if (child.isInline && !child.isText && !["hardbreak", "footnote_reference"].includes(child.type.name)) plain = false;
46692
47426
  return plain;
46693
47427
  });
46694
47428
  return plain;
@@ -46972,6 +47706,12 @@ var Preservation = class Preservation {
46972
47706
  const block = position == null ? void 0 : this.blocks[position];
46973
47707
  if (index === 0) return position === 0 && block ? this.source.slice(0, block.start) : this.source.startsWith("") ? "" : "";
46974
47708
  const previous = positions[index - 1];
47709
+ if (previous === this.blocks.length - 1 && position == null) {
47710
+ const ending = this.source.slice(this.blocks[previous].end);
47711
+ const lines = ending.replace(/\r\n?/g, "\n");
47712
+ if (/\n[ \t]*\n/.test(lines)) return ending + (lines.endsWith("\n") ? "" : this.lineEnding);
47713
+ return ending + this.lineEnding.repeat(lines.endsWith("\n") ? 1 : 2);
47714
+ }
46975
47715
  return previous != null && position === previous + 1 && block ? this.source.slice(this.blocks[previous].end, block.start) : this.lineEnding.repeat(2);
46976
47716
  }
46977
47717
  build(doc, positions, current, carried, from, to, canonical, tree) {
@@ -47008,7 +47748,7 @@ var Preservation = class Preservation {
47008
47748
  const kind = block && block.form === text && !carried.within.has(index) ? position == null ? "moved" : "unchanged" : block && tree && position != null ? "edited" : "canonical";
47009
47749
  kept.push(kind);
47010
47750
  const node = tree ? positionalNode(tree.raw, transformed(index)) : void 0;
47011
- pieces.push(kind === "unchanged" || kind === "moved" ? this.source.slice(block.start, block.end) : kind === "edited" ? this.retainingForms(block, doc.child(index), () => this.retainBlock(block, node, canonical, this.around(positions, index).filter((other) => !paired.has(other) && other.ast.type === block.ast.type).flatMap((other) => other.ast.children ?? []), carried.children ? this.carriedChildren(doc.child(index), position) : void 0)) : (node && this.retainPart(positions, index, node, canonical, taken)) ?? restoreCodeEndings(this.ctx, doc.type.create(null, [doc.child(index)]), withEnding(text, this.lineEnding)));
47751
+ pieces.push(kind === "unchanged" || kind === "moved" ? this.source.slice(block.start, block.end) : kind === "edited" ? this.retainingForms(block, doc.child(index), () => this.retainBlock(block, node, canonical, this.around(positions, index).filter((other) => !paired.has(other) && other.ast.type === block.ast.type).flatMap((other) => other.ast.children ?? []), carried.children ? this.carriedChildren(doc.child(index), position) : void 0)) : (node && (this.retainPart(positions, index, node, canonical, taken) ?? this.retainLifted(positions, index, node))) ?? restoreCodeEndings(this.ctx, doc.type.create(null, [doc.child(index)]), withEnding(text, this.lineEnding)));
47012
47752
  }
47013
47753
  return {
47014
47754
  gaps,
@@ -47544,23 +48284,6 @@ function completedAngle(tr, chars, from, to) {
47544
48284
  form: "angle"
47545
48285
  })).removeStoredMark(tr.doc.type.schema.marks.link);
47546
48286
  }
47547
- function closeAngleEnds(tr, transactions) {
47548
- const steps = transactions.flatMap((transaction) => transaction.steps.map((step, index) => ({
47549
- doc: transaction.docs[index],
47550
- map: step.getMap()
47551
- })));
47552
- steps.forEach(({ doc, map }, index) => {
47553
- const remaining = new Mapping(steps.slice(index + 1).map(({ map }) => map));
47554
- map.forEach((from, to, start, end) => {
47555
- if (from !== to || start === end) return;
47556
- const at = doc.resolve(from);
47557
- const before = at.nodeBefore?.marks.find((mark) => mark.attrs.form === "angle");
47558
- const after = at.nodeAfter?.marks.find((mark) => mark.attrs.form === "angle");
47559
- if (before && after && before.eq(after)) return;
47560
- for (const mark of [before, after]) if (mark) tr.removeMark(remaining.map(start, 1), remaining.map(end, -1), mark);
47561
- });
47562
- });
47563
- }
47564
48287
  function codeAngles(tr, edited) {
47565
48288
  const spans = [];
47566
48289
  for (const block of edited) tr.doc.nodesBetween(block.pos, block.pos + block.block.nodeSize, (node, pos) => {
@@ -47769,10 +48492,7 @@ var autolinkLiterals = $prose((ctx) => new Plugin({
47769
48492
  if (!transactions.some((tr) => tr.docChanged)) return null;
47770
48493
  const base = newState.tr;
47771
48494
  const written = transactions.every(isWrittenEdit);
47772
- if (written) {
47773
- closeAngleEnds(base, transactions);
47774
- codeAngles(base, editedBlocks(oldState.doc, base.doc));
47775
- }
48495
+ if (written) codeAngles(base, editedBlocks(oldState.doc, base.doc));
47776
48496
  let edited = editedBlocks(oldState.doc, base.doc);
47777
48497
  if (written && edited.length === 1) {
47778
48498
  const { pos, block, from, to } = edited[0];
@@ -48024,81 +48744,6 @@ function normalizeTableColumnAlignment(element) {
48024
48744
  return normalizeTableAlignment(header ?? element);
48025
48745
  }
48026
48746
  //#endregion
48027
- //#region src/parser.ts
48028
- /**
48029
- * Milkdown's parser state, except that it throws where Milkdown would drop content. Milkdown
48030
- * catches the failure to build a node (`createNodeInParserFail`), logs it and carries on without
48031
- * the node and everything inside it, so the document it returns can silently lack part of its
48032
- * source.
48033
- */
48034
- var CheckedParserState = class extends ParserState {
48035
- constructor(schema) {
48036
- super(schema);
48037
- const closeNode = this.closeNode;
48038
- const addNode = this.addNode;
48039
- const unbuildable = (type, attrs, content) => {
48040
- try {
48041
- return !type.createAndFill(attrs, content);
48042
- } catch {
48043
- return true;
48044
- }
48045
- };
48046
- this.closeNode = () => {
48047
- const element = this.top();
48048
- if (!element || this.size() < 2 || unbuildable(element.type, element.attrs, element.content)) throw new Error(`Cannot build ${element?.type.name ?? "a node"}`);
48049
- return closeNode();
48050
- };
48051
- this.addNode = (type, attrs, content) => {
48052
- if (!this.top() || unbuildable(type, attrs, content)) throw new Error(`Cannot build ${type.name}`);
48053
- return addNode(type, attrs, content);
48054
- };
48055
- }
48056
- };
48057
- /** The document, or undefined when Milkdown cannot build all of it. */
48058
- function build(schema, tree) {
48059
- try {
48060
- return new CheckedParserState(schema).next(tree).toDoc();
48061
- } catch {
48062
- return;
48063
- }
48064
- }
48065
- /**
48066
- * Parses Markdown as Milkdown does, but never returns a document that lacks part of its source.
48067
- * A top-level block that cannot be built is kept as its literal source, as unsupported syntax is,
48068
- * so the rest of the document stays editable; when that is not enough, it throws `preservation`.
48069
- */
48070
- function parseMarkdown(schema, remark, markdown) {
48071
- const tree = remark.runSync(remark.parse(markdown), markdown);
48072
- const doc = build(schema, tree);
48073
- if (doc) return doc;
48074
- const source = markdown.replace(/^\uFEFF/, "");
48075
- tree.children = tree.children.map((node) => {
48076
- if (build(schema, {
48077
- type: "root",
48078
- children: [node]
48079
- })) return node;
48080
- const start = node.position?.start.offset;
48081
- const end = node.position?.end.offset;
48082
- if (start == null || end == null) return node;
48083
- const value = source.slice(start, end);
48084
- return {
48085
- type: "inkkitLiteral",
48086
- value,
48087
- comments: literalCommentSpans(value),
48088
- position: node.position
48089
- };
48090
- });
48091
- const kept = build(schema, tree);
48092
- if (kept) return kept;
48093
- throw new InkKitError("preservation", "The Markdown cannot be shown without losing part of it");
48094
- }
48095
- /** Replaces Milkdown's parser with `parseMarkdown`, for every caller of `parserCtx`. */
48096
- var checkedParser = (ctx) => async () => {
48097
- await ctx.wait(ParserReady);
48098
- const schema = ctx.get(schemaCtx);
48099
- ctx.set(parserCtx, (markdown) => parseMarkdown(schema, ctx.get(remarkCtx), markdown));
48100
- };
48101
- //#endregion
48102
48747
  //#region src/callouts.ts
48103
48748
  var kinds = /* @__PURE__ */ new Set([
48104
48749
  "NOTE",
@@ -48141,7 +48786,8 @@ function remarkCallouts() {
48141
48786
  const visit = (node) => {
48142
48787
  if (node.type === "blockquote") {
48143
48788
  const start = node.position?.start.offset, end = node.position?.end.offset;
48144
- const raw = start == null || end == null ? "" : source.slice(start, end);
48789
+ const gutter = start == null ? "" : gutterAt(source, start);
48790
+ const raw = start == null || end == null ? "" : source.slice(start, end).replace(/(\r?\n)([^\r\n]*)/g, (line, eol, text) => gutter && text.startsWith(gutter) ? eol + text.slice(gutter.length) : line);
48145
48791
  const header = /^>[ \t]*([\s\S]*?)(?:\r?\n|$)/.exec(raw)?.[1] ?? "";
48146
48792
  if (header.startsWith("[!")) {
48147
48793
  const match = /^(\[!([A-Za-z]+)\])([+-]?)(?:[ \t]+([^\r\n]*))?$/.exec(header);
@@ -48313,7 +48959,7 @@ var calloutView = $prose((ctx) => new Plugin({
48313
48959
  const render = () => {
48314
48960
  title.textContent = node.attrs.title || String(node.attrs.kind).toLowerCase().replace(/^./, (letter) => letter.toUpperCase());
48315
48961
  dom.setAttribute("data-inkkit-folded", String(folded));
48316
- view.dom.dispatchEvent(new Event("inkkit-media-policy"));
48962
+ view.dom.dispatchEvent(new Event(mediaPolicyEvent));
48317
48963
  button.hidden = !node.attrs.fold;
48318
48964
  button.setAttribute("aria-expanded", String(!folded));
48319
48965
  button.setAttribute("aria-label", `${folded ? editorLabels(ctx).expandCallout : editorLabels(ctx).collapseCallout} ${title.textContent}`);
@@ -50080,12 +50726,50 @@ function writesBare(node, index, ancestors) {
50080
50726
  return side(-1) && side(1);
50081
50727
  }
50082
50728
  var bareLinks = /* @__PURE__ */ new WeakSet();
50729
+ function linkFormatting(node) {
50730
+ const plain = node.type === "link" && !formOf(node) || node.type === "linkReference" && node.referenceType === "full";
50731
+ const [inner] = plain ? node.children : [];
50732
+ return plain && node.children.length === 1 && inner && wrappers.has(inner.type) ? inner : void 0;
50733
+ }
50734
+ function shareFormatting(children) {
50735
+ const same = (left, right) => {
50736
+ const { children: _left, position: _a, ...leftRest } = left;
50737
+ const { children: _right, position: _b, ...rightRest } = right;
50738
+ return JSON.stringify(leftRest) === JSON.stringify(rightRest);
50739
+ };
50740
+ for (let index = 0; index < children.length; index++) {
50741
+ const child = children[index];
50742
+ const formatting = linkFormatting(child);
50743
+ if (!formatting) continue;
50744
+ const link = {
50745
+ ...child,
50746
+ children: formatting.children
50747
+ };
50748
+ const previous = children[index - 1];
50749
+ const next = children[index + 1];
50750
+ const before = previous && same(previous, formatting);
50751
+ const after = next && same(next, formatting);
50752
+ if (!before && !after) continue;
50753
+ const merged = {
50754
+ ...formatting,
50755
+ children: [
50756
+ ...before ? previous.children : [],
50757
+ link,
50758
+ ...after ? next.children : []
50759
+ ]
50760
+ };
50761
+ const from = before ? index - 1 : index;
50762
+ children.splice(from, 1 + Number(before) + Number(after), merged);
50763
+ index = from - 1;
50764
+ }
50765
+ }
50083
50766
  var root = (node, parent, state, info) => {
50084
50767
  bareLinks = /* @__PURE__ */ new WeakSet();
50085
50768
  const empty = (child) => child.type === "text" && child.value === "" || wrappers.has(child.type) && "children" in child && child.children.every(empty);
50086
50769
  const visit = (current, ancestors) => {
50087
50770
  if (current.children.some((child) => empty(child))) current.children = current.children.filter((child) => !empty(child));
50088
50771
  const children = current.children;
50772
+ shareFormatting(children);
50089
50773
  children.forEach((child, index) => {
50090
50774
  if (child.type === "link" && formOf(child)) {
50091
50775
  const outside = formattedOutside(child);
@@ -50306,6 +50990,16 @@ var breaksWithoutMarks = $prose((ctx) => new Plugin({
50306
50990
  }
50307
50991
  }));
50308
50992
  var remarkLinkFormsPlugin = $remark("inkkitLinkForms", () => remarkLinkForms);
50993
+ var remarkFileSourcesPlugin = $remark("inkkitFileSources", () => remarkFileSources);
50994
+ var imageWriter = (ctx) => async () => {
50995
+ await ctx.wait(SerializerReady);
50996
+ const schema = ctx.get(schemaCtx);
50997
+ setImageWriter(schema, (node) => {
50998
+ node = node.mark([]);
50999
+ const block = node.isBlock ? node : schema.nodes.paragraph.create(null, node);
51000
+ return ctx.get(serializerCtx)(schema.topNodeType.create(null, block)).replace(/\n+$/, "");
51001
+ });
51002
+ };
50309
51003
  var preserveSpacerParagraphs = paragraphSchema.extendSchema((base) => (ctx) => {
50310
51004
  const schema = base(ctx);
50311
51005
  return {
@@ -50436,7 +51130,16 @@ var normalizedImages = imageSchema.extendSchema((base) => (ctx) => {
50436
51130
  ...schema.toMarkdown,
50437
51131
  runner(state, node) {
50438
51132
  const raw = namedFileMarkdown(node, Boolean(node.attrs.inkkitFileInTable));
50439
- if (raw == null) return schema.toMarkdown.runner(state, node);
51133
+ if (raw == null) {
51134
+ state.addNode("image", void 0, void 0, {
51135
+ url: node.attrs.src,
51136
+ alt: node.attrs.alt,
51137
+ title: node.attrs.title || null,
51138
+ inkkitFileSource: authoredFileSource(node),
51139
+ inkkitFileInTable: node.attrs.inkkitFileInTable
51140
+ });
51141
+ return;
51142
+ }
50440
51143
  state.addNode("image", void 0, void 0, {
50441
51144
  url: node.attrs.src,
50442
51145
  alt: node.attrs.alt,
@@ -50444,7 +51147,7 @@ var normalizedImages = imageSchema.extendSchema((base) => (ctx) => {
50444
51147
  inkkitFileRaw: raw,
50445
51148
  inkkitFileTarget: node.attrs.inkkitFileTarget,
50446
51149
  inkkitFileFragment: node.attrs.inkkitFileFragment,
50447
- inkkitFileWidth: fileReference(node).width ?? null,
51150
+ inkkitFileWidth: fileFields(node).width ?? null,
50448
51151
  inkkitFileInTable: node.attrs.inkkitFileInTable
50449
51152
  });
50450
51153
  }
@@ -50557,6 +51260,7 @@ function createDialect(images, wikiLinks = false, files = false) {
50557
51260
  remarkCodeFences,
50558
51261
  remarkReferencesPlugin,
50559
51262
  remarkLinkFormsPlugin,
51263
+ remarkFileSourcesPlugin,
50560
51264
  remarkCalloutsPlugin,
50561
51265
  remarkCommentsPlugin,
50562
51266
  wikiLinks || files ? linkedSyntax(wikiLinks, files) : [],
@@ -50576,6 +51280,7 @@ function createDialect(images, wikiLinks = false, files = false) {
50576
51280
  wikiLinkComposition,
50577
51281
  wikiLinkInputRule
50578
51282
  ] : [],
51283
+ wikiLinks || files ? referenceTableContext : [],
50579
51284
  callouts,
50580
51285
  comments,
50581
51286
  hiddenCommentEditing,
@@ -50603,7 +51308,8 @@ function createDialect(images, wikiLinks = false, files = false) {
50603
51308
  remarkDialectPlugin,
50604
51309
  tables,
50605
51310
  literalBlock,
50606
- checkedParser
51311
+ checkedParser,
51312
+ imageWriter
50607
51313
  ].flat();
50608
51314
  }
50609
51315
  createDialect(false);
@@ -51106,8 +51812,7 @@ function modeSelectionMap(ctx, source, doc) {
51106
51812
  if (at >= 0) append(value, Array.from({ length: value.length + 1 }, (_, index) => start + bom + at + index));
51107
51813
  }
51108
51814
  };
51109
- const processor = ctx.get(remarkCtx);
51110
- processor.runSync(processor.parse(source), { value: source }).children.forEach((node) => visit(node));
51815
+ Preservation.read(ctx, source).sourceTree.children.forEach((node) => visit(node));
51111
51816
  let cursor = 0;
51112
51817
  doc.descendants((node, position) => {
51113
51818
  if ([
@@ -51315,7 +52020,7 @@ var ImageView = class {
51315
52020
  view;
51316
52021
  getPos;
51317
52022
  adapter;
51318
- runtime;
52023
+ host;
51319
52024
  canMutate;
51320
52025
  dom;
51321
52026
  /** The card or picture, which a standalone image's block row holds. */
@@ -51327,22 +52032,31 @@ var ImageView = class {
51327
52032
  stopResize;
51328
52033
  resolution;
51329
52034
  previewFailed = false;
51330
- constructor(node, view, getPos, adapter, runtime, canMutate = () => true) {
52035
+ suspended = false;
52036
+ /** Present when the host sets `files`, which then shows every image. */
52037
+ runtime;
52038
+ constructor(node, view, getPos, adapter, host, files, cache, canMutate = () => true) {
51331
52039
  this.node = node;
51332
52040
  this.view = view;
51333
52041
  this.getPos = getPos;
51334
52042
  this.adapter = adapter;
51335
- this.runtime = runtime;
52043
+ this.host = host;
51336
52044
  this.canMutate = canMutate;
52045
+ const runtime = files && host && {
52046
+ ...host,
52047
+ adapter: files
52048
+ };
52049
+ this.runtime = runtime;
51337
52050
  this.element = document.createElement("span");
51338
52051
  this.element.className = "inkkit-image";
52052
+ this.element.addEventListener("contextmenu", this.contextMenu);
51339
52053
  if (node.isBlock) {
51340
52054
  this.dom = document.createElement("div");
51341
52055
  this.dom.className = "inkkit-image-block";
51342
52056
  this.dom.append(this.element);
51343
52057
  } else this.dom = this.element;
51344
52058
  this.view.dom.addEventListener("inkkit-cancel-resize", this.cancelResize);
51345
- this.view.dom.addEventListener("inkkit-media-policy", this.mediaPolicy);
52059
+ this.view.dom.addEventListener(mediaPolicyEvent, this.mediaPolicy);
51346
52060
  if (runtime) {
51347
52061
  this.resolution = new FileResolution({
51348
52062
  ...runtime,
@@ -51350,7 +52064,7 @@ var ImageView = class {
51350
52064
  }, () => {
51351
52065
  this.previewFailed = false;
51352
52066
  this.render();
51353
- });
52067
+ }, cache);
51354
52068
  this.resolution.update(this.reference());
51355
52069
  }
51356
52070
  this.render();
@@ -51379,15 +52093,37 @@ var ImageView = class {
51379
52093
  ignoreMutation() {
51380
52094
  return true;
51381
52095
  }
51382
- mediaPolicy = () => {
52096
+ mediaPolicy = (event) => {
51383
52097
  if (!this.runtime) return;
52098
+ const refresh = refreshOf(event);
52099
+ if (refresh) {
52100
+ if (!refreshes(refresh, this.reference().target)) return;
52101
+ if (!this.active()) {
52102
+ this.resolution?.invalidate();
52103
+ return;
52104
+ }
52105
+ if (this.previewFailed) this.resolution?.retry();
52106
+ else this.resolution?.refresh();
52107
+ return;
52108
+ }
51384
52109
  if (!this.active()) {
52110
+ if (this.suspended) return;
52111
+ this.suspended = true;
51385
52112
  this.stopResize?.();
51386
52113
  this.stopResize = void 0;
51387
- this.resolution?.cancel();
51388
- this.releaseMedia();
51389
- this.render();
51390
- } else this.resolution?.update(this.reference());
52114
+ this.resolution?.suspend();
52115
+ if (this.media || this.pdf) this.render();
52116
+ } else {
52117
+ const suspended = this.suspended;
52118
+ this.suspended = false;
52119
+ this.resolution?.update(this.reference());
52120
+ const state = this.resolution?.state;
52121
+ if (suspended && state?.status === "resolved" && [
52122
+ "audio",
52123
+ "video",
52124
+ "pdf"
52125
+ ].includes(state.presentation.kind)) this.render();
52126
+ }
51391
52127
  };
51392
52128
  cancelResize = () => {
51393
52129
  if (!this.stopResize) return;
@@ -51408,7 +52144,7 @@ var ImageView = class {
51408
52144
  }
51409
52145
  destroy() {
51410
52146
  this.view.dom.removeEventListener("inkkit-cancel-resize", this.cancelResize);
51411
- this.view.dom.removeEventListener("inkkit-media-policy", this.mediaPolicy);
52147
+ this.view.dom.removeEventListener(mediaPolicyEvent, this.mediaPolicy);
51412
52148
  this.stopResize?.();
51413
52149
  this.stopResize = void 0;
51414
52150
  this.resolution?.destroy();
@@ -51437,6 +52173,37 @@ var ImageView = class {
51437
52173
  report(error) {
51438
52174
  this.runtime?.error?.(coded(error, "file-unavailable"));
51439
52175
  }
52176
+ /** Hands the request to the host's hook, which replaces the web view's own menu. A file card
52177
+ * uses `FileAdapter.contextMenu`; any other image selects itself and uses
52178
+ * `ImageAdapter.contextMenu`. */
52179
+ contextMenu = (event) => {
52180
+ const { host, runtime, adapter } = this;
52181
+ if (!host?.active() || !(runtime ? runtime.adapter.contextMenu : adapter?.contextMenu)) return;
52182
+ const captured = host.capture();
52183
+ if (!captured.isCurrent()) return;
52184
+ event.preventDefault();
52185
+ const point = {
52186
+ clientX: event.clientX,
52187
+ clientY: event.clientY
52188
+ };
52189
+ try {
52190
+ if (runtime) runtime.adapter.contextMenu?.(this.reference(), point, captured.context);
52191
+ else {
52192
+ this.select();
52193
+ adapter?.contextMenu?.(String(this.node.attrs.src ?? ""), point, captured.context);
52194
+ }
52195
+ } catch (error) {
52196
+ if (runtime) this.report(error);
52197
+ else host.error?.(coded(error, "image-unavailable"));
52198
+ }
52199
+ };
52200
+ select() {
52201
+ const pos = this.getPos();
52202
+ if (pos == null || this.view.composing) return;
52203
+ const { state } = this.view;
52204
+ const selection = NodeSelection.create(state.doc, pos);
52205
+ if (!state.selection.eq(selection)) this.view.dispatch(state.tr.setSelection(selection));
52206
+ }
51440
52207
  card(label, status, retry = false) {
51441
52208
  const footer = document.createElement("span");
51442
52209
  footer.className = "inkkit-file-footer";
@@ -51485,20 +52252,6 @@ var ImageView = class {
51485
52252
  this.image = void 0;
51486
52253
  if (this.runtime) {
51487
52254
  this.element.classList.add("inkkit-file");
51488
- this.element.oncontextmenu = (event) => {
51489
- if (!this.runtime?.adapter.contextMenu || !this.active()) return;
51490
- const captured = this.runtime.capture();
51491
- if (!captured.isCurrent()) return;
51492
- event.preventDefault();
51493
- try {
51494
- this.runtime.adapter.contextMenu(this.reference(), {
51495
- clientX: event.clientX,
51496
- clientY: event.clientY
51497
- }, captured.context);
51498
- } catch (error) {
51499
- this.report(error);
51500
- }
51501
- };
51502
52255
  const state = this.resolution.state;
51503
52256
  const label = state.status === "resolved" ? state.presentation.label || alt || reference.reference : alt || reference.reference;
51504
52257
  this.element.setAttribute("role", "group");
@@ -51518,6 +52271,14 @@ var ImageView = class {
51518
52271
  return;
51519
52272
  }
51520
52273
  const presentation = state.presentation;
52274
+ if (!this.active() && [
52275
+ "audio",
52276
+ "video",
52277
+ "pdf"
52278
+ ].includes(presentation.kind)) {
52279
+ this.card(label);
52280
+ return;
52281
+ }
51521
52282
  if (presentation.kind === "missing") this.card(label, this.runtime.labels.fileMissing, true);
51522
52283
  else if (presentation.kind === "error") this.card(label, presentation.message || this.runtime.labels.fileError, true);
51523
52284
  else if (presentation.kind === "image") {
@@ -51647,8 +52408,9 @@ function openArrow() {
51647
52408
  icon.append(path);
51648
52409
  return icon;
51649
52410
  }
51650
- function imageView(adapter, runtime, canMutate = () => true) {
51651
- const view = (schema) => $view(schema.node, () => (node, view, getPos) => new ImageView(node, view, getPos, adapter, runtime, canMutate));
52411
+ function imageView(adapter, host, files, canMutate = () => true) {
52412
+ const cache = new FilePresentationCache();
52413
+ const view = (schema) => $view(schema.node, () => (node, view, getPos) => new ImageView(node, view, getPos, adapter, host, files, cache, canMutate));
51652
52414
  return [view(imageSchema), view(imageBlockSchema)];
51653
52415
  }
51654
52416
  //#endregion
@@ -51660,26 +52422,29 @@ function wikiLinkView(adapter, runtime) {
51660
52422
  dom.className = "inkkit-wiki-link";
51661
52423
  dom.setAttribute("role", "link");
51662
52424
  dom.tabIndex = 0;
51663
- const reference = () => ({
51664
- target: String(current.attrs.target),
51665
- ...current.attrs.fragment ? { fragment: String(current.attrs.fragment) } : {},
51666
- label: String(current.attrs.label)
51667
- });
52425
+ const reference = () => wikiReference(current);
51668
52426
  const report = (error) => runtime.error?.(coded(error, "file-unavailable"));
51669
52427
  const render = () => {
51670
- dom.textContent = reference().label;
51671
- dom.removeAttribute("title");
51672
- dom.removeAttribute("data-missing");
51673
- if (!runtime.active()) return;
51674
- const captured = runtime.capture();
51675
- try {
51676
- const result = adapter.resolve?.(reference(), captured.context);
51677
- if (captured.isCurrent() && result?.missing) {
51678
- dom.dataset.missing = "true";
51679
- dom.title = runtime.labels.wikiMissing;
52428
+ const { label } = reference();
52429
+ if (dom.textContent !== label) dom.textContent = label;
52430
+ let missing = false;
52431
+ if (runtime.active()) {
52432
+ const captured = runtime.capture();
52433
+ try {
52434
+ const result = adapter.resolve?.(reference(), captured.context);
52435
+ missing = captured.isCurrent() && Boolean(result?.missing);
52436
+ } catch (error) {
52437
+ report(error);
51680
52438
  }
51681
- } catch (error) {
51682
- report(error);
52439
+ }
52440
+ if (missing) {
52441
+ dom.dataset.missing = "true";
52442
+ dom.title = runtime.labels.wikiMissing;
52443
+ dom.setAttribute("aria-description", runtime.labels.wikiMissing);
52444
+ } else {
52445
+ dom.removeAttribute("data-missing");
52446
+ dom.removeAttribute("title");
52447
+ dom.removeAttribute("aria-description");
51683
52448
  }
51684
52449
  };
51685
52450
  const open = (event) => {
@@ -51698,7 +52463,11 @@ function wikiLinkView(adapter, runtime) {
51698
52463
  dom.addEventListener("keydown", (event) => {
51699
52464
  if (event.key === "Enter" || event.key === " ") open(event);
51700
52465
  });
51701
- view.dom.addEventListener("inkkit-media-policy", render);
52466
+ const policy = (event) => {
52467
+ const refresh = refreshOf(event);
52468
+ if (!refresh || refreshes(refresh, String(current.attrs.target))) render();
52469
+ };
52470
+ view.dom.addEventListener(mediaPolicyEvent, policy);
51702
52471
  render();
51703
52472
  return {
51704
52473
  dom,
@@ -51713,7 +52482,7 @@ function wikiLinkView(adapter, runtime) {
51713
52482
  },
51714
52483
  ignoreMutation: () => true,
51715
52484
  destroy() {
51716
- view.dom.removeEventListener("inkkit-media-policy", render);
52485
+ view.dom.removeEventListener(mediaPolicyEvent, policy);
51717
52486
  }
51718
52487
  };
51719
52488
  });
@@ -52784,7 +53553,7 @@ function readableProjection(doc) {
52784
53553
  }
52785
53554
  if (name === "image_block") {
52786
53555
  block(position);
52787
- append(fileReference(node).label || "Image", position, position + node.nodeSize, "embed");
53556
+ append(fileFields(node).label || "Image", position, position + node.nodeSize, "embed");
52788
53557
  return false;
52789
53558
  }
52790
53559
  if (node.isTextblock) {
@@ -52801,7 +53570,7 @@ function readableProjection(doc) {
52801
53570
  return false;
52802
53571
  }
52803
53572
  if (name === "image" || name === "footnote_reference" || name === "inkkit_wiki_link") {
52804
- append(name === "image" ? fileReference(node).label || "Image" : name === "inkkit_wiki_link" ? String(node.attrs.label || node.attrs.target || "Link") : `[${node.attrs.label}]`, position, position + node.nodeSize, "embed");
53573
+ append(name === "image" ? fileFields(node).label || "Image" : name === "inkkit_wiki_link" ? String(node.attrs.label || node.attrs.target || "Link") : `[${node.attrs.label}]`, position, position + node.nodeSize, "embed");
52805
53574
  return false;
52806
53575
  }
52807
53576
  return true;
@@ -54053,6 +54822,96 @@ function htmlGrid(schema, html) {
54053
54822
  };
54054
54823
  }
54055
54824
  //#endregion
54825
+ //#region src/image-identity.ts
54826
+ var pngSignature = [
54827
+ 137,
54828
+ 80,
54829
+ 78,
54830
+ 71,
54831
+ 13,
54832
+ 10,
54833
+ 26,
54834
+ 10
54835
+ ];
54836
+ function format(bytes) {
54837
+ if (bytes.length >= 8 && pngSignature.every((value, index) => bytes[index] === value)) return "png";
54838
+ if (bytes.length >= 8 && (bytes[0] === 73 && bytes[1] === 73 && bytes[2] === 42 && bytes[3] === 0 || bytes[0] === 77 && bytes[1] === 77 && bytes[2] === 0 && bytes[3] === 42)) return "tiff";
54839
+ }
54840
+ /** The pixel size in the header of a PNG or the first image of a TIFF. Nothing is decoded, so a
54841
+ * truncated or unusual file gives `undefined` and is never treated as a match. */
54842
+ function size(bytes, kind) {
54843
+ const view = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength);
54844
+ if (kind === "png") {
54845
+ if (bytes.length < 24 || view.getUint32(12) !== 1229472850) return void 0;
54846
+ return {
54847
+ width: view.getUint32(16),
54848
+ height: view.getUint32(20)
54849
+ };
54850
+ }
54851
+ const little = bytes[0] === 73;
54852
+ const directory = view.getUint32(4, little);
54853
+ if (directory + 2 > bytes.length) return void 0;
54854
+ const entries = view.getUint16(directory, little);
54855
+ let width;
54856
+ let height;
54857
+ for (let index = 0; index < entries; index++) {
54858
+ const entry = directory + 2 + index * 12;
54859
+ if (entry + 12 > bytes.length) return void 0;
54860
+ const tag = view.getUint16(entry, little);
54861
+ if (tag !== 256 && tag !== 257) continue;
54862
+ const type = view.getUint16(entry + 2, little);
54863
+ const value = type === 3 ? view.getUint16(entry + 8, little) : type === 4 ? view.getUint32(entry + 8, little) : void 0;
54864
+ if (tag === 256) width = value;
54865
+ else height = value;
54866
+ }
54867
+ return width && height ? {
54868
+ width,
54869
+ height
54870
+ } : void 0;
54871
+ }
54872
+ function identical(a, b) {
54873
+ if (a.length !== b.length) return false;
54874
+ for (let index = 0; index < a.length; index++) if (a[index] !== b[index]) return false;
54875
+ return true;
54876
+ }
54877
+ function stem(filename) {
54878
+ const name = (filename ?? "").split(/[\\/]/).pop() ?? "";
54879
+ const dot = name.lastIndexOf(".");
54880
+ return (dot > 0 ? name.slice(0, dot) : name).normalize("NFC").toLowerCase();
54881
+ }
54882
+ /** Whether two captured images are one picture in two representations. */
54883
+ function sameImage(a, b) {
54884
+ if ((a.source ?? "") !== (b.source ?? "")) return false;
54885
+ if (identical(a.bytes, b.bytes)) return true;
54886
+ const kindA = format(a.bytes);
54887
+ const kindB = format(b.bytes);
54888
+ if (!kindA || !kindB || kindA === kindB) return false;
54889
+ const nameA = stem(a.filename);
54890
+ const nameB = stem(b.filename);
54891
+ if (nameA && nameB && nameA !== nameB) return false;
54892
+ const sizeA = size(a.bytes, kindA);
54893
+ const sizeB = size(b.bytes, kindB);
54894
+ return !!sizeA && !!sizeB && sizeA.width === sizeB.width && sizeA.height === sizeB.height;
54895
+ }
54896
+ /** Keeps one image for each picture in a single paste or drop. A clipboard can hold a picture
54897
+ * more than once, such as a screenshot as PNG and as TIFF, and each copy would be stored and
54898
+ * inserted separately.
54899
+ *
54900
+ * Two images are one picture when their bytes are identical, or when one is a PNG and the other a
54901
+ * TIFF with the same pixel size and compatible names (the same name without its extension, or a
54902
+ * name missing). Anything else is a different image, including a copy that was resized, re-encoded
54903
+ * as JPEG or renamed. The first image keeps its place; a PNG replaces a TIFF of the same picture
54904
+ * because every web view can show it. */
54905
+ function distinctImages(images) {
54906
+ const kept = [];
54907
+ for (const image of images) {
54908
+ const index = kept.findIndex((other) => sameImage(other, image));
54909
+ if (index < 0) kept.push(image);
54910
+ else if (format(kept[index].bytes) === "tiff" && format(image.bytes) === "png") kept[index] = image;
54911
+ }
54912
+ return kept;
54913
+ }
54914
+ //#endregion
54056
54915
  //#region src/paste.ts
54057
54916
  var PasteController = class {
54058
54917
  options;
@@ -54214,12 +55073,12 @@ var PasteController = class {
54214
55073
  this.active = operation;
54215
55074
  this.options.onPendingChanged?.();
54216
55075
  try {
54217
- const images = [...input.images ?? []];
54218
- for (const file of files) images.push({
55076
+ const fromFiles = files.length ? distinctImages(await Promise.all(files.map(async (file) => ({
54219
55077
  bytes: new Uint8Array(await file.arrayBuffer()),
54220
55078
  mimeType: file.type,
54221
55079
  filename: file.name
54222
- });
55080
+ })))) : [];
55081
+ const images = distinctImages([...input.images ?? [], ...fromFiles]);
54223
55082
  this.assertContext(context, operation);
54224
55083
  const schema = view.state.schema;
54225
55084
  let slice;
@@ -54238,8 +55097,7 @@ var PasteController = class {
54238
55097
  }
54239
55098
  const anchor = this.anchor(operation);
54240
55099
  const start = view.state.doc.resolve(anchor.from);
54241
- const end = view.state.doc.resolve(anchor.to);
54242
- const marks = view.state.storedMarks ?? (anchor.from === anchor.to ? start.marks() : start.marksAcross(end)) ?? [];
55100
+ const marks = replacementMarks(view.state, anchor.from, anchor.to);
54243
55101
  const nodes = literalTextNodes(schema, input.text, marks, htmlBreakAt(start));
54244
55102
  slice = new Slice(Fragment.from(schema.nodes.paragraph.create(null, nodes)), 1, 1);
54245
55103
  } else if (input.html) {
@@ -54247,7 +55105,7 @@ var PasteController = class {
54247
55105
  const imported = /* @__PURE__ */ new Map();
54248
55106
  const elements = [...container.querySelectorAll("img")];
54249
55107
  const uniqueSources = new Set(elements.map((element) => element.getAttribute("src") ?? ""));
54250
- const positional = (files.length === 0 || files.length === 1 && uniqueSources.size === 1 ? images : input.images ?? []).filter((image) => !image.source);
55108
+ const positional = (fromFiles.length === 0 || fromFiles.length === 1 && uniqueSources.size === 1 ? images : input.images ?? []).filter((image) => !image.source);
54251
55109
  let index = 0;
54252
55110
  for (const element of elements) {
54253
55111
  const source = element.getAttribute("src") ?? "";
@@ -54744,60 +55602,259 @@ var DocumentSession = class {
54744
55602
  //#region src/selection.ts
54745
55603
  /** The stylesheet paints the selection under this name. */
54746
55604
  var name = "inkkit-selection";
54747
- var ranges = /* @__PURE__ */ new Map();
55605
+ var activeClass = "inkkit-selection-active";
54748
55606
  /** The selected text within each block. */
54749
55607
  function covered(state) {
54750
55608
  const { from, to, empty, visible } = state.selection;
54751
- if (empty || !visible) return null;
55609
+ if (empty || !visible) return [];
54752
55610
  const text = [];
54753
- state.doc.nodesBetween(from, to, (node, pos) => {
55611
+ let index = 0;
55612
+ state.doc.descendants((node, pos) => {
54754
55613
  if (!node.isTextblock) return true;
54755
55614
  const start = Math.max(from, pos + 1);
54756
55615
  const end = Math.min(to, pos + node.nodeSize - 1);
54757
55616
  if (end > start) text.push({
54758
55617
  from: start,
54759
- to: end
55618
+ to: end,
55619
+ start: pos + 1,
55620
+ end: pos + node.nodeSize - 1,
55621
+ index
54760
55622
  });
55623
+ index++;
54761
55624
  return false;
54762
55625
  });
54763
55626
  return text;
54764
55627
  }
55628
+ function blockContents(view) {
55629
+ return Array.from(view.dom.querySelectorAll("p,h1,h2,h3,h4,h5,h6,pre,.inkkit-comment-block")).filter((element) => {
55630
+ const excluded = element.closest(".ProseMirror-widget,[contenteditable=\"false\"]");
55631
+ return !excluded || excluded === view.dom;
55632
+ }).map((element) => element.tagName === "PRE" && !element.classList.contains("inkkit-literal-markdown") ? element.querySelector(":scope > code") ?? element : element);
55633
+ }
55634
+ function matchesBlock(node, content) {
55635
+ if (!content) return false;
55636
+ switch (node.type.name) {
55637
+ case "paragraph": return content.tagName === "P";
55638
+ case "heading": return content.tagName === `H${node.attrs.level}`;
55639
+ case "code_block": return content.tagName === "CODE" && content.parentElement?.tagName === "PRE";
55640
+ case "literal_markdown": return content.classList.contains("inkkit-literal-markdown");
55641
+ case "comment_block": return content.classList.contains("inkkit-comment-block");
55642
+ default: return false;
55643
+ }
55644
+ }
55645
+ function blockRange(view, span, content) {
55646
+ const range = view.dom.ownerDocument.createRange();
55647
+ range.selectNodeContents(content);
55648
+ const children = Array.from(content.childNodes);
55649
+ const decoration = (node) => node instanceof Element && (node.classList.contains("ProseMirror-widget") || node.classList.contains("ProseMirror-trailingBreak"));
55650
+ const first = children.find((node) => !decoration(node));
55651
+ const last = [...children].reverse().find((node) => !decoration(node));
55652
+ if (first) range.setStartBefore(first);
55653
+ if (last) range.setEndAfter(last);
55654
+ if (span.from !== span.start) {
55655
+ const start = view.domAtPos(span.from);
55656
+ range.setStart(start.node, start.offset);
55657
+ }
55658
+ if (span.to !== span.end) {
55659
+ const end = view.domAtPos(span.to);
55660
+ range.setEnd(end.node, end.offset);
55661
+ }
55662
+ return range;
55663
+ }
55664
+ function addBlockRanges(highlight, view, spans, contents) {
55665
+ let first;
55666
+ let last;
55667
+ const flush = () => {
55668
+ if (!first || !last) return;
55669
+ const range = view.dom.ownerDocument.createRange();
55670
+ range.setStart(first.content.firstChild, first.span.from - first.span.start);
55671
+ range.setEnd(last.content.firstChild, last.span.to - last.span.start);
55672
+ highlight.add(range);
55673
+ first = last = void 0;
55674
+ };
55675
+ for (const span of spans) {
55676
+ const content = contents[span.index];
55677
+ if (!(/^(P|H[1-6])$/.test(content.tagName) && content.childNodes.length === 1 && content.firstChild?.nodeType === Node.TEXT_NODE)) {
55678
+ flush();
55679
+ highlight.add(blockRange(view, span, content));
55680
+ continue;
55681
+ }
55682
+ if (last && last.content.nextSibling !== content) flush();
55683
+ first ??= {
55684
+ span,
55685
+ content
55686
+ };
55687
+ last = {
55688
+ span,
55689
+ content
55690
+ };
55691
+ }
55692
+ flush();
55693
+ }
55694
+ function setSelectionHostActive(view, active) {
55695
+ Painter.of(view.dom.ownerDocument).hostActive(view, active);
55696
+ }
54765
55697
  function domRange(view, span) {
54766
55698
  const start = view.domAtPos(span.from);
54767
55699
  const end = view.domAtPos(span.to);
54768
- const range = document.createRange();
55700
+ const range = view.dom.ownerDocument.createRange();
54769
55701
  range.setStart(start.node, start.offset);
54770
55702
  range.setEnd(end.node, end.offset);
54771
55703
  return range;
54772
55704
  }
54773
- function paint(view) {
54774
- ranges.set(view, (covered(view.state) ?? []).map((span) => domRange(view, span)));
54775
- refresh();
54776
- }
54777
- function refresh() {
54778
- if (ranges.size === 0) {
54779
- CSS.highlights.delete(name);
54780
- return;
54781
- }
54782
- const highlight = new Highlight();
54783
- for (const selection of ranges.values()) for (const range of selection) highlight.add(range);
54784
- CSS.highlights.set(name, highlight);
54785
- }
55705
+ var rootOf = (view) => view.dom.closest(".inkkit-root");
54786
55706
  var paintable = () => typeof Highlight === "function" && "highlights" in CSS && CSS.supports(`selector(::highlight(${name}))`);
54787
- var selectionPlugin = $prose(() => paintable() ? new Plugin({
54788
- key: new PluginKey("selection"),
54789
- props: { attributes: { class: "inkkit-painted-selection" } },
54790
- view(view) {
54791
- paint(view);
54792
- return {
54793
- update: (view) => paint(view),
54794
- destroy: () => {
54795
- ranges.delete(view);
54796
- refresh();
54797
- }
55707
+ /**
55708
+ * Whether the view holds the user's input: its formatted surface, or the source or TXT
55709
+ * textarea beside it, has focus, or, in a read-only editor that takes no focus, the
55710
+ * document selection is inside it.
55711
+ */
55712
+ function focused(view) {
55713
+ const doc = view.dom.ownerDocument;
55714
+ const surface = doc.activeElement;
55715
+ if (surface === view.dom || view.dom.contains(surface) || surface instanceof HTMLTextAreaElement && surface.classList.contains("inkkit-plain") && rootOf(view).contains(surface)) return true;
55716
+ const anchor = doc.getSelection()?.anchorNode;
55717
+ return !view.editable && !!anchor && view.dom.contains(anchor);
55718
+ }
55719
+ /** Whether the focused element takes typing, so a selection kept elsewhere is still what the host is working on. */
55720
+ function takesText(element) {
55721
+ return element instanceof HTMLInputElement || element instanceof HTMLTextAreaElement || element instanceof HTMLSelectElement || element instanceof HTMLElement && element.isContentEditable;
55722
+ }
55723
+ /**
55724
+ * One document has one selection, so the editors on it share one painter and one highlight.
55725
+ * The owner is the editor that most recently held the selection. Its selection is painted
55726
+ * in the active colour while it has focus in a focused window, and in the inactive colour
55727
+ * otherwise, so a host that selects a range from its search field, or blurs the editor,
55728
+ * still shows what is selected. Anything that takes the selection elsewhere releases it.
55729
+ */
55730
+ var Painter = class Painter {
55731
+ doc;
55732
+ static all = /* @__PURE__ */ new WeakMap();
55733
+ views = /* @__PURE__ */ new Map();
55734
+ win;
55735
+ custom = paintable();
55736
+ owner = null;
55737
+ windowFocused;
55738
+ hostActivation = /* @__PURE__ */ new WeakMap();
55739
+ painted;
55740
+ hostActive(view, active) {
55741
+ if (active === null) this.hostActivation.delete(view);
55742
+ else this.hostActivation.set(view, active);
55743
+ this.sync();
55744
+ }
55745
+ static of(doc) {
55746
+ let painter = Painter.all.get(doc);
55747
+ if (!painter) Painter.all.set(doc, painter = new Painter(doc));
55748
+ return painter;
55749
+ }
55750
+ constructor(doc) {
55751
+ this.doc = doc;
55752
+ this.win = doc.defaultView;
55753
+ this.windowFocused = doc.hasFocus();
55754
+ }
55755
+ add(view) {
55756
+ if (!this.views.size) this.listen("add");
55757
+ this.views.set(view, rootOf(view));
55758
+ this.sync();
55759
+ }
55760
+ remove(view) {
55761
+ this.views.get(view)?.classList.remove(activeClass);
55762
+ this.views.delete(view);
55763
+ if (this.owner === view) this.owner = null;
55764
+ this.sync();
55765
+ if (!this.views.size) {
55766
+ this.listen("remove");
55767
+ Painter.all.delete(this.doc);
55768
+ }
55769
+ }
55770
+ /** A transaction gave this view a selection. A focused owner is not taken over by an editor that is not. */
55771
+ take(view) {
55772
+ if (!this.owner || this.owner === view || focused(view) || !focused(this.owner)) this.owner = view;
55773
+ this.sync();
55774
+ }
55775
+ /** The owner's document changed, or its selection moved with it. */
55776
+ changed(view) {
55777
+ if (this.owner === view) this.sync();
55778
+ }
55779
+ onFocusIn = () => {
55780
+ for (const view of this.views.keys()) if (focused(view)) this.owner = view;
55781
+ this.sync();
55782
+ };
55783
+ onFocusOut = () => this.sync();
55784
+ onWindow = (event) => {
55785
+ this.windowFocused = event.type === "focus";
55786
+ this.sync();
55787
+ };
55788
+ onSelectionChange = () => {
55789
+ const owner = this.owner;
55790
+ if (!owner || focused(owner)) return;
55791
+ const selection = this.doc.getSelection();
55792
+ if (!selection?.rangeCount || owner.dom.contains(selection.anchorNode)) return;
55793
+ if (selection.isCollapsed && takesText(this.doc.activeElement)) return;
55794
+ this.owner = null;
55795
+ this.sync();
55796
+ };
55797
+ listen(action) {
55798
+ const method = `${action}EventListener`;
55799
+ this.doc[method]("focusin", this.onFocusIn);
55800
+ this.doc[method]("focusout", this.onFocusOut);
55801
+ this.doc[method]("selectionchange", this.onSelectionChange);
55802
+ this.win[method]("focus", this.onWindow);
55803
+ this.win[method]("blur", this.onWindow);
55804
+ }
55805
+ sync() {
55806
+ for (const [view, root] of this.views) root.classList.toggle(activeClass, (this.hostActivation.get(view) ?? this.windowFocused) && focused(view));
55807
+ if (!this.custom) return;
55808
+ const { owner } = this;
55809
+ if (owner && this.painted?.owner === owner && this.painted.doc === owner.state.doc && this.painted.selection.eq(owner.state.selection)) return;
55810
+ const spans = owner ? covered(owner.state) : [];
55811
+ if (!owner || !spans.length) {
55812
+ CSS.highlights.delete(name);
55813
+ this.painted = void 0;
55814
+ return;
55815
+ }
55816
+ const highlight = new Highlight();
55817
+ const contents = blockContents(owner);
55818
+ let blocks = 0;
55819
+ let mapped = true;
55820
+ owner.state.doc.descendants((node) => {
55821
+ if (!node.isTextblock) return true;
55822
+ if (!matchesBlock(node, contents[blocks])) mapped = false;
55823
+ blocks++;
55824
+ return false;
55825
+ });
55826
+ if (mapped && contents.length === blocks) addBlockRanges(highlight, owner, spans, contents);
55827
+ else for (const span of spans) highlight.add(domRange(owner, span));
55828
+ CSS.highlights.set(name, highlight);
55829
+ this.painted = {
55830
+ owner,
55831
+ doc: owner.state.doc,
55832
+ selection: owner.state.selection
54798
55833
  };
54799
55834
  }
54800
- }) : new Plugin({ key: new PluginKey("selection") }));
55835
+ };
55836
+ var selectionPlugin = $prose(() => {
55837
+ const key = new PluginKey("selection");
55838
+ return new Plugin({
55839
+ key,
55840
+ state: {
55841
+ init: () => 0,
55842
+ apply: (tr, count) => tr.selectionSet ? count + 1 : count
55843
+ },
55844
+ props: paintable() ? { attributes: { class: "inkkit-painted-selection" } } : {},
55845
+ view(view) {
55846
+ const painter = Painter.of(view.dom.ownerDocument);
55847
+ painter.add(view);
55848
+ return {
55849
+ update(view, previous) {
55850
+ if (key.getState(view.state) !== key.getState(previous)) painter.take(view);
55851
+ else painter.changed(view);
55852
+ },
55853
+ destroy: () => painter.remove(view)
55854
+ };
55855
+ }
55856
+ });
55857
+ });
54801
55858
  //#endregion
54802
55859
  //#region node_modules/.pnpm/prosemirror-search@1.1.1/node_modules/prosemirror-search/dist/index.js
54803
55860
  var SearchQuery = class {
@@ -55095,7 +56152,7 @@ var searchKey = new PluginKey("search");
55095
56152
  Returns a plugin that stores a current search query and searched
55096
56153
  range, and highlights matches of the query.
55097
56154
  */
55098
- function search(options = {}) {
56155
+ function search$1(options = {}) {
55099
56156
  return new Plugin({
55100
56157
  key: searchKey,
55101
56158
  state: {
@@ -55144,6 +56201,56 @@ function setSearchState(tr, query, range = null) {
55144
56201
  }
55145
56202
  //#endregion
55146
56203
  //#region src/search.ts
56204
+ function search() {
56205
+ const original = search$1();
56206
+ return new Plugin({
56207
+ ...original.spec,
56208
+ state: {
56209
+ init: original.spec.state.init,
56210
+ apply(tr, current, oldState, state) {
56211
+ if (tr.getMeta(original) || current.range) return original.spec.state.apply.call(this, tr, current, oldState, state);
56212
+ if (!current.query.valid || !tr.docChanged && !tr.selectionSet) return current;
56213
+ let deco = tr.docChanged ? current.deco.map(tr.mapping, tr.doc) : current.deco;
56214
+ let from = tr.doc.content.size, to = 0;
56215
+ if (tr.docChanged) {
56216
+ tr.mapping.maps.forEach((map, index) => map.forEach((_a, _b, start, end) => {
56217
+ const rest = tr.mapping.slice(index + 1);
56218
+ from = Math.min(from, rest.map(start, -1));
56219
+ to = Math.max(to, rest.map(end, 1));
56220
+ }));
56221
+ if (from <= to) {
56222
+ const left = tr.doc.resolve(Math.max(0, from - 1));
56223
+ const right = tr.doc.resolve(Math.min(tr.doc.content.size, to + 1));
56224
+ from = left.depth ? left.before(1) : left.pos;
56225
+ to = right.depth ? right.after(1) : right.pos;
56226
+ deco = deco.remove(deco.find(from, to));
56227
+ const added = [];
56228
+ for (let position = from; position < to;) {
56229
+ const match = current.query.findNext(state, position, to);
56230
+ if (!match) break;
56231
+ added.push(Decoration.inline(match.from, match.to, { class: "ProseMirror-search-match" }));
56232
+ position = Math.max(match.to, position + 1);
56233
+ }
56234
+ deco = deco.add(state.doc, added);
56235
+ }
56236
+ }
56237
+ const previous = {
56238
+ from: tr.mapping.map(oldState.selection.from),
56239
+ to: tr.mapping.map(oldState.selection.to)
56240
+ };
56241
+ for (const [selection, active] of [[previous, false], [state.selection, true]]) {
56242
+ const matches = deco.find(selection.from, selection.to).filter((item) => item.from === selection.from && item.to === selection.to);
56243
+ const replacements = matches.map((item) => Decoration.inline(item.from, item.to, { class: active ? "ProseMirror-active-search-match" : "ProseMirror-search-match" }));
56244
+ deco = deco.remove(matches).add(state.doc, replacements);
56245
+ }
56246
+ return {
56247
+ ...current,
56248
+ deco
56249
+ };
56250
+ }
56251
+ }
56252
+ });
56253
+ }
55147
56254
  function escapedPattern(text) {
55148
56255
  return text.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
55149
56256
  }
@@ -55227,8 +56334,8 @@ function findFormatted(view, text) {
55227
56334
  return found;
55228
56335
  }
55229
56336
  function replacementSlice(state, from, to, value) {
55230
- const start = state.doc.resolve(from), end = state.doc.resolve(to);
55231
- const marks = start.marksAcross(end) ?? start.marks();
56337
+ const start = state.doc.resolve(from);
56338
+ const marks = replacementMarks(state, from, to);
55232
56339
  const nodes = [];
55233
56340
  if (start.parent.type.spec.code) {
55234
56341
  if (value) nodes.push(state.schema.text(value, marks));
@@ -55305,8 +56412,10 @@ function formattedLocations(doc) {
55305
56412
  return headings;
55306
56413
  }
55307
56414
  function sourceLocations(ctx, source) {
55308
- const processor = ctx.get(remarkCtx);
55309
- const tree = processor.runSync(processor.parse(source), { value: source });
56415
+ const cached = sourceOutlines.get(ctx);
56416
+ if (cached?.source === source) return cached.headings;
56417
+ const preservation = Preservation.read(ctx, source);
56418
+ const tree = preservation.sourceTree;
55310
56419
  const positions = [];
55311
56420
  const bom = source.startsWith("") ? 1 : 0;
55312
56421
  const visit = (node) => {
@@ -55317,13 +56426,19 @@ function sourceLocations(ctx, source) {
55317
56426
  node.children?.forEach(visit);
55318
56427
  };
55319
56428
  visit(tree);
55320
- const parsed = formattedLocations(ctx.get(parserCtx)(source));
56429
+ const parsed = formattedLocations(preservation.baseline);
55321
56430
  if (positions.length !== parsed.length || positions.some((item, index) => item.level !== parsed[index].node.attrs.level)) throw new InkKitError("preservation", "The current Markdown headings cannot be located safely");
55322
- return parsed.map((heading, index) => ({
56431
+ const headings = parsed.map((heading, index) => ({
55323
56432
  ...heading,
55324
56433
  position: positions[index].position
55325
56434
  }));
56435
+ sourceOutlines.set(ctx, {
56436
+ source,
56437
+ headings
56438
+ });
56439
+ return headings;
55326
56440
  }
56441
+ var sourceOutlines = /* @__PURE__ */ new WeakMap();
55327
56442
  function entry(heading, context) {
55328
56443
  return Object.freeze({
55329
56444
  id: `heading:${context.key}:${heading.position}`,
@@ -55845,6 +56960,8 @@ var shortcutNames = [
55845
56960
  "redo",
55846
56961
  "moveBlockUp",
55847
56962
  "moveBlockDown",
56963
+ "indent",
56964
+ "outdent",
55848
56965
  ...tableCommands.map(tableShortcut)
55849
56966
  ];
55850
56967
  /** Checks a keymap and drops omitted entries, so a `null` binding keeps its default. */
@@ -55896,6 +57013,7 @@ function editorOptions(value) {
55896
57013
  "textInput",
55897
57014
  "labels",
55898
57015
  "keymap",
57016
+ "linkSchemes",
55899
57017
  "rendering",
55900
57018
  "slashMenu"
55901
57019
  ]) ?? {};
@@ -55905,6 +57023,7 @@ function editorOptions(value) {
55905
57023
  optionalBoolean(options.editable, "options.editable");
55906
57024
  if (!isOmitted(options.textInput)) textInputPreferences(options.textInput, "options.textInput");
55907
57025
  if (!isOmitted(options.keymap)) keymapBindings(options.keymap, "options.keymap");
57026
+ const linkSchemes = isOmitted(options.linkSchemes) ? void 0 : extraLinkSchemes(options.linkSchemes, "options.linkSchemes");
55908
57027
  const rendering = optionalRecord(options.rendering, "options.rendering", ["codeHighlighting", "diagramPreview"]);
55909
57028
  optionalBoolean(rendering?.codeHighlighting, "options.rendering.codeHighlighting");
55910
57029
  optionalBoolean(rendering?.diagramPreview, "options.rendering.diagramPreview");
@@ -55912,6 +57031,7 @@ function editorOptions(value) {
55912
57031
  return {
55913
57032
  ...options,
55914
57033
  slashMenu: slashMenuOptions(options.slashMenu),
57034
+ linkSchemes,
55915
57035
  labels: labels && Object.fromEntries(Object.entries(labels).filter(([, label]) => !isOmitted(label)).map(([key, label]) => [key, expectString(label, `options.labels.${key}`)]))
55916
57036
  };
55917
57037
  }
@@ -56108,6 +57228,18 @@ function blockAt(parent, $from) {
56108
57228
  return { type: "paragraph" };
56109
57229
  }
56110
57230
  var quotes = /* @__PURE__ */ new Set(["blockquote", "inkkit_callout"]);
57231
+ function liftQuote(state, range, target) {
57232
+ const tr = state.tr.lift(range, target);
57233
+ const { depth } = range;
57234
+ const item = range.$from.node(depth - 1);
57235
+ const spacer = item.firstChild;
57236
+ if (item.type.name === "list_item" && range.$from.index(depth - 1) === 1 && spacer?.type.name === "paragraph" && spacer.content.size === 0) {
57237
+ const from = range.$from.start(depth - 1);
57238
+ if (tr.doc.nodeAt(from + spacer.nodeSize)?.type === spacer.type) tr.delete(from, from + spacer.nodeSize);
57239
+ }
57240
+ return tr;
57241
+ }
57242
+ var undoTypedRule = $prose(() => keymap$3({ Backspace: undoInputRule }));
56111
57243
  var quoteBackspace = $prose(() => keymap$3({ Backspace: (state, dispatch) => {
56112
57244
  const { $from, empty } = state.selection;
56113
57245
  if (!empty || $from.parentOffset > 0 || $from.depth < 2) return false;
@@ -56117,7 +57249,7 @@ var quoteBackspace = $prose(() => keymap$3({ Backspace: (state, dispatch) => {
56117
57249
  const range = $from.blockRange($from, (node) => quotes.has(node.type.name));
56118
57250
  const target = range && liftTarget(range);
56119
57251
  if (!range || target == null) return false;
56120
- dispatch?.(state.tr.lift(range, target).scrollIntoView());
57252
+ dispatch?.(liftQuote(state, range, target).scrollIntoView());
56121
57253
  return true;
56122
57254
  } }));
56123
57255
  var calloutEnter = $prose(() => keymap$3({ Enter: (state, dispatch) => {
@@ -56159,7 +57291,6 @@ var liftItem = (state, dispatch) => {
56159
57291
  dispatch(lifted);
56160
57292
  return true;
56161
57293
  };
56162
- var listItemShiftTab = $prose(() => keymap$3({ "Shift-Tab": liftItem }));
56163
57294
  var rejoinLists = $prose(() => keymap$3({ Backspace: (state, dispatch) => {
56164
57295
  const { $from, empty } = state.selection;
56165
57296
  if (!empty || $from.parent.type.name !== "paragraph" || $from.parent.content.size > 0) return false;
@@ -56252,11 +57383,7 @@ var movedListItems = $prose(() => new Plugin({
56252
57383
  return tr.docChanged ? tr : null;
56253
57384
  }
56254
57385
  }));
56255
- var controlCharacters = /^[\u0000-\u0008\u000B\u000C\u000E-\u001F\u007F]+$/;
56256
- var textInputPolicy = $prose(() => new Plugin({
56257
- key: new PluginKey("inkkitTextInput"),
56258
- props: { handleTextInput: (view, from, to, text, deflt) => controlCharacters.test(text) || insertTextChunk(view, from, to, text, deflt) }
56259
- }));
57386
+ var textInputPolicy = $prose(() => textInputPlugin());
56260
57387
  function outermostListDepth($pos) {
56261
57388
  for (let depth = 1; depth <= $pos.depth; depth++) if (isList($pos.node(depth))) return depth;
56262
57389
  return null;
@@ -56381,6 +57508,7 @@ var InkKitEditor = class InkKitEditor {
56381
57508
  inputPreferences = {};
56382
57509
  literalKeys = () => false;
56383
57510
  configuredKeys = {};
57511
+ indentKeys = () => false;
56384
57512
  readOnlyKeys = () => false;
56385
57513
  suggestions;
56386
57514
  suggestionBase;
@@ -56407,7 +57535,7 @@ var InkKitEditor = class InkKitEditor {
56407
57535
  const anchor = view.state.doc.attrs[sourceAttribute];
56408
57536
  const from = anchor?.from ?? this.plain.selectionStart;
56409
57537
  const to = anchor?.to ?? this.plain.selectionEnd;
56410
- if (!this.plainComposing) this.plain.value = markdown;
57538
+ if (!this.plainComposing) this.setSourceValue(markdown);
56411
57539
  if (!this.plainComposing) {
56412
57540
  this.plain.setSelectionRange(Math.min(from, this.plain.value.length), Math.min(to, this.plain.value.length));
56413
57541
  this.revealLiteralSelection();
@@ -56420,6 +57548,8 @@ var InkKitEditor = class InkKitEditor {
56420
57548
  }));
56421
57549
  keys = () => false;
56422
57550
  events;
57551
+ /** The host's extra link schemes, in lower case. */
57552
+ linkSchemes;
56423
57553
  /** How many of each state event the host has been sent. */
56424
57554
  sentEvents = /* @__PURE__ */ new Map();
56425
57555
  constructor(events, options) {
@@ -56439,6 +57569,7 @@ var InkKitEditor = class InkKitEditor {
56439
57569
  else send();
56440
57570
  };
56441
57571
  } });
57572
+ this.linkSchemes = new Set(options.linkSchemes);
56442
57573
  this.writable = options.editable ?? true;
56443
57574
  this.inputPreferences = textInputPreferences(options.textInput ?? {}, "options.textInput");
56444
57575
  }
@@ -56499,7 +57630,7 @@ var InkKitEditor = class InkKitEditor {
56499
57630
  };
56500
57631
  }
56501
57632
  mediaPolicy() {
56502
- if (this.editor && this.root) this.editor.ctx.get(editorViewCtx).dom.dispatchEvent(new Event("inkkit-media-policy"));
57633
+ if (this.editor && this.root) this.editor.ctx.get(editorViewCtx).dom.dispatchEvent(new Event(mediaPolicyEvent));
56503
57634
  }
56504
57635
  assertAlive() {
56505
57636
  if (this.destroyed) throw new InkKitError("destroyed", "The editor has been destroyed");
@@ -56653,6 +57784,12 @@ var InkKitEditor = class InkKitEditor {
56653
57784
  }
56654
57785
  this.publishCommandState();
56655
57786
  }
57787
+ /** Supplies native host activation for selection presentation. Null restores browser detection. */
57788
+ setHostActive(active) {
57789
+ this.assertAlive();
57790
+ if (active !== null) expectBoolean(active, "active");
57791
+ setSelectionHostActive(this.editor.ctx.get(editorViewCtx), active);
57792
+ }
56656
57793
  /** Replaces the input preferences. A field left out takes its default. Throws `composition`
56657
57794
  * during text composition. */
56658
57795
  setTextInputPreferences(preferences) {
@@ -56671,11 +57808,13 @@ var InkKitEditor = class InkKitEditor {
56671
57808
  }
56672
57809
  applyInputPolicy() {
56673
57810
  const view = this.editor.ctx.get(editorViewCtx);
56674
- const attributes = typeof view.props.attributes === "function" ? view.props.attributes(view.state) : view.props.attributes;
57811
+ const inputAttributes = { ...typeof view.props.attributes === "function" ? view.props.attributes(view.state) : view.props.attributes };
57812
+ delete inputAttributes["aria-readonly"];
57813
+ if (!this.writable) inputAttributes["aria-readonly"] = "true";
56675
57814
  view.setProps({
56676
57815
  editable: () => this.writable && !this.literalSurface,
56677
57816
  attributes: {
56678
- ...attributes,
57817
+ ...inputAttributes,
56679
57818
  role: "textbox",
56680
57819
  "aria-multiline": "true",
56681
57820
  "aria-label": this.options.labels?.formattedEditor ?? defaultLabels.formattedEditor
@@ -56750,6 +57889,7 @@ var InkKitEditor = class InkKitEditor {
56750
57889
  replace: mutable,
56751
57890
  replaceSource: mutable,
56752
57891
  insertImages: formatted && this.canInsertImages,
57892
+ appendImages: formatted && this.canInsertImages,
56753
57893
  insertPaths: mutable,
56754
57894
  insertFootnote: formatted,
56755
57895
  editReferenceDefinition: formatted && referenceDefinitions(state.doc).size > 0,
@@ -56839,7 +57979,7 @@ var InkKitEditor = class InkKitEditor {
56839
57979
  if (provenance) {
56840
57980
  preservation = this.sourcePreservations.get(provenance);
56841
57981
  if (!preservation) {
56842
- preservation = new Preservation(this.editor.ctx, provenance.text);
57982
+ preservation = Preservation.read(this.editor.ctx, provenance.text);
56843
57983
  this.sourcePreservations.set(provenance, preservation);
56844
57984
  }
56845
57985
  }
@@ -56881,7 +58021,7 @@ var InkKitEditor = class InkKitEditor {
56881
58021
  this.plainSearch = "";
56882
58022
  this.literalMatch = void 0;
56883
58023
  if (mode === "source") {
56884
- this.plain.value = text;
58024
+ this.setSourceValue(text);
56885
58025
  this.plain.setSelectionRange(Math.min(anchor, head), Math.max(anchor, head), anchor > head ? "backward" : "forward");
56886
58026
  } else view.dispatch(view.state.tr.setSelection(TextSelection.between(view.state.doc.resolve(Math.min(anchor, view.state.doc.content.size)), view.state.doc.resolve(Math.min(head, view.state.doc.content.size)))).setMeta("addToHistory", false));
56887
58027
  const suppressed = this.reportingSuppressed;
@@ -56966,10 +58106,27 @@ var InkKitEditor = class InkKitEditor {
56966
58106
  if (this.typedSteps.length > sourceStepLimit) this.typedSteps.shift();
56967
58107
  }
56968
58108
  showTypedSource(text, from, to) {
56969
- this.plain.value = text;
58109
+ this.setSourceValue(text);
56970
58110
  this.plain.setSelectionRange(Math.min(from, this.plain.value.length), Math.min(to, this.plain.value.length));
56971
58111
  this.revealLiteralSelection();
56972
58112
  }
58113
+ setSourceValue(text) {
58114
+ const plain = this.plain;
58115
+ if (text.length < 65536 || !plain.isConnected || this.plainComposing) {
58116
+ plain.value = text;
58117
+ return;
58118
+ }
58119
+ const focused = plain.ownerDocument.activeElement === plain;
58120
+ const { selectionStart, selectionEnd, selectionDirection, scrollTop, scrollLeft } = plain;
58121
+ const marker = plain.ownerDocument.createComment("source");
58122
+ plain.replaceWith(marker);
58123
+ plain.value = text;
58124
+ marker.replaceWith(plain);
58125
+ plain.setSelectionRange(selectionStart, selectionEnd, selectionDirection);
58126
+ if (focused) plain.focus({ preventScroll: true });
58127
+ plain.scrollTop = scrollTop;
58128
+ plain.scrollLeft = scrollLeft;
58129
+ }
56973
58130
  /**
56974
58131
  * Undoes or redoes typed source that the formatted document has not taken
56975
58132
  * in, on the text alone. Returns whether there was a step to take.
@@ -57041,7 +58198,7 @@ var InkKitEditor = class InkKitEditor {
57041
58198
  try {
57042
58199
  if (this.formatType === "txt") parsed = view.state.schema.topNodeType.create(null, view.state.schema.nodes.paragraph.create(null, text ? view.state.schema.text(text) : void 0));
57043
58200
  else {
57044
- const preservation = new Preservation(this.editor.ctx, text);
58201
+ const preservation = Preservation.read(this.editor.ctx, text);
57045
58202
  this.sourcePreservations.set(provenance, preservation);
57046
58203
  parsed = preservation.baseline;
57047
58204
  }
@@ -57205,11 +58362,11 @@ var InkKitEditor = class InkKitEditor {
57205
58362
  const valid = content.firstChild?.isInline ? Fragment.from(schema.nodes.paragraph.create(null, content)) : content;
57206
58363
  let markdown;
57207
58364
  try {
57208
- markdown = selectedSource ? selectedSource.markdown : all ? snapshot.text : completeDiagrams.size ? new Preservation(this.editor.ctx, snapshot.text).serialize(doc.type.create(null, selectionContent(doc, valid))) : selectionMarkdown(this.editor.ctx, doc, valid);
58365
+ markdown = selectedSource ? selectedSource.markdown : all ? snapshot.text : completeDiagrams.size ? Preservation.read(this.editor.ctx, snapshot.text).serialize(doc.type.create(null, selectionContent(doc, valid))) : selectionMarkdown(this.editor.ctx, doc, valid);
57209
58366
  } catch (error) {
57210
58367
  throw coded(error, "preservation");
57211
58368
  }
57212
- const result = await this.exportContent(captured, (signal) => portableClipboard(all || selectedDoc ? content : selectionContent(doc, content), schema, markdown, this.options.images, this.context(), completeDiagrams, this.options.files, signal));
58369
+ const result = await this.exportContent(captured, (signal) => portableClipboard(all || selectedDoc ? content : selectionContent(doc, content), schema, markdown, this.options.images, this.context(), completeDiagrams, this.options.files, signal, this.linkSchemes));
57213
58370
  const metadata = referenceMetadata(this.editor.ctx, selectedDoc ?? doc, content);
57214
58371
  if (metadata != null) result.html = withReferenceMetadata(result.html, metadata);
57215
58372
  this.assertCaptured(captured);
@@ -57260,7 +58417,8 @@ var InkKitEditor = class InkKitEditor {
57260
58417
  if (view.state.selection.$from.parent.type.spec.code && !inHiddenComment) view.dispatch(view.state.tr.insertText(text.replace(/\r\n?/g, "\n")).scrollIntoView());
57261
58418
  else {
57262
58419
  const { schema } = view.state;
57263
- const marks = view.state.storedMarks ?? (view.state.selection.empty ? view.state.selection.$from.marks() : view.state.selection.$from.marksAcross(view.state.selection.$to)) ?? [];
58420
+ const { from, to } = view.state.selection;
58421
+ const marks = replacementMarks(view.state, from, to);
57264
58422
  const isHTML = !inHiddenComment && htmlBreakAt(view.state.selection.$from);
57265
58423
  const lines = text.replace(/\r\n?/g, "\n").split("\n");
57266
58424
  const children = literalTextNodes(schema, text, marks, isHTML);
@@ -57336,6 +58494,10 @@ var InkKitEditor = class InkKitEditor {
57336
58494
  return this.keys(view, event);
57337
58495
  } }
57338
58496
  }));
58497
+ indentPlugin = new Plugin({
58498
+ key: new PluginKey("listIndent"),
58499
+ props: { handleKeyDown: (view, event) => this.indentKeys(view, event) }
58500
+ });
57339
58501
  copySourceSelection(event) {
57340
58502
  event.preventDefault();
57341
58503
  event.stopPropagation();
@@ -57361,7 +58523,7 @@ var InkKitEditor = class InkKitEditor {
57361
58523
  }
57362
58524
  }).catch((error) => this.report(error));
57363
58525
  else {
57364
- const output = clipboardContent(parsed.content, parsed.type.schema);
58526
+ const output = clipboardContent(parsed.content, parsed.type.schema, this.linkSchemes);
57365
58527
  if (!event.clipboardData) return;
57366
58528
  this.rememberCopy(output, selected.markdown, parsed.content, selected.markdown !== selected.shareable, true);
57367
58529
  event.clipboardData.setData("text/plain", output.text);
@@ -57427,6 +58589,7 @@ var InkKitEditor = class InkKitEditor {
57427
58589
  ctx.set(rootCtx, root);
57428
58590
  ctx.set(authoredSourcesCtx.key, (doc, positions) => instance.authoredSources(doc, positions));
57429
58591
  ctx.set(smartPunctuationCtx.key, () => instance.inputPreferences.smartPunctuation === true);
58592
+ ctx.set(linkSchemesCtx.key, instance.linkSchemes);
57430
58593
  ctx.update(labelsCtx.key, () => ({
57431
58594
  ...defaultLabels,
57432
58595
  ...options.labels
@@ -57441,9 +58604,16 @@ var InkKitEditor = class InkKitEditor {
57441
58604
  }), false));
57442
58605
  ctx.set(defaultValueCtx, "");
57443
58606
  ctx.set(remarkStringifyOptionsCtx, stringifyOptions);
58607
+ ctx.update(editorStateOptionsCtx, (override) => (options) => {
58608
+ const next = override(options);
58609
+ return {
58610
+ ...next,
58611
+ plugins: [...next.plugins ?? [], instance.indentPlugin]
58612
+ };
58613
+ });
57444
58614
  ctx.update(listItemKeymap.key, (keys) => ({
57445
58615
  ...keys,
57446
- SinkListItem: { shortcuts: "Tab" },
58616
+ SinkListItem: { shortcuts: [] },
57447
58617
  LiftListItem: { shortcuts: [] }
57448
58618
  }));
57449
58619
  const unbind = (keymap, keep = []) => ctx.update(keymap.key, (keys) => {
@@ -57507,7 +58677,7 @@ var InkKitEditor = class InkKitEditor {
57507
58677
  return true;
57508
58678
  }
57509
58679
  }));
57510
- }).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-D6evOlOs.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, {
58680
+ }).use(labelsCtx).use(linkSchemesCtx).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(undoTypedRule).use(quoteBackspace).use(calloutEnter).use(literalEnter).use(literalReread).use(listItemBackspace).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-LB7Mkd5W.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, {
57511
58681
  capture: () => instance.adapterCapture(),
57512
58682
  active: () => instance.ready && !instance.destroyed && !instance.literalSurface,
57513
58683
  labels: {
@@ -57515,8 +58685,7 @@ var InkKitEditor = class InkKitEditor {
57515
58685
  ...options.labels
57516
58686
  },
57517
58687
  error: (error) => instance.report(error)
57518
- }) : []).use(options.images || options.files ? imageView(options.images, options.files ? {
57519
- adapter: options.files,
58688
+ }) : []).use(options.images || options.files ? imageView(options.images, {
57520
58689
  capture: () => instance.adapterCapture(),
57521
58690
  active: () => instance.ready && !instance.destroyed && !instance.literalSurface,
57522
58691
  labels: {
@@ -57524,7 +58693,7 @@ var InkKitEditor = class InkKitEditor {
57524
58693
  ...options.labels
57525
58694
  },
57526
58695
  error: (error) => instance.report(error)
57527
- } : void 0, () => {
58696
+ }, options.files, () => {
57528
58697
  try {
57529
58698
  instance.assertMutation();
57530
58699
  return true;
@@ -57558,7 +58727,7 @@ var InkKitEditor = class InkKitEditor {
57558
58727
  instance.plain.addEventListener("input", () => {
57559
58728
  if (!instance.ready || !instance.literalSurface) return;
57560
58729
  if (!instance.writable) {
57561
- instance.plain.value = instance.currentText();
58730
+ instance.setSourceValue(instance.currentText());
57562
58731
  return;
57563
58732
  }
57564
58733
  try {
@@ -57658,7 +58827,7 @@ var InkKitEditor = class InkKitEditor {
57658
58827
  const content = expanded;
57659
58828
  const valid = fragment.firstChild?.isInline ? Fragment.from(view.state.schema.nodes.paragraph.create(null, fragment)) : fragment;
57660
58829
  const markdown = selectionMarkdown(instance.editor.ctx, view.state.doc, valid);
57661
- const output = clipboardContent(content, view.state.schema);
58830
+ const output = clipboardContent(content, view.state.schema, instance.linkSchemes);
57662
58831
  const metadata = referenceMetadata(instance.editor.ctx, view.state.doc, valid);
57663
58832
  if (metadata != null) output.html = withReferenceMetadata(output.html, metadata);
57664
58833
  instance.rememberCopy(output, markdown, fragment, false, true);
@@ -57709,7 +58878,7 @@ var InkKitEditor = class InkKitEditor {
57709
58878
  const markdown = input.format === "txt" ? "" : input.text;
57710
58879
  let preservation;
57711
58880
  try {
57712
- preservation = new Preservation(this.editor.ctx, markdown);
58881
+ preservation = Preservation.read(this.editor.ctx, markdown);
57713
58882
  } catch (error) {
57714
58883
  throw new InkKitError("preservation", "The document was not loaded because its Markdown could not be shown without losing part of it", { cause: error });
57715
58884
  }
@@ -57734,7 +58903,7 @@ var InkKitEditor = class InkKitEditor {
57734
58903
  this.plainSearch = "";
57735
58904
  this.literalMatch = void 0;
57736
58905
  this.formatType = input.format;
57737
- this.plain.value = input.text;
58906
+ this.setSourceValue(input.text);
57738
58907
  this.plain.setSelectionRange(this.plain.value.length, this.plain.value.length);
57739
58908
  this.load(markdown, preservation);
57740
58909
  this.originalSource = input.text;
@@ -58311,10 +59480,23 @@ var InkKitEditor = class InkKitEditor {
58311
59480
  this.assertReady(expectedGeneration);
58312
59481
  this.releaseFocus();
58313
59482
  }
59483
+ /**
59484
+ * Resolves wiki links and file embeds again, all of them or those whose `target` is named, after
59485
+ * something they point to changed. A file keeps its card until the new answer differs. Changes no
59486
+ * document state, so it works in a read-only editor and during composition. Source and TXT modes
59487
+ * draw no references; named file answers are invalidated and resolved when formatted mode returns.
59488
+ */
59489
+ refreshReferences(targets, expectedGeneration) {
59490
+ this.assertAlive();
59491
+ const names = isOmitted(targets) ? void 0 : new Set(expectArray(targets, "targets").map((target) => expectString(target, "targets entry")));
59492
+ this.assertReady(expectedGeneration);
59493
+ requestRefresh(this.editor.ctx.get(editorViewCtx).dom, { ...names && { targets: names } });
59494
+ }
58314
59495
  /** Replaces every binding. Keys are in ProseMirror's notation. A key given to two shortcuts goes
58315
59496
  * to the later one, in the order history, formatting, block moves, tables, so it lands the same
58316
59497
  * way every time. A table binding passes a key it shares with a block move to the move outside
58317
- * a table. */
59498
+ * a table. List indentation keys apply after all of these, and after the table keys the editor
59499
+ * keeps for itself. */
58318
59500
  setKeymap(keymap) {
58319
59501
  this.assertAlive();
58320
59502
  keymap = keymapBindings(keymap, "keymap");
@@ -58324,6 +59506,8 @@ var InkKitEditor = class InkKitEditor {
58324
59506
  moveBlockUp: ["Alt-ArrowUp"],
58325
59507
  moveBlockDown: ["Alt-ArrowDown"],
58326
59508
  tableExit: ["Mod-Enter", "Enter"],
59509
+ indent: ["Tab"],
59510
+ outdent: ["Shift-Tab"],
58327
59511
  ...keymap
58328
59512
  };
58329
59513
  const bindings = {};
@@ -58350,6 +59534,9 @@ var InkKitEditor = class InkKitEditor {
58350
59534
  if (move === "move" && !this.composing && !this.pasteController.pending) this.forPerson(() => this.moveBlock(direction), false);
58351
59535
  return true;
58352
59536
  };
59537
+ const indents = {};
59538
+ const sink = (state, dispatch) => sinkListItem(state.schema.nodes.list_item)(state, dispatch);
59539
+ for (const [name, command] of [["indent", sink], ["outdent", liftItem]]) for (const key of this.configuredKeys[name] ?? []) indents[key] = (state, dispatch, view) => this.ready && this.writable && !this.literalSurface ? command(state, dispatch, view) : false;
58353
59540
  for (const command of tableCommands) for (const key of this.configuredKeys[tableShortcut(command)] ?? []) bindings[key] = (state, dispatch, view) => {
58354
59541
  if (!this.ready || !this.commandState().commands.table[command]) return moves[key]?.(state, dispatch, view) ?? false;
58355
59542
  return this.forPerson(() => this.table(command), true);
@@ -58358,6 +59545,7 @@ var InkKitEditor = class InkKitEditor {
58358
59545
  throw new InkKitError("read-only", "The editor is read-only");
58359
59546
  }])));
58360
59547
  this.keys = keydownHandler(bindings);
59548
+ this.indentKeys = keydownHandler(indents);
58361
59549
  this.literalKeys = keydownHandler(historyBindings);
58362
59550
  }
58363
59551
  /** Dropped files land as one paragraph per path at the drop point: in place of an empty block, after
@@ -58385,17 +59573,12 @@ var InkKitEditor = class InkKitEditor {
58385
59573
  this.assertMutation(expectedGeneration);
58386
59574
  if (!images.length || !this.canInsertImages || this.literalSurface) return false;
58387
59575
  const view = this.editor.ctx.get(editorViewCtx);
58388
- const { schema } = view.state;
58389
59576
  const placement = point ? void 0 : imagePlacement(view.state);
58390
59577
  if (placement?.kind === "inline") {
58391
59578
  this.insertInline(images);
58392
59579
  return true;
58393
59580
  }
58394
- const blocks = images.map((image) => schema.nodes.paragraph.create(null, schema.nodes.image.create({
58395
- src: image.reference,
58396
- alt: image.alt,
58397
- title: image.title ?? ""
58398
- })));
59581
+ const blocks = this.imageBlocks(images);
58399
59582
  if (point) {
58400
59583
  this.insertBlocks(blocks, point);
58401
59584
  return true;
@@ -58411,6 +59594,31 @@ var InkKitEditor = class InkKitEditor {
58411
59594
  view.dispatch(tr.scrollIntoView());
58412
59595
  return true;
58413
59596
  }
59597
+ /** Adds images as the last blocks of the document and leaves everything else as it is: no focus,
59598
+ * no scrolling, and the selection stays on the text it covered. For a host that attaches images
59599
+ * to a document the person is not editing at that moment. Returns false when it does nothing. */
59600
+ appendImages(images, expectedGeneration) {
59601
+ this.assertAlive();
59602
+ images = imageInsertions(images);
59603
+ this.assertMutation(expectedGeneration);
59604
+ if (!images.length || !this.canInsertImages || this.literalSurface) return false;
59605
+ const view = this.editor.ctx.get(editorViewCtx);
59606
+ const { doc, tr } = view.state;
59607
+ const blocks = this.imageBlocks(images);
59608
+ if (doc.childCount === 1 && doc.firstChild.type.name === "paragraph" && doc.firstChild.content.size === 0 && this.currentText().trim() === "") tr.replaceWith(0, doc.content.size, blocks);
59609
+ else tr.insert(doc.content.size, blocks);
59610
+ view.dispatch(closeHistory(tr));
59611
+ view.dispatch(closeHistory(view.state.tr).setMeta("addToHistory", false));
59612
+ return true;
59613
+ }
59614
+ imageBlocks(images) {
59615
+ const { schema } = this.editor.ctx.get(editorViewCtx).state;
59616
+ return images.map((image) => schema.nodes.paragraph.create(null, schema.nodes.image.create({
59617
+ src: image.reference,
59618
+ alt: image.alt,
59619
+ title: image.title ?? ""
59620
+ })));
59621
+ }
58414
59622
  insertInline(images) {
58415
59623
  const view = this.editor.ctx.get(editorViewCtx);
58416
59624
  const { state } = view;
@@ -58528,7 +59736,7 @@ var InkKitEditor = class InkKitEditor {
58528
59736
  if (!range) return;
58529
59737
  const target = liftTarget(range);
58530
59738
  if (target == null) return;
58531
- view.dispatch(view.state.tr.lift(range, target));
59739
+ view.dispatch(liftQuote(view.state, range, target));
58532
59740
  }
58533
59741
  /** Changes the selected textblocks' type, keeping everything they hold. */
58534
59742
  textblockType(type, attr) {
@@ -58613,25 +59821,27 @@ var InkKitEditor = class InkKitEditor {
58613
59821
  commit(command);
58614
59822
  return;
58615
59823
  }
59824
+ const onHostError = (error) => {
59825
+ if (!this.destroyed) this.report(coded(error, "slash-command-failed"));
59826
+ };
59827
+ const insert = (markdown) => {
59828
+ if (this.destroyed) return;
59829
+ if (markdown === void 0) return;
59830
+ if (typeof markdown !== "string") {
59831
+ this.report(invalidArgument("A slash command must return Markdown text"));
59832
+ return;
59833
+ }
59834
+ commit({ markdown });
59835
+ };
59836
+ let result;
58616
59837
  try {
58617
- const result = command.action(this.context());
58618
- const insert = (markdown) => {
58619
- if (this.destroyed) return;
58620
- if (markdown === void 0) return;
58621
- if (typeof markdown !== "string") {
58622
- this.report(invalidArgument("A slash command must return Markdown text"));
58623
- return;
58624
- }
58625
- commit({ markdown });
58626
- };
58627
- if (result instanceof Promise) result.then(insert).catch((error) => {
58628
- if (this.destroyed) return;
58629
- this.report(error);
58630
- });
58631
- else insert(result);
59838
+ result = command.action(this.context());
58632
59839
  } catch (error) {
58633
- this.report(error);
59840
+ onHostError(error);
59841
+ return;
58634
59842
  }
59843
+ if (result instanceof Promise) result.then(insert, onHostError);
59844
+ else insert(result);
58635
59845
  }
58636
59846
  /** Returns whether the document or the marks the next typed text takes changed. */
58637
59847
  format(command, options, expectedGeneration) {
@@ -58692,12 +59902,13 @@ var InkKitEditor = class InkKitEditor {
58692
59902
  return !after.doc.eq(before.doc) || !Mark.sameSet(marks(after), marks(before));
58693
59903
  }
58694
59904
  /**
58695
- * Refuses a link destination that is empty, that opens anything but a web, mail or phone address,
58696
- * or that the Markdown writer cannot save so that it reads back the same.
59905
+ * Refuses a link destination that is empty, that opens anything but a web, mail or phone address
59906
+ * or a scheme the host listed, or that the Markdown writer cannot save so that it reads back the
59907
+ * same.
58697
59908
  */
58698
59909
  checkDestination(destination, name) {
58699
59910
  if (destination.trim() === "") throw invalidArgument(`${name} must not be empty`);
58700
- if (!isSafeLinkTarget(destination)) throw invalidArgument(`${name} must be relative, or an http, https, mailto or tel address, with no control characters`);
59911
+ if (!isSafeLinkTarget(destination, this.linkSchemes)) throw invalidArgument(`${name} must be relative, or an address with an allowed scheme (http, https, mailto, tel or one of linkSchemes), with no control characters`);
58701
59912
  const { schema } = this.editor.ctx.get(editorViewCtx).state;
58702
59913
  const written = serialize(this.editor.ctx, schema.node("doc", null, [schema.node("paragraph", null, [schema.text("x", [schema.marks.link.create({ href: destination })])])]));
58703
59914
  let read;
@@ -58719,13 +59930,18 @@ var InkKitEditor = class InkKitEditor {
58719
59930
  const { selection, storedMarks } = state;
58720
59931
  const { from, to, empty } = selection;
58721
59932
  const stored = empty ? type.isInSet(storedMarks ?? []) : void 0;
58722
- if (stored && !stored.isInSet(selection.$from.marks())) {
59933
+ const beside = empty ? selection.$from.nodeBefore?.marks ?? [] : [];
59934
+ if (stored && !stored.isInSet(selection.$from.marks()) && !stored.isInSet(beside)) {
58723
59935
  const tr = state.tr.removeStoredMark(type);
58724
59936
  if (destination !== void 0) tr.addStoredMark(type.create({ href: destination }));
58725
59937
  view.dispatch(tr);
58726
59938
  return;
58727
59939
  }
58728
- const mark = type.isInSet(empty ? [...selection.$from.marks(), ...state.doc.nodeAt(from)?.marks ?? []] : state.doc.nodeAt(from)?.marks ?? []);
59940
+ const mark = type.isInSet(empty ? [
59941
+ ...selection.$from.marks(),
59942
+ ...beside,
59943
+ ...state.doc.nodeAt(from)?.marks ?? []
59944
+ ] : state.doc.nodeAt(from)?.marks ?? []);
58729
59945
  const extent = mark && markRun(state.doc, mark, from, to);
58730
59946
  const keepStored = (tr, link) => {
58731
59947
  if (!storedMarks) return tr;
@@ -58857,6 +60073,7 @@ var InkKitEditor = class InkKitEditor {
58857
60073
  "pasteAsPlainText",
58858
60074
  "commandState",
58859
60075
  "setEditable",
60076
+ "setHostActive",
58860
60077
  "setTextInputPreferences",
58861
60078
  "setEditingMode",
58862
60079
  "replaceSource",
@@ -58872,12 +60089,14 @@ var InkKitEditor = class InkKitEditor {
58872
60089
  "keyDown",
58873
60090
  "insertPaths",
58874
60091
  "insertImages",
60092
+ "appendImages",
58875
60093
  "insertFootnote",
58876
60094
  "navigateFootnote",
58877
60095
  "editReferenceDefinition",
58878
60096
  "setCommentsVisible",
58879
60097
  "focus",
58880
60098
  "blur",
60099
+ "refreshReferences",
58881
60100
  "setKeymap",
58882
60101
  "textSnapshot",
58883
60102
  "selectTextRange",