@aicayzer/inkkit 0.3.0 → 0.3.1

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,
@@ -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,6 +44127,25 @@ 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;
@@ -43977,6 +44160,7 @@ var FileResolution = class {
43977
44160
  }
43978
44161
  update(reference) {
43979
44162
  const key = JSON.stringify([
44163
+ reference.raw,
43980
44164
  reference.reference,
43981
44165
  reference.kind,
43982
44166
  reference.fragment
@@ -43986,8 +44170,23 @@ var FileResolution = class {
43986
44170
  this.key = key;
43987
44171
  this.retry();
43988
44172
  }
44173
+ /** Resolves again from a blank loading card. */
43989
44174
  retry() {
43990
44175
  this.cancel();
44176
+ this.start(true);
44177
+ }
44178
+ /**
44179
+ * Resolves again behind the current card, which stays until the answer differs, so a card that
44180
+ * the host's change doesn't affect neither blanks nor moves.
44181
+ */
44182
+ refresh() {
44183
+ if (this.dead || !this.runtime.active()) return;
44184
+ if (this.state.status === "idle") this.retry();
44185
+ else this.start(false);
44186
+ }
44187
+ start(blank) {
44188
+ this.request?.controller.abort();
44189
+ this.request = void 0;
43991
44190
  if (this.dead || !this.reference || !this.runtime.active()) return;
43992
44191
  const captured = this.runtime.capture();
43993
44192
  const request = {
@@ -43995,18 +44194,21 @@ var FileResolution = class {
43995
44194
  isCurrent: captured.isCurrent
43996
44195
  };
43997
44196
  this.request = request;
43998
- this.state = { status: "loading" };
43999
- this.changed();
44197
+ if (blank) {
44198
+ this.state = { status: "loading" };
44199
+ this.changed();
44200
+ }
44000
44201
  resolveFile(this.runtime.adapter, this.reference, {
44001
44202
  ...captured.context,
44002
44203
  signal: request.controller.signal
44003
44204
  }).then((presentation) => {
44004
44205
  if (!this.current(request)) return;
44206
+ const before = this.state;
44005
44207
  this.state = {
44006
44208
  status: "resolved",
44007
44209
  presentation
44008
44210
  };
44009
- this.changed();
44211
+ if (before.status !== "resolved" || !samePresentation(before.presentation, presentation)) this.changed();
44010
44212
  }, (failure) => {
44011
44213
  if (!this.current(request)) return;
44012
44214
  const error = coded(failure, "file-unavailable");
@@ -44117,7 +44319,7 @@ function nodeText(node, missingImages, fileDescriptions) {
44117
44319
  if (node.type.name === "image") {
44118
44320
  const description = fileDescriptions.get(node.attrs);
44119
44321
  if (description != null) return description;
44120
- const alt = fileReference(node).label || "Image";
44322
+ const alt = fileFields(node).label || "Image";
44121
44323
  return missingImages.has(String(node.attrs.src ?? "")) ? `[${alt}: image unavailable]` : alt;
44122
44324
  }
44123
44325
  if (node.type.name === "table") {
@@ -44131,7 +44333,9 @@ function nodeText(node, missingImages, fileDescriptions) {
44131
44333
  }
44132
44334
  return clipboardText(node.content, missingImages, fileDescriptions);
44133
44335
  }
44134
- function clipboardDOM(content, schema) {
44336
+ /** `linkSchemes` are the schemes beyond the built-in ones that a copied link keeps. Print and paste
44337
+ * leave it out, so they keep only the built-in ones. */
44338
+ function clipboardDOM(content, schema, linkSchemes) {
44135
44339
  content = shareableFragment(content);
44136
44340
  const container = document.createElement("div");
44137
44341
  const serializer = DOMSerializer.fromSchema(schema);
@@ -44144,7 +44348,7 @@ function clipboardDOM(content, schema) {
44144
44348
  }]
44145
44349
  }, {
44146
44350
  ...serializer.marks,
44147
- link: (mark, inline) => isSafeLinkTarget(String(mark.attrs.href ?? "")) ? serializer.marks.link(mark, inline) : ["span", 0]
44351
+ link: (mark, inline) => isSafeLinkTarget(String(mark.attrs.href ?? ""), linkSchemes) ? serializer.marks.link(mark, inline) : ["span", 0]
44148
44352
  });
44149
44353
  container.append(safe.serializeFragment(content));
44150
44354
  if (!onlyDefinitions(content)) for (const definition of container.querySelectorAll("[data-inkkit-reference-definition]")) definition.remove();
@@ -44163,18 +44367,18 @@ function clipboardDOM(content, schema) {
44163
44367
  }
44164
44368
  return container;
44165
44369
  }
44166
- function clipboardContent(content, schema) {
44370
+ function clipboardContent(content, schema, linkSchemes) {
44167
44371
  return {
44168
44372
  text: clipboardText(content),
44169
- html: clipboardDOM(content, schema).innerHTML
44373
+ html: clipboardDOM(content, schema, linkSchemes).innerHTML
44170
44374
  };
44171
44375
  }
44172
- function clipboardSerializer(schema) {
44376
+ function clipboardSerializer(schema, linkSchemes) {
44173
44377
  const serializer = DOMSerializer.fromSchema(schema);
44174
44378
  const safe = new DOMSerializer(serializer.nodes, serializer.marks);
44175
44379
  safe.serializeFragment = (content, options = {}, target) => {
44176
44380
  const output = target ?? (options.document ?? document).createDocumentFragment();
44177
- output.append(...clipboardDOM(content, schema).childNodes);
44381
+ output.append(...clipboardDOM(content, schema, linkSchemes).childNodes);
44178
44382
  return output;
44179
44383
  };
44180
44384
  return safe;
@@ -44182,15 +44386,15 @@ function clipboardSerializer(schema) {
44182
44386
  var visibleClipboard = $prose((ctx) => new Plugin({
44183
44387
  key: new PluginKey("visibleClipboard"),
44184
44388
  props: {
44185
- clipboardSerializer: clipboardSerializer(ctx.get(schemaCtx)),
44389
+ clipboardSerializer: clipboardSerializer(ctx.get(schemaCtx), editorLinkSchemes(ctx)),
44186
44390
  clipboardTextSerializer: (slice) => clipboardText(slice.content)
44187
44391
  }
44188
44392
  }));
44189
44393
  /** 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) {
44394
+ async function portableClipboard(content, schema, markdown, adapter, context, completeDiagrams, files, signal = new AbortController().signal, linkSchemes) {
44191
44395
  signal.throwIfAborted();
44192
44396
  content = shareableFragment(content);
44193
- const base = clipboardContent(content, schema);
44397
+ const base = clipboardContent(content, schema, linkSchemes);
44194
44398
  const container = document.createElement("div");
44195
44399
  container.innerHTML = base.html;
44196
44400
  const images = [];
@@ -44202,7 +44406,7 @@ async function portableClipboard(content, schema, markdown, adapter, context, co
44202
44406
  const captured = [...container.querySelectorAll("img")].map((element, index) => {
44203
44407
  const node = references[index];
44204
44408
  const reference = String(node?.attrs.src ?? "");
44205
- const { label: authoredAlt, width } = fileReference(node);
44409
+ const { label: authoredAlt, width } = fileFields(node);
44206
44410
  const alt = authoredAlt || "Image";
44207
44411
  if (width != null && width > 0) element.setAttribute("width", String(Math.min(4096, width)));
44208
44412
  element.removeAttribute("src");
@@ -44805,10 +45009,82 @@ var cursor = [
44805
45009
  //#endregion
44806
45010
  //#region src/text-input.ts
44807
45011
  var isInputRules = (plugin) => plugin.spec.isInputRules === true;
45012
+ function heldLink(doc, from, to) {
45013
+ const type = doc.type.schema.marks.link;
45014
+ if (!type) return void 0;
45015
+ let link;
45016
+ let whole = true;
45017
+ doc.nodesBetween(from, to, (node) => {
45018
+ if (!node.isInline) return true;
45019
+ const own = type.isInSet(node.marks);
45020
+ if (!own || link && !own.eq(link)) whole = false;
45021
+ link ??= own;
45022
+ return false;
45023
+ });
45024
+ return whole ? link : void 0;
45025
+ }
44808
45026
  /**
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.
45027
+ * The marks that text put in place of `from` to `to` takes. Links are not
45028
+ * inclusive, so text typed at either end of a link stays outside it, but text
45029
+ * that replaces part or all of one link's text stays in that link. Marks
45030
+ * stored for the selection, such as a link started at the caret, come first.
45031
+ */
45032
+ function replacementMarks(state, from, to) {
45033
+ const { doc, selection, storedMarks } = state;
45034
+ if (storedMarks && selection.from === from && selection.to === to) return storedMarks;
45035
+ const $from = doc.resolve(from);
45036
+ if (from === to) return $from.marks();
45037
+ const $to = doc.resolve(to);
45038
+ const marks = $from.marksAcross($to) ?? $from.marks();
45039
+ const link = $from.sameParent($to) ? heldLink(doc, from, to) : void 0;
45040
+ return link && !link.isInSet(marks) ? link.addToSet(marks) : marks;
45041
+ }
45042
+ /**
45043
+ * The link that typing over its end continues, after typing replaced the end
45044
+ * or all of its text. Its stored marks keep it for the next keystrokes until
45045
+ * the caret moves or another edit clears them.
45046
+ */
45047
+ var typingKey = new PluginKey("inkkitTextInput");
45048
+ var typingState = {
45049
+ init: () => null,
45050
+ apply(tr, link, _old, state) {
45051
+ const meta = tr.getMeta(typingKey);
45052
+ if (meta !== void 0) return meta;
45053
+ return link && link.isInSet(state.storedMarks ?? []) ? link : null;
45054
+ }
45055
+ };
45056
+ function linkTyping(state, from, to, text) {
45057
+ const type = state.schema.marks.link;
45058
+ if (!type) return void 0;
45059
+ const marks = replacementMarks(state, from, to);
45060
+ const link = type.isInSet(marks);
45061
+ if (!link) return void 0;
45062
+ const stored = type.isInSet(state.storedMarks ?? []);
45063
+ const continued = typingKey.getState(state);
45064
+ const retyped = stored !== void 0 && continued?.eq(stored) || from < to && !type.isInSet(state.doc.resolve(from).marksAcross(state.doc.resolve(to)) ?? []);
45065
+ if (!retyped && !stored) return void 0;
45066
+ const space = retyped ? -1 : text.search(/\s/);
45067
+ const linked = space < 0 ? text.length : space;
45068
+ const address = link.attrs.form != null;
45069
+ return {
45070
+ marks,
45071
+ link,
45072
+ linked,
45073
+ stored: address ? null : linked < text.length ? link.removeFromSet(marks) : marks,
45074
+ retyped: retyped && !address
45075
+ };
45076
+ }
45077
+ function continueLink(tr, from, text, typing) {
45078
+ const linked = Math.min(typing.linked, text.length);
45079
+ tr.removeMark(from, from + text.length, typing.link.type);
45080
+ if (linked) tr.addMark(from, from + linked, typing.link);
45081
+ if (typing.stored) tr.setStoredMarks(typing.stored);
45082
+ return tr.setMeta(typingKey, typing.retyped ? typing.link : null);
45083
+ }
45084
+ /**
45085
+ * Inserts typed text at `from` to `to`, and returns whether it did. It handles
45086
+ * text that arrives in one piece, such as buffered native typing, dictation or
45087
+ * a text replacement, and typing that continues a link.
44812
45088
  *
44813
45089
  * Input rules are written for one typed character: Milkdown's take their
44814
45090
  * positions from the document before the text goes in, so given more than
@@ -44816,19 +45092,25 @@ var isInputRules = (plugin) => plugin.spec.isInputRules === true;
44816
45092
  * in except for its last character, and the input rules see that character
44817
45093
  * typed after the rest, all in one transaction.
44818
45094
  */
44819
- function insertTextChunk(view, from, to, text, deflt) {
45095
+ function insertTypedText(view, from, to, text, deflt) {
44820
45096
  const characters = Array.from(text);
44821
- if (characters.length < 2 || view.composing || /[\r\n]/.test(text)) return false;
45097
+ if (!characters.length || view.composing || /[\r\n]/.test(text)) return false;
44822
45098
  const { state } = view;
44823
- const $from = state.doc.resolve(from);
44824
- const $to = state.doc.resolve(to);
45099
+ const typing = linkTyping(state, from, to, text);
45100
+ if (characters.length < 2 && !typing) return false;
45101
+ const fallback = () => {
45102
+ const plain = deflt();
45103
+ return typing ? continueLink(plain, from, text, typing) : plain;
45104
+ };
45105
+ const marks = typing?.marks ?? replacementMarks(state, from, to);
44825
45106
  const last = characters.at(-1);
44826
45107
  const rest = text.slice(0, -last.length);
44827
- const tr = state.tr.insertText(rest, from, to);
45108
+ const tr = rest ? state.tr.setStoredMarks(marks).insertText(rest, from, to) : state.tr.delete(from, to);
45109
+ if (typing) continueLink(tr, from, rest, typing);
44828
45110
  const $at = Selection.near(tr.doc.resolve(tr.mapping.map(to)), -1).$head;
44829
45111
  const at = $at.pos;
44830
45112
  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());
45113
+ view.dispatch(fallback().scrollIntoView());
44832
45114
  return true;
44833
45115
  }
44834
45116
  const rules = state.plugins.filter(isInputRules);
@@ -44836,7 +45118,7 @@ function insertTextChunk(view, from, to, text, deflt) {
44836
45118
  schema: state.schema,
44837
45119
  doc: tr.doc,
44838
45120
  selection: TextSelection.create(tr.doc, at),
44839
- storedMarks: state.storedMarks ?? (from === to ? $from.marks() : $from.marksAcross($to))
45121
+ storedMarks: typing && typing.linked < text.length ? typing.link.removeFromSet(marks) : marks
44840
45122
  }, rest);
44841
45123
  let ruled;
44842
45124
  const preparedView = Object.create(view, {
@@ -44846,7 +45128,7 @@ function insertTextChunk(view, from, to, text, deflt) {
44846
45128
  } }
44847
45129
  });
44848
45130
  if (!rules.some((rule) => rule.props.handleTextInput?.call(rule, preparedView, at, at, last, () => prepared.tr.insertText(last, at)))) {
44849
- const plain = deflt().scrollIntoView();
45131
+ const plain = fallback().scrollIntoView();
44850
45132
  view.dispatch(last === "-" ? insertedHyphen(plain) : plain);
44851
45133
  return true;
44852
45134
  }
@@ -44854,6 +45136,7 @@ function insertTextChunk(view, from, to, text, deflt) {
44854
45136
  for (const step of ruled.steps) tr.step(step);
44855
45137
  tr.setSelection(ruled.selection.map(tr.doc, StepMap.empty));
44856
45138
  if (ruled.storedMarksSet) tr.setStoredMarks(ruled.storedMarks);
45139
+ else if (typing?.stored) tr.setStoredMarks(typing.stored);
44857
45140
  for (const rule of rules) {
44858
45141
  const meta = ruled.getMeta(rule);
44859
45142
  if (meta !== void 0) tr.setMeta(rule, meta);
@@ -44862,6 +45145,65 @@ function insertTextChunk(view, from, to, text, deflt) {
44862
45145
  view.dispatch(tr.scrollIntoView());
44863
45146
  return true;
44864
45147
  }
45148
+ function finishComposition(view, composing) {
45149
+ const { state } = view;
45150
+ const { start, link, retyped } = composing;
45151
+ const end = state.selection.head;
45152
+ if (view.isDestroyed || view.composing || state.doc !== composing.doc || !state.selection.empty || end <= start || !state.doc.resolve(start).sameParent(state.selection.$head)) return;
45153
+ const text = state.doc.textBetween(start, end);
45154
+ if (text.length !== end - start) return;
45155
+ const space = retyped ? -1 : text.search(/\s/);
45156
+ const linked = start + (space < 0 ? text.length : space);
45157
+ const tr = state.tr;
45158
+ if (linked > start) tr.addMark(start, linked, link);
45159
+ const marks = link.type.removeFromSet(tr.doc.resolve(end).marks());
45160
+ tr.setStoredMarks(linked === end ? link.addToSet(marks) : marks).setMeta(typingKey, retyped && linked === end ? link : null).setMeta("composition", composing.composition);
45161
+ view.dispatch(tr);
45162
+ }
45163
+ var controlCharacters = /^[\u0000-\u0008\u000B\u000C\u000E-\u001F\u007F]+$/;
45164
+ /** Typed text: chunks, control characters, and the link rule for typing and composition. */
45165
+ function textInputPlugin() {
45166
+ let composing;
45167
+ return new Plugin({
45168
+ key: typingKey,
45169
+ state: typingState,
45170
+ props: {
45171
+ handleTextInput: (view, from, to, text, deflt) => controlCharacters.test(text) || insertTypedText(view, from, to, text, deflt),
45172
+ handleDOMEvents: {
45173
+ compositionstart(view) {
45174
+ if (view.composing) return false;
45175
+ const { state } = view;
45176
+ const { from, to } = state.selection;
45177
+ const typing = linkTyping(state, from, to, "");
45178
+ composing = typing?.stored && !view.isDestroyed ? {
45179
+ link: typing.link,
45180
+ retyped: typing.retyped,
45181
+ start: from,
45182
+ doc: state.doc
45183
+ } : void 0;
45184
+ return false;
45185
+ },
45186
+ compositionend(view) {
45187
+ const ended = composing;
45188
+ if (ended) setTimeout(() => {
45189
+ if (composing !== ended) return;
45190
+ composing = void 0;
45191
+ finishComposition(view, ended);
45192
+ });
45193
+ return false;
45194
+ }
45195
+ }
45196
+ },
45197
+ appendTransaction(transactions, _old, state) {
45198
+ if (composing && transactions.some((tr) => tr.docChanged)) for (const tr of transactions) {
45199
+ composing.start = tr.mapping.map(composing.start, -1);
45200
+ composing.composition ??= tr.getMeta("composition");
45201
+ }
45202
+ if (composing) composing.doc = state.doc;
45203
+ return null;
45204
+ }
45205
+ });
45206
+ }
44865
45207
  //#endregion
44866
45208
  //#region src/drop-placement.ts
44867
45209
  /**
@@ -44977,6 +45319,20 @@ function caretAfterInsertion(tr, view) {
44977
45319
  return tr.setSelection($end.parent.inlineContent ? TextSelection.create(tr.doc, end) : caretAfter(tr.doc, end, view && calloutFolds(view, tr)));
44978
45320
  }
44979
45321
  //#endregion
45322
+ //#region src/gutter.ts
45323
+ var listMarker = /(?:[-*+]|\d{1,9}[.)])(?=[ \t])/g;
45324
+ /**
45325
+ * What the lines of a block repeat before it: the quote markers and
45326
+ * indentation that open the line `offset` is on, up to `offset`, with a list
45327
+ * marker as the spaces it takes, which is how the lines after the item's first
45328
+ * are indented. Anything else before `offset` on its line is content, and ends
45329
+ * the gutter.
45330
+ */
45331
+ function gutterAt(text, offset) {
45332
+ const line = text.slice(text.lastIndexOf("\n", offset - 1) + 1, offset);
45333
+ return /^(?:[ \t]|>|(?:[-*+]|\d{1,9}[.)])(?=[ \t]))*/.exec(line)[0].replace(listMarker, (marker) => " ".repeat(marker.length));
45334
+ }
45335
+ //#endregion
44980
45336
  //#region src/code-fences.ts
44981
45337
  function readFence(value) {
44982
45338
  if (typeof value !== "object" || value == null) return null;
@@ -45368,10 +45724,11 @@ var formatting = /* @__PURE__ */ new Set([
45368
45724
  "delete",
45369
45725
  "highlight"
45370
45726
  ]);
45727
+ var linkTypes = /* @__PURE__ */ new Set(["link", "linkReference"]);
45371
45728
  function ordered(node) {
45372
45729
  let current = node;
45373
45730
  const inner = current.children?.[0];
45374
- if (current.type === "link" && current.children.length === 1 && formatting.has(inner.type)) current = {
45731
+ if (linkTypes.has(current.type) && current.children.length === 1 && formatting.has(inner.type)) current = {
45375
45732
  ...inner,
45376
45733
  children: [{
45377
45734
  ...current,
@@ -45383,6 +45740,32 @@ function ordered(node) {
45383
45740
  children: current.children.map(ordered)
45384
45741
  } : current;
45385
45742
  }
45743
+ function wrappers$1(node) {
45744
+ const chain = [];
45745
+ 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);
45746
+ return chain;
45747
+ }
45748
+ function authoredNesting(before, after) {
45749
+ if (before.type === after.type || !formatting.has(before.type)) return after;
45750
+ const authored = wrappers$1(before);
45751
+ const written = wrappers$1(after);
45752
+ const types = (chain) => chain.map((node) => node.type).sort().join();
45753
+ const linksIn = (chain) => chain.filter((node) => linkTypes.has(node.type));
45754
+ const shape = (node) => ownShape({
45755
+ ...node,
45756
+ children: []
45757
+ });
45758
+ const [authoredLink] = linksIn(authored);
45759
+ const [writtenLink] = linksIn(written);
45760
+ 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;
45761
+ const inner = written.at(-1).children;
45762
+ const byType = new Map(written.map((node) => [node.type, node]));
45763
+ return authored.reduceRight((child, node, level) => ({
45764
+ ...byType.get(node.type),
45765
+ ...level === 0 && { position: after.position },
45766
+ children: child ? [child] : inner
45767
+ }), void 0);
45768
+ }
45386
45769
  /** Where the delimiter row ends, given where the header row before it ends. */
45387
45770
  function delimiterRowEnd(text, headerEnd) {
45388
45771
  const next = text.indexOf("\n", headerEnd);
@@ -45391,15 +45774,17 @@ function delimiterRowEnd(text, headerEnd) {
45391
45774
  ending.lastIndex = next + 1;
45392
45775
  return ending.exec(text)?.index ?? text.length;
45393
45776
  }
45394
- function ownShape(node) {
45777
+ function ownShape(node, spreadless = false) {
45395
45778
  return JSON.stringify(ordered(node), (key, value) => [
45396
45779
  "position",
45397
45780
  "data",
45398
45781
  "children",
45399
45782
  "value",
45400
45783
  "marker"
45401
- ].includes(key) ? void 0 : value);
45784
+ ].includes(key) || spreadless && key === "spread" ? void 0 : value);
45402
45785
  }
45786
+ var holdsLiteralBlock = (node) => (node.type === "listItem" || node.type === "list") && !!node.children?.some((child) => child.type === "inkkitLiteral" || literals$1.has(child) || holdsLiteralBlock(child));
45787
+ var sameShape = (before, after) => ownShape(before) === ownShape(after) || (holdsLiteralBlock(before) || holdsLiteralBlock(after)) && ownShape(before, true) === ownShape(after, true);
45403
45788
  var matchShape = (node) => ownShape({
45404
45789
  ...node,
45405
45790
  checked: void 0
@@ -45456,10 +45841,25 @@ function authoredMarker(list, source) {
45456
45841
  return at == null ? void 0 : markerAt(source, at)?.marker;
45457
45842
  }
45458
45843
  var sameList = (before, after) => before.type === "list" && after.type === "list" && before.ordered === after.ordered;
45459
- function canonicalSlice(marker, children, canonical, from, to, previous, next) {
45844
+ function childGutters(before, after, source, canonical, fromStart = false) {
45845
+ if (inlineParents.has(before.type) || before.type === "table") return void 0;
45846
+ const [first, now] = [before, after].map((node) => fromStart ? node.position?.start.offset : node.children?.[0]?.position?.start.offset);
45847
+ return first == null || now == null ? void 0 : [gutterAt(source, first), gutterAt(canonical, now)];
45848
+ }
45849
+ function regutter(text, gutters, open = false) {
45850
+ if (!gutters || gutters[0] === gutters[1]) return text;
45851
+ const [authored, canonical] = gutters;
45852
+ const bare = canonical.trimEnd();
45853
+ return text.replace(/(\r?\n)([^\r\n]*)/g, (match, ending, line, at) => {
45854
+ if (open && at + match.length === text.length && (line === bare || line === canonical)) return ending + authored;
45855
+ if (line === bare) return ending + authored.trimEnd();
45856
+ return line.startsWith(canonical) ? ending + authored + line.slice(canonical.length) : match;
45857
+ });
45858
+ }
45859
+ function canonicalSlice(marker, children, canonical, from, to, previous, next, gutters, open = false, inherited) {
45460
45860
  let text = canonical.slice(from, to);
45461
45861
  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);
45862
+ if (marker) return regutter(relabel(text, children.map((item) => item.position.start.offset - from), marker), gutters, open);
45463
45863
  children.forEach((child, index) => {
45464
45864
  if (child.type !== "list") {
45465
45865
  previous = void 0;
@@ -45467,19 +45867,24 @@ function canonicalSlice(marker, children, canonical, from, to, previous, next) {
45467
45867
  }
45468
45868
  const avoid = [previous, index === children.length - 1 ? next : void 0].filter((neighbour) => neighbour?.ordered === child.ordered).map((neighbour) => neighbour.marker);
45469
45869
  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));
45870
+ const choice = [
45871
+ inherited && /[.)]/.test(inherited) === !!child.ordered ? inherited : void 0,
45872
+ own,
45873
+ ...child.ordered ? [".", ")"] : [
45874
+ "-",
45875
+ "*",
45876
+ "+"
45877
+ ]
45878
+ ].find((candidate) => candidate && !avoid.includes(candidate));
45475
45879
  if (choice) text = relabel(text, offsets(child), choice);
45476
45880
  previous = {
45477
45881
  ordered: child.ordered,
45478
45882
  marker: choice ?? own ?? ""
45479
45883
  };
45480
45884
  });
45481
- return text;
45885
+ return regutter(text, gutters, open);
45482
45886
  }
45887
+ var itemMarker = (item, source) => item.type === "listItem" ? markerAt(source, item.position.start.offset)?.marker : void 0;
45483
45888
  function authoredNeighbour(before, after, source) {
45484
45889
  if (!before || !after || !sameList(before, after)) return void 0;
45485
45890
  const marker = authoredMarker(before, source);
@@ -45579,35 +45984,90 @@ function retainSplit(candidates, after, source, canonical, lineEnding, taken) {
45579
45984
  text: retainSource(part, after, source, canonical, lineEnding)
45580
45985
  };
45581
45986
  }
45582
- function retainParts(marker, before, after, source, canonical, from, to, lineEnding, previous, next) {
45583
- let items;
45987
+ function liftedSource(blocks, after, source) {
45988
+ if (after.type === "list" || after.type === "blockquote") return void 0;
45989
+ const wanted = signature(after);
45990
+ const found = [];
45991
+ const visit = (node) => {
45992
+ for (const child of node.children ?? []) if (child.type === "list" || child.type === "listItem" || child.type === "blockquote") visit(child);
45993
+ else if ((node.type === "blockquote" || node.type === "listItem" && node.checked == null) && signature(child) === wanted) found.push(child);
45994
+ };
45995
+ blocks.forEach(visit);
45996
+ if (found.length !== 1) return void 0;
45997
+ const start = found[0].position?.start.offset;
45998
+ const end = found[0].position?.end.offset;
45999
+ if (start == null || end == null) return void 0;
46000
+ return regutter(source.slice(start, end), ["", gutterAt(source, start)]);
46001
+ }
46002
+ function strayRun(strays, wanted, source, opening) {
46003
+ const keys = wanted.map(opening);
46004
+ for (const [at, stray] of strays.entries()) {
46005
+ if (opening(stray) !== keys[0]) continue;
46006
+ const run = [stray];
46007
+ for (const next of strays.slice(at + 1)) {
46008
+ if (opening(next) !== keys[run.length] || !/^[ \t>]*(\r?\n[ \t>]*)+$/.test(source.slice(run.at(-1).position.end.offset, next.position.start.offset))) break;
46009
+ run.push(next);
46010
+ }
46011
+ return run;
46012
+ }
46013
+ return [];
46014
+ }
46015
+ function retainParts(marker, before, after, source, canonical, from, to, lineEnding, previous, next, gutters, open = false, inherited) {
45584
46016
  const taken = /* @__PURE__ */ new Set();
45585
46017
  const opening = (item) => item.children?.length ? matchShape(item) + signature(item.children[0]) : void 0;
45586
- const pieces = after.map((child) => {
46018
+ const items = /* @__PURE__ */ new Map();
46019
+ for (let row = before; row.length;) {
46020
+ const deeper = [];
46021
+ for (const node of row) {
46022
+ if (node.type !== "listItem") continue;
46023
+ const key = opening(node);
46024
+ if (key) {
46025
+ const same = items.get(key);
46026
+ if (same) same.push(node);
46027
+ else items.set(key, [node]);
46028
+ }
46029
+ for (const child of node.children ?? []) if (child.type === "list") deeper.push(...child.children ?? []);
46030
+ }
46031
+ row = deeper;
46032
+ }
46033
+ const isNumbered = (item) => /[.)]/.test(itemMarker(item, source) ?? "");
46034
+ const originals = after.map((child) => {
46035
+ const original = child.type === "listItem" ? items.get(opening(child) ?? "")?.shift() : void 0;
46036
+ return original && marker && isNumbered(original) !== /[.)]/.test(marker) ? void 0 : original;
46037
+ });
46038
+ const ownLevel = new Set(before);
46039
+ const strays = [...items.values()].flat().sort((left, right) => left.position.start.offset - right.position.start.offset);
46040
+ const pieces = after.map((child, index) => {
45587
46041
  const end = child.position.end.offset;
45588
46042
  if (child.type === "list" || child.type === "blockquote") {
45589
46043
  const part = retainSplit(before, child, source, canonical, lineEnding, taken);
45590
46044
  if (part) return part.text + canonical.slice(child.children.at(-1).position.end.offset, end);
45591
46045
  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]);
46046
+ if (child.type === "list" && !lists.length) {
46047
+ const run = strayRun(strays, child.children, source, opening);
46048
+ if (!run.length) return void 0;
46049
+ const first = run[0].position.start.offset;
46050
+ return regutter(retainSource({
46051
+ type: "list",
46052
+ ordered: child.ordered,
46053
+ start: child.ordered ? Number(/^\d+/.exec(source.slice(first))[0]) : null,
46054
+ spread: false,
46055
+ children: run,
46056
+ position: {
46057
+ start: { offset: first },
46058
+ end: { offset: run.at(-1).position.end.offset }
46059
+ }
46060
+ }, child, source, canonical, lineEnding), gutters && [gutters[0], gutterAt(source, first)]) + canonical.slice(child.children.at(-1).position.end.offset, end);
45603
46061
  }
46062
+ 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
46063
  }
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;
46064
+ if (child.type !== "listItem") return void 0;
46065
+ const original = originals[index];
46066
+ 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
46067
  return text !== void 0 && marker ? relabel(text, [0], marker) : text;
45608
46068
  });
45609
46069
  if (pieces.every((piece) => piece === void 0)) return void 0;
45610
- let text = "", at = from;
46070
+ let text = "", at = from, lastNumber;
45611
46071
  after.forEach((child, index) => {
45612
46072
  const childStart = child.position.start.offset;
45613
46073
  const childEnd = child.position.end.offset;
@@ -45615,15 +46075,18 @@ function retainParts(marker, before, after, source, canonical, from, to, lineEnd
45615
46075
  let piece = pieces[index];
45616
46076
  const own = piece && markerAt(piece, 0)?.marker;
45617
46077
  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);
46078
+ const number = /^\d+/.exec(piece ?? "")?.[0];
46079
+ 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);
46080
+ piece ??= canonicalSlice(marker, [child], canonical, childStart, childEnd, previous, following, gutters, false, inherited);
46081
+ lastNumber = child.type === "listItem" && /^\d/.test(piece) ? Number(/^\d+/.exec(piece)[0]) : void 0;
45619
46082
  previous = child.type === "list" ? {
45620
46083
  ordered: child.ordered,
45621
46084
  marker: markerAt(piece, /^[ \t]*/.exec(piece)[0].length)?.marker ?? ""
45622
46085
  } : void 0;
45623
- text += canonical.slice(at, childStart) + piece;
46086
+ text += regutter(canonical.slice(at, childStart), gutters, true) + piece;
45624
46087
  at = childEnd;
45625
46088
  });
45626
- return text + canonical.slice(at, to);
46089
+ return text + regutter(canonical.slice(at, to), gutters, open);
45627
46090
  }
45628
46091
  function decodedOffsets(raw, lines = false) {
45629
46092
  const offsets = [0];
@@ -45631,7 +46094,7 @@ function decodedOffsets(raw, lines = false) {
45631
46094
  for (let index = 0; index < raw.length;) {
45632
46095
  const rest = raw.slice(index);
45633
46096
  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];
46097
+ let token = line ?? /^\\[!"#$%&'()*+,\-./:;<=>?@[\]\\^_`{|}~]/.exec(rest)?.[0] ?? /^&(?:#[xX][\da-fA-F]+|#\d+|[A-Za-z][A-Za-z\d]+);/.exec(rest)?.[0];
45635
46098
  let value = raw[index];
45636
46099
  if (line) {
45637
46100
  if (!lines) return void 0;
@@ -45640,7 +46103,8 @@ function decodedOffsets(raw, lines = false) {
45640
46103
  else if (token) {
45641
46104
  const element = document.createElement("textarea");
45642
46105
  element.innerHTML = token;
45643
- value = element.value;
46106
+ if (element.value === token) token = void 0;
46107
+ else value = element.value;
45644
46108
  }
45645
46109
  index += token?.length ?? 1;
45646
46110
  decoded += value;
@@ -45757,7 +46221,10 @@ function retainRun(before, after, source, canonical, [from, to], [newFrom, newTo
45757
46221
  }
45758
46222
  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
46223
  }
45760
- function retainText(raw, before, after, inTable = false, written, beside = [false, false]) {
46224
+ function retainText(raw, before, after, inTable = false, written, beside = [false, false], readGutters) {
46225
+ let found;
46226
+ const gutters = () => found ??= readGutters?.();
46227
+ const guttered = (text) => text.includes("\n") ? regutter(text, gutters()) : text;
45761
46228
  before = lf(before);
45762
46229
  after = lf(after);
45763
46230
  let prefix = 0;
@@ -45775,19 +46242,34 @@ function retainText(raw, before, after, inTable = false, written, beside = [fals
45775
46242
  const marker = /^(?:\d+)?\\[#+.\->)]/.exec(opening)?.[0];
45776
46243
  if (marker && prefix - lineStart <= decodedOffsets(marker).decoded.length) {
45777
46244
  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]);
46245
+ return raw.slice(0, offsets[prefix]) + guttered(written.slice(canonical.offsets[prefix], canonical.offsets[after.length - suffix])) + raw.slice(offsets[before.length - suffix]);
45779
46246
  }
45780
46247
  }
45781
46248
  }
45782
46249
  if (written !== void 0 && (beside[0] && prefix === 0 || beside[1] && suffix === 0)) {
45783
46250
  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]);
46251
+ 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
46252
  }
45786
46253
  const inserted = after.slice(prefix, after.length - suffix).split("").map((character, index) => {
45787
46254
  const at = prefix + index;
45788
- return (character === "%" || character === "=") && (after[at - 1] === character || after[at + 1] === character) || /[\\`*_[\]<>]/.test(character) || inTable && character === "|" ? `\\${character}` : character;
46255
+ return (character === "%" || character === "=") && (after[at - 1] === character || after[at + 1] === character) || /[\\`*_[\]<>]/.test(character) || inTable && character === "|" ? `\\${character}` : character === "\n" ? character + (gutters()?.[0] ?? "") : character;
45789
46256
  }).join("");
45790
- return raw.slice(0, offsets[prefix]) + inserted + raw.slice(offsets[before.length - suffix]);
46257
+ return escapeReferences(raw.slice(0, offsets[prefix]), inserted, raw.slice(offsets[before.length - suffix]));
46258
+ }
46259
+ var characterReference = /&(?:#[xX][\da-fA-F]+|#\d+|[A-Za-z][A-Za-z\d]*);/g;
46260
+ function escapeReferences(head, inserted, tail) {
46261
+ const text = head + inserted + tail;
46262
+ const from = head.length;
46263
+ const to = from + inserted.length;
46264
+ let output = "", at = 0;
46265
+ for (const match of text.matchAll(characterReference)) {
46266
+ const start = match.index;
46267
+ const end = start + match[0].length;
46268
+ if (start >= to || end <= from || (/\\*$/.exec(text.slice(0, start))[0].length & 1) === 1) continue;
46269
+ output += text.slice(at, end - 1) + "\\";
46270
+ at = end - 1;
46271
+ }
46272
+ return output + text.slice(at);
45791
46273
  }
45792
46274
  function retainBoundaryText(raw, canonical, afterBreak = false) {
45793
46275
  if (afterBreak) {
@@ -45984,6 +46466,7 @@ function markLiterals(raw, transformed, blocks = []) {
45984
46466
  raw.children.forEach(mark);
45985
46467
  }
45986
46468
  function retainSource(before, after, source, canonical, lineEnding, inTable = false, width, beside = [false, false], joined = [], reorder, afterBreak = false) {
46469
+ after = authoredNesting(before, after);
45987
46470
  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
46471
  if (before.type === "list" && after.type === "list" && before.ordered && after.ordered && !numbered.has(before) && before.position?.start.offset != null) {
45989
46472
  const own = {
@@ -46080,15 +46563,20 @@ function retainSource(before, after, source, canonical, lineEnding, inTable = fa
46080
46563
  const to = first.position.end.offset - start;
46081
46564
  return retained.startsWith(source.slice(start, before.children[1].position.start.offset)) ? retained.slice(0, from) + "<br />" + retained.slice(to) : retained;
46082
46565
  }
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);
46566
+ 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
46567
  if (signature(before) === signature(after) && ownShape(before) === ownShape(after) && !holdsLiteral(after) && !reorderedWithin(before, reorder)) return raw;
46568
+ if (before.type !== after.type) {
46569
+ const lifted = liftedSource([before], after, source);
46570
+ if (lifted !== void 0) return lifted;
46571
+ }
46085
46572
  const marker = sameList(before, after) ? authoredMarker(before, source) : void 0;
46086
46573
  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)) {
46574
+ 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
46575
  let output = "", at = start;
46089
46576
  let previous;
46090
46577
  const same = (candidate, incoming) => signature(candidate) === signature(incoming) && ownShape(candidate) === ownShape(incoming);
46091
46578
  const reorderable = before.type === "table" || before.type === "tableRow";
46579
+ const gutters = childGutters(before, after, source, canonical);
46092
46580
  const moves = reorder?.get(parentKey(before));
46093
46581
  const retained = new Set(reorderable ? before.children.flatMap((child, index) => same(child, after.children[index]) ? [index] : []) : []);
46094
46582
  before.children.forEach((child, index) => {
@@ -46099,7 +46587,7 @@ function retainSource(before, after, source, canonical, lineEnding, inTable = fa
46099
46587
  if (movedIndex >= 0) retained.add(movedIndex);
46100
46588
  const childStart = child.position?.start.offset, childEnd = child.position?.end.offset;
46101
46589
  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");
46590
+ 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
46591
  if (before.type === "tableRow" && movedIndex >= 0 && movedIndex !== index) {
46104
46592
  const originalFrom = original.children?.[0]?.position?.start.offset;
46105
46593
  const originalTo = original.children?.at(-1)?.position?.end.offset;
@@ -46118,7 +46606,7 @@ function retainSource(before, after, source, canonical, lineEnding, inTable = fa
46118
46606
  });
46119
46607
  return output + source.slice(at, end);
46120
46608
  }
46121
- if (before.type === after.type && ownShape(before) === ownShape(after) && before.children && after.children) {
46609
+ if (before.type === after.type && sameShape(before, after) && before.children && after.children) {
46122
46610
  let prefix = 0, suffix = 0;
46123
46611
  while (prefix < before.children.length && prefix < after.children.length && signature(before.children[prefix]) === signature(after.children[prefix])) prefix++;
46124
46612
  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 +46631,15 @@ function retainSource(before, after, source, canonical, lineEnding, inTable = fa
46143
46631
  const changes = after.children.slice(prefix, after.children.length - suffix);
46144
46632
  const previous = authoredNeighbour(before.children[prefix - 1], after.children[prefix - 1], source);
46145
46633
  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);
46634
+ const gutters = childGutters(before, after, source, canonical, !prefix);
46635
+ const inherited = itemMarker(before, source);
46636
+ 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
46637
  }
46148
46638
  }
46149
46639
  const lastAfter = after.children?.at(-1)?.position?.end.offset;
46150
46640
  const lastBefore = before.children?.at(-1)?.position?.end.offset;
46151
46641
  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) : "");
46642
+ 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
46643
  }
46154
46644
  function loose(list, text) {
46155
46645
  const blank = (from, to) => from != null && to != null && /\n[ \t>]*\r?\n/.test(text.slice(from, to));
@@ -46246,6 +46736,13 @@ function semanticSignature(doc) {
46246
46736
  attrs
46247
46737
  };
46248
46738
  }
46739
+ if (value.type === "comment_block" && value.attrs) {
46740
+ const { block: _block, ...attrs } = value.attrs;
46741
+ value = {
46742
+ ...value,
46743
+ attrs
46744
+ };
46745
+ }
46249
46746
  const content = value.content;
46250
46747
  if (!content) return value;
46251
46748
  const tokens = [];
@@ -46288,6 +46785,7 @@ function semanticSignature(doc) {
46288
46785
  "authoredFence",
46289
46786
  "form",
46290
46787
  "inkkitFileRaw",
46788
+ "inkkitFileSource",
46291
46789
  "inkkitFileWidth",
46292
46790
  "inkkitFileInTable"
46293
46791
  ].includes(key) ? void 0 : value));
@@ -46499,7 +46997,7 @@ var Preservation = class Preservation {
46499
46997
  try {
46500
46998
  const parse = (text) => parseDoc(ctx, text);
46501
46999
  this.baseline = parse(source);
46502
- this.lineEnding = source.includes("\r\n") && !source.replaceAll("\r\n", "").includes("\n") ? "\r\n" : "\n";
47000
+ this.lineEnding = !source.includes("\n") ? source.includes("\r") ? "\r" : "\n" : source.includes("\r\n") && !source.replaceAll("\r\n", "").includes("\n") ? "\r\n" : "\n";
46503
47001
  const processor = ctx.get(remarkCtx);
46504
47002
  const offset = source.startsWith("") ? 1 : 0;
46505
47003
  const body = source.slice(offset);
@@ -46594,6 +47092,9 @@ var Preservation = class Preservation {
46594
47092
  const indent = source.slice(source.lastIndexOf("\n", part.from - 1) + 1, part.from);
46595
47093
  return (/^[ \t]*$/.test(indent) ? indent : "") + part.text;
46596
47094
  }
47095
+ retainLifted(positions, index, after) {
47096
+ return liftedSource(this.around(positions, index).map((block) => block.ast), after, this.source.slice(this.source.startsWith("") ? 1 : 0));
47097
+ }
46597
47098
  localEdits(doc) {
46598
47099
  if (this.baseline.childCount !== this.blocks.length || doc.childCount !== this.blocks.length || !doc.sameMarkup(this.baseline)) return void 0;
46599
47100
  let output = "", previous = 0;
@@ -47008,7 +47509,7 @@ var Preservation = class Preservation {
47008
47509
  const kind = block && block.form === text && !carried.within.has(index) ? position == null ? "moved" : "unchanged" : block && tree && position != null ? "edited" : "canonical";
47009
47510
  kept.push(kind);
47010
47511
  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)));
47512
+ 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
47513
  }
47013
47514
  return {
47014
47515
  gaps,
@@ -47544,23 +48045,6 @@ function completedAngle(tr, chars, from, to) {
47544
48045
  form: "angle"
47545
48046
  })).removeStoredMark(tr.doc.type.schema.marks.link);
47546
48047
  }
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
48048
  function codeAngles(tr, edited) {
47565
48049
  const spans = [];
47566
48050
  for (const block of edited) tr.doc.nodesBetween(block.pos, block.pos + block.block.nodeSize, (node, pos) => {
@@ -47769,10 +48253,7 @@ var autolinkLiterals = $prose((ctx) => new Plugin({
47769
48253
  if (!transactions.some((tr) => tr.docChanged)) return null;
47770
48254
  const base = newState.tr;
47771
48255
  const written = transactions.every(isWrittenEdit);
47772
- if (written) {
47773
- closeAngleEnds(base, transactions);
47774
- codeAngles(base, editedBlocks(oldState.doc, base.doc));
47775
- }
48256
+ if (written) codeAngles(base, editedBlocks(oldState.doc, base.doc));
47776
48257
  let edited = editedBlocks(oldState.doc, base.doc);
47777
48258
  if (written && edited.length === 1) {
47778
48259
  const { pos, block, from, to } = edited[0];
@@ -48141,7 +48622,8 @@ function remarkCallouts() {
48141
48622
  const visit = (node) => {
48142
48623
  if (node.type === "blockquote") {
48143
48624
  const start = node.position?.start.offset, end = node.position?.end.offset;
48144
- const raw = start == null || end == null ? "" : source.slice(start, end);
48625
+ const gutter = start == null ? "" : gutterAt(source, start);
48626
+ 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
48627
  const header = /^>[ \t]*([\s\S]*?)(?:\r?\n|$)/.exec(raw)?.[1] ?? "";
48146
48628
  if (header.startsWith("[!")) {
48147
48629
  const match = /^(\[!([A-Za-z]+)\])([+-]?)(?:[ \t]+([^\r\n]*))?$/.exec(header);
@@ -48313,7 +48795,7 @@ var calloutView = $prose((ctx) => new Plugin({
48313
48795
  const render = () => {
48314
48796
  title.textContent = node.attrs.title || String(node.attrs.kind).toLowerCase().replace(/^./, (letter) => letter.toUpperCase());
48315
48797
  dom.setAttribute("data-inkkit-folded", String(folded));
48316
- view.dom.dispatchEvent(new Event("inkkit-media-policy"));
48798
+ view.dom.dispatchEvent(new Event(mediaPolicyEvent));
48317
48799
  button.hidden = !node.attrs.fold;
48318
48800
  button.setAttribute("aria-expanded", String(!folded));
48319
48801
  button.setAttribute("aria-label", `${folded ? editorLabels(ctx).expandCallout : editorLabels(ctx).collapseCallout} ${title.textContent}`);
@@ -50080,12 +50562,50 @@ function writesBare(node, index, ancestors) {
50080
50562
  return side(-1) && side(1);
50081
50563
  }
50082
50564
  var bareLinks = /* @__PURE__ */ new WeakSet();
50565
+ function linkFormatting(node) {
50566
+ const plain = node.type === "link" && !formOf(node) || node.type === "linkReference" && node.referenceType === "full";
50567
+ const [inner] = plain ? node.children : [];
50568
+ return plain && node.children.length === 1 && inner && wrappers.has(inner.type) ? inner : void 0;
50569
+ }
50570
+ function shareFormatting(children) {
50571
+ const same = (left, right) => {
50572
+ const { children: _left, position: _a, ...leftRest } = left;
50573
+ const { children: _right, position: _b, ...rightRest } = right;
50574
+ return JSON.stringify(leftRest) === JSON.stringify(rightRest);
50575
+ };
50576
+ for (let index = 0; index < children.length; index++) {
50577
+ const child = children[index];
50578
+ const formatting = linkFormatting(child);
50579
+ if (!formatting) continue;
50580
+ const link = {
50581
+ ...child,
50582
+ children: formatting.children
50583
+ };
50584
+ const previous = children[index - 1];
50585
+ const next = children[index + 1];
50586
+ const before = previous && same(previous, formatting);
50587
+ const after = next && same(next, formatting);
50588
+ if (!before && !after) continue;
50589
+ const merged = {
50590
+ ...formatting,
50591
+ children: [
50592
+ ...before ? previous.children : [],
50593
+ link,
50594
+ ...after ? next.children : []
50595
+ ]
50596
+ };
50597
+ const from = before ? index - 1 : index;
50598
+ children.splice(from, 1 + Number(before) + Number(after), merged);
50599
+ index = from - 1;
50600
+ }
50601
+ }
50083
50602
  var root = (node, parent, state, info) => {
50084
50603
  bareLinks = /* @__PURE__ */ new WeakSet();
50085
50604
  const empty = (child) => child.type === "text" && child.value === "" || wrappers.has(child.type) && "children" in child && child.children.every(empty);
50086
50605
  const visit = (current, ancestors) => {
50087
50606
  if (current.children.some((child) => empty(child))) current.children = current.children.filter((child) => !empty(child));
50088
50607
  const children = current.children;
50608
+ shareFormatting(children);
50089
50609
  children.forEach((child, index) => {
50090
50610
  if (child.type === "link" && formOf(child)) {
50091
50611
  const outside = formattedOutside(child);
@@ -50306,6 +50826,16 @@ var breaksWithoutMarks = $prose((ctx) => new Plugin({
50306
50826
  }
50307
50827
  }));
50308
50828
  var remarkLinkFormsPlugin = $remark("inkkitLinkForms", () => remarkLinkForms);
50829
+ var remarkFileSourcesPlugin = $remark("inkkitFileSources", () => remarkFileSources);
50830
+ var imageWriter = (ctx) => async () => {
50831
+ await ctx.wait(SerializerReady);
50832
+ const schema = ctx.get(schemaCtx);
50833
+ setImageWriter(schema, (node) => {
50834
+ node = node.mark([]);
50835
+ const block = node.isBlock ? node : schema.nodes.paragraph.create(null, node);
50836
+ return ctx.get(serializerCtx)(schema.topNodeType.create(null, block)).replace(/\n+$/, "");
50837
+ });
50838
+ };
50309
50839
  var preserveSpacerParagraphs = paragraphSchema.extendSchema((base) => (ctx) => {
50310
50840
  const schema = base(ctx);
50311
50841
  return {
@@ -50436,7 +50966,16 @@ var normalizedImages = imageSchema.extendSchema((base) => (ctx) => {
50436
50966
  ...schema.toMarkdown,
50437
50967
  runner(state, node) {
50438
50968
  const raw = namedFileMarkdown(node, Boolean(node.attrs.inkkitFileInTable));
50439
- if (raw == null) return schema.toMarkdown.runner(state, node);
50969
+ if (raw == null) {
50970
+ state.addNode("image", void 0, void 0, {
50971
+ url: node.attrs.src,
50972
+ alt: node.attrs.alt,
50973
+ title: node.attrs.title || null,
50974
+ inkkitFileSource: authoredFileSource(node),
50975
+ inkkitFileInTable: node.attrs.inkkitFileInTable
50976
+ });
50977
+ return;
50978
+ }
50440
50979
  state.addNode("image", void 0, void 0, {
50441
50980
  url: node.attrs.src,
50442
50981
  alt: node.attrs.alt,
@@ -50444,7 +50983,7 @@ var normalizedImages = imageSchema.extendSchema((base) => (ctx) => {
50444
50983
  inkkitFileRaw: raw,
50445
50984
  inkkitFileTarget: node.attrs.inkkitFileTarget,
50446
50985
  inkkitFileFragment: node.attrs.inkkitFileFragment,
50447
- inkkitFileWidth: fileReference(node).width ?? null,
50986
+ inkkitFileWidth: fileFields(node).width ?? null,
50448
50987
  inkkitFileInTable: node.attrs.inkkitFileInTable
50449
50988
  });
50450
50989
  }
@@ -50557,6 +51096,7 @@ function createDialect(images, wikiLinks = false, files = false) {
50557
51096
  remarkCodeFences,
50558
51097
  remarkReferencesPlugin,
50559
51098
  remarkLinkFormsPlugin,
51099
+ remarkFileSourcesPlugin,
50560
51100
  remarkCalloutsPlugin,
50561
51101
  remarkCommentsPlugin,
50562
51102
  wikiLinks || files ? linkedSyntax(wikiLinks, files) : [],
@@ -50576,6 +51116,7 @@ function createDialect(images, wikiLinks = false, files = false) {
50576
51116
  wikiLinkComposition,
50577
51117
  wikiLinkInputRule
50578
51118
  ] : [],
51119
+ wikiLinks || files ? referenceTableContext : [],
50579
51120
  callouts,
50580
51121
  comments,
50581
51122
  hiddenCommentEditing,
@@ -50603,7 +51144,8 @@ function createDialect(images, wikiLinks = false, files = false) {
50603
51144
  remarkDialectPlugin,
50604
51145
  tables,
50605
51146
  literalBlock,
50606
- checkedParser
51147
+ checkedParser,
51148
+ imageWriter
50607
51149
  ].flat();
50608
51150
  }
50609
51151
  createDialect(false);
@@ -51315,7 +51857,7 @@ var ImageView = class {
51315
51857
  view;
51316
51858
  getPos;
51317
51859
  adapter;
51318
- runtime;
51860
+ host;
51319
51861
  canMutate;
51320
51862
  dom;
51321
51863
  /** The card or picture, which a standalone image's block row holds. */
@@ -51327,22 +51869,30 @@ var ImageView = class {
51327
51869
  stopResize;
51328
51870
  resolution;
51329
51871
  previewFailed = false;
51330
- constructor(node, view, getPos, adapter, runtime, canMutate = () => true) {
51872
+ /** Present when the host sets `files`, which then shows every image. */
51873
+ runtime;
51874
+ constructor(node, view, getPos, adapter, host, files, canMutate = () => true) {
51331
51875
  this.node = node;
51332
51876
  this.view = view;
51333
51877
  this.getPos = getPos;
51334
51878
  this.adapter = adapter;
51335
- this.runtime = runtime;
51879
+ this.host = host;
51336
51880
  this.canMutate = canMutate;
51881
+ const runtime = files && host && {
51882
+ ...host,
51883
+ adapter: files
51884
+ };
51885
+ this.runtime = runtime;
51337
51886
  this.element = document.createElement("span");
51338
51887
  this.element.className = "inkkit-image";
51888
+ this.element.addEventListener("contextmenu", this.contextMenu);
51339
51889
  if (node.isBlock) {
51340
51890
  this.dom = document.createElement("div");
51341
51891
  this.dom.className = "inkkit-image-block";
51342
51892
  this.dom.append(this.element);
51343
51893
  } else this.dom = this.element;
51344
51894
  this.view.dom.addEventListener("inkkit-cancel-resize", this.cancelResize);
51345
- this.view.dom.addEventListener("inkkit-media-policy", this.mediaPolicy);
51895
+ this.view.dom.addEventListener(mediaPolicyEvent, this.mediaPolicy);
51346
51896
  if (runtime) {
51347
51897
  this.resolution = new FileResolution({
51348
51898
  ...runtime,
@@ -51379,8 +51929,15 @@ var ImageView = class {
51379
51929
  ignoreMutation() {
51380
51930
  return true;
51381
51931
  }
51382
- mediaPolicy = () => {
51932
+ mediaPolicy = (event) => {
51383
51933
  if (!this.runtime) return;
51934
+ const refresh = refreshOf(event);
51935
+ if (refresh) {
51936
+ if (!this.active() || !refreshes(refresh, this.reference().target)) return;
51937
+ if (this.previewFailed) this.resolution?.retry();
51938
+ else this.resolution?.refresh();
51939
+ return;
51940
+ }
51384
51941
  if (!this.active()) {
51385
51942
  this.stopResize?.();
51386
51943
  this.stopResize = void 0;
@@ -51408,7 +51965,7 @@ var ImageView = class {
51408
51965
  }
51409
51966
  destroy() {
51410
51967
  this.view.dom.removeEventListener("inkkit-cancel-resize", this.cancelResize);
51411
- this.view.dom.removeEventListener("inkkit-media-policy", this.mediaPolicy);
51968
+ this.view.dom.removeEventListener(mediaPolicyEvent, this.mediaPolicy);
51412
51969
  this.stopResize?.();
51413
51970
  this.stopResize = void 0;
51414
51971
  this.resolution?.destroy();
@@ -51437,6 +51994,37 @@ var ImageView = class {
51437
51994
  report(error) {
51438
51995
  this.runtime?.error?.(coded(error, "file-unavailable"));
51439
51996
  }
51997
+ /** Hands the request to the host's hook, which replaces the web view's own menu. A file card
51998
+ * uses `FileAdapter.contextMenu`; any other image selects itself and uses
51999
+ * `ImageAdapter.contextMenu`. */
52000
+ contextMenu = (event) => {
52001
+ const { host, runtime, adapter } = this;
52002
+ if (!host?.active() || !(runtime ? runtime.adapter.contextMenu : adapter?.contextMenu)) return;
52003
+ const captured = host.capture();
52004
+ if (!captured.isCurrent()) return;
52005
+ event.preventDefault();
52006
+ const point = {
52007
+ clientX: event.clientX,
52008
+ clientY: event.clientY
52009
+ };
52010
+ try {
52011
+ if (runtime) runtime.adapter.contextMenu?.(this.reference(), point, captured.context);
52012
+ else {
52013
+ this.select();
52014
+ adapter?.contextMenu?.(String(this.node.attrs.src ?? ""), point, captured.context);
52015
+ }
52016
+ } catch (error) {
52017
+ if (runtime) this.report(error);
52018
+ else host.error?.(coded(error, "image-unavailable"));
52019
+ }
52020
+ };
52021
+ select() {
52022
+ const pos = this.getPos();
52023
+ if (pos == null || this.view.composing) return;
52024
+ const { state } = this.view;
52025
+ const selection = NodeSelection.create(state.doc, pos);
52026
+ if (!state.selection.eq(selection)) this.view.dispatch(state.tr.setSelection(selection));
52027
+ }
51440
52028
  card(label, status, retry = false) {
51441
52029
  const footer = document.createElement("span");
51442
52030
  footer.className = "inkkit-file-footer";
@@ -51485,20 +52073,6 @@ var ImageView = class {
51485
52073
  this.image = void 0;
51486
52074
  if (this.runtime) {
51487
52075
  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
52076
  const state = this.resolution.state;
51503
52077
  const label = state.status === "resolved" ? state.presentation.label || alt || reference.reference : alt || reference.reference;
51504
52078
  this.element.setAttribute("role", "group");
@@ -51647,8 +52221,8 @@ function openArrow() {
51647
52221
  icon.append(path);
51648
52222
  return icon;
51649
52223
  }
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));
52224
+ function imageView(adapter, host, files, canMutate = () => true) {
52225
+ const view = (schema) => $view(schema.node, () => (node, view, getPos) => new ImageView(node, view, getPos, adapter, host, files, canMutate));
51652
52226
  return [view(imageSchema), view(imageBlockSchema)];
51653
52227
  }
51654
52228
  //#endregion
@@ -51660,26 +52234,29 @@ function wikiLinkView(adapter, runtime) {
51660
52234
  dom.className = "inkkit-wiki-link";
51661
52235
  dom.setAttribute("role", "link");
51662
52236
  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
- });
52237
+ const reference = () => wikiReference(current);
51668
52238
  const report = (error) => runtime.error?.(coded(error, "file-unavailable"));
51669
52239
  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;
52240
+ const { label } = reference();
52241
+ if (dom.textContent !== label) dom.textContent = label;
52242
+ let missing = false;
52243
+ if (runtime.active()) {
52244
+ const captured = runtime.capture();
52245
+ try {
52246
+ const result = adapter.resolve?.(reference(), captured.context);
52247
+ missing = captured.isCurrent() && Boolean(result?.missing);
52248
+ } catch (error) {
52249
+ report(error);
51680
52250
  }
51681
- } catch (error) {
51682
- report(error);
52251
+ }
52252
+ if (missing) {
52253
+ dom.dataset.missing = "true";
52254
+ dom.title = runtime.labels.wikiMissing;
52255
+ dom.setAttribute("aria-description", runtime.labels.wikiMissing);
52256
+ } else {
52257
+ dom.removeAttribute("data-missing");
52258
+ dom.removeAttribute("title");
52259
+ dom.removeAttribute("aria-description");
51683
52260
  }
51684
52261
  };
51685
52262
  const open = (event) => {
@@ -51698,7 +52275,11 @@ function wikiLinkView(adapter, runtime) {
51698
52275
  dom.addEventListener("keydown", (event) => {
51699
52276
  if (event.key === "Enter" || event.key === " ") open(event);
51700
52277
  });
51701
- view.dom.addEventListener("inkkit-media-policy", render);
52278
+ const policy = (event) => {
52279
+ const refresh = refreshOf(event);
52280
+ if (!refresh || refreshes(refresh, String(current.attrs.target))) render();
52281
+ };
52282
+ view.dom.addEventListener(mediaPolicyEvent, policy);
51702
52283
  render();
51703
52284
  return {
51704
52285
  dom,
@@ -51713,7 +52294,7 @@ function wikiLinkView(adapter, runtime) {
51713
52294
  },
51714
52295
  ignoreMutation: () => true,
51715
52296
  destroy() {
51716
- view.dom.removeEventListener("inkkit-media-policy", render);
52297
+ view.dom.removeEventListener(mediaPolicyEvent, policy);
51717
52298
  }
51718
52299
  };
51719
52300
  });
@@ -52784,7 +53365,7 @@ function readableProjection(doc) {
52784
53365
  }
52785
53366
  if (name === "image_block") {
52786
53367
  block(position);
52787
- append(fileReference(node).label || "Image", position, position + node.nodeSize, "embed");
53368
+ append(fileFields(node).label || "Image", position, position + node.nodeSize, "embed");
52788
53369
  return false;
52789
53370
  }
52790
53371
  if (node.isTextblock) {
@@ -52801,7 +53382,7 @@ function readableProjection(doc) {
52801
53382
  return false;
52802
53383
  }
52803
53384
  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");
53385
+ 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
53386
  return false;
52806
53387
  }
52807
53388
  return true;
@@ -54053,6 +54634,96 @@ function htmlGrid(schema, html) {
54053
54634
  };
54054
54635
  }
54055
54636
  //#endregion
54637
+ //#region src/image-identity.ts
54638
+ var pngSignature = [
54639
+ 137,
54640
+ 80,
54641
+ 78,
54642
+ 71,
54643
+ 13,
54644
+ 10,
54645
+ 26,
54646
+ 10
54647
+ ];
54648
+ function format(bytes) {
54649
+ if (bytes.length >= 8 && pngSignature.every((value, index) => bytes[index] === value)) return "png";
54650
+ 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";
54651
+ }
54652
+ /** The pixel size in the header of a PNG or the first image of a TIFF. Nothing is decoded, so a
54653
+ * truncated or unusual file gives `undefined` and is never treated as a match. */
54654
+ function size(bytes, kind) {
54655
+ const view = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength);
54656
+ if (kind === "png") {
54657
+ if (bytes.length < 24 || view.getUint32(12) !== 1229472850) return void 0;
54658
+ return {
54659
+ width: view.getUint32(16),
54660
+ height: view.getUint32(20)
54661
+ };
54662
+ }
54663
+ const little = bytes[0] === 73;
54664
+ const directory = view.getUint32(4, little);
54665
+ if (directory + 2 > bytes.length) return void 0;
54666
+ const entries = view.getUint16(directory, little);
54667
+ let width;
54668
+ let height;
54669
+ for (let index = 0; index < entries; index++) {
54670
+ const entry = directory + 2 + index * 12;
54671
+ if (entry + 12 > bytes.length) return void 0;
54672
+ const tag = view.getUint16(entry, little);
54673
+ if (tag !== 256 && tag !== 257) continue;
54674
+ const type = view.getUint16(entry + 2, little);
54675
+ const value = type === 3 ? view.getUint16(entry + 8, little) : type === 4 ? view.getUint32(entry + 8, little) : void 0;
54676
+ if (tag === 256) width = value;
54677
+ else height = value;
54678
+ }
54679
+ return width && height ? {
54680
+ width,
54681
+ height
54682
+ } : void 0;
54683
+ }
54684
+ function identical(a, b) {
54685
+ if (a.length !== b.length) return false;
54686
+ for (let index = 0; index < a.length; index++) if (a[index] !== b[index]) return false;
54687
+ return true;
54688
+ }
54689
+ function stem(filename) {
54690
+ const name = (filename ?? "").split(/[\\/]/).pop() ?? "";
54691
+ const dot = name.lastIndexOf(".");
54692
+ return (dot > 0 ? name.slice(0, dot) : name).normalize("NFC").toLowerCase();
54693
+ }
54694
+ /** Whether two captured images are one picture in two representations. */
54695
+ function sameImage(a, b) {
54696
+ if ((a.source ?? "") !== (b.source ?? "")) return false;
54697
+ if (identical(a.bytes, b.bytes)) return true;
54698
+ const kindA = format(a.bytes);
54699
+ const kindB = format(b.bytes);
54700
+ if (!kindA || !kindB || kindA === kindB) return false;
54701
+ const nameA = stem(a.filename);
54702
+ const nameB = stem(b.filename);
54703
+ if (nameA && nameB && nameA !== nameB) return false;
54704
+ const sizeA = size(a.bytes, kindA);
54705
+ const sizeB = size(b.bytes, kindB);
54706
+ return !!sizeA && !!sizeB && sizeA.width === sizeB.width && sizeA.height === sizeB.height;
54707
+ }
54708
+ /** Keeps one image for each picture in a single paste or drop. A clipboard can hold a picture
54709
+ * more than once, such as a screenshot as PNG and as TIFF, and each copy would be stored and
54710
+ * inserted separately.
54711
+ *
54712
+ * Two images are one picture when their bytes are identical, or when one is a PNG and the other a
54713
+ * TIFF with the same pixel size and compatible names (the same name without its extension, or a
54714
+ * name missing). Anything else is a different image, including a copy that was resized, re-encoded
54715
+ * as JPEG or renamed. The first image keeps its place; a PNG replaces a TIFF of the same picture
54716
+ * because every web view can show it. */
54717
+ function distinctImages(images) {
54718
+ const kept = [];
54719
+ for (const image of images) {
54720
+ const index = kept.findIndex((other) => sameImage(other, image));
54721
+ if (index < 0) kept.push(image);
54722
+ else if (format(kept[index].bytes) === "tiff" && format(image.bytes) === "png") kept[index] = image;
54723
+ }
54724
+ return kept;
54725
+ }
54726
+ //#endregion
54056
54727
  //#region src/paste.ts
54057
54728
  var PasteController = class {
54058
54729
  options;
@@ -54214,12 +54885,12 @@ var PasteController = class {
54214
54885
  this.active = operation;
54215
54886
  this.options.onPendingChanged?.();
54216
54887
  try {
54217
- const images = [...input.images ?? []];
54218
- for (const file of files) images.push({
54888
+ const fromFiles = files.length ? distinctImages(await Promise.all(files.map(async (file) => ({
54219
54889
  bytes: new Uint8Array(await file.arrayBuffer()),
54220
54890
  mimeType: file.type,
54221
54891
  filename: file.name
54222
- });
54892
+ })))) : [];
54893
+ const images = distinctImages([...input.images ?? [], ...fromFiles]);
54223
54894
  this.assertContext(context, operation);
54224
54895
  const schema = view.state.schema;
54225
54896
  let slice;
@@ -54238,8 +54909,7 @@ var PasteController = class {
54238
54909
  }
54239
54910
  const anchor = this.anchor(operation);
54240
54911
  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)) ?? [];
54912
+ const marks = replacementMarks(view.state, anchor.from, anchor.to);
54243
54913
  const nodes = literalTextNodes(schema, input.text, marks, htmlBreakAt(start));
54244
54914
  slice = new Slice(Fragment.from(schema.nodes.paragraph.create(null, nodes)), 1, 1);
54245
54915
  } else if (input.html) {
@@ -54247,7 +54917,7 @@ var PasteController = class {
54247
54917
  const imported = /* @__PURE__ */ new Map();
54248
54918
  const elements = [...container.querySelectorAll("img")];
54249
54919
  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);
54920
+ const positional = (fromFiles.length === 0 || fromFiles.length === 1 && uniqueSources.size === 1 ? images : input.images ?? []).filter((image) => !image.source);
54251
54921
  let index = 0;
54252
54922
  for (const element of elements) {
54253
54923
  const source = element.getAttribute("src") ?? "";
@@ -54744,11 +55414,11 @@ var DocumentSession = class {
54744
55414
  //#region src/selection.ts
54745
55415
  /** The stylesheet paints the selection under this name. */
54746
55416
  var name = "inkkit-selection";
54747
- var ranges = /* @__PURE__ */ new Map();
55417
+ var activeClass = "inkkit-selection-active";
54748
55418
  /** The selected text within each block. */
54749
55419
  function covered(state) {
54750
55420
  const { from, to, empty, visible } = state.selection;
54751
- if (empty || !visible) return null;
55421
+ if (empty || !visible) return [];
54752
55422
  const text = [];
54753
55423
  state.doc.nodesBetween(from, to, (node, pos) => {
54754
55424
  if (!node.isTextblock) return true;
@@ -54765,39 +55435,140 @@ function covered(state) {
54765
55435
  function domRange(view, span) {
54766
55436
  const start = view.domAtPos(span.from);
54767
55437
  const end = view.domAtPos(span.to);
54768
- const range = document.createRange();
55438
+ const range = view.dom.ownerDocument.createRange();
54769
55439
  range.setStart(start.node, start.offset);
54770
55440
  range.setEnd(end.node, end.offset);
54771
55441
  return range;
54772
55442
  }
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
- }
55443
+ var rootOf = (view) => view.dom.closest(".inkkit-root");
54786
55444
  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
- }
54798
- };
55445
+ /**
55446
+ * Whether the view holds the user's input: its formatted surface, or the source or TXT
55447
+ * textarea beside it, has focus, or, in a read-only editor that takes no focus, the
55448
+ * document selection is inside it.
55449
+ */
55450
+ function focused(view) {
55451
+ const doc = view.dom.ownerDocument;
55452
+ const surface = doc.activeElement;
55453
+ if (surface === view.dom || view.dom.contains(surface) || surface instanceof HTMLTextAreaElement && surface.classList.contains("inkkit-plain") && rootOf(view).contains(surface)) return true;
55454
+ const anchor = doc.getSelection()?.anchorNode;
55455
+ return !view.editable && !!anchor && view.dom.contains(anchor);
55456
+ }
55457
+ /** Whether the focused element takes typing, so a selection kept elsewhere is still what the host is working on. */
55458
+ function takesText(element) {
55459
+ return element instanceof HTMLInputElement || element instanceof HTMLTextAreaElement || element instanceof HTMLSelectElement || element instanceof HTMLElement && element.isContentEditable;
55460
+ }
55461
+ /**
55462
+ * One document has one selection, so the editors on it share one painter and one highlight.
55463
+ * The owner is the editor that most recently held the selection. Its selection is painted
55464
+ * in the active colour while it has focus in a focused window, and in the inactive colour
55465
+ * otherwise, so a host that selects a range from its search field, or blurs the editor,
55466
+ * still shows what is selected. Anything that takes the selection elsewhere releases it.
55467
+ */
55468
+ var Painter = class Painter {
55469
+ doc;
55470
+ static all = /* @__PURE__ */ new WeakMap();
55471
+ views = /* @__PURE__ */ new Map();
55472
+ win;
55473
+ custom = paintable();
55474
+ owner = null;
55475
+ windowFocused;
55476
+ static of(doc) {
55477
+ let painter = Painter.all.get(doc);
55478
+ if (!painter) Painter.all.set(doc, painter = new Painter(doc));
55479
+ return painter;
54799
55480
  }
54800
- }) : new Plugin({ key: new PluginKey("selection") }));
55481
+ constructor(doc) {
55482
+ this.doc = doc;
55483
+ this.win = doc.defaultView;
55484
+ this.windowFocused = doc.hasFocus();
55485
+ }
55486
+ add(view) {
55487
+ if (!this.views.size) this.listen("add");
55488
+ this.views.set(view, rootOf(view));
55489
+ this.sync();
55490
+ }
55491
+ remove(view) {
55492
+ this.views.get(view)?.classList.remove(activeClass);
55493
+ this.views.delete(view);
55494
+ if (this.owner === view) this.owner = null;
55495
+ this.sync();
55496
+ if (!this.views.size) {
55497
+ this.listen("remove");
55498
+ Painter.all.delete(this.doc);
55499
+ }
55500
+ }
55501
+ /** A transaction gave this view a selection. A focused owner is not taken over by an editor that is not. */
55502
+ take(view) {
55503
+ if (!this.owner || this.owner === view || focused(view) || !focused(this.owner)) this.owner = view;
55504
+ this.sync();
55505
+ }
55506
+ /** The owner's document changed, or its selection moved with it. */
55507
+ changed(view) {
55508
+ if (this.owner === view) this.sync();
55509
+ }
55510
+ onFocusIn = () => {
55511
+ for (const view of this.views.keys()) if (focused(view)) this.owner = view;
55512
+ this.sync();
55513
+ };
55514
+ onFocusOut = () => this.sync();
55515
+ onWindow = (event) => {
55516
+ this.windowFocused = event.type === "focus";
55517
+ this.sync();
55518
+ };
55519
+ onSelectionChange = () => {
55520
+ const owner = this.owner;
55521
+ if (!owner || focused(owner)) return;
55522
+ const selection = this.doc.getSelection();
55523
+ if (!selection?.rangeCount || owner.dom.contains(selection.anchorNode)) return;
55524
+ if (selection.isCollapsed && takesText(this.doc.activeElement)) return;
55525
+ this.owner = null;
55526
+ this.sync();
55527
+ };
55528
+ listen(action) {
55529
+ const method = `${action}EventListener`;
55530
+ this.doc[method]("focusin", this.onFocusIn);
55531
+ this.doc[method]("focusout", this.onFocusOut);
55532
+ this.doc[method]("selectionchange", this.onSelectionChange);
55533
+ this.win[method]("focus", this.onWindow);
55534
+ this.win[method]("blur", this.onWindow);
55535
+ }
55536
+ sync() {
55537
+ for (const [view, root] of this.views) root.classList.toggle(activeClass, this.windowFocused && focused(view));
55538
+ if (!this.custom) return;
55539
+ const { owner } = this;
55540
+ const spans = owner ? covered(owner.state) : [];
55541
+ if (!owner || !spans.length) {
55542
+ CSS.highlights.delete(name);
55543
+ return;
55544
+ }
55545
+ const highlight = new Highlight();
55546
+ for (const span of spans) highlight.add(domRange(owner, span));
55547
+ CSS.highlights.set(name, highlight);
55548
+ }
55549
+ };
55550
+ var selectionPlugin = $prose(() => {
55551
+ const key = new PluginKey("selection");
55552
+ return new Plugin({
55553
+ key,
55554
+ state: {
55555
+ init: () => 0,
55556
+ apply: (tr, count) => tr.selectionSet ? count + 1 : count
55557
+ },
55558
+ props: paintable() ? { attributes: { class: "inkkit-painted-selection" } } : {},
55559
+ view(view) {
55560
+ const painter = Painter.of(view.dom.ownerDocument);
55561
+ painter.add(view);
55562
+ return {
55563
+ update(view, previous) {
55564
+ if (key.getState(view.state) !== key.getState(previous)) painter.take(view);
55565
+ else painter.changed(view);
55566
+ },
55567
+ destroy: () => painter.remove(view)
55568
+ };
55569
+ }
55570
+ });
55571
+ });
54801
55572
  //#endregion
54802
55573
  //#region node_modules/.pnpm/prosemirror-search@1.1.1/node_modules/prosemirror-search/dist/index.js
54803
55574
  var SearchQuery = class {
@@ -55227,8 +55998,8 @@ function findFormatted(view, text) {
55227
55998
  return found;
55228
55999
  }
55229
56000
  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();
56001
+ const start = state.doc.resolve(from);
56002
+ const marks = replacementMarks(state, from, to);
55232
56003
  const nodes = [];
55233
56004
  if (start.parent.type.spec.code) {
55234
56005
  if (value) nodes.push(state.schema.text(value, marks));
@@ -55845,6 +56616,8 @@ var shortcutNames = [
55845
56616
  "redo",
55846
56617
  "moveBlockUp",
55847
56618
  "moveBlockDown",
56619
+ "indent",
56620
+ "outdent",
55848
56621
  ...tableCommands.map(tableShortcut)
55849
56622
  ];
55850
56623
  /** Checks a keymap and drops omitted entries, so a `null` binding keeps its default. */
@@ -55896,6 +56669,7 @@ function editorOptions(value) {
55896
56669
  "textInput",
55897
56670
  "labels",
55898
56671
  "keymap",
56672
+ "linkSchemes",
55899
56673
  "rendering",
55900
56674
  "slashMenu"
55901
56675
  ]) ?? {};
@@ -55905,6 +56679,7 @@ function editorOptions(value) {
55905
56679
  optionalBoolean(options.editable, "options.editable");
55906
56680
  if (!isOmitted(options.textInput)) textInputPreferences(options.textInput, "options.textInput");
55907
56681
  if (!isOmitted(options.keymap)) keymapBindings(options.keymap, "options.keymap");
56682
+ const linkSchemes = isOmitted(options.linkSchemes) ? void 0 : extraLinkSchemes(options.linkSchemes, "options.linkSchemes");
55908
56683
  const rendering = optionalRecord(options.rendering, "options.rendering", ["codeHighlighting", "diagramPreview"]);
55909
56684
  optionalBoolean(rendering?.codeHighlighting, "options.rendering.codeHighlighting");
55910
56685
  optionalBoolean(rendering?.diagramPreview, "options.rendering.diagramPreview");
@@ -55912,6 +56687,7 @@ function editorOptions(value) {
55912
56687
  return {
55913
56688
  ...options,
55914
56689
  slashMenu: slashMenuOptions(options.slashMenu),
56690
+ linkSchemes,
55915
56691
  labels: labels && Object.fromEntries(Object.entries(labels).filter(([, label]) => !isOmitted(label)).map(([key, label]) => [key, expectString(label, `options.labels.${key}`)]))
55916
56692
  };
55917
56693
  }
@@ -56108,6 +56884,18 @@ function blockAt(parent, $from) {
56108
56884
  return { type: "paragraph" };
56109
56885
  }
56110
56886
  var quotes = /* @__PURE__ */ new Set(["blockquote", "inkkit_callout"]);
56887
+ function liftQuote(state, range, target) {
56888
+ const tr = state.tr.lift(range, target);
56889
+ const { depth } = range;
56890
+ const item = range.$from.node(depth - 1);
56891
+ const spacer = item.firstChild;
56892
+ if (item.type.name === "list_item" && range.$from.index(depth - 1) === 1 && spacer?.type.name === "paragraph" && spacer.content.size === 0) {
56893
+ const from = range.$from.start(depth - 1);
56894
+ if (tr.doc.nodeAt(from + spacer.nodeSize)?.type === spacer.type) tr.delete(from, from + spacer.nodeSize);
56895
+ }
56896
+ return tr;
56897
+ }
56898
+ var undoTypedRule = $prose(() => keymap$3({ Backspace: undoInputRule }));
56111
56899
  var quoteBackspace = $prose(() => keymap$3({ Backspace: (state, dispatch) => {
56112
56900
  const { $from, empty } = state.selection;
56113
56901
  if (!empty || $from.parentOffset > 0 || $from.depth < 2) return false;
@@ -56117,7 +56905,7 @@ var quoteBackspace = $prose(() => keymap$3({ Backspace: (state, dispatch) => {
56117
56905
  const range = $from.blockRange($from, (node) => quotes.has(node.type.name));
56118
56906
  const target = range && liftTarget(range);
56119
56907
  if (!range || target == null) return false;
56120
- dispatch?.(state.tr.lift(range, target).scrollIntoView());
56908
+ dispatch?.(liftQuote(state, range, target).scrollIntoView());
56121
56909
  return true;
56122
56910
  } }));
56123
56911
  var calloutEnter = $prose(() => keymap$3({ Enter: (state, dispatch) => {
@@ -56159,7 +56947,6 @@ var liftItem = (state, dispatch) => {
56159
56947
  dispatch(lifted);
56160
56948
  return true;
56161
56949
  };
56162
- var listItemShiftTab = $prose(() => keymap$3({ "Shift-Tab": liftItem }));
56163
56950
  var rejoinLists = $prose(() => keymap$3({ Backspace: (state, dispatch) => {
56164
56951
  const { $from, empty } = state.selection;
56165
56952
  if (!empty || $from.parent.type.name !== "paragraph" || $from.parent.content.size > 0) return false;
@@ -56252,11 +57039,7 @@ var movedListItems = $prose(() => new Plugin({
56252
57039
  return tr.docChanged ? tr : null;
56253
57040
  }
56254
57041
  }));
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
- }));
57042
+ var textInputPolicy = $prose(() => textInputPlugin());
56260
57043
  function outermostListDepth($pos) {
56261
57044
  for (let depth = 1; depth <= $pos.depth; depth++) if (isList($pos.node(depth))) return depth;
56262
57045
  return null;
@@ -56381,6 +57164,7 @@ var InkKitEditor = class InkKitEditor {
56381
57164
  inputPreferences = {};
56382
57165
  literalKeys = () => false;
56383
57166
  configuredKeys = {};
57167
+ indentKeys = () => false;
56384
57168
  readOnlyKeys = () => false;
56385
57169
  suggestions;
56386
57170
  suggestionBase;
@@ -56420,6 +57204,8 @@ var InkKitEditor = class InkKitEditor {
56420
57204
  }));
56421
57205
  keys = () => false;
56422
57206
  events;
57207
+ /** The host's extra link schemes, in lower case. */
57208
+ linkSchemes;
56423
57209
  /** How many of each state event the host has been sent. */
56424
57210
  sentEvents = /* @__PURE__ */ new Map();
56425
57211
  constructor(events, options) {
@@ -56439,6 +57225,7 @@ var InkKitEditor = class InkKitEditor {
56439
57225
  else send();
56440
57226
  };
56441
57227
  } });
57228
+ this.linkSchemes = new Set(options.linkSchemes);
56442
57229
  this.writable = options.editable ?? true;
56443
57230
  this.inputPreferences = textInputPreferences(options.textInput ?? {}, "options.textInput");
56444
57231
  }
@@ -56499,7 +57286,7 @@ var InkKitEditor = class InkKitEditor {
56499
57286
  };
56500
57287
  }
56501
57288
  mediaPolicy() {
56502
- if (this.editor && this.root) this.editor.ctx.get(editorViewCtx).dom.dispatchEvent(new Event("inkkit-media-policy"));
57289
+ if (this.editor && this.root) this.editor.ctx.get(editorViewCtx).dom.dispatchEvent(new Event(mediaPolicyEvent));
56503
57290
  }
56504
57291
  assertAlive() {
56505
57292
  if (this.destroyed) throw new InkKitError("destroyed", "The editor has been destroyed");
@@ -56750,6 +57537,7 @@ var InkKitEditor = class InkKitEditor {
56750
57537
  replace: mutable,
56751
57538
  replaceSource: mutable,
56752
57539
  insertImages: formatted && this.canInsertImages,
57540
+ appendImages: formatted && this.canInsertImages,
56753
57541
  insertPaths: mutable,
56754
57542
  insertFootnote: formatted,
56755
57543
  editReferenceDefinition: formatted && referenceDefinitions(state.doc).size > 0,
@@ -57209,7 +57997,7 @@ var InkKitEditor = class InkKitEditor {
57209
57997
  } catch (error) {
57210
57998
  throw coded(error, "preservation");
57211
57999
  }
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));
58000
+ 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
58001
  const metadata = referenceMetadata(this.editor.ctx, selectedDoc ?? doc, content);
57214
58002
  if (metadata != null) result.html = withReferenceMetadata(result.html, metadata);
57215
58003
  this.assertCaptured(captured);
@@ -57260,7 +58048,8 @@ var InkKitEditor = class InkKitEditor {
57260
58048
  if (view.state.selection.$from.parent.type.spec.code && !inHiddenComment) view.dispatch(view.state.tr.insertText(text.replace(/\r\n?/g, "\n")).scrollIntoView());
57261
58049
  else {
57262
58050
  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)) ?? [];
58051
+ const { from, to } = view.state.selection;
58052
+ const marks = replacementMarks(view.state, from, to);
57264
58053
  const isHTML = !inHiddenComment && htmlBreakAt(view.state.selection.$from);
57265
58054
  const lines = text.replace(/\r\n?/g, "\n").split("\n");
57266
58055
  const children = literalTextNodes(schema, text, marks, isHTML);
@@ -57336,6 +58125,10 @@ var InkKitEditor = class InkKitEditor {
57336
58125
  return this.keys(view, event);
57337
58126
  } }
57338
58127
  }));
58128
+ indentPlugin = new Plugin({
58129
+ key: new PluginKey("listIndent"),
58130
+ props: { handleKeyDown: (view, event) => this.indentKeys(view, event) }
58131
+ });
57339
58132
  copySourceSelection(event) {
57340
58133
  event.preventDefault();
57341
58134
  event.stopPropagation();
@@ -57361,7 +58154,7 @@ var InkKitEditor = class InkKitEditor {
57361
58154
  }
57362
58155
  }).catch((error) => this.report(error));
57363
58156
  else {
57364
- const output = clipboardContent(parsed.content, parsed.type.schema);
58157
+ const output = clipboardContent(parsed.content, parsed.type.schema, this.linkSchemes);
57365
58158
  if (!event.clipboardData) return;
57366
58159
  this.rememberCopy(output, selected.markdown, parsed.content, selected.markdown !== selected.shareable, true);
57367
58160
  event.clipboardData.setData("text/plain", output.text);
@@ -57427,6 +58220,7 @@ var InkKitEditor = class InkKitEditor {
57427
58220
  ctx.set(rootCtx, root);
57428
58221
  ctx.set(authoredSourcesCtx.key, (doc, positions) => instance.authoredSources(doc, positions));
57429
58222
  ctx.set(smartPunctuationCtx.key, () => instance.inputPreferences.smartPunctuation === true);
58223
+ ctx.set(linkSchemesCtx.key, instance.linkSchemes);
57430
58224
  ctx.update(labelsCtx.key, () => ({
57431
58225
  ...defaultLabels,
57432
58226
  ...options.labels
@@ -57441,9 +58235,16 @@ var InkKitEditor = class InkKitEditor {
57441
58235
  }), false));
57442
58236
  ctx.set(defaultValueCtx, "");
57443
58237
  ctx.set(remarkStringifyOptionsCtx, stringifyOptions);
58238
+ ctx.update(editorStateOptionsCtx, (override) => (options) => {
58239
+ const next = override(options);
58240
+ return {
58241
+ ...next,
58242
+ plugins: [...next.plugins ?? [], instance.indentPlugin]
58243
+ };
58244
+ });
57444
58245
  ctx.update(listItemKeymap.key, (keys) => ({
57445
58246
  ...keys,
57446
- SinkListItem: { shortcuts: "Tab" },
58247
+ SinkListItem: { shortcuts: [] },
57447
58248
  LiftListItem: { shortcuts: [] }
57448
58249
  }));
57449
58250
  const unbind = (keymap, keep = []) => ctx.update(keymap.key, (keys) => {
@@ -57507,7 +58308,7 @@ var InkKitEditor = class InkKitEditor {
57507
58308
  return true;
57508
58309
  }
57509
58310
  }));
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, {
58311
+ }).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-GlZZ_7st.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
58312
  capture: () => instance.adapterCapture(),
57512
58313
  active: () => instance.ready && !instance.destroyed && !instance.literalSurface,
57513
58314
  labels: {
@@ -57515,8 +58316,7 @@ var InkKitEditor = class InkKitEditor {
57515
58316
  ...options.labels
57516
58317
  },
57517
58318
  error: (error) => instance.report(error)
57518
- }) : []).use(options.images || options.files ? imageView(options.images, options.files ? {
57519
- adapter: options.files,
58319
+ }) : []).use(options.images || options.files ? imageView(options.images, {
57520
58320
  capture: () => instance.adapterCapture(),
57521
58321
  active: () => instance.ready && !instance.destroyed && !instance.literalSurface,
57522
58322
  labels: {
@@ -57524,7 +58324,7 @@ var InkKitEditor = class InkKitEditor {
57524
58324
  ...options.labels
57525
58325
  },
57526
58326
  error: (error) => instance.report(error)
57527
- } : void 0, () => {
58327
+ }, options.files, () => {
57528
58328
  try {
57529
58329
  instance.assertMutation();
57530
58330
  return true;
@@ -57658,7 +58458,7 @@ var InkKitEditor = class InkKitEditor {
57658
58458
  const content = expanded;
57659
58459
  const valid = fragment.firstChild?.isInline ? Fragment.from(view.state.schema.nodes.paragraph.create(null, fragment)) : fragment;
57660
58460
  const markdown = selectionMarkdown(instance.editor.ctx, view.state.doc, valid);
57661
- const output = clipboardContent(content, view.state.schema);
58461
+ const output = clipboardContent(content, view.state.schema, instance.linkSchemes);
57662
58462
  const metadata = referenceMetadata(instance.editor.ctx, view.state.doc, valid);
57663
58463
  if (metadata != null) output.html = withReferenceMetadata(output.html, metadata);
57664
58464
  instance.rememberCopy(output, markdown, fragment, false, true);
@@ -58311,10 +59111,23 @@ var InkKitEditor = class InkKitEditor {
58311
59111
  this.assertReady(expectedGeneration);
58312
59112
  this.releaseFocus();
58313
59113
  }
59114
+ /**
59115
+ * Resolves wiki links and file embeds again, all of them or those whose `target` is named, after
59116
+ * something they point to changed. A file keeps its card until the new answer differs. Changes no
59117
+ * document state, so it works in a read-only editor and during composition. Source and TXT modes
59118
+ * draw no references, and they resolve afresh when formatted mode returns.
59119
+ */
59120
+ refreshReferences(targets, expectedGeneration) {
59121
+ this.assertAlive();
59122
+ const names = isOmitted(targets) ? void 0 : new Set(expectArray(targets, "targets").map((target) => expectString(target, "targets entry")));
59123
+ this.assertReady(expectedGeneration);
59124
+ requestRefresh(this.editor.ctx.get(editorViewCtx).dom, { ...names && { targets: names } });
59125
+ }
58314
59126
  /** Replaces every binding. Keys are in ProseMirror's notation. A key given to two shortcuts goes
58315
59127
  * to the later one, in the order history, formatting, block moves, tables, so it lands the same
58316
59128
  * way every time. A table binding passes a key it shares with a block move to the move outside
58317
- * a table. */
59129
+ * a table. List indentation keys apply after all of these, and after the table keys the editor
59130
+ * keeps for itself. */
58318
59131
  setKeymap(keymap) {
58319
59132
  this.assertAlive();
58320
59133
  keymap = keymapBindings(keymap, "keymap");
@@ -58324,6 +59137,8 @@ var InkKitEditor = class InkKitEditor {
58324
59137
  moveBlockUp: ["Alt-ArrowUp"],
58325
59138
  moveBlockDown: ["Alt-ArrowDown"],
58326
59139
  tableExit: ["Mod-Enter", "Enter"],
59140
+ indent: ["Tab"],
59141
+ outdent: ["Shift-Tab"],
58327
59142
  ...keymap
58328
59143
  };
58329
59144
  const bindings = {};
@@ -58350,6 +59165,9 @@ var InkKitEditor = class InkKitEditor {
58350
59165
  if (move === "move" && !this.composing && !this.pasteController.pending) this.forPerson(() => this.moveBlock(direction), false);
58351
59166
  return true;
58352
59167
  };
59168
+ const indents = {};
59169
+ const sink = (state, dispatch) => sinkListItem(state.schema.nodes.list_item)(state, dispatch);
59170
+ 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
59171
  for (const command of tableCommands) for (const key of this.configuredKeys[tableShortcut(command)] ?? []) bindings[key] = (state, dispatch, view) => {
58354
59172
  if (!this.ready || !this.commandState().commands.table[command]) return moves[key]?.(state, dispatch, view) ?? false;
58355
59173
  return this.forPerson(() => this.table(command), true);
@@ -58358,6 +59176,7 @@ var InkKitEditor = class InkKitEditor {
58358
59176
  throw new InkKitError("read-only", "The editor is read-only");
58359
59177
  }])));
58360
59178
  this.keys = keydownHandler(bindings);
59179
+ this.indentKeys = keydownHandler(indents);
58361
59180
  this.literalKeys = keydownHandler(historyBindings);
58362
59181
  }
58363
59182
  /** Dropped files land as one paragraph per path at the drop point: in place of an empty block, after
@@ -58385,17 +59204,12 @@ var InkKitEditor = class InkKitEditor {
58385
59204
  this.assertMutation(expectedGeneration);
58386
59205
  if (!images.length || !this.canInsertImages || this.literalSurface) return false;
58387
59206
  const view = this.editor.ctx.get(editorViewCtx);
58388
- const { schema } = view.state;
58389
59207
  const placement = point ? void 0 : imagePlacement(view.state);
58390
59208
  if (placement?.kind === "inline") {
58391
59209
  this.insertInline(images);
58392
59210
  return true;
58393
59211
  }
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
- })));
59212
+ const blocks = this.imageBlocks(images);
58399
59213
  if (point) {
58400
59214
  this.insertBlocks(blocks, point);
58401
59215
  return true;
@@ -58411,6 +59225,31 @@ var InkKitEditor = class InkKitEditor {
58411
59225
  view.dispatch(tr.scrollIntoView());
58412
59226
  return true;
58413
59227
  }
59228
+ /** Adds images as the last blocks of the document and leaves everything else as it is: no focus,
59229
+ * no scrolling, and the selection stays on the text it covered. For a host that attaches images
59230
+ * to a document the person is not editing at that moment. Returns false when it does nothing. */
59231
+ appendImages(images, expectedGeneration) {
59232
+ this.assertAlive();
59233
+ images = imageInsertions(images);
59234
+ this.assertMutation(expectedGeneration);
59235
+ if (!images.length || !this.canInsertImages || this.literalSurface) return false;
59236
+ const view = this.editor.ctx.get(editorViewCtx);
59237
+ const { doc, tr } = view.state;
59238
+ const blocks = this.imageBlocks(images);
59239
+ if (doc.childCount === 1 && doc.firstChild.content.size === 0) tr.replaceWith(0, doc.content.size, blocks);
59240
+ else tr.insert(doc.content.size, blocks);
59241
+ view.dispatch(closeHistory(tr));
59242
+ view.dispatch(closeHistory(view.state.tr).setMeta("addToHistory", false));
59243
+ return true;
59244
+ }
59245
+ imageBlocks(images) {
59246
+ const { schema } = this.editor.ctx.get(editorViewCtx).state;
59247
+ return images.map((image) => schema.nodes.paragraph.create(null, schema.nodes.image.create({
59248
+ src: image.reference,
59249
+ alt: image.alt,
59250
+ title: image.title ?? ""
59251
+ })));
59252
+ }
58414
59253
  insertInline(images) {
58415
59254
  const view = this.editor.ctx.get(editorViewCtx);
58416
59255
  const { state } = view;
@@ -58528,7 +59367,7 @@ var InkKitEditor = class InkKitEditor {
58528
59367
  if (!range) return;
58529
59368
  const target = liftTarget(range);
58530
59369
  if (target == null) return;
58531
- view.dispatch(view.state.tr.lift(range, target));
59370
+ view.dispatch(liftQuote(view.state, range, target));
58532
59371
  }
58533
59372
  /** Changes the selected textblocks' type, keeping everything they hold. */
58534
59373
  textblockType(type, attr) {
@@ -58613,25 +59452,27 @@ var InkKitEditor = class InkKitEditor {
58613
59452
  commit(command);
58614
59453
  return;
58615
59454
  }
59455
+ const onHostError = (error) => {
59456
+ if (!this.destroyed) this.report(coded(error, "slash-command-failed"));
59457
+ };
59458
+ const insert = (markdown) => {
59459
+ if (this.destroyed) return;
59460
+ if (markdown === void 0) return;
59461
+ if (typeof markdown !== "string") {
59462
+ this.report(invalidArgument("A slash command must return Markdown text"));
59463
+ return;
59464
+ }
59465
+ commit({ markdown });
59466
+ };
59467
+ let result;
58616
59468
  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);
59469
+ result = command.action(this.context());
58632
59470
  } catch (error) {
58633
- this.report(error);
59471
+ onHostError(error);
59472
+ return;
58634
59473
  }
59474
+ if (result instanceof Promise) result.then(insert, onHostError);
59475
+ else insert(result);
58635
59476
  }
58636
59477
  /** Returns whether the document or the marks the next typed text takes changed. */
58637
59478
  format(command, options, expectedGeneration) {
@@ -58692,12 +59533,13 @@ var InkKitEditor = class InkKitEditor {
58692
59533
  return !after.doc.eq(before.doc) || !Mark.sameSet(marks(after), marks(before));
58693
59534
  }
58694
59535
  /**
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.
59536
+ * Refuses a link destination that is empty, that opens anything but a web, mail or phone address
59537
+ * or a scheme the host listed, or that the Markdown writer cannot save so that it reads back the
59538
+ * same.
58697
59539
  */
58698
59540
  checkDestination(destination, name) {
58699
59541
  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`);
59542
+ 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
59543
  const { schema } = this.editor.ctx.get(editorViewCtx).state;
58702
59544
  const written = serialize(this.editor.ctx, schema.node("doc", null, [schema.node("paragraph", null, [schema.text("x", [schema.marks.link.create({ href: destination })])])]));
58703
59545
  let read;
@@ -58719,13 +59561,18 @@ var InkKitEditor = class InkKitEditor {
58719
59561
  const { selection, storedMarks } = state;
58720
59562
  const { from, to, empty } = selection;
58721
59563
  const stored = empty ? type.isInSet(storedMarks ?? []) : void 0;
58722
- if (stored && !stored.isInSet(selection.$from.marks())) {
59564
+ const beside = empty ? selection.$from.nodeBefore?.marks ?? [] : [];
59565
+ if (stored && !stored.isInSet(selection.$from.marks()) && !stored.isInSet(beside)) {
58723
59566
  const tr = state.tr.removeStoredMark(type);
58724
59567
  if (destination !== void 0) tr.addStoredMark(type.create({ href: destination }));
58725
59568
  view.dispatch(tr);
58726
59569
  return;
58727
59570
  }
58728
- const mark = type.isInSet(empty ? [...selection.$from.marks(), ...state.doc.nodeAt(from)?.marks ?? []] : state.doc.nodeAt(from)?.marks ?? []);
59571
+ const mark = type.isInSet(empty ? [
59572
+ ...selection.$from.marks(),
59573
+ ...beside,
59574
+ ...state.doc.nodeAt(from)?.marks ?? []
59575
+ ] : state.doc.nodeAt(from)?.marks ?? []);
58729
59576
  const extent = mark && markRun(state.doc, mark, from, to);
58730
59577
  const keepStored = (tr, link) => {
58731
59578
  if (!storedMarks) return tr;
@@ -58872,12 +59719,14 @@ var InkKitEditor = class InkKitEditor {
58872
59719
  "keyDown",
58873
59720
  "insertPaths",
58874
59721
  "insertImages",
59722
+ "appendImages",
58875
59723
  "insertFootnote",
58876
59724
  "navigateFootnote",
58877
59725
  "editReferenceDefinition",
58878
59726
  "setCommentsVisible",
58879
59727
  "focus",
58880
59728
  "blur",
59729
+ "refreshReferences",
58881
59730
  "setKeymap",
58882
59731
  "textSnapshot",
58883
59732
  "selectTextRange",