@aicayzer/inkkit 0.2.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 = [];
@@ -43687,27 +43850,9 @@ function remarkComments() {
43687
43850
  });
43688
43851
  return (tree) => {
43689
43852
  const visit = (node, inline, parentType) => {
43690
- if (parentType === "listItem" && node.type === "inkkitCommentBlock") return {
43691
- type: "paragraph",
43692
- children: [{
43693
- ...node,
43694
- type: "inkkitCommentInline",
43695
- block: true
43696
- }],
43697
- position: node.position
43698
- };
43699
43853
  if (node.type === "html") {
43700
43854
  const spans = htmlSpans(node.value);
43701
- if (spans.length === 1 && spans[0].start === 0 && spans[0].end === node.value.length) return parentType === "listItem" ? {
43702
- type: "paragraph",
43703
- children: [{
43704
- type: "inkkitCommentInline",
43705
- syntax: "html",
43706
- value: spans[0].value,
43707
- position: node.position
43708
- }],
43709
- position: node.position
43710
- } : {
43855
+ if (spans.length === 1 && spans[0].start === 0 && spans[0].end === node.value.length) return {
43711
43856
  type: inline ? "inkkitCommentInline" : "inkkitCommentBlock",
43712
43857
  syntax: "html",
43713
43858
  value: spans[0].value,
@@ -43718,6 +43863,7 @@ function remarkComments() {
43718
43863
  if (node.type === "paragraph" && parentType !== "listItem" && node.children.length === 1 && node.children[0]?.type === "inkkitCommentInline") return {
43719
43864
  ...node.children[0],
43720
43865
  type: "inkkitCommentBlock",
43866
+ block: false,
43721
43867
  position: node.position
43722
43868
  };
43723
43869
  return node;
@@ -43757,7 +43903,7 @@ function commentNode(inline) {
43757
43903
  runner: (state, node, type) => {
43758
43904
  state.addNode(type, {
43759
43905
  syntax: node.syntax,
43760
- block: inline ? Boolean(node.block) : true
43906
+ block: inline ? Boolean(node.block) : node.block !== false
43761
43907
  }, node.value ? [state.schema.text(String(node.value))] : void 0);
43762
43908
  }
43763
43909
  },
@@ -43981,6 +44127,25 @@ async function resolveFile(adapter, reference, context) {
43981
44127
  ].includes(presentation.kind)) throw new TypeError("File presentation needs a resource URL");
43982
44128
  return Object.freeze({ ...presentation });
43983
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
+ }
43984
44149
  var FileResolution = class {
43985
44150
  runtime;
43986
44151
  changed;
@@ -43995,6 +44160,7 @@ var FileResolution = class {
43995
44160
  }
43996
44161
  update(reference) {
43997
44162
  const key = JSON.stringify([
44163
+ reference.raw,
43998
44164
  reference.reference,
43999
44165
  reference.kind,
44000
44166
  reference.fragment
@@ -44004,8 +44170,23 @@ var FileResolution = class {
44004
44170
  this.key = key;
44005
44171
  this.retry();
44006
44172
  }
44173
+ /** Resolves again from a blank loading card. */
44007
44174
  retry() {
44008
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;
44009
44190
  if (this.dead || !this.reference || !this.runtime.active()) return;
44010
44191
  const captured = this.runtime.capture();
44011
44192
  const request = {
@@ -44013,18 +44194,21 @@ var FileResolution = class {
44013
44194
  isCurrent: captured.isCurrent
44014
44195
  };
44015
44196
  this.request = request;
44016
- this.state = { status: "loading" };
44017
- this.changed();
44197
+ if (blank) {
44198
+ this.state = { status: "loading" };
44199
+ this.changed();
44200
+ }
44018
44201
  resolveFile(this.runtime.adapter, this.reference, {
44019
44202
  ...captured.context,
44020
44203
  signal: request.controller.signal
44021
44204
  }).then((presentation) => {
44022
44205
  if (!this.current(request)) return;
44206
+ const before = this.state;
44023
44207
  this.state = {
44024
44208
  status: "resolved",
44025
44209
  presentation
44026
44210
  };
44027
- this.changed();
44211
+ if (before.status !== "resolved" || !samePresentation(before.presentation, presentation)) this.changed();
44028
44212
  }, (failure) => {
44029
44213
  if (!this.current(request)) return;
44030
44214
  const error = coded(failure, "file-unavailable");
@@ -44135,7 +44319,7 @@ function nodeText(node, missingImages, fileDescriptions) {
44135
44319
  if (node.type.name === "image") {
44136
44320
  const description = fileDescriptions.get(node.attrs);
44137
44321
  if (description != null) return description;
44138
- const alt = fileReference(node).label || "Image";
44322
+ const alt = fileFields(node).label || "Image";
44139
44323
  return missingImages.has(String(node.attrs.src ?? "")) ? `[${alt}: image unavailable]` : alt;
44140
44324
  }
44141
44325
  if (node.type.name === "table") {
@@ -44149,7 +44333,9 @@ function nodeText(node, missingImages, fileDescriptions) {
44149
44333
  }
44150
44334
  return clipboardText(node.content, missingImages, fileDescriptions);
44151
44335
  }
44152
- 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) {
44153
44339
  content = shareableFragment(content);
44154
44340
  const container = document.createElement("div");
44155
44341
  const serializer = DOMSerializer.fromSchema(schema);
@@ -44162,7 +44348,7 @@ function clipboardDOM(content, schema) {
44162
44348
  }]
44163
44349
  }, {
44164
44350
  ...serializer.marks,
44165
- 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]
44166
44352
  });
44167
44353
  container.append(safe.serializeFragment(content));
44168
44354
  if (!onlyDefinitions(content)) for (const definition of container.querySelectorAll("[data-inkkit-reference-definition]")) definition.remove();
@@ -44181,18 +44367,18 @@ function clipboardDOM(content, schema) {
44181
44367
  }
44182
44368
  return container;
44183
44369
  }
44184
- function clipboardContent(content, schema) {
44370
+ function clipboardContent(content, schema, linkSchemes) {
44185
44371
  return {
44186
44372
  text: clipboardText(content),
44187
- html: clipboardDOM(content, schema).innerHTML
44373
+ html: clipboardDOM(content, schema, linkSchemes).innerHTML
44188
44374
  };
44189
44375
  }
44190
- function clipboardSerializer(schema) {
44376
+ function clipboardSerializer(schema, linkSchemes) {
44191
44377
  const serializer = DOMSerializer.fromSchema(schema);
44192
44378
  const safe = new DOMSerializer(serializer.nodes, serializer.marks);
44193
44379
  safe.serializeFragment = (content, options = {}, target) => {
44194
44380
  const output = target ?? (options.document ?? document).createDocumentFragment();
44195
- output.append(...clipboardDOM(content, schema).childNodes);
44381
+ output.append(...clipboardDOM(content, schema, linkSchemes).childNodes);
44196
44382
  return output;
44197
44383
  };
44198
44384
  return safe;
@@ -44200,15 +44386,15 @@ function clipboardSerializer(schema) {
44200
44386
  var visibleClipboard = $prose((ctx) => new Plugin({
44201
44387
  key: new PluginKey("visibleClipboard"),
44202
44388
  props: {
44203
- clipboardSerializer: clipboardSerializer(ctx.get(schemaCtx)),
44389
+ clipboardSerializer: clipboardSerializer(ctx.get(schemaCtx), editorLinkSchemes(ctx)),
44204
44390
  clipboardTextSerializer: (slice) => clipboardText(slice.content)
44205
44391
  }
44206
44392
  }));
44207
44393
  /** Export a frozen document fragment; presentation URLs never enter the clipboard. */
44208
- 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) {
44209
44395
  signal.throwIfAborted();
44210
44396
  content = shareableFragment(content);
44211
- const base = clipboardContent(content, schema);
44397
+ const base = clipboardContent(content, schema, linkSchemes);
44212
44398
  const container = document.createElement("div");
44213
44399
  container.innerHTML = base.html;
44214
44400
  const images = [];
@@ -44220,7 +44406,7 @@ async function portableClipboard(content, schema, markdown, adapter, context, co
44220
44406
  const captured = [...container.querySelectorAll("img")].map((element, index) => {
44221
44407
  const node = references[index];
44222
44408
  const reference = String(node?.attrs.src ?? "");
44223
- const { label: authoredAlt, width } = fileReference(node);
44409
+ const { label: authoredAlt, width } = fileFields(node);
44224
44410
  const alt = authoredAlt || "Image";
44225
44411
  if (width != null && width > 0) element.setAttribute("width", String(Math.min(4096, width)));
44226
44412
  element.removeAttribute("src");
@@ -44483,7 +44669,7 @@ function isHTMLElement(node) {
44483
44669
  return isElement(node) && node.namespaceURI === "http://www.w3.org/1999/xhtml";
44484
44670
  }
44485
44671
  //#endregion
44486
- //#region node_modules/.pnpm/prosemirror-drop-indicator@0.1.4/node_modules/prosemirror-drop-indicator/dist/index.js
44672
+ //#region node_modules/.pnpm/prosemirror-drop-indicator@0.1.4_patch_hash=4afd7e652c59a5f25f8ecc7b74d6370bde7273ee46a28766df1490663f928940/node_modules/prosemirror-drop-indicator/dist/index.js
44487
44673
  function getTargetsByView(view) {
44488
44674
  const stack = [[-1, view.state.doc]];
44489
44675
  const targets = [];
@@ -44639,6 +44825,8 @@ function createDropIndicatorView(view, getTarget, options) {
44639
44825
  let hasDragOverEvent = false;
44640
44826
  const scheduleHide = () => {
44641
44827
  if (hideId) clearTimeout(hideId);
44828
+ prevX = void 0;
44829
+ prevY = void 0;
44642
44830
  hasDragOverEvent = false;
44643
44831
  hideId = setTimeout(() => {
44644
44832
  if (hasDragOverEvent) return;
@@ -44679,6 +44867,7 @@ function createDropIndicatorView(view, getTarget, options) {
44679
44867
  dom.addEventListener("drop", scheduleHide);
44680
44868
  dom.addEventListener("dragleave", scheduleHide);
44681
44869
  const destroy = () => {
44870
+ if (hideId) clearTimeout(hideId);
44682
44871
  dom.removeEventListener("dragover", handleDragOver);
44683
44872
  dom.removeEventListener("dragend", scheduleHide);
44684
44873
  dom.removeEventListener("drop", scheduleHide);
@@ -44820,10 +45009,82 @@ var cursor = [
44820
45009
  //#endregion
44821
45010
  //#region src/text-input.ts
44822
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
+ }
44823
45026
  /**
44824
- * Inserts text that arrives in one piece, such as buffered native typing,
44825
- * dictation or a text replacement, at `from` to `to`, and returns whether it
44826
- * 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.
44827
45088
  *
44828
45089
  * Input rules are written for one typed character: Milkdown's take their
44829
45090
  * positions from the document before the text goes in, so given more than
@@ -44831,19 +45092,25 @@ var isInputRules = (plugin) => plugin.spec.isInputRules === true;
44831
45092
  * in except for its last character, and the input rules see that character
44832
45093
  * typed after the rest, all in one transaction.
44833
45094
  */
44834
- function insertTextChunk(view, from, to, text, deflt) {
45095
+ function insertTypedText(view, from, to, text, deflt) {
44835
45096
  const characters = Array.from(text);
44836
- if (characters.length < 2 || view.composing || /[\r\n]/.test(text)) return false;
45097
+ if (!characters.length || view.composing || /[\r\n]/.test(text)) return false;
44837
45098
  const { state } = view;
44838
- const $from = state.doc.resolve(from);
44839
- 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);
44840
45106
  const last = characters.at(-1);
44841
45107
  const rest = text.slice(0, -last.length);
44842
- 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);
44843
45110
  const $at = Selection.near(tr.doc.resolve(tr.mapping.map(to)), -1).$head;
44844
45111
  const at = $at.pos;
44845
45112
  if (!$at.parent.inlineContent || $at.parent.type.spec.code || $at.parentOffset < rest.length || $at.parent.textBetween($at.parentOffset - rest.length, $at.parentOffset) !== rest) {
44846
- view.dispatch(deflt().scrollIntoView());
45113
+ view.dispatch(fallback().scrollIntoView());
44847
45114
  return true;
44848
45115
  }
44849
45116
  const rules = state.plugins.filter(isInputRules);
@@ -44851,7 +45118,7 @@ function insertTextChunk(view, from, to, text, deflt) {
44851
45118
  schema: state.schema,
44852
45119
  doc: tr.doc,
44853
45120
  selection: TextSelection.create(tr.doc, at),
44854
- storedMarks: state.storedMarks ?? (from === to ? $from.marks() : $from.marksAcross($to))
45121
+ storedMarks: typing && typing.linked < text.length ? typing.link.removeFromSet(marks) : marks
44855
45122
  }, rest);
44856
45123
  let ruled;
44857
45124
  const preparedView = Object.create(view, {
@@ -44861,7 +45128,7 @@ function insertTextChunk(view, from, to, text, deflt) {
44861
45128
  } }
44862
45129
  });
44863
45130
  if (!rules.some((rule) => rule.props.handleTextInput?.call(rule, preparedView, at, at, last, () => prepared.tr.insertText(last, at)))) {
44864
- const plain = deflt().scrollIntoView();
45131
+ const plain = fallback().scrollIntoView();
44865
45132
  view.dispatch(last === "-" ? insertedHyphen(plain) : plain);
44866
45133
  return true;
44867
45134
  }
@@ -44869,6 +45136,7 @@ function insertTextChunk(view, from, to, text, deflt) {
44869
45136
  for (const step of ruled.steps) tr.step(step);
44870
45137
  tr.setSelection(ruled.selection.map(tr.doc, StepMap.empty));
44871
45138
  if (ruled.storedMarksSet) tr.setStoredMarks(ruled.storedMarks);
45139
+ else if (typing?.stored) tr.setStoredMarks(typing.stored);
44872
45140
  for (const rule of rules) {
44873
45141
  const meta = ruled.getMeta(rule);
44874
45142
  if (meta !== void 0) tr.setMeta(rule, meta);
@@ -44877,6 +45145,65 @@ function insertTextChunk(view, from, to, text, deflt) {
44877
45145
  view.dispatch(tr.scrollIntoView());
44878
45146
  return true;
44879
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
+ }
44880
45207
  //#endregion
44881
45208
  //#region src/drop-placement.ts
44882
45209
  /**
@@ -44992,6 +45319,20 @@ function caretAfterInsertion(tr, view) {
44992
45319
  return tr.setSelection($end.parent.inlineContent ? TextSelection.create(tr.doc, end) : caretAfter(tr.doc, end, view && calloutFolds(view, tr)));
44993
45320
  }
44994
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
44995
45336
  //#region src/code-fences.ts
44996
45337
  function readFence(value) {
44997
45338
  if (typeof value !== "object" || value == null) return null;
@@ -45383,10 +45724,11 @@ var formatting = /* @__PURE__ */ new Set([
45383
45724
  "delete",
45384
45725
  "highlight"
45385
45726
  ]);
45727
+ var linkTypes = /* @__PURE__ */ new Set(["link", "linkReference"]);
45386
45728
  function ordered(node) {
45387
45729
  let current = node;
45388
45730
  const inner = current.children?.[0];
45389
- 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 = {
45390
45732
  ...inner,
45391
45733
  children: [{
45392
45734
  ...current,
@@ -45398,6 +45740,32 @@ function ordered(node) {
45398
45740
  children: current.children.map(ordered)
45399
45741
  } : current;
45400
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
+ }
45401
45769
  /** Where the delimiter row ends, given where the header row before it ends. */
45402
45770
  function delimiterRowEnd(text, headerEnd) {
45403
45771
  const next = text.indexOf("\n", headerEnd);
@@ -45406,15 +45774,17 @@ function delimiterRowEnd(text, headerEnd) {
45406
45774
  ending.lastIndex = next + 1;
45407
45775
  return ending.exec(text)?.index ?? text.length;
45408
45776
  }
45409
- function ownShape(node) {
45777
+ function ownShape(node, spreadless = false) {
45410
45778
  return JSON.stringify(ordered(node), (key, value) => [
45411
45779
  "position",
45412
45780
  "data",
45413
45781
  "children",
45414
45782
  "value",
45415
45783
  "marker"
45416
- ].includes(key) ? void 0 : value);
45784
+ ].includes(key) || spreadless && key === "spread" ? void 0 : value);
45417
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);
45418
45788
  var matchShape = (node) => ownShape({
45419
45789
  ...node,
45420
45790
  checked: void 0
@@ -45471,10 +45841,25 @@ function authoredMarker(list, source) {
45471
45841
  return at == null ? void 0 : markerAt(source, at)?.marker;
45472
45842
  }
45473
45843
  var sameList = (before, after) => before.type === "list" && after.type === "list" && before.ordered === after.ordered;
45474
- 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) {
45475
45860
  let text = canonical.slice(from, to);
45476
45861
  const offsets = (list) => (list.children ?? []).map((item) => item.position.start.offset - from);
45477
- 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);
45478
45863
  children.forEach((child, index) => {
45479
45864
  if (child.type !== "list") {
45480
45865
  previous = void 0;
@@ -45482,19 +45867,24 @@ function canonicalSlice(marker, children, canonical, from, to, previous, next) {
45482
45867
  }
45483
45868
  const avoid = [previous, index === children.length - 1 ? next : void 0].filter((neighbour) => neighbour?.ordered === child.ordered).map((neighbour) => neighbour.marker);
45484
45869
  const own = markerAt(canonical, child.children[0].position.start.offset)?.marker;
45485
- const choice = [own, ...child.ordered ? [".", ")"] : [
45486
- "-",
45487
- "*",
45488
- "+"
45489
- ]].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));
45490
45879
  if (choice) text = relabel(text, offsets(child), choice);
45491
45880
  previous = {
45492
45881
  ordered: child.ordered,
45493
45882
  marker: choice ?? own ?? ""
45494
45883
  };
45495
45884
  });
45496
- return text;
45885
+ return regutter(text, gutters, open);
45497
45886
  }
45887
+ var itemMarker = (item, source) => item.type === "listItem" ? markerAt(source, item.position.start.offset)?.marker : void 0;
45498
45888
  function authoredNeighbour(before, after, source) {
45499
45889
  if (!before || !after || !sameList(before, after)) return void 0;
45500
45890
  const marker = authoredMarker(before, source);
@@ -45594,35 +45984,90 @@ function retainSplit(candidates, after, source, canonical, lineEnding, taken) {
45594
45984
  text: retainSource(part, after, source, canonical, lineEnding)
45595
45985
  };
45596
45986
  }
45597
- function retainParts(marker, before, after, source, canonical, from, to, lineEnding, previous, next) {
45598
- 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) {
45599
46016
  const taken = /* @__PURE__ */ new Set();
45600
46017
  const opening = (item) => item.children?.length ? matchShape(item) + signature(item.children[0]) : void 0;
45601
- 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) => {
45602
46041
  const end = child.position.end.offset;
45603
46042
  if (child.type === "list" || child.type === "blockquote") {
45604
46043
  const part = retainSplit(before, child, source, canonical, lineEnding, taken);
45605
46044
  if (part) return part.text + canonical.slice(child.children.at(-1).position.end.offset, end);
45606
46045
  const lists = before.filter((node) => node.type === "list" && node.ordered === child.ordered);
45607
- 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;
45608
- }
45609
- if (child.type !== "listItem") return void 0;
45610
- if (!items) {
45611
- items = /* @__PURE__ */ new Map();
45612
- for (const node of before) {
45613
- const key = node.type === "listItem" ? opening(node) : void 0;
45614
- if (!key) continue;
45615
- const same = items.get(key);
45616
- if (same) same.push(node);
45617
- 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);
45618
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;
45619
46063
  }
45620
- const original = items.get(opening(child) ?? "")?.shift();
45621
- 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;
45622
46067
  return text !== void 0 && marker ? relabel(text, [0], marker) : text;
45623
46068
  });
45624
46069
  if (pieces.every((piece) => piece === void 0)) return void 0;
45625
- let text = "", at = from;
46070
+ let text = "", at = from, lastNumber;
45626
46071
  after.forEach((child, index) => {
45627
46072
  const childStart = child.position.start.offset;
45628
46073
  const childEnd = child.position.end.offset;
@@ -45630,15 +46075,18 @@ function retainParts(marker, before, after, source, canonical, from, to, lineEnd
45630
46075
  let piece = pieces[index];
45631
46076
  const own = piece && markerAt(piece, 0)?.marker;
45632
46077
  if (child.type === "list" && [previous, following].some((neighbour) => neighbour?.ordered === child.ordered && neighbour?.marker === own)) piece = void 0;
45633
- 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;
45634
46082
  previous = child.type === "list" ? {
45635
46083
  ordered: child.ordered,
45636
46084
  marker: markerAt(piece, /^[ \t]*/.exec(piece)[0].length)?.marker ?? ""
45637
46085
  } : void 0;
45638
- text += canonical.slice(at, childStart) + piece;
46086
+ text += regutter(canonical.slice(at, childStart), gutters, true) + piece;
45639
46087
  at = childEnd;
45640
46088
  });
45641
- return text + canonical.slice(at, to);
46089
+ return text + regutter(canonical.slice(at, to), gutters, open);
45642
46090
  }
45643
46091
  function decodedOffsets(raw, lines = false) {
45644
46092
  const offsets = [0];
@@ -45646,7 +46094,7 @@ function decodedOffsets(raw, lines = false) {
45646
46094
  for (let index = 0; index < raw.length;) {
45647
46095
  const rest = raw.slice(index);
45648
46096
  const line = /[ \t]/.test(raw[index - 1] ?? "") && /[ \t]/.test(raw[index]) ? void 0 : /^[ \t]*(?:\r\n?|\n)(?:[ \t]*>)*[ \t]*/.exec(rest)?.[0];
45649
- 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];
45650
46098
  let value = raw[index];
45651
46099
  if (line) {
45652
46100
  if (!lines) return void 0;
@@ -45655,7 +46103,8 @@ function decodedOffsets(raw, lines = false) {
45655
46103
  else if (token) {
45656
46104
  const element = document.createElement("textarea");
45657
46105
  element.innerHTML = token;
45658
- value = element.value;
46106
+ if (element.value === token) token = void 0;
46107
+ else value = element.value;
45659
46108
  }
45660
46109
  index += token?.length ?? 1;
45661
46110
  decoded += value;
@@ -45772,7 +46221,10 @@ function retainRun(before, after, source, canonical, [from, to], [newFrom, newTo
45772
46221
  }
45773
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));
45774
46223
  }
45775
- 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;
45776
46228
  before = lf(before);
45777
46229
  after = lf(after);
45778
46230
  let prefix = 0;
@@ -45782,15 +46234,42 @@ function retainText(raw, before, after, inTable = false, written, beside = [fals
45782
46234
  const read = decodedOffsets(raw, true);
45783
46235
  if (read?.decoded !== before) return after;
45784
46236
  const { offsets } = read;
46237
+ const lineStart = after.lastIndexOf("\n", prefix - 1) + 1;
46238
+ if (lineStart > 0 && written !== void 0) {
46239
+ const canonical = decodedOffsets(written, true);
46240
+ if (canonical?.decoded === after) {
46241
+ const opening = written.slice(canonical.offsets[lineStart]);
46242
+ const marker = /^(?:\d+)?\\[#+.\->)]/.exec(opening)?.[0];
46243
+ if (marker && prefix - lineStart <= decodedOffsets(marker).decoded.length) {
46244
+ prefix = lineStart;
46245
+ return raw.slice(0, offsets[prefix]) + guttered(written.slice(canonical.offsets[prefix], canonical.offsets[after.length - suffix])) + raw.slice(offsets[before.length - suffix]);
46246
+ }
46247
+ }
46248
+ }
45785
46249
  if (written !== void 0 && (beside[0] && prefix === 0 || beside[1] && suffix === 0)) {
45786
46250
  const writtenOffsets = decodedOffsets(written);
45787
- 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]);
45788
46252
  }
45789
46253
  const inserted = after.slice(prefix, after.length - suffix).split("").map((character, index) => {
45790
46254
  const at = prefix + index;
45791
- 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;
45792
46256
  }).join("");
45793
- 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);
45794
46273
  }
45795
46274
  function retainBoundaryText(raw, canonical, afterBreak = false) {
45796
46275
  if (afterBreak) {
@@ -45987,6 +46466,7 @@ function markLiterals(raw, transformed, blocks = []) {
45987
46466
  raw.children.forEach(mark);
45988
46467
  }
45989
46468
  function retainSource(before, after, source, canonical, lineEnding, inTable = false, width, beside = [false, false], joined = [], reorder, afterBreak = false) {
46469
+ after = authoredNesting(before, after);
45990
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);
45991
46471
  if (before.type === "list" && after.type === "list" && before.ordered && after.ordered && !numbered.has(before) && before.position?.start.offset != null) {
45992
46472
  const own = {
@@ -46083,15 +46563,20 @@ function retainSource(before, after, source, canonical, lineEnding, inTable = fa
46083
46563
  const to = first.position.end.offset - start;
46084
46564
  return retained.startsWith(source.slice(start, before.children[1].position.start.offset)) ? retained.slice(0, from) + "<br />" + retained.slice(to) : retained;
46085
46565
  }
46086
- 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);
46087
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
+ }
46088
46572
  const marker = sameList(before, after) ? authoredMarker(before, source) : void 0;
46089
46573
  const located = (nodes) => nodes.every((node) => node.position?.start.offset != null);
46090
- 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)) {
46091
46575
  let output = "", at = start;
46092
46576
  let previous;
46093
46577
  const same = (candidate, incoming) => signature(candidate) === signature(incoming) && ownShape(candidate) === ownShape(incoming);
46094
46578
  const reorderable = before.type === "table" || before.type === "tableRow";
46579
+ const gutters = childGutters(before, after, source, canonical);
46095
46580
  const moves = reorder?.get(parentKey(before));
46096
46581
  const retained = new Set(reorderable ? before.children.flatMap((child, index) => same(child, after.children[index]) ? [index] : []) : []);
46097
46582
  before.children.forEach((child, index) => {
@@ -46102,7 +46587,7 @@ function retainSource(before, after, source, canonical, lineEnding, inTable = fa
46102
46587
  if (movedIndex >= 0) retained.add(movedIndex);
46103
46588
  const childStart = child.position?.start.offset, childEnd = child.position?.end.offset;
46104
46589
  if (childStart == null || childEnd == null) throw new PreservationError("A source token has no location");
46105
- 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");
46106
46591
  if (before.type === "tableRow" && movedIndex >= 0 && movedIndex !== index) {
46107
46592
  const originalFrom = original.children?.[0]?.position?.start.offset;
46108
46593
  const originalTo = original.children?.at(-1)?.position?.end.offset;
@@ -46121,7 +46606,7 @@ function retainSource(before, after, source, canonical, lineEnding, inTable = fa
46121
46606
  });
46122
46607
  return output + source.slice(at, end);
46123
46608
  }
46124
- 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) {
46125
46610
  let prefix = 0, suffix = 0;
46126
46611
  while (prefix < before.children.length && prefix < after.children.length && signature(before.children[prefix]) === signature(after.children[prefix])) prefix++;
46127
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++;
@@ -46146,13 +46631,15 @@ function retainSource(before, after, source, canonical, lineEnding, inTable = fa
46146
46631
  const changes = after.children.slice(prefix, after.children.length - suffix);
46147
46632
  const previous = authoredNeighbour(before.children[prefix - 1], after.children[prefix - 1], source);
46148
46633
  const next = authoredNeighbour(before.children[before.children.length - suffix], after.children[after.children.length - suffix], source);
46149
- 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);
46150
46637
  }
46151
46638
  }
46152
46639
  const lastAfter = after.children?.at(-1)?.position?.end.offset;
46153
46640
  const lastBefore = before.children?.at(-1)?.position?.end.offset;
46154
46641
  const trailing = marker != null && lastAfter != null && lastBefore != null && loose(before, source) === loose(after, canonical);
46155
- 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) : "");
46156
46643
  }
46157
46644
  function loose(list, text) {
46158
46645
  const blank = (from, to) => from != null && to != null && /\n[ \t>]*\r?\n/.test(text.slice(from, to));
@@ -46249,6 +46736,13 @@ function semanticSignature(doc) {
46249
46736
  attrs
46250
46737
  };
46251
46738
  }
46739
+ if (value.type === "comment_block" && value.attrs) {
46740
+ const { block: _block, ...attrs } = value.attrs;
46741
+ value = {
46742
+ ...value,
46743
+ attrs
46744
+ };
46745
+ }
46252
46746
  const content = value.content;
46253
46747
  if (!content) return value;
46254
46748
  const tokens = [];
@@ -46291,6 +46785,7 @@ function semanticSignature(doc) {
46291
46785
  "authoredFence",
46292
46786
  "form",
46293
46787
  "inkkitFileRaw",
46788
+ "inkkitFileSource",
46294
46789
  "inkkitFileWidth",
46295
46790
  "inkkitFileInTable"
46296
46791
  ].includes(key) ? void 0 : value));
@@ -46502,7 +46997,7 @@ var Preservation = class Preservation {
46502
46997
  try {
46503
46998
  const parse = (text) => parseDoc(ctx, text);
46504
46999
  this.baseline = parse(source);
46505
- 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";
46506
47001
  const processor = ctx.get(remarkCtx);
46507
47002
  const offset = source.startsWith("") ? 1 : 0;
46508
47003
  const body = source.slice(offset);
@@ -46597,6 +47092,9 @@ var Preservation = class Preservation {
46597
47092
  const indent = source.slice(source.lastIndexOf("\n", part.from - 1) + 1, part.from);
46598
47093
  return (/^[ \t]*$/.test(indent) ? indent : "") + part.text;
46599
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
+ }
46600
47098
  localEdits(doc) {
46601
47099
  if (this.baseline.childCount !== this.blocks.length || doc.childCount !== this.blocks.length || !doc.sameMarkup(this.baseline)) return void 0;
46602
47100
  let output = "", previous = 0;
@@ -47011,7 +47509,7 @@ var Preservation = class Preservation {
47011
47509
  const kind = block && block.form === text && !carried.within.has(index) ? position == null ? "moved" : "unchanged" : block && tree && position != null ? "edited" : "canonical";
47012
47510
  kept.push(kind);
47013
47511
  const node = tree ? positionalNode(tree.raw, transformed(index)) : void 0;
47014
- 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)));
47015
47513
  }
47016
47514
  return {
47017
47515
  gaps,
@@ -47547,23 +48045,6 @@ function completedAngle(tr, chars, from, to) {
47547
48045
  form: "angle"
47548
48046
  })).removeStoredMark(tr.doc.type.schema.marks.link);
47549
48047
  }
47550
- function closeAngleEnds(tr, transactions) {
47551
- const steps = transactions.flatMap((transaction) => transaction.steps.map((step, index) => ({
47552
- doc: transaction.docs[index],
47553
- map: step.getMap()
47554
- })));
47555
- steps.forEach(({ doc, map }, index) => {
47556
- const remaining = new Mapping(steps.slice(index + 1).map(({ map }) => map));
47557
- map.forEach((from, to, start, end) => {
47558
- if (from !== to || start === end) return;
47559
- const at = doc.resolve(from);
47560
- const before = at.nodeBefore?.marks.find((mark) => mark.attrs.form === "angle");
47561
- const after = at.nodeAfter?.marks.find((mark) => mark.attrs.form === "angle");
47562
- if (before && after && before.eq(after)) return;
47563
- for (const mark of [before, after]) if (mark) tr.removeMark(remaining.map(start, 1), remaining.map(end, -1), mark);
47564
- });
47565
- });
47566
- }
47567
48048
  function codeAngles(tr, edited) {
47568
48049
  const spans = [];
47569
48050
  for (const block of edited) tr.doc.nodesBetween(block.pos, block.pos + block.block.nodeSize, (node, pos) => {
@@ -47772,10 +48253,7 @@ var autolinkLiterals = $prose((ctx) => new Plugin({
47772
48253
  if (!transactions.some((tr) => tr.docChanged)) return null;
47773
48254
  const base = newState.tr;
47774
48255
  const written = transactions.every(isWrittenEdit);
47775
- if (written) {
47776
- closeAngleEnds(base, transactions);
47777
- codeAngles(base, editedBlocks(oldState.doc, base.doc));
47778
- }
48256
+ if (written) codeAngles(base, editedBlocks(oldState.doc, base.doc));
47779
48257
  let edited = editedBlocks(oldState.doc, base.doc);
47780
48258
  if (written && edited.length === 1) {
47781
48259
  const { pos, block, from, to } = edited[0];
@@ -48144,7 +48622,8 @@ function remarkCallouts() {
48144
48622
  const visit = (node) => {
48145
48623
  if (node.type === "blockquote") {
48146
48624
  const start = node.position?.start.offset, end = node.position?.end.offset;
48147
- 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);
48148
48627
  const header = /^>[ \t]*([\s\S]*?)(?:\r?\n|$)/.exec(raw)?.[1] ?? "";
48149
48628
  if (header.startsWith("[!")) {
48150
48629
  const match = /^(\[!([A-Za-z]+)\])([+-]?)(?:[ \t]+([^\r\n]*))?$/.exec(header);
@@ -48316,7 +48795,7 @@ var calloutView = $prose((ctx) => new Plugin({
48316
48795
  const render = () => {
48317
48796
  title.textContent = node.attrs.title || String(node.attrs.kind).toLowerCase().replace(/^./, (letter) => letter.toUpperCase());
48318
48797
  dom.setAttribute("data-inkkit-folded", String(folded));
48319
- view.dom.dispatchEvent(new Event("inkkit-media-policy"));
48798
+ view.dom.dispatchEvent(new Event(mediaPolicyEvent));
48320
48799
  button.hidden = !node.attrs.fold;
48321
48800
  button.setAttribute("aria-expanded", String(!folded));
48322
48801
  button.setAttribute("aria-label", `${folded ? editorLabels(ctx).expandCallout : editorLabels(ctx).collapseCallout} ${title.textContent}`);
@@ -50083,12 +50562,50 @@ function writesBare(node, index, ancestors) {
50083
50562
  return side(-1) && side(1);
50084
50563
  }
50085
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
+ }
50086
50602
  var root = (node, parent, state, info) => {
50087
50603
  bareLinks = /* @__PURE__ */ new WeakSet();
50088
50604
  const empty = (child) => child.type === "text" && child.value === "" || wrappers.has(child.type) && "children" in child && child.children.every(empty);
50089
50605
  const visit = (current, ancestors) => {
50090
50606
  if (current.children.some((child) => empty(child))) current.children = current.children.filter((child) => !empty(child));
50091
50607
  const children = current.children;
50608
+ shareFormatting(children);
50092
50609
  children.forEach((child, index) => {
50093
50610
  if (child.type === "link" && formOf(child)) {
50094
50611
  const outside = formattedOutside(child);
@@ -50309,6 +50826,16 @@ var breaksWithoutMarks = $prose((ctx) => new Plugin({
50309
50826
  }
50310
50827
  }));
50311
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
+ };
50312
50839
  var preserveSpacerParagraphs = paragraphSchema.extendSchema((base) => (ctx) => {
50313
50840
  const schema = base(ctx);
50314
50841
  return {
@@ -50439,7 +50966,16 @@ var normalizedImages = imageSchema.extendSchema((base) => (ctx) => {
50439
50966
  ...schema.toMarkdown,
50440
50967
  runner(state, node) {
50441
50968
  const raw = namedFileMarkdown(node, Boolean(node.attrs.inkkitFileInTable));
50442
- 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
+ }
50443
50979
  state.addNode("image", void 0, void 0, {
50444
50980
  url: node.attrs.src,
50445
50981
  alt: node.attrs.alt,
@@ -50447,7 +50983,7 @@ var normalizedImages = imageSchema.extendSchema((base) => (ctx) => {
50447
50983
  inkkitFileRaw: raw,
50448
50984
  inkkitFileTarget: node.attrs.inkkitFileTarget,
50449
50985
  inkkitFileFragment: node.attrs.inkkitFileFragment,
50450
- inkkitFileWidth: fileReference(node).width ?? null,
50986
+ inkkitFileWidth: fileFields(node).width ?? null,
50451
50987
  inkkitFileInTable: node.attrs.inkkitFileInTable
50452
50988
  });
50453
50989
  }
@@ -50560,6 +51096,7 @@ function createDialect(images, wikiLinks = false, files = false) {
50560
51096
  remarkCodeFences,
50561
51097
  remarkReferencesPlugin,
50562
51098
  remarkLinkFormsPlugin,
51099
+ remarkFileSourcesPlugin,
50563
51100
  remarkCalloutsPlugin,
50564
51101
  remarkCommentsPlugin,
50565
51102
  wikiLinks || files ? linkedSyntax(wikiLinks, files) : [],
@@ -50579,6 +51116,7 @@ function createDialect(images, wikiLinks = false, files = false) {
50579
51116
  wikiLinkComposition,
50580
51117
  wikiLinkInputRule
50581
51118
  ] : [],
51119
+ wikiLinks || files ? referenceTableContext : [],
50582
51120
  callouts,
50583
51121
  comments,
50584
51122
  hiddenCommentEditing,
@@ -50606,7 +51144,8 @@ function createDialect(images, wikiLinks = false, files = false) {
50606
51144
  remarkDialectPlugin,
50607
51145
  tables,
50608
51146
  literalBlock,
50609
- checkedParser
51147
+ checkedParser,
51148
+ imageWriter
50610
51149
  ].flat();
50611
51150
  }
50612
51151
  createDialect(false);
@@ -51318,7 +51857,7 @@ var ImageView = class {
51318
51857
  view;
51319
51858
  getPos;
51320
51859
  adapter;
51321
- runtime;
51860
+ host;
51322
51861
  canMutate;
51323
51862
  dom;
51324
51863
  /** The card or picture, which a standalone image's block row holds. */
@@ -51330,22 +51869,30 @@ var ImageView = class {
51330
51869
  stopResize;
51331
51870
  resolution;
51332
51871
  previewFailed = false;
51333
- 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) {
51334
51875
  this.node = node;
51335
51876
  this.view = view;
51336
51877
  this.getPos = getPos;
51337
51878
  this.adapter = adapter;
51338
- this.runtime = runtime;
51879
+ this.host = host;
51339
51880
  this.canMutate = canMutate;
51881
+ const runtime = files && host && {
51882
+ ...host,
51883
+ adapter: files
51884
+ };
51885
+ this.runtime = runtime;
51340
51886
  this.element = document.createElement("span");
51341
51887
  this.element.className = "inkkit-image";
51888
+ this.element.addEventListener("contextmenu", this.contextMenu);
51342
51889
  if (node.isBlock) {
51343
51890
  this.dom = document.createElement("div");
51344
51891
  this.dom.className = "inkkit-image-block";
51345
51892
  this.dom.append(this.element);
51346
51893
  } else this.dom = this.element;
51347
51894
  this.view.dom.addEventListener("inkkit-cancel-resize", this.cancelResize);
51348
- this.view.dom.addEventListener("inkkit-media-policy", this.mediaPolicy);
51895
+ this.view.dom.addEventListener(mediaPolicyEvent, this.mediaPolicy);
51349
51896
  if (runtime) {
51350
51897
  this.resolution = new FileResolution({
51351
51898
  ...runtime,
@@ -51382,8 +51929,15 @@ var ImageView = class {
51382
51929
  ignoreMutation() {
51383
51930
  return true;
51384
51931
  }
51385
- mediaPolicy = () => {
51932
+ mediaPolicy = (event) => {
51386
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
+ }
51387
51941
  if (!this.active()) {
51388
51942
  this.stopResize?.();
51389
51943
  this.stopResize = void 0;
@@ -51411,7 +51965,7 @@ var ImageView = class {
51411
51965
  }
51412
51966
  destroy() {
51413
51967
  this.view.dom.removeEventListener("inkkit-cancel-resize", this.cancelResize);
51414
- this.view.dom.removeEventListener("inkkit-media-policy", this.mediaPolicy);
51968
+ this.view.dom.removeEventListener(mediaPolicyEvent, this.mediaPolicy);
51415
51969
  this.stopResize?.();
51416
51970
  this.stopResize = void 0;
51417
51971
  this.resolution?.destroy();
@@ -51440,6 +51994,37 @@ var ImageView = class {
51440
51994
  report(error) {
51441
51995
  this.runtime?.error?.(coded(error, "file-unavailable"));
51442
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
+ }
51443
52028
  card(label, status, retry = false) {
51444
52029
  const footer = document.createElement("span");
51445
52030
  footer.className = "inkkit-file-footer";
@@ -51488,20 +52073,6 @@ var ImageView = class {
51488
52073
  this.image = void 0;
51489
52074
  if (this.runtime) {
51490
52075
  this.element.classList.add("inkkit-file");
51491
- this.element.oncontextmenu = (event) => {
51492
- if (!this.runtime?.adapter.contextMenu || !this.active()) return;
51493
- const captured = this.runtime.capture();
51494
- if (!captured.isCurrent()) return;
51495
- event.preventDefault();
51496
- try {
51497
- this.runtime.adapter.contextMenu(this.reference(), {
51498
- clientX: event.clientX,
51499
- clientY: event.clientY
51500
- }, captured.context);
51501
- } catch (error) {
51502
- this.report(error);
51503
- }
51504
- };
51505
52076
  const state = this.resolution.state;
51506
52077
  const label = state.status === "resolved" ? state.presentation.label || alt || reference.reference : alt || reference.reference;
51507
52078
  this.element.setAttribute("role", "group");
@@ -51650,8 +52221,8 @@ function openArrow() {
51650
52221
  icon.append(path);
51651
52222
  return icon;
51652
52223
  }
51653
- function imageView(adapter, runtime, canMutate = () => true) {
51654
- 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));
51655
52226
  return [view(imageSchema), view(imageBlockSchema)];
51656
52227
  }
51657
52228
  //#endregion
@@ -51663,26 +52234,29 @@ function wikiLinkView(adapter, runtime) {
51663
52234
  dom.className = "inkkit-wiki-link";
51664
52235
  dom.setAttribute("role", "link");
51665
52236
  dom.tabIndex = 0;
51666
- const reference = () => ({
51667
- target: String(current.attrs.target),
51668
- ...current.attrs.fragment ? { fragment: String(current.attrs.fragment) } : {},
51669
- label: String(current.attrs.label)
51670
- });
52237
+ const reference = () => wikiReference(current);
51671
52238
  const report = (error) => runtime.error?.(coded(error, "file-unavailable"));
51672
52239
  const render = () => {
51673
- dom.textContent = reference().label;
51674
- dom.removeAttribute("title");
51675
- dom.removeAttribute("data-missing");
51676
- if (!runtime.active()) return;
51677
- const captured = runtime.capture();
51678
- try {
51679
- const result = adapter.resolve?.(reference(), captured.context);
51680
- if (captured.isCurrent() && result?.missing) {
51681
- dom.dataset.missing = "true";
51682
- 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);
51683
52250
  }
51684
- } catch (error) {
51685
- 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");
51686
52260
  }
51687
52261
  };
51688
52262
  const open = (event) => {
@@ -51701,7 +52275,11 @@ function wikiLinkView(adapter, runtime) {
51701
52275
  dom.addEventListener("keydown", (event) => {
51702
52276
  if (event.key === "Enter" || event.key === " ") open(event);
51703
52277
  });
51704
- 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);
51705
52283
  render();
51706
52284
  return {
51707
52285
  dom,
@@ -51716,7 +52294,7 @@ function wikiLinkView(adapter, runtime) {
51716
52294
  },
51717
52295
  ignoreMutation: () => true,
51718
52296
  destroy() {
51719
- view.dom.removeEventListener("inkkit-media-policy", render);
52297
+ view.dom.removeEventListener(mediaPolicyEvent, policy);
51720
52298
  }
51721
52299
  };
51722
52300
  });
@@ -52787,7 +53365,7 @@ function readableProjection(doc) {
52787
53365
  }
52788
53366
  if (name === "image_block") {
52789
53367
  block(position);
52790
- append(fileReference(node).label || "Image", position, position + node.nodeSize, "embed");
53368
+ append(fileFields(node).label || "Image", position, position + node.nodeSize, "embed");
52791
53369
  return false;
52792
53370
  }
52793
53371
  if (node.isTextblock) {
@@ -52804,7 +53382,7 @@ function readableProjection(doc) {
52804
53382
  return false;
52805
53383
  }
52806
53384
  if (name === "image" || name === "footnote_reference" || name === "inkkit_wiki_link") {
52807
- 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");
52808
53386
  return false;
52809
53387
  }
52810
53388
  return true;
@@ -54056,6 +54634,96 @@ function htmlGrid(schema, html) {
54056
54634
  };
54057
54635
  }
54058
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
54059
54727
  //#region src/paste.ts
54060
54728
  var PasteController = class {
54061
54729
  options;
@@ -54217,12 +54885,12 @@ var PasteController = class {
54217
54885
  this.active = operation;
54218
54886
  this.options.onPendingChanged?.();
54219
54887
  try {
54220
- const images = [...input.images ?? []];
54221
- for (const file of files) images.push({
54888
+ const fromFiles = files.length ? distinctImages(await Promise.all(files.map(async (file) => ({
54222
54889
  bytes: new Uint8Array(await file.arrayBuffer()),
54223
54890
  mimeType: file.type,
54224
54891
  filename: file.name
54225
- });
54892
+ })))) : [];
54893
+ const images = distinctImages([...input.images ?? [], ...fromFiles]);
54226
54894
  this.assertContext(context, operation);
54227
54895
  const schema = view.state.schema;
54228
54896
  let slice;
@@ -54241,8 +54909,7 @@ var PasteController = class {
54241
54909
  }
54242
54910
  const anchor = this.anchor(operation);
54243
54911
  const start = view.state.doc.resolve(anchor.from);
54244
- const end = view.state.doc.resolve(anchor.to);
54245
- const marks = view.state.storedMarks ?? (anchor.from === anchor.to ? start.marks() : start.marksAcross(end)) ?? [];
54912
+ const marks = replacementMarks(view.state, anchor.from, anchor.to);
54246
54913
  const nodes = literalTextNodes(schema, input.text, marks, htmlBreakAt(start));
54247
54914
  slice = new Slice(Fragment.from(schema.nodes.paragraph.create(null, nodes)), 1, 1);
54248
54915
  } else if (input.html) {
@@ -54250,7 +54917,7 @@ var PasteController = class {
54250
54917
  const imported = /* @__PURE__ */ new Map();
54251
54918
  const elements = [...container.querySelectorAll("img")];
54252
54919
  const uniqueSources = new Set(elements.map((element) => element.getAttribute("src") ?? ""));
54253
- 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);
54254
54921
  let index = 0;
54255
54922
  for (const element of elements) {
54256
54923
  const source = element.getAttribute("src") ?? "";
@@ -54747,11 +55414,11 @@ var DocumentSession = class {
54747
55414
  //#region src/selection.ts
54748
55415
  /** The stylesheet paints the selection under this name. */
54749
55416
  var name = "inkkit-selection";
54750
- var ranges = /* @__PURE__ */ new Map();
55417
+ var activeClass = "inkkit-selection-active";
54751
55418
  /** The selected text within each block. */
54752
55419
  function covered(state) {
54753
55420
  const { from, to, empty, visible } = state.selection;
54754
- if (empty || !visible) return null;
55421
+ if (empty || !visible) return [];
54755
55422
  const text = [];
54756
55423
  state.doc.nodesBetween(from, to, (node, pos) => {
54757
55424
  if (!node.isTextblock) return true;
@@ -54768,39 +55435,140 @@ function covered(state) {
54768
55435
  function domRange(view, span) {
54769
55436
  const start = view.domAtPos(span.from);
54770
55437
  const end = view.domAtPos(span.to);
54771
- const range = document.createRange();
55438
+ const range = view.dom.ownerDocument.createRange();
54772
55439
  range.setStart(start.node, start.offset);
54773
55440
  range.setEnd(end.node, end.offset);
54774
55441
  return range;
54775
55442
  }
54776
- function paint(view) {
54777
- ranges.set(view, (covered(view.state) ?? []).map((span) => domRange(view, span)));
54778
- refresh();
54779
- }
54780
- function refresh() {
54781
- if (ranges.size === 0) {
54782
- CSS.highlights.delete(name);
54783
- return;
54784
- }
54785
- const highlight = new Highlight();
54786
- for (const selection of ranges.values()) for (const range of selection) highlight.add(range);
54787
- CSS.highlights.set(name, highlight);
54788
- }
55443
+ var rootOf = (view) => view.dom.closest(".inkkit-root");
54789
55444
  var paintable = () => typeof Highlight === "function" && "highlights" in CSS && CSS.supports(`selector(::highlight(${name}))`);
54790
- var selectionPlugin = $prose(() => paintable() ? new Plugin({
54791
- key: new PluginKey("selection"),
54792
- props: { attributes: { class: "inkkit-painted-selection" } },
54793
- view(view) {
54794
- paint(view);
54795
- return {
54796
- update: (view) => paint(view),
54797
- destroy: () => {
54798
- ranges.delete(view);
54799
- refresh();
54800
- }
54801
- };
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;
54802
55480
  }
54803
- }) : 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
+ });
54804
55572
  //#endregion
54805
55573
  //#region node_modules/.pnpm/prosemirror-search@1.1.1/node_modules/prosemirror-search/dist/index.js
54806
55574
  var SearchQuery = class {
@@ -55230,8 +55998,8 @@ function findFormatted(view, text) {
55230
55998
  return found;
55231
55999
  }
55232
56000
  function replacementSlice(state, from, to, value) {
55233
- const start = state.doc.resolve(from), end = state.doc.resolve(to);
55234
- const marks = start.marksAcross(end) ?? start.marks();
56001
+ const start = state.doc.resolve(from);
56002
+ const marks = replacementMarks(state, from, to);
55235
56003
  const nodes = [];
55236
56004
  if (start.parent.type.spec.code) {
55237
56005
  if (value) nodes.push(state.schema.text(value, marks));
@@ -55848,6 +56616,8 @@ var shortcutNames = [
55848
56616
  "redo",
55849
56617
  "moveBlockUp",
55850
56618
  "moveBlockDown",
56619
+ "indent",
56620
+ "outdent",
55851
56621
  ...tableCommands.map(tableShortcut)
55852
56622
  ];
55853
56623
  /** Checks a keymap and drops omitted entries, so a `null` binding keeps its default. */
@@ -55899,6 +56669,7 @@ function editorOptions(value) {
55899
56669
  "textInput",
55900
56670
  "labels",
55901
56671
  "keymap",
56672
+ "linkSchemes",
55902
56673
  "rendering",
55903
56674
  "slashMenu"
55904
56675
  ]) ?? {};
@@ -55908,6 +56679,7 @@ function editorOptions(value) {
55908
56679
  optionalBoolean(options.editable, "options.editable");
55909
56680
  if (!isOmitted(options.textInput)) textInputPreferences(options.textInput, "options.textInput");
55910
56681
  if (!isOmitted(options.keymap)) keymapBindings(options.keymap, "options.keymap");
56682
+ const linkSchemes = isOmitted(options.linkSchemes) ? void 0 : extraLinkSchemes(options.linkSchemes, "options.linkSchemes");
55911
56683
  const rendering = optionalRecord(options.rendering, "options.rendering", ["codeHighlighting", "diagramPreview"]);
55912
56684
  optionalBoolean(rendering?.codeHighlighting, "options.rendering.codeHighlighting");
55913
56685
  optionalBoolean(rendering?.diagramPreview, "options.rendering.diagramPreview");
@@ -55915,6 +56687,7 @@ function editorOptions(value) {
55915
56687
  return {
55916
56688
  ...options,
55917
56689
  slashMenu: slashMenuOptions(options.slashMenu),
56690
+ linkSchemes,
55918
56691
  labels: labels && Object.fromEntries(Object.entries(labels).filter(([, label]) => !isOmitted(label)).map(([key, label]) => [key, expectString(label, `options.labels.${key}`)]))
55919
56692
  };
55920
56693
  }
@@ -56111,6 +56884,18 @@ function blockAt(parent, $from) {
56111
56884
  return { type: "paragraph" };
56112
56885
  }
56113
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 }));
56114
56899
  var quoteBackspace = $prose(() => keymap$3({ Backspace: (state, dispatch) => {
56115
56900
  const { $from, empty } = state.selection;
56116
56901
  if (!empty || $from.parentOffset > 0 || $from.depth < 2) return false;
@@ -56120,7 +56905,7 @@ var quoteBackspace = $prose(() => keymap$3({ Backspace: (state, dispatch) => {
56120
56905
  const range = $from.blockRange($from, (node) => quotes.has(node.type.name));
56121
56906
  const target = range && liftTarget(range);
56122
56907
  if (!range || target == null) return false;
56123
- dispatch?.(state.tr.lift(range, target).scrollIntoView());
56908
+ dispatch?.(liftQuote(state, range, target).scrollIntoView());
56124
56909
  return true;
56125
56910
  } }));
56126
56911
  var calloutEnter = $prose(() => keymap$3({ Enter: (state, dispatch) => {
@@ -56162,7 +56947,6 @@ var liftItem = (state, dispatch) => {
56162
56947
  dispatch(lifted);
56163
56948
  return true;
56164
56949
  };
56165
- var listItemShiftTab = $prose(() => keymap$3({ "Shift-Tab": liftItem }));
56166
56950
  var rejoinLists = $prose(() => keymap$3({ Backspace: (state, dispatch) => {
56167
56951
  const { $from, empty } = state.selection;
56168
56952
  if (!empty || $from.parent.type.name !== "paragraph" || $from.parent.content.size > 0) return false;
@@ -56255,11 +57039,7 @@ var movedListItems = $prose(() => new Plugin({
56255
57039
  return tr.docChanged ? tr : null;
56256
57040
  }
56257
57041
  }));
56258
- var controlCharacters = /^[\u0000-\u0008\u000B\u000C\u000E-\u001F\u007F]+$/;
56259
- var textInputPolicy = $prose(() => new Plugin({
56260
- key: new PluginKey("inkkitTextInput"),
56261
- props: { handleTextInput: (view, from, to, text, deflt) => controlCharacters.test(text) || insertTextChunk(view, from, to, text, deflt) }
56262
- }));
57042
+ var textInputPolicy = $prose(() => textInputPlugin());
56263
57043
  function outermostListDepth($pos) {
56264
57044
  for (let depth = 1; depth <= $pos.depth; depth++) if (isList($pos.node(depth))) return depth;
56265
57045
  return null;
@@ -56384,6 +57164,7 @@ var InkKitEditor = class InkKitEditor {
56384
57164
  inputPreferences = {};
56385
57165
  literalKeys = () => false;
56386
57166
  configuredKeys = {};
57167
+ indentKeys = () => false;
56387
57168
  readOnlyKeys = () => false;
56388
57169
  suggestions;
56389
57170
  suggestionBase;
@@ -56423,6 +57204,8 @@ var InkKitEditor = class InkKitEditor {
56423
57204
  }));
56424
57205
  keys = () => false;
56425
57206
  events;
57207
+ /** The host's extra link schemes, in lower case. */
57208
+ linkSchemes;
56426
57209
  /** How many of each state event the host has been sent. */
56427
57210
  sentEvents = /* @__PURE__ */ new Map();
56428
57211
  constructor(events, options) {
@@ -56442,6 +57225,7 @@ var InkKitEditor = class InkKitEditor {
56442
57225
  else send();
56443
57226
  };
56444
57227
  } });
57228
+ this.linkSchemes = new Set(options.linkSchemes);
56445
57229
  this.writable = options.editable ?? true;
56446
57230
  this.inputPreferences = textInputPreferences(options.textInput ?? {}, "options.textInput");
56447
57231
  }
@@ -56502,7 +57286,7 @@ var InkKitEditor = class InkKitEditor {
56502
57286
  };
56503
57287
  }
56504
57288
  mediaPolicy() {
56505
- 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));
56506
57290
  }
56507
57291
  assertAlive() {
56508
57292
  if (this.destroyed) throw new InkKitError("destroyed", "The editor has been destroyed");
@@ -56753,6 +57537,7 @@ var InkKitEditor = class InkKitEditor {
56753
57537
  replace: mutable,
56754
57538
  replaceSource: mutable,
56755
57539
  insertImages: formatted && this.canInsertImages,
57540
+ appendImages: formatted && this.canInsertImages,
56756
57541
  insertPaths: mutable,
56757
57542
  insertFootnote: formatted,
56758
57543
  editReferenceDefinition: formatted && referenceDefinitions(state.doc).size > 0,
@@ -57212,7 +57997,7 @@ var InkKitEditor = class InkKitEditor {
57212
57997
  } catch (error) {
57213
57998
  throw coded(error, "preservation");
57214
57999
  }
57215
- 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));
57216
58001
  const metadata = referenceMetadata(this.editor.ctx, selectedDoc ?? doc, content);
57217
58002
  if (metadata != null) result.html = withReferenceMetadata(result.html, metadata);
57218
58003
  this.assertCaptured(captured);
@@ -57263,7 +58048,8 @@ var InkKitEditor = class InkKitEditor {
57263
58048
  if (view.state.selection.$from.parent.type.spec.code && !inHiddenComment) view.dispatch(view.state.tr.insertText(text.replace(/\r\n?/g, "\n")).scrollIntoView());
57264
58049
  else {
57265
58050
  const { schema } = view.state;
57266
- 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);
57267
58053
  const isHTML = !inHiddenComment && htmlBreakAt(view.state.selection.$from);
57268
58054
  const lines = text.replace(/\r\n?/g, "\n").split("\n");
57269
58055
  const children = literalTextNodes(schema, text, marks, isHTML);
@@ -57339,6 +58125,10 @@ var InkKitEditor = class InkKitEditor {
57339
58125
  return this.keys(view, event);
57340
58126
  } }
57341
58127
  }));
58128
+ indentPlugin = new Plugin({
58129
+ key: new PluginKey("listIndent"),
58130
+ props: { handleKeyDown: (view, event) => this.indentKeys(view, event) }
58131
+ });
57342
58132
  copySourceSelection(event) {
57343
58133
  event.preventDefault();
57344
58134
  event.stopPropagation();
@@ -57364,7 +58154,7 @@ var InkKitEditor = class InkKitEditor {
57364
58154
  }
57365
58155
  }).catch((error) => this.report(error));
57366
58156
  else {
57367
- const output = clipboardContent(parsed.content, parsed.type.schema);
58157
+ const output = clipboardContent(parsed.content, parsed.type.schema, this.linkSchemes);
57368
58158
  if (!event.clipboardData) return;
57369
58159
  this.rememberCopy(output, selected.markdown, parsed.content, selected.markdown !== selected.shareable, true);
57370
58160
  event.clipboardData.setData("text/plain", output.text);
@@ -57430,6 +58220,7 @@ var InkKitEditor = class InkKitEditor {
57430
58220
  ctx.set(rootCtx, root);
57431
58221
  ctx.set(authoredSourcesCtx.key, (doc, positions) => instance.authoredSources(doc, positions));
57432
58222
  ctx.set(smartPunctuationCtx.key, () => instance.inputPreferences.smartPunctuation === true);
58223
+ ctx.set(linkSchemesCtx.key, instance.linkSchemes);
57433
58224
  ctx.update(labelsCtx.key, () => ({
57434
58225
  ...defaultLabels,
57435
58226
  ...options.labels
@@ -57444,9 +58235,16 @@ var InkKitEditor = class InkKitEditor {
57444
58235
  }), false));
57445
58236
  ctx.set(defaultValueCtx, "");
57446
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
+ });
57447
58245
  ctx.update(listItemKeymap.key, (keys) => ({
57448
58246
  ...keys,
57449
- SinkListItem: { shortcuts: "Tab" },
58247
+ SinkListItem: { shortcuts: [] },
57450
58248
  LiftListItem: { shortcuts: [] }
57451
58249
  }));
57452
58250
  const unbind = (keymap, keep = []) => ctx.update(keymap.key, (keys) => {
@@ -57510,7 +58308,7 @@ var InkKitEditor = class InkKitEditor {
57510
58308
  return true;
57511
58309
  }
57512
58310
  }));
57513
- }).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-AI86evan.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, {
57514
58312
  capture: () => instance.adapterCapture(),
57515
58313
  active: () => instance.ready && !instance.destroyed && !instance.literalSurface,
57516
58314
  labels: {
@@ -57518,8 +58316,7 @@ var InkKitEditor = class InkKitEditor {
57518
58316
  ...options.labels
57519
58317
  },
57520
58318
  error: (error) => instance.report(error)
57521
- }) : []).use(options.images || options.files ? imageView(options.images, options.files ? {
57522
- adapter: options.files,
58319
+ }) : []).use(options.images || options.files ? imageView(options.images, {
57523
58320
  capture: () => instance.adapterCapture(),
57524
58321
  active: () => instance.ready && !instance.destroyed && !instance.literalSurface,
57525
58322
  labels: {
@@ -57527,7 +58324,7 @@ var InkKitEditor = class InkKitEditor {
57527
58324
  ...options.labels
57528
58325
  },
57529
58326
  error: (error) => instance.report(error)
57530
- } : void 0, () => {
58327
+ }, options.files, () => {
57531
58328
  try {
57532
58329
  instance.assertMutation();
57533
58330
  return true;
@@ -57661,7 +58458,7 @@ var InkKitEditor = class InkKitEditor {
57661
58458
  const content = expanded;
57662
58459
  const valid = fragment.firstChild?.isInline ? Fragment.from(view.state.schema.nodes.paragraph.create(null, fragment)) : fragment;
57663
58460
  const markdown = selectionMarkdown(instance.editor.ctx, view.state.doc, valid);
57664
- const output = clipboardContent(content, view.state.schema);
58461
+ const output = clipboardContent(content, view.state.schema, instance.linkSchemes);
57665
58462
  const metadata = referenceMetadata(instance.editor.ctx, view.state.doc, valid);
57666
58463
  if (metadata != null) output.html = withReferenceMetadata(output.html, metadata);
57667
58464
  instance.rememberCopy(output, markdown, fragment, false, true);
@@ -58314,10 +59111,23 @@ var InkKitEditor = class InkKitEditor {
58314
59111
  this.assertReady(expectedGeneration);
58315
59112
  this.releaseFocus();
58316
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
+ }
58317
59126
  /** Replaces every binding. Keys are in ProseMirror's notation. A key given to two shortcuts goes
58318
59127
  * to the later one, in the order history, formatting, block moves, tables, so it lands the same
58319
59128
  * way every time. A table binding passes a key it shares with a block move to the move outside
58320
- * a table. */
59129
+ * a table. List indentation keys apply after all of these, and after the table keys the editor
59130
+ * keeps for itself. */
58321
59131
  setKeymap(keymap) {
58322
59132
  this.assertAlive();
58323
59133
  keymap = keymapBindings(keymap, "keymap");
@@ -58327,6 +59137,8 @@ var InkKitEditor = class InkKitEditor {
58327
59137
  moveBlockUp: ["Alt-ArrowUp"],
58328
59138
  moveBlockDown: ["Alt-ArrowDown"],
58329
59139
  tableExit: ["Mod-Enter", "Enter"],
59140
+ indent: ["Tab"],
59141
+ outdent: ["Shift-Tab"],
58330
59142
  ...keymap
58331
59143
  };
58332
59144
  const bindings = {};
@@ -58353,6 +59165,9 @@ var InkKitEditor = class InkKitEditor {
58353
59165
  if (move === "move" && !this.composing && !this.pasteController.pending) this.forPerson(() => this.moveBlock(direction), false);
58354
59166
  return true;
58355
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;
58356
59171
  for (const command of tableCommands) for (const key of this.configuredKeys[tableShortcut(command)] ?? []) bindings[key] = (state, dispatch, view) => {
58357
59172
  if (!this.ready || !this.commandState().commands.table[command]) return moves[key]?.(state, dispatch, view) ?? false;
58358
59173
  return this.forPerson(() => this.table(command), true);
@@ -58361,6 +59176,7 @@ var InkKitEditor = class InkKitEditor {
58361
59176
  throw new InkKitError("read-only", "The editor is read-only");
58362
59177
  }])));
58363
59178
  this.keys = keydownHandler(bindings);
59179
+ this.indentKeys = keydownHandler(indents);
58364
59180
  this.literalKeys = keydownHandler(historyBindings);
58365
59181
  }
58366
59182
  /** Dropped files land as one paragraph per path at the drop point: in place of an empty block, after
@@ -58388,17 +59204,12 @@ var InkKitEditor = class InkKitEditor {
58388
59204
  this.assertMutation(expectedGeneration);
58389
59205
  if (!images.length || !this.canInsertImages || this.literalSurface) return false;
58390
59206
  const view = this.editor.ctx.get(editorViewCtx);
58391
- const { schema } = view.state;
58392
59207
  const placement = point ? void 0 : imagePlacement(view.state);
58393
59208
  if (placement?.kind === "inline") {
58394
59209
  this.insertInline(images);
58395
59210
  return true;
58396
59211
  }
58397
- const blocks = images.map((image) => schema.nodes.paragraph.create(null, schema.nodes.image.create({
58398
- src: image.reference,
58399
- alt: image.alt,
58400
- title: image.title ?? ""
58401
- })));
59212
+ const blocks = this.imageBlocks(images);
58402
59213
  if (point) {
58403
59214
  this.insertBlocks(blocks, point);
58404
59215
  return true;
@@ -58414,6 +59225,31 @@ var InkKitEditor = class InkKitEditor {
58414
59225
  view.dispatch(tr.scrollIntoView());
58415
59226
  return true;
58416
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
+ }
58417
59253
  insertInline(images) {
58418
59254
  const view = this.editor.ctx.get(editorViewCtx);
58419
59255
  const { state } = view;
@@ -58531,7 +59367,7 @@ var InkKitEditor = class InkKitEditor {
58531
59367
  if (!range) return;
58532
59368
  const target = liftTarget(range);
58533
59369
  if (target == null) return;
58534
- view.dispatch(view.state.tr.lift(range, target));
59370
+ view.dispatch(liftQuote(view.state, range, target));
58535
59371
  }
58536
59372
  /** Changes the selected textblocks' type, keeping everything they hold. */
58537
59373
  textblockType(type, attr) {
@@ -58616,25 +59452,27 @@ var InkKitEditor = class InkKitEditor {
58616
59452
  commit(command);
58617
59453
  return;
58618
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;
58619
59468
  try {
58620
- const result = command.action(this.context());
58621
- const insert = (markdown) => {
58622
- if (this.destroyed) return;
58623
- if (markdown === void 0) return;
58624
- if (typeof markdown !== "string") {
58625
- this.report(invalidArgument("A slash command must return Markdown text"));
58626
- return;
58627
- }
58628
- commit({ markdown });
58629
- };
58630
- if (result instanceof Promise) result.then(insert).catch((error) => {
58631
- if (this.destroyed) return;
58632
- this.report(error);
58633
- });
58634
- else insert(result);
59469
+ result = command.action(this.context());
58635
59470
  } catch (error) {
58636
- this.report(error);
59471
+ onHostError(error);
59472
+ return;
58637
59473
  }
59474
+ if (result instanceof Promise) result.then(insert, onHostError);
59475
+ else insert(result);
58638
59476
  }
58639
59477
  /** Returns whether the document or the marks the next typed text takes changed. */
58640
59478
  format(command, options, expectedGeneration) {
@@ -58695,12 +59533,13 @@ var InkKitEditor = class InkKitEditor {
58695
59533
  return !after.doc.eq(before.doc) || !Mark.sameSet(marks(after), marks(before));
58696
59534
  }
58697
59535
  /**
58698
- * Refuses a link destination that is empty, that opens anything but a web, mail or phone address,
58699
- * 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.
58700
59539
  */
58701
59540
  checkDestination(destination, name) {
58702
59541
  if (destination.trim() === "") throw invalidArgument(`${name} must not be empty`);
58703
- 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`);
58704
59543
  const { schema } = this.editor.ctx.get(editorViewCtx).state;
58705
59544
  const written = serialize(this.editor.ctx, schema.node("doc", null, [schema.node("paragraph", null, [schema.text("x", [schema.marks.link.create({ href: destination })])])]));
58706
59545
  let read;
@@ -58722,13 +59561,18 @@ var InkKitEditor = class InkKitEditor {
58722
59561
  const { selection, storedMarks } = state;
58723
59562
  const { from, to, empty } = selection;
58724
59563
  const stored = empty ? type.isInSet(storedMarks ?? []) : void 0;
58725
- 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)) {
58726
59566
  const tr = state.tr.removeStoredMark(type);
58727
59567
  if (destination !== void 0) tr.addStoredMark(type.create({ href: destination }));
58728
59568
  view.dispatch(tr);
58729
59569
  return;
58730
59570
  }
58731
- 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 ?? []);
58732
59576
  const extent = mark && markRun(state.doc, mark, from, to);
58733
59577
  const keepStored = (tr, link) => {
58734
59578
  if (!storedMarks) return tr;
@@ -58875,12 +59719,14 @@ var InkKitEditor = class InkKitEditor {
58875
59719
  "keyDown",
58876
59720
  "insertPaths",
58877
59721
  "insertImages",
59722
+ "appendImages",
58878
59723
  "insertFootnote",
58879
59724
  "navigateFootnote",
58880
59725
  "editReferenceDefinition",
58881
59726
  "setCommentsVisible",
58882
59727
  "focus",
58883
59728
  "blur",
59729
+ "refreshReferences",
58884
59730
  "setKeymap",
58885
59731
  "textSnapshot",
58886
59732
  "selectTextRange",