@aicayzer/inkkit 0.1.3 → 0.1.5

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.
@@ -28357,6 +28357,10 @@ var defaultLabels = {
28357
28357
  sourceEditor: "Markdown source editor",
28358
28358
  placeholder: "Start typing…",
28359
28359
  copyCode: "Copy",
28360
+ codeLanguage: "Code language",
28361
+ codeLanguageSearch: "Search languages",
28362
+ codeAuto: "Auto",
28363
+ codePlainText: "Plain text",
28360
28364
  expandCallout: "Expand",
28361
28365
  collapseCallout: "Collapse",
28362
28366
  diagramRendering: "Rendering diagram…",
@@ -35086,6 +35090,194 @@ var references = [
35086
35090
  }))
35087
35091
  ].flat();
35088
35092
  //#endregion
35093
+ //#region src/smart-punctuation.ts
35094
+ /**
35095
+ * Smart punctuation: the quote or dash being typed becomes a curly quote or a dash.
35096
+ *
35097
+ * - `"` and `'` open after the start of a block, a line break, whitespace, an opening bracket, a
35098
+ * dash or another opening quote, and close everywhere else. A `'` between letters is an
35099
+ * apostrophe, which is the closing form.
35100
+ * - `--` gives an en dash and `---` an em dash: the third hyphen turns the en dash that the second
35101
+ * one made into an em dash. Only a hyphen that text input has just inserted counts as the first
35102
+ * of the two, so a hyphen already in the note, or typed before the caret moved, stays one.
35103
+ * - A quote that a dead key made arrives as a composition. When the composition commits exactly
35104
+ * that quote at the caret, it follows the same rules.
35105
+ *
35106
+ * These are input rules, so only the character being typed changes, Backspace and Undo straight
35107
+ * after restore what was typed, and nothing in a note is rewritten. The editor answers
35108
+ * `smartPunctuationCtx` with the current option, so turning it on or off needs no new editor.
35109
+ */
35110
+ var smartPunctuationCtx = $ctx(() => false, "inkkitSmartPunctuation");
35111
+ /** Set on the transaction of each rule that this file makes. */
35112
+ var smartRule = "inkkitSmartPunctuation";
35113
+ var enDash = "–";
35114
+ var emDash = "—";
35115
+ /** What may come before a quote for it to open. */
35116
+ var opensAfter = /[\s([{<\u2013\u2014\u2018\u201C]/u;
35117
+ /**
35118
+ * Markdown that a person is typing out in a line of text, not words: a code span, a link
35119
+ * destination, a web address, a wiki link or footnote label, an HTML tag or comment and a
35120
+ * reference definition. Smart punctuation leaves a line alone while one is open, because the
35121
+ * curly character would change what the Markdown means.
35122
+ */
35123
+ var typingSyntax = [
35124
+ (before) => (before.match(/`+/g)?.length ?? 0) % 2 === 1,
35125
+ (before) => /\]\([^)]*$/.test(before),
35126
+ (before) => /\[\[[^\]]*$/.test(before),
35127
+ (before) => /\[\^[^\]]*$/.test(before),
35128
+ (before) => /(?:^|[^\w])(?:[a-z][a-z0-9+.-]*:\/\/|www\.|mailto:)\S*$/i.test(before),
35129
+ (before) => /(?:^|[^\w])<[A-Za-z!/?][^>]*$/.test(before),
35130
+ (before) => /^\s*\[[^\]]+\]:/.test(before)
35131
+ ];
35132
+ /**
35133
+ * The text of the block before `pos`, with each code span and each non-text node as one
35134
+ * placeholder, so that a backtick or `<` inside code is not syntax being typed.
35135
+ */
35136
+ function textBefore(state, pos) {
35137
+ const $pos = state.doc.resolve(pos);
35138
+ let text = "";
35139
+ $pos.parent.forEach((child, offset) => {
35140
+ if (offset >= $pos.parentOffset) return;
35141
+ text += child.isText && !child.marks.some((mark) => mark.type.spec.code) ? child.text.slice(0, $pos.parentOffset - offset) : "";
35142
+ });
35143
+ return text;
35144
+ }
35145
+ var input = new PluginKey("inkkitSmartPunctuationInput");
35146
+ /** The text a transaction inserted, when it is one step that inserts text at `at` and nothing else. */
35147
+ function insertedAt(tr, at) {
35148
+ const [step] = tr.steps;
35149
+ if (tr.steps.length !== 1 || !(step instanceof ReplaceStep)) return void 0;
35150
+ if (step.from !== at || step.slice.openStart || step.slice.openEnd) return void 0;
35151
+ return step.slice.content.textBetween(0, step.slice.content.size);
35152
+ }
35153
+ var inputTracking = $prose(() => new Plugin({
35154
+ key: input,
35155
+ state: {
35156
+ init: () => ({
35157
+ pending: null,
35158
+ hyphen: null,
35159
+ composition: null,
35160
+ composedQuote: null
35161
+ }),
35162
+ apply(tr, value) {
35163
+ const { pending, composition } = value;
35164
+ value.pending = value.hyphen = value.composedQuote = null;
35165
+ if (tr.getMeta(hyphenReceived)) value.hyphen = tr.selection.from;
35166
+ if (!tr.docChanged) return value;
35167
+ if (pending !== null && insertedAt(tr, pending) === "-") value.hyphen = pending + 1;
35168
+ if (composition) {
35169
+ value.composition = null;
35170
+ const text = insertedAt(tr, composition.pos);
35171
+ if (tr.getMeta("composition") !== void 0 && (text === "\"" || text === "'") && tr.doc.content.size === composition.size + 1) value.composedQuote = composition.pos + 1;
35172
+ }
35173
+ return value;
35174
+ }
35175
+ },
35176
+ props: {
35177
+ handleTextInput(view, from, _to, text) {
35178
+ input.getState(view.state).pending = text === "-" ? from : null;
35179
+ return false;
35180
+ },
35181
+ handleDOMEvents: { compositionstart(view) {
35182
+ const { from, empty } = view.state.selection;
35183
+ const tracked = input.getState(view.state);
35184
+ tracked.composedQuote = null;
35185
+ tracked.composition = empty ? {
35186
+ pos: from,
35187
+ size: view.state.doc.content.size
35188
+ } : null;
35189
+ return false;
35190
+ } }
35191
+ }
35192
+ }));
35193
+ /** Set on a transaction that inserted text ending in a hyphen, whatever else came with it. */
35194
+ var hyphenReceived = "inkkitHyphenReceived";
35195
+ /** Marks a transaction that inserted text ending in a hyphen that no rule changed. */
35196
+ function insertedHyphen(tr) {
35197
+ return tr.setMeta(hyphenReceived, true);
35198
+ }
35199
+ /**
35200
+ * The state that input rules see for text that arrived in one piece, which holds only what the
35201
+ * rules need: it follows the document, with the tracking plugin when the editor has one. When
35202
+ * `rest` ends in a hyphen, the last character follows a hyphen that came in the same piece.
35203
+ */
35204
+ function chunkState(state, config, rest) {
35205
+ const tracker = input.get(state);
35206
+ const prepared = EditorState.create({
35207
+ ...config,
35208
+ plugins: tracker ? [tracker] : []
35209
+ });
35210
+ return tracker && rest.endsWith("-") ? prepared.apply(insertedHyphen(prepared.tr)) : prepared;
35211
+ }
35212
+ /**
35213
+ * Whether a rule may change what `match` found at `pos`: the option is on, the last character of
35214
+ * the match is the one being typed, which is not in the document yet, and the text is ordinary,
35215
+ * with no Markdown being typed. The input rules plugin also runs every rule when a composition
35216
+ * ends, over text that is all in the document; that is not typing, and is left alone.
35217
+ */
35218
+ function applies(on, state, match, pos) {
35219
+ if (!on()) return false;
35220
+ const typed = pos + match[0].length - 1 === state.selection.from;
35221
+ const composed = !typed && match[0].length === 1 && input.getState(state)?.composedQuote === state.selection.from && pos === state.selection.from - 1;
35222
+ if (!typed && !composed) return false;
35223
+ const $pos = state.doc.resolve(pos);
35224
+ if ($pos.parent.type.spec.code || !$pos.parent.inlineContent) return false;
35225
+ if ((state.storedMarks ?? $pos.marks()).some((mark) => mark.type.spec.code)) return false;
35226
+ const before = textBefore(state, pos);
35227
+ return !typingSyntax.some((open) => open(before));
35228
+ }
35229
+ function opensHere(state, pos) {
35230
+ const node = state.doc.resolve(pos).nodeBefore;
35231
+ if (!node) return true;
35232
+ if (node.isText) {
35233
+ const text = node.text ?? "";
35234
+ return opensAfter.test(String.fromCodePoint(text.codePointAt(text.length - 1)));
35235
+ }
35236
+ return node.type.name === "hardbreak";
35237
+ }
35238
+ /** The input rule that ran at the last change, when nothing has happened since. */
35239
+ function lastRule(state) {
35240
+ for (const plugin of state.plugins) if (plugin.spec.isInputRules) {
35241
+ const stored = plugin.getState(state);
35242
+ if (stored) return stored;
35243
+ }
35244
+ }
35245
+ var quotes$1 = {
35246
+ "\"": ["“", "”"],
35247
+ "'": ["‘", "’"]
35248
+ };
35249
+ var smartPunctuation = [
35250
+ smartPunctuationCtx,
35251
+ inputTracking,
35252
+ $inputRule((ctx) => new InputRule(/["']$/, (state, match, start, end) => {
35253
+ if (!applies(ctx.get(smartPunctuationCtx.key), state, match, start)) return null;
35254
+ const [opening, closing] = quotes$1[match[0]];
35255
+ return state.tr.insertText(opensHere(state, start) ? opening : closing, start, end).setMeta(smartRule, "quote");
35256
+ }, { inCodeMark: false })),
35257
+ $inputRule((ctx) => new InputRule(/--$/, (state, match, start, end) => {
35258
+ if (!applies(ctx.get(smartPunctuationCtx.key), state, match, start)) return null;
35259
+ if (input.getState(state)?.hyphen !== state.selection.from) return null;
35260
+ const before = textBefore(state, start);
35261
+ if (!before.trim() || before.endsWith("-") || /^\s*\|/.test(before)) return null;
35262
+ return state.tr.insertText(enDash, start, end).setMeta(smartRule, "en");
35263
+ }, { inCodeMark: false })),
35264
+ $inputRule((ctx) => new InputRule(/\u2013-$/, (state, match, start, end) => {
35265
+ if (!applies(ctx.get(smartPunctuationCtx.key), state, match, start)) return null;
35266
+ if (lastRule(state)?.transform.getMeta(smartRule) !== "en") return null;
35267
+ return state.tr.insertText(emDash, start, end).setMeta(smartRule, "em");
35268
+ }, { inCodeMark: false }))
35269
+ ].flat();
35270
+ /**
35271
+ * Undo straight after a smart quote or dash puts back the character that was typed, as Backspace
35272
+ * does for any input rule. The replacement joins the typing's history step, so the next Undo
35273
+ * removes the typing and not the curly character. Returns whether it applied.
35274
+ */
35275
+ var undoSmartPunctuation = (state, dispatch) => {
35276
+ const rule = lastRule(state);
35277
+ if (!rule?.transform.getMeta(smartRule)) return false;
35278
+ return undoInputRule(state, dispatch && ((tr) => dispatch(tr.setMeta("appendedTransaction", rule.transform))));
35279
+ };
35280
+ //#endregion
35089
35281
  //#region src/linked-syntax.ts
35090
35282
  var fromTable = (raw) => raw.replace(/(\\*)\|/g, (_match, escapes) => `${"\\".repeat(Math.floor(escapes.length / 2))}|`);
35091
35283
  var toTable = (raw) => raw.replace(/(\\*)\|/g, (_match, escapes) => `${"\\".repeat(escapes.length * 2 + 1)}|`);
@@ -35277,6 +35469,76 @@ var wikiLinkSchema = $node("inkkit_wiki_link", () => ({
35277
35469
  }
35278
35470
  }
35279
35471
  }));
35472
+ var composed = new PluginKey("inkkitWikiLinkComposition");
35473
+ /**
35474
+ * Lets the rule below tell a `]` that a composition committed from text that was already in the
35475
+ * note: ProseMirror runs every input rule when a composition ends, with nothing typed.
35476
+ */
35477
+ var wikiLinkComposition = $prose(() => new Plugin({
35478
+ key: composed,
35479
+ state: {
35480
+ init: () => ({
35481
+ composition: null,
35482
+ closing: null
35483
+ }),
35484
+ apply(tr, value) {
35485
+ const { composition } = value;
35486
+ value.closing = null;
35487
+ if (!tr.docChanged || !composition) return value;
35488
+ if (tr.getMeta("composition") === void 0) value.composition = null;
35489
+ else if (insertedAt(tr, composition.pos) === "]" && tr.doc.content.size === composition.size + 1) value.closing = composition.pos + 1;
35490
+ return value;
35491
+ }
35492
+ },
35493
+ props: { handleDOMEvents: { compositionstart(view) {
35494
+ const { from, empty } = view.state.selection;
35495
+ const tracked = composed.getState(view.state);
35496
+ tracked.closing = null;
35497
+ tracked.composition = empty ? {
35498
+ pos: from,
35499
+ size: view.state.doc.content.size
35500
+ } : null;
35501
+ return false;
35502
+ } } }
35503
+ }));
35504
+ /**
35505
+ * Turns `[[target]]`, as the person types its last bracket, into the wiki link
35506
+ * it reads as when the note is opened, so the note keeps the characters that
35507
+ * were typed. Left as text, the brackets save as `\[\[target]]`, which no
35508
+ * other tool reads as a link. The last bracket has to be the one just typed,
35509
+ * or committed by a composition: text already in the note, such as escaped
35510
+ * brackets, is never changed, and that includes the run of every rule when a
35511
+ * composition ends.
35512
+ */
35513
+ var wikiLinkInputRule = $inputRule(() => new InputRule(/\[\[(?:\\.|[^[\]\\\ufffc])*\]\]$/, (state, match, start, end) => {
35514
+ const { schema, doc } = state;
35515
+ const type = schema.nodes.inkkit_wiki_link;
35516
+ const caret = state.selection.from;
35517
+ if (start + match[0].length - 1 !== caret && !(start + match[0].length === caret && composed.getState(state)?.closing === caret)) return null;
35518
+ const $start = doc.resolve(start);
35519
+ const parent = $start.parent;
35520
+ if (!type || !parent.inlineContent || !parent.canReplaceWith($start.index(), $start.index(), type) || /[!\\[]/.test(doc.textBetween(Math.max($start.start(), start - 1), start))) return null;
35521
+ let marks;
35522
+ let plain = true;
35523
+ doc.nodesBetween(start, end, (node) => {
35524
+ if (!node.isInline) return;
35525
+ if (!node.isText) plain = false;
35526
+ else if (marks && !Mark.sameSet(marks, node.marks)) plain = false;
35527
+ else marks ??= node.marks;
35528
+ });
35529
+ if (!plain || !marks || marks.some((mark) => ["inlineCode", "link"].includes(mark.type.name)) || state.storedMarks && !Mark.sameSet(state.storedMarks, marks)) return null;
35530
+ let inTable = false;
35531
+ for (let depth = $start.depth; depth > 0; depth--) if (["table_cell", "table_header"].includes($start.node(depth).type.name)) inTable = true;
35532
+ const parsed = parseLinkedSyntax(inTable ? toTable(match[0]) : match[0], inTable);
35533
+ if (!parsed || parsed.embed) return null;
35534
+ return state.tr.replaceWith(start, end, type.create({
35535
+ raw: parsed.raw,
35536
+ target: parsed.target,
35537
+ fragment: parsed.fragment,
35538
+ label: parsed.label,
35539
+ inTable: parsed.inTable
35540
+ }, void 0, marks));
35541
+ }, { inCodeMark: false }));
35280
35542
  function linkedSyntax(wikiLinks, files) {
35281
35543
  const syntax = { text: {} };
35282
35544
  if (wikiLinks) syntax.text[91] = {
@@ -44348,278 +44610,90 @@ var dropIndicatorDOMPlugin = $prose((ctx) => new Plugin({
44348
44610
  }));
44349
44611
  withMeta(dropIndicatorDOMPlugin, { displayName: "Prose<dropIndicatorDOM>" });
44350
44612
  function renderIndicator(dom, state, config) {
44351
- if (!state) {
44352
- Object.assign(dom.style, { display: "none" });
44353
- return;
44354
- }
44355
- const { line } = state;
44356
- const { width: lineWidth } = config;
44357
- const { p1: { x: x1, y: y1 }, p2: { x: x2, y: y2 } } = line;
44358
- const horizontal = y1 === y2;
44359
- let width;
44360
- let height;
44361
- let top = y1;
44362
- let left = x1;
44363
- if (horizontal) {
44364
- width = x2 - x1;
44365
- height = lineWidth;
44366
- top -= lineWidth / 2;
44367
- } else {
44368
- width = lineWidth;
44369
- height = y2 - y1;
44370
- left -= lineWidth / 2;
44371
- }
44372
- top = Math.round(top);
44373
- left = Math.round(left);
44374
- Object.assign(dom.style, {
44375
- display: "block",
44376
- width: `${width}px`,
44377
- height: `${height}px`,
44378
- transform: `translate(${left}px, ${top}px)`
44379
- });
44380
- }
44381
- var dropIndicatorPlugin = $prose((ctx) => {
44382
- const onShow = (options) => {
44383
- ctx.set(dropIndicatorState.key, options);
44384
- };
44385
- const onHide = () => {
44386
- ctx.set(dropIndicatorState.key, null);
44387
- };
44388
- return createDropIndicatorPlugin({
44389
- onShow,
44390
- onHide,
44391
- onDrag: () => true
44392
- });
44393
- });
44394
- withMeta(dropIndicatorPlugin, { displayName: "Prose<dropIndicator>" });
44395
- var gapCursorPlugin = $prose(() => gapCursor());
44396
- withMeta(gapCursorPlugin, { displayName: "Prose<gapCursor>" });
44397
- [
44398
- gapCursorPlugin,
44399
- dropIndicatorConfig,
44400
- dropIndicatorState,
44401
- dropIndicatorDOMPlugin,
44402
- dropIndicatorPlugin
44403
- ].flat();
44404
- var cursor = [
44405
- gapCursorPlugin,
44406
- dropIndicatorConfig,
44407
- dropIndicatorState,
44408
- dropIndicatorDOMPlugin,
44409
- $prose((ctx) => {
44410
- let live = false;
44411
- const plugin = createDropIndicatorPlugin({
44412
- onShow: (options) => ctx.set(dropIndicatorState.key, options),
44413
- onHide: () => {
44414
- if (live) ctx.set(dropIndicatorState.key, null);
44415
- },
44416
- onDrag: () => true
44417
- });
44418
- const view = plugin.spec.view;
44419
- return new Plugin({
44420
- ...plugin.spec,
44421
- view(editorView) {
44422
- live = true;
44423
- const inner = view(editorView);
44424
- return {
44425
- ...inner,
44426
- destroy() {
44427
- live = false;
44428
- inner.destroy?.();
44429
- }
44430
- };
44431
- }
44432
- });
44433
- })
44434
- ].flat();
44435
- //#endregion
44436
- //#region src/smart-punctuation.ts
44437
- /**
44438
- * Smart punctuation: the quote or dash being typed becomes a curly quote or a dash.
44439
- *
44440
- * - `"` and `'` open after the start of a block, a line break, whitespace, an opening bracket, a
44441
- * dash or another opening quote, and close everywhere else. A `'` between letters is an
44442
- * apostrophe, which is the closing form.
44443
- * - `--` gives an en dash and `---` an em dash: the third hyphen turns the en dash that the second
44444
- * one made into an em dash. Only a hyphen that text input has just inserted counts as the first
44445
- * of the two, so a hyphen already in the note, or typed before the caret moved, stays one.
44446
- * - A quote that a dead key made arrives as a composition. When the composition commits exactly
44447
- * that quote at the caret, it follows the same rules.
44448
- *
44449
- * These are input rules, so only the character being typed changes, Backspace and Undo straight
44450
- * after restore what was typed, and nothing in a note is rewritten. The editor answers
44451
- * `smartPunctuationCtx` with the current option, so turning it on or off needs no new editor.
44452
- */
44453
- var smartPunctuationCtx = $ctx(() => false, "inkkitSmartPunctuation");
44454
- /** Set on the transaction of each rule that this file makes. */
44455
- var smartRule = "inkkitSmartPunctuation";
44456
- var enDash = "–";
44457
- var emDash = "—";
44458
- /** What may come before a quote for it to open. */
44459
- var opensAfter = /[\s([{<\u2013\u2014\u2018\u201C]/u;
44460
- /**
44461
- * Markdown that a person is typing out in a line of text, not words: a code span, a link
44462
- * destination, a web address, a wiki link or footnote label, an HTML tag or comment and a
44463
- * reference definition. Smart punctuation leaves a line alone while one is open, because the
44464
- * curly character would change what the Markdown means.
44465
- */
44466
- var typingSyntax = [
44467
- (before) => (before.match(/`+/g)?.length ?? 0) % 2 === 1,
44468
- (before) => /\]\([^)]*$/.test(before),
44469
- (before) => /\[\[[^\]]*$/.test(before),
44470
- (before) => /\[\^[^\]]*$/.test(before),
44471
- (before) => /(?:^|[^\w])(?:[a-z][a-z0-9+.-]*:\/\/|www\.|mailto:)\S*$/i.test(before),
44472
- (before) => /(?:^|[^\w])<[A-Za-z!/?][^>]*$/.test(before),
44473
- (before) => /^\s*\[[^\]]+\]:/.test(before)
44474
- ];
44475
- /**
44476
- * The text of the block before `pos`, with each code span and each non-text node as one
44477
- * placeholder, so that a backtick or `<` inside code is not syntax being typed.
44478
- */
44479
- function textBefore(state, pos) {
44480
- const $pos = state.doc.resolve(pos);
44481
- let text = "";
44482
- $pos.parent.forEach((child, offset) => {
44483
- if (offset >= $pos.parentOffset) return;
44484
- text += child.isText && !child.marks.some((mark) => mark.type.spec.code) ? child.text.slice(0, $pos.parentOffset - offset) : "";
44485
- });
44486
- return text;
44487
- }
44488
- var input = new PluginKey("inkkitSmartPunctuationInput");
44489
- /** The text a transaction inserted, when it is one step that inserts text at `at` and nothing else. */
44490
- function insertedAt(tr, at) {
44491
- const [step] = tr.steps;
44492
- if (tr.steps.length !== 1 || !(step instanceof ReplaceStep)) return void 0;
44493
- if (step.from !== at || step.slice.openStart || step.slice.openEnd) return void 0;
44494
- return step.slice.content.textBetween(0, step.slice.content.size);
44495
- }
44496
- var inputTracking = $prose(() => new Plugin({
44497
- key: input,
44498
- state: {
44499
- init: () => ({
44500
- pending: null,
44501
- hyphen: null,
44502
- composition: null,
44503
- composedQuote: null
44504
- }),
44505
- apply(tr, value) {
44506
- const { pending, composition } = value;
44507
- value.pending = value.hyphen = value.composedQuote = null;
44508
- if (tr.getMeta(hyphenReceived)) value.hyphen = tr.selection.from;
44509
- if (!tr.docChanged) return value;
44510
- if (pending !== null && insertedAt(tr, pending) === "-") value.hyphen = pending + 1;
44511
- if (composition) {
44512
- value.composition = null;
44513
- const text = insertedAt(tr, composition.pos);
44514
- if (tr.getMeta("composition") !== void 0 && (text === "\"" || text === "'") && tr.doc.content.size === composition.size + 1) value.composedQuote = composition.pos + 1;
44515
- }
44516
- return value;
44517
- }
44518
- },
44519
- props: {
44520
- handleTextInput(view, from, _to, text) {
44521
- input.getState(view.state).pending = text === "-" ? from : null;
44522
- return false;
44523
- },
44524
- handleDOMEvents: { compositionstart(view) {
44525
- const { from, empty } = view.state.selection;
44526
- const tracked = input.getState(view.state);
44527
- tracked.composedQuote = null;
44528
- tracked.composition = empty ? {
44529
- pos: from,
44530
- size: view.state.doc.content.size
44531
- } : null;
44532
- return false;
44533
- } }
44534
- }
44535
- }));
44536
- /** Set on a transaction that inserted text ending in a hyphen, whatever else came with it. */
44537
- var hyphenReceived = "inkkitHyphenReceived";
44538
- /** Marks a transaction that inserted text ending in a hyphen that no rule changed. */
44539
- function insertedHyphen(tr) {
44540
- return tr.setMeta(hyphenReceived, true);
44541
- }
44542
- /**
44543
- * The state that input rules see for text that arrived in one piece, which holds only what the
44544
- * rules need: it follows the document, with the tracking plugin when the editor has one. When
44545
- * `rest` ends in a hyphen, the last character follows a hyphen that came in the same piece.
44546
- */
44547
- function chunkState(state, config, rest) {
44548
- const tracker = input.get(state);
44549
- const prepared = EditorState.create({
44550
- ...config,
44551
- plugins: tracker ? [tracker] : []
44552
- });
44553
- return tracker && rest.endsWith("-") ? prepared.apply(insertedHyphen(prepared.tr)) : prepared;
44554
- }
44555
- /**
44556
- * Whether a rule may change what `match` found at `pos`: the option is on, the last character of
44557
- * the match is the one being typed, which is not in the document yet, and the text is ordinary,
44558
- * with no Markdown being typed. The input rules plugin also runs every rule when a composition
44559
- * ends, over text that is all in the document; that is not typing, and is left alone.
44560
- */
44561
- function applies(on, state, match, pos) {
44562
- if (!on()) return false;
44563
- const typed = pos + match[0].length - 1 === state.selection.from;
44564
- const composed = !typed && match[0].length === 1 && input.getState(state)?.composedQuote === state.selection.from && pos === state.selection.from - 1;
44565
- if (!typed && !composed) return false;
44566
- const $pos = state.doc.resolve(pos);
44567
- if ($pos.parent.type.spec.code || !$pos.parent.inlineContent) return false;
44568
- if ((state.storedMarks ?? $pos.marks()).some((mark) => mark.type.spec.code)) return false;
44569
- const before = textBefore(state, pos);
44570
- return !typingSyntax.some((open) => open(before));
44571
- }
44572
- function opensHere(state, pos) {
44573
- const node = state.doc.resolve(pos).nodeBefore;
44574
- if (!node) return true;
44575
- if (node.isText) {
44576
- const text = node.text ?? "";
44577
- return opensAfter.test(String.fromCodePoint(text.codePointAt(text.length - 1)));
44578
- }
44579
- return node.type.name === "hardbreak";
44580
- }
44581
- /** The input rule that ran at the last change, when nothing has happened since. */
44582
- function lastRule(state) {
44583
- for (const plugin of state.plugins) if (plugin.spec.isInputRules) {
44584
- const stored = plugin.getState(state);
44585
- if (stored) return stored;
44613
+ if (!state) {
44614
+ Object.assign(dom.style, { display: "none" });
44615
+ return;
44616
+ }
44617
+ const { line } = state;
44618
+ const { width: lineWidth } = config;
44619
+ const { p1: { x: x1, y: y1 }, p2: { x: x2, y: y2 } } = line;
44620
+ const horizontal = y1 === y2;
44621
+ let width;
44622
+ let height;
44623
+ let top = y1;
44624
+ let left = x1;
44625
+ if (horizontal) {
44626
+ width = x2 - x1;
44627
+ height = lineWidth;
44628
+ top -= lineWidth / 2;
44629
+ } else {
44630
+ width = lineWidth;
44631
+ height = y2 - y1;
44632
+ left -= lineWidth / 2;
44586
44633
  }
44634
+ top = Math.round(top);
44635
+ left = Math.round(left);
44636
+ Object.assign(dom.style, {
44637
+ display: "block",
44638
+ width: `${width}px`,
44639
+ height: `${height}px`,
44640
+ transform: `translate(${left}px, ${top}px)`
44641
+ });
44587
44642
  }
44588
- var quotes$1 = {
44589
- "\"": ["“", "”"],
44590
- "'": ["‘", "’"]
44591
- };
44592
- var smartPunctuation = [
44593
- smartPunctuationCtx,
44594
- inputTracking,
44595
- $inputRule((ctx) => new InputRule(/["']$/, (state, match, start, end) => {
44596
- if (!applies(ctx.get(smartPunctuationCtx.key), state, match, start)) return null;
44597
- const [opening, closing] = quotes$1[match[0]];
44598
- return state.tr.insertText(opensHere(state, start) ? opening : closing, start, end).setMeta(smartRule, "quote");
44599
- }, { inCodeMark: false })),
44600
- $inputRule((ctx) => new InputRule(/--$/, (state, match, start, end) => {
44601
- if (!applies(ctx.get(smartPunctuationCtx.key), state, match, start)) return null;
44602
- if (input.getState(state)?.hyphen !== state.selection.from) return null;
44603
- const before = textBefore(state, start);
44604
- if (!before.trim() || before.endsWith("-") || /^\s*\|/.test(before)) return null;
44605
- return state.tr.insertText(enDash, start, end).setMeta(smartRule, "en");
44606
- }, { inCodeMark: false })),
44607
- $inputRule((ctx) => new InputRule(/\u2013-$/, (state, match, start, end) => {
44608
- if (!applies(ctx.get(smartPunctuationCtx.key), state, match, start)) return null;
44609
- if (lastRule(state)?.transform.getMeta(smartRule) !== "en") return null;
44610
- return state.tr.insertText(emDash, start, end).setMeta(smartRule, "em");
44611
- }, { inCodeMark: false }))
44643
+ var dropIndicatorPlugin = $prose((ctx) => {
44644
+ const onShow = (options) => {
44645
+ ctx.set(dropIndicatorState.key, options);
44646
+ };
44647
+ const onHide = () => {
44648
+ ctx.set(dropIndicatorState.key, null);
44649
+ };
44650
+ return createDropIndicatorPlugin({
44651
+ onShow,
44652
+ onHide,
44653
+ onDrag: () => true
44654
+ });
44655
+ });
44656
+ withMeta(dropIndicatorPlugin, { displayName: "Prose<dropIndicator>" });
44657
+ var gapCursorPlugin = $prose(() => gapCursor());
44658
+ withMeta(gapCursorPlugin, { displayName: "Prose<gapCursor>" });
44659
+ [
44660
+ gapCursorPlugin,
44661
+ dropIndicatorConfig,
44662
+ dropIndicatorState,
44663
+ dropIndicatorDOMPlugin,
44664
+ dropIndicatorPlugin
44665
+ ].flat();
44666
+ var cursor = [
44667
+ gapCursorPlugin,
44668
+ dropIndicatorConfig,
44669
+ dropIndicatorState,
44670
+ dropIndicatorDOMPlugin,
44671
+ $prose((ctx) => {
44672
+ let live = false;
44673
+ const plugin = createDropIndicatorPlugin({
44674
+ onShow: (options) => ctx.set(dropIndicatorState.key, options),
44675
+ onHide: () => {
44676
+ if (live) ctx.set(dropIndicatorState.key, null);
44677
+ },
44678
+ onDrag: () => true
44679
+ });
44680
+ const view = plugin.spec.view;
44681
+ return new Plugin({
44682
+ ...plugin.spec,
44683
+ view(editorView) {
44684
+ live = true;
44685
+ const inner = view(editorView);
44686
+ return {
44687
+ ...inner,
44688
+ destroy() {
44689
+ live = false;
44690
+ inner.destroy?.();
44691
+ }
44692
+ };
44693
+ }
44694
+ });
44695
+ })
44612
44696
  ].flat();
44613
- /**
44614
- * Undo straight after a smart quote or dash puts back the character that was typed, as Backspace
44615
- * does for any input rule. The replacement joins the typing's history step, so the next Undo
44616
- * removes the typing and not the curly character. Returns whether it applied.
44617
- */
44618
- var undoSmartPunctuation = (state, dispatch) => {
44619
- const rule = lastRule(state);
44620
- if (!rule?.transform.getMeta(smartRule)) return false;
44621
- return undoInputRule(state, dispatch && ((tr) => dispatch(tr.setMeta("appendedTransaction", rule.transform))));
44622
- };
44623
44697
  //#endregion
44624
44698
  //#region src/text-input.ts
44625
44699
  var isInputRules = (plugin) => plugin.spec.isInputRules === true;
@@ -44921,6 +44995,16 @@ function finalCodeEnding(doc) {
44921
44995
  last = last.lastChild;
44922
44996
  }
44923
44997
  }
44998
+ /** Change only the language word, retaining fence spelling and extra metadata. */
44999
+ function languageFence(value, language) {
45000
+ const fence = readFence(value);
45001
+ if (!fence) return null;
45002
+ const match = /^(\s*)(\S+)?([\s\S]*)$/.exec(fence.info);
45003
+ return {
45004
+ ...fence,
45005
+ info: match[1] + language + match[3]
45006
+ };
45007
+ }
44924
45008
  //#endregion
44925
45009
  //#region src/literals.ts
44926
45010
  var unsupported = /* @__PURE__ */ new Set([
@@ -44957,6 +45041,7 @@ function containsUnsupported(node, images, inline = false) {
44957
45041
  return unsupported.has(node.type) && !(images && node.type === "image") || "children" in node && node.children.some((child) => containsUnsupported(child, images, inline || node.type === "paragraph" || node.type === "heading"));
44958
45042
  }
44959
45043
  function containsUnresolved(node, source) {
45044
+ if (node.type === "link") return false;
44960
45045
  if (node.type === "text") {
44961
45046
  const start = node.position?.start.offset;
44962
45047
  const end = node.position?.end.offset;
@@ -45125,14 +45210,25 @@ var literalBlock = $node("literal_markdown", (ctx) => ({
45125
45210
  }));
45126
45211
  //#endregion
45127
45212
  //#region src/preserve.ts
45213
+ var linkFormsMatter = false;
45214
+ function withLinkForms(run) {
45215
+ const before = linkFormsMatter;
45216
+ linkFormsMatter = true;
45217
+ try {
45218
+ return run();
45219
+ } finally {
45220
+ linkFormsMatter = before;
45221
+ }
45222
+ }
45128
45223
  function signature(node) {
45129
45224
  return JSON.stringify(ordered(node), function(key, value) {
45225
+ const holder = this;
45226
+ if (key === "data" && holder.type === "link" && linkFormsMatter) return { inkkitLinkForm: value?.inkkitLinkForm };
45130
45227
  if ([
45131
45228
  "position",
45132
45229
  "data",
45133
45230
  "marker"
45134
45231
  ].includes(key)) return void 0;
45135
- const holder = this;
45136
45232
  if (key === "value" && holder.type === "html" && lineBreakHTML(value)) return "<br />";
45137
45233
  if (key === "children" && holder.type === "tableRow") {
45138
45234
  let end = value.length;
@@ -45955,7 +46051,17 @@ function parseDoc(ctx, text) {
45955
46051
  }
45956
46052
  function parseTree(ctx, text) {
45957
46053
  preservationWork.characters += text.length;
45958
- return ctx.get(remarkCtx).parse(text);
46054
+ const tree = ctx.get(remarkCtx).parse(text);
46055
+ const body = text.replace(/^\uFEFF/, "");
46056
+ const visit = (node) => {
46057
+ if (node.type === "link") node.data = {
46058
+ ...node.data,
46059
+ inkkitLinkForm: linkForm(node, body)
46060
+ };
46061
+ node.children?.forEach(visit);
46062
+ };
46063
+ visit(tree);
46064
+ return tree;
45959
46065
  }
45960
46066
  function write(ctx, doc) {
45961
46067
  const text = serialize(ctx, doc);
@@ -46233,6 +46339,7 @@ var Preservation = class Preservation {
46233
46339
  blocks = [];
46234
46340
  lineEnding;
46235
46341
  originalForms = /* @__PURE__ */ new Set();
46342
+ linkEdits = false;
46236
46343
  blockForms = [];
46237
46344
  forms = /* @__PURE__ */ new WeakMap();
46238
46345
  blockEdits = /* @__PURE__ */ new WeakMap();
@@ -46759,7 +46866,7 @@ var Preservation = class Preservation {
46759
46866
  const kind = block && block.form === text && !carried.within.has(index) ? position == null ? "moved" : "unchanged" : block && tree && position != null ? "edited" : "canonical";
46760
46867
  kept.push(kind);
46761
46868
  const node = tree ? positionalNode(tree.raw, transformed(index)) : void 0;
46762
- pieces.push(kind === "unchanged" || kind === "moved" ? this.source.slice(block.start, block.end) : kind === "edited" ? 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)));
46869
+ 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)));
46763
46870
  }
46764
46871
  return {
46765
46872
  gaps,
@@ -46955,6 +47062,31 @@ var Preservation = class Preservation {
46955
47062
  for (let index = 0; index < nodes.length; index++) rewritten.push(again.child(index));
46956
47063
  return actual === normalized(write(this.ctx, read.type.create(null, rewritten)));
46957
47064
  }
47065
+ withLinkEdits(run) {
47066
+ this.linkEdits = true;
47067
+ try {
47068
+ return run();
47069
+ } finally {
47070
+ this.linkEdits = false;
47071
+ }
47072
+ }
47073
+ retainingForms(block, node, retain) {
47074
+ if (!this.linkEdits) return retain();
47075
+ const forms = (value) => {
47076
+ const found = [];
47077
+ let previous;
47078
+ value.descendants((child) => {
47079
+ if (!child.isText) return true;
47080
+ const link = child.marks.find((mark) => mark.type.name === "link");
47081
+ if (link && !previous?.eq(link)) found.push(link.attrs.form ?? null);
47082
+ previous = link;
47083
+ return false;
47084
+ });
47085
+ return JSON.stringify(found);
47086
+ };
47087
+ const original = this.baseline.maybeChild(this.blocks.indexOf(block));
47088
+ return !original || this.baseline.childCount !== this.blocks.length || forms(original) !== forms(node) ? withLinkForms(retain) : retain();
47089
+ }
46958
47090
  wholeDocument(doc) {
46959
47091
  try {
46960
47092
  const canonical = write(this.ctx, doc);
@@ -46963,20 +47095,24 @@ var Preservation = class Preservation {
46963
47095
  doc.forEach((node) => current.push(this.formOf(node)));
46964
47096
  const pairing = this.pairing(doc, current);
46965
47097
  const carries = pairing.carried.moved.size > 0 || pairing.carried.children;
46966
- if (carries) {
46967
- let output;
46968
- try {
46969
- output = this.writeDocument(doc, current, pairing);
46970
- } catch {
46971
- output = void 0;
47098
+ const attempt = () => {
47099
+ if (carries) {
47100
+ let output;
47101
+ try {
47102
+ output = this.writeDocument(doc, current, pairing);
47103
+ } catch {
47104
+ output = void 0;
47105
+ }
47106
+ if (output !== void 0 && this.reopens(doc, output, canonical)) return output;
46972
47107
  }
46973
- if (output !== void 0 && this.reopens(doc, output, canonical)) return output;
46974
- }
46975
- const output = this.writeDocument(doc, current, carries ? {
46976
- positions: correspondence(this.blockForms, current),
46977
- carried: nothingCarried
46978
- } : pairing);
46979
- if (this.reopens(doc, output, canonical)) return output;
47108
+ const output = this.writeDocument(doc, current, carries ? {
47109
+ positions: correspondence(this.blockForms, current),
47110
+ carried: nothingCarried
47111
+ } : pairing);
47112
+ return this.reopens(doc, output, canonical) ? output : void 0;
47113
+ };
47114
+ const output = attempt() ?? this.withLinkEdits(attempt);
47115
+ if (output !== void 0) return output;
46980
47116
  throw new PreservationError("The edited Markdown cannot be reopened without changing its content");
46981
47117
  } catch (cause) {
46982
47118
  if (cause instanceof PreservationError) throw cause;
@@ -47180,16 +47316,126 @@ function read(ctx, sources, texts) {
47180
47316
  return valid ? found : void 0;
47181
47317
  });
47182
47318
  }
47319
+ function literalFormattingLinks(text) {
47320
+ if (!/[*_~`]/.test(text) || /[<>]/.test(text)) return void 0;
47321
+ const tree = fromMarkdown(`x ${text}`, {
47322
+ extensions: [gfmAutolinkLiteral(), { disable: { null: [
47323
+ "attention",
47324
+ "codeText",
47325
+ "htmlText",
47326
+ "labelStartLink",
47327
+ "labelStartImage",
47328
+ "characterEscape",
47329
+ "characterReference",
47330
+ "autolink"
47331
+ ] } }],
47332
+ mdastExtensions: [gfmAutolinkLiteralFromMarkdown()]
47333
+ });
47334
+ const paragraph = tree.children[0];
47335
+ if (tree.children.length !== 1 || paragraph?.type !== "paragraph") return void 0;
47336
+ const found = [];
47337
+ let readable = "";
47338
+ for (const child of paragraph.children) if (child.type === "text") readable += child.value;
47339
+ else if (child.type === "link" && child.children.every((node) => node.type === "text")) {
47340
+ const start = readable.length - 2;
47341
+ for (const node of child.children) if (node.type === "text") readable += node.value;
47342
+ found.push([
47343
+ start,
47344
+ readable.length - 2,
47345
+ child.url
47346
+ ]);
47347
+ } else return void 0;
47348
+ return readable === `x ${text}` ? found : void 0;
47349
+ }
47183
47350
  function literals(ctx, texts) {
47184
47351
  const possible = texts.map((text) => /www\.|https?:\/\/|@/i.test(text));
47185
47352
  const candidates = texts.filter((_, index) => possible[index]);
47186
47353
  const found = read(ctx, candidates.map((text) => text.replace(/[\\`[\]<>]/g, "\\$&")), candidates);
47187
- return texts.map((_, index) => possible[index] ? found.shift() : []);
47354
+ return texts.map((text, index) => possible[index] ? endAtBackticks(ctx, found.shift() ?? literalFormattingLinks(text), text) : []);
47355
+ }
47356
+ function endAtBackticks(ctx, found, text) {
47357
+ if (!found) return found;
47358
+ const result = [];
47359
+ for (const [start, end, href] of found) {
47360
+ const parts = text.slice(start, end).split("`");
47361
+ if (parts.length === 1) {
47362
+ result.push([
47363
+ start,
47364
+ end,
47365
+ href
47366
+ ]);
47367
+ continue;
47368
+ }
47369
+ let at = start;
47370
+ literals(ctx, parts).forEach((links, index) => {
47371
+ for (const [from, to, target] of links ?? []) result.push([
47372
+ at + from,
47373
+ at + to,
47374
+ target
47375
+ ]);
47376
+ at += parts[index].length + 1;
47377
+ });
47378
+ }
47379
+ return result;
47188
47380
  }
47189
47381
  function angleHref(text) {
47190
47382
  if (/^[a-zA-Z][a-zA-Z0-9+.-]{1,31}:[^\s<>\u0000-\u001f]*$/.test(text)) return text;
47191
47383
  if (/^[a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$/.test(text)) return `mailto:${text}`;
47192
47384
  }
47385
+ function completedAngle(tr, chars, from, to) {
47386
+ if (to !== from + 1) return void 0;
47387
+ const end = chars.findIndex((char) => char?.pos === from);
47388
+ const closing = chars[end];
47389
+ if (closing?.text !== ">" || closing.marks.some((mark) => mark.type.spec.code) || closing.link && closing.form !== "literal") return void 0;
47390
+ let start = end - 1;
47391
+ for (; start >= 0; start--) {
47392
+ const char = chars[start];
47393
+ if (!char || char.marks.some((mark) => mark.type.spec.code) || char.link && char.form !== "literal" || char.text !== "<" && !sameMarks(char.marks, closing.marks) || /[\s>]/.test(char.text)) return void 0;
47394
+ if (char.text === "<") break;
47395
+ }
47396
+ if (start < 0) return void 0;
47397
+ const href = angleHref(chars.slice(start + 1, end).map((char) => char.text).join(""));
47398
+ if (!href) return void 0;
47399
+ const opening = chars[start].pos;
47400
+ return tr.delete(from, to).delete(opening, opening + 1).addMark(opening, from - 1, tr.doc.type.schema.marks.link.create({
47401
+ href,
47402
+ form: "angle"
47403
+ })).removeStoredMark(tr.doc.type.schema.marks.link);
47404
+ }
47405
+ function closeAngleEnds(tr, transactions) {
47406
+ const steps = transactions.flatMap((transaction) => transaction.steps.map((step, index) => ({
47407
+ doc: transaction.docs[index],
47408
+ map: step.getMap()
47409
+ })));
47410
+ steps.forEach(({ doc, map }, index) => {
47411
+ const remaining = new Mapping(steps.slice(index + 1).map(({ map }) => map));
47412
+ map.forEach((from, to, start, end) => {
47413
+ if (from !== to || start === end) return;
47414
+ const at = doc.resolve(from);
47415
+ const before = at.nodeBefore?.marks.find((mark) => mark.attrs.form === "angle");
47416
+ const after = at.nodeAfter?.marks.find((mark) => mark.attrs.form === "angle");
47417
+ if (before && after && before.eq(after)) return;
47418
+ for (const mark of [before, after]) if (mark) tr.removeMark(remaining.map(start, 1), remaining.map(end, -1), mark);
47419
+ });
47420
+ });
47421
+ }
47422
+ function codeAngles(tr, edited) {
47423
+ const spans = [];
47424
+ for (const block of edited) tr.doc.nodesBetween(block.pos, block.pos + block.block.nodeSize, (node, pos) => {
47425
+ if (!node.isText || !node.marks.some((mark) => mark.type.spec.code)) return;
47426
+ const link = node.marks.find((mark) => mark.attrs.form === "angle");
47427
+ if (link) spans.push({
47428
+ from: pos,
47429
+ to: pos + node.nodeSize,
47430
+ marks: node.marks.filter((mark) => mark !== link)
47431
+ });
47432
+ });
47433
+ for (const { from, to, marks } of spans.reverse()) {
47434
+ tr.removeMark(from, to, tr.doc.type.schema.marks.link);
47435
+ tr.insert(to, tr.doc.type.schema.text(">", marks));
47436
+ tr.insert(from, tr.doc.type.schema.text("<", marks));
47437
+ }
47438
+ }
47193
47439
  function wantedLinks(ctx, chars, from, to) {
47194
47440
  const wanted = /* @__PURE__ */ new Map();
47195
47441
  const released = /* @__PURE__ */ new Set();
@@ -47214,6 +47460,20 @@ function wantedLinks(ctx, chars, from, to) {
47214
47460
  }
47215
47461
  index = end;
47216
47462
  }
47463
+ const codeAddress = (index) => {
47464
+ const char = chars[index];
47465
+ return char?.form === "literal" && char.marks.some((mark) => mark.type.spec.code);
47466
+ };
47467
+ for (let index = 0; index < chars.length;) {
47468
+ if (!codeAddress(index)) {
47469
+ index++;
47470
+ continue;
47471
+ }
47472
+ let end = index + 1;
47473
+ while (end < chars.length && codeAddress(end)) end++;
47474
+ if (end >= from && index <= to) for (let at = index; at < end; at++) wanted.set(at, null);
47475
+ index = end;
47476
+ }
47217
47477
  const free = (index) => {
47218
47478
  const char = chars[index];
47219
47479
  return char !== void 0 && !char.marks.some((mark) => mark.type.spec.code) && (released.has(index) || char.form !== "angle" && !(char.link && !char.form));
@@ -47305,7 +47565,12 @@ function linked(tr, blocks) {
47305
47565
  }
47306
47566
  function readsBack(ctx, tr, pos) {
47307
47567
  const block = tr.doc.nodeAt(pos);
47308
- const doc = tr.doc.type.create(null, [tr.doc.type.schema.nodes.paragraph.create(null, block.content)]);
47568
+ const references = /* @__PURE__ */ new Set();
47569
+ block.descendants((node) => {
47570
+ for (const mark of node.marks) if (mark.type.name === "link" && mark.attrs.identifier) references.add(normaliseLabel(String(mark.attrs.identifier)));
47571
+ });
47572
+ const definitions = references.size ? [...referenceDefinitions(tr.doc)].filter(([identifier]) => references.has(identifier)).map(([, definition]) => definition.node) : [];
47573
+ const doc = tr.doc.type.create(null, [tr.doc.type.schema.nodes.paragraph.create(null, block.content), ...definitions]);
47309
47574
  return semanticSignature(ctx.get(parserCtx)(serialize(ctx, doc))) === semanticSignature(doc);
47310
47575
  }
47311
47576
  var textblocks = (doc, from, to) => {
@@ -47360,8 +47625,25 @@ var autolinkLiterals = $prose((ctx) => new Plugin({
47360
47625
  key: new PluginKey("autolinkLiterals"),
47361
47626
  appendTransaction(transactions, oldState, newState) {
47362
47627
  if (!transactions.some((tr) => tr.docChanged)) return null;
47628
+ const base = newState.tr;
47629
+ const written = transactions.every(isWrittenEdit);
47630
+ if (written) {
47631
+ closeAngleEnds(base, transactions);
47632
+ codeAngles(base, editedBlocks(oldState.doc, base.doc));
47633
+ }
47634
+ let edited = editedBlocks(oldState.doc, base.doc);
47635
+ if (written && edited.length === 1) {
47636
+ const { pos, block, from, to } = edited[0];
47637
+ const start = base.steps.length;
47638
+ if (completedAngle(base, characters(block, pos + 1), from, to)) {
47639
+ edited = editedBlocks(oldState.doc, base.doc);
47640
+ const mapping = base.mapping.slice(start);
47641
+ edited[0].from = Math.min(edited[0].from, mapping.map(from, -1));
47642
+ edited[0].to = Math.max(edited[0].to, mapping.map(to, 1));
47643
+ }
47644
+ }
47363
47645
  const blocks = [];
47364
- for (const { pos, block, from, to } of editedBlocks(oldState.doc, newState.doc)) {
47646
+ for (const { pos, block, from, to } of edited) {
47365
47647
  const chars = characters(block, pos + 1);
47366
47648
  const index = (at) => {
47367
47649
  const found = chars.findIndex((char) => char !== void 0 && char.pos >= at);
@@ -47374,15 +47656,32 @@ var autolinkLiterals = $prose((ctx) => new Plugin({
47374
47656
  wanted
47375
47657
  });
47376
47658
  }
47377
- if (!blocks.length) return null;
47378
- let tr = linked(newState.tr, blocks);
47379
- const failed = blocks.filter((block) => !readsBack(ctx, tr, block.pos));
47380
- if (!failed.length) return tr;
47381
- for (const block of failed) for (const [at, want] of block.wanted) if (want?.form === "literal") block.wanted.set(at, null);
47382
- tr = linked(newState.tr, blocks);
47383
- const kept = blocks.filter((block) => readsBack(ctx, tr, block.pos));
47384
- if (kept.length === blocks.length) return tr;
47385
- return kept.length ? linked(newState.tr, kept) : null;
47659
+ if (!base.steps.length && !blocks.length) return null;
47660
+ const stored = base.storedMarks ?? newState.storedMarks;
47661
+ const finish = (tr) => {
47662
+ if (stored) tr.setStoredMarks(stored);
47663
+ if (!tr.steps.length) return null;
47664
+ for (const plugin of newState.plugins) if (plugin.spec.isInputRules) for (const transaction of transactions) {
47665
+ const meta = transaction.getMeta(plugin);
47666
+ if (meta !== void 0) tr.setMeta(plugin, meta);
47667
+ }
47668
+ return tr;
47669
+ };
47670
+ const attempt = (wanted) => {
47671
+ const tr = newState.tr;
47672
+ for (const step of base.steps) tr.step(step);
47673
+ tr.setSelection(base.selection.getBookmark().resolve(tr.doc));
47674
+ return linked(tr, wanted);
47675
+ };
47676
+ let tr = attempt(blocks);
47677
+ const failed = edited.filter((block) => !readsBack(ctx, tr, block.pos));
47678
+ if (!failed.length) return finish(tr);
47679
+ for (const block of blocks) if (failed.some(({ pos }) => pos === block.pos)) {
47680
+ for (const [at, want] of block.wanted) if (want?.form === "literal") block.wanted.set(at, null);
47681
+ }
47682
+ tr = attempt(blocks);
47683
+ if (edited.every((block) => readsBack(ctx, tr, block.pos))) return finish(tr);
47684
+ return null;
47386
47685
  }
47387
47686
  }));
47388
47687
  //#endregion
@@ -49720,7 +50019,8 @@ function formattedOutside(node) {
49720
50019
  };
49721
50020
  return result;
49722
50021
  }
49723
- function written(node) {
50022
+ function written(node, inTable = false) {
50023
+ if (inTable && node.url.includes("|")) return "brackets";
49724
50024
  if (bracketLinks.has(node)) return "brackets";
49725
50025
  const form = formOf(node);
49726
50026
  if (form === "literal" && bareLinks.has(node)) return "bare";
@@ -49736,12 +50036,12 @@ function withResourceLink(state, resourceLink, write) {
49736
50036
  }
49737
50037
  }
49738
50038
  var link = (node, parent, state, info) => {
49739
- const form = written(node);
50039
+ const form = written(node, state.stack.includes("tableCell"));
49740
50040
  if (form === "bare") return node.children[0].value;
49741
50041
  return withResourceLink(state, form === "brackets", () => handle.link(node, parent, state, info));
49742
50042
  };
49743
50043
  link.peek = (node, parent, state) => {
49744
- const form = written(node);
50044
+ const form = written(node, state.stack.includes("tableCell"));
49745
50045
  if (form === "bare") return node.children[0].value[0];
49746
50046
  return withResourceLink(state, form === "brackets", () => handle.link.peek(node, parent, state));
49747
50047
  };
@@ -50096,7 +50396,11 @@ function createDialect(images, wikiLinks = false, files = false) {
50096
50396
  }
50097
50397
  })),
50098
50398
  references,
50099
- wikiLinks ? wikiLinkSchema : [],
50399
+ wikiLinks ? [
50400
+ wikiLinkSchema,
50401
+ wikiLinkComposition,
50402
+ wikiLinkInputRule
50403
+ ] : [],
50100
50404
  callouts,
50101
50405
  comments,
50102
50406
  hiddenCommentEditing,
@@ -50300,16 +50604,16 @@ var literalReread = $prose((ctx) => new Plugin({
50300
50604
  //#region src/decorations.ts
50301
50605
  var copyIcon = "<svg width=\"14\" height=\"14\" viewBox=\"0 0 16 16\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><rect x=\"5.5\" y=\"5.5\" width=\"8\" height=\"8\" rx=\"1.5\"/><path d=\"M10.5 5.5v-2a1 1 0 0 0-1-1h-6a1 1 0 0 0-1 1v6a1 1 0 0 0 1 1h2\"/></svg>";
50302
50606
  var doneIcon = "<svg width=\"14\" height=\"14\" viewBox=\"0 0 16 16\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.8\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M3.5 8.5l3 3 6-6.5\"/></svg>";
50303
- function copyButton(onCopy, label) {
50607
+ function copyButton(onCopy, label, text) {
50304
50608
  const button = document.createElement("button");
50305
50609
  button.type = "button";
50610
+ button.tabIndex = 0;
50306
50611
  button.className = "inkkit-copy";
50307
50612
  button.setAttribute("aria-label", label);
50308
50613
  button.innerHTML = copyIcon;
50309
50614
  button.addEventListener("mousedown", (event) => event.preventDefault());
50310
50615
  button.addEventListener("click", () => {
50311
- const code = button.closest("pre")?.querySelector("code");
50312
- onCopy(code?.textContent ?? "");
50616
+ onCopy(text());
50313
50617
  button.dataset.copied = "";
50314
50618
  button.innerHTML = doneIcon;
50315
50619
  window.setTimeout(() => {
@@ -50326,7 +50630,10 @@ function codeCopyPlugin(onCopy) {
50326
50630
  const widgets = [];
50327
50631
  state.doc.descendants((node, pos) => {
50328
50632
  if (node.type.name !== "code_block") return true;
50329
- widgets.push(Decoration.widget(pos + 1, () => copyButton(onCopy, editorLabels(ctx).copyCode), {
50633
+ widgets.push(Decoration.widget(pos + 1, (view, getPos) => copyButton(onCopy, editorLabels(ctx).copyCode, () => {
50634
+ const at = getPos();
50635
+ return at == null ? "" : view.state.doc.nodeAt(at - 1)?.textContent ?? "";
50636
+ }), {
50330
50637
  side: -1,
50331
50638
  key: "copy",
50332
50639
  ignoreSelection: true
@@ -50349,6 +50656,315 @@ var placeholderPlugin = $prose((ctx) => new Plugin({
50349
50656
  } }
50350
50657
  }));
50351
50658
  //#endregion
50659
+ //#region src/code-languages.ts
50660
+ var codeLanguages = [
50661
+ ["arduino", "Arduino"],
50662
+ ["bash", "Bash"],
50663
+ ["c", "C"],
50664
+ ["cpp", "C++"],
50665
+ ["csharp", "C#"],
50666
+ ["css", "CSS"],
50667
+ ["diff", "Diff"],
50668
+ ["go", "Go"],
50669
+ ["graphql", "GraphQL"],
50670
+ ["ini", "INI"],
50671
+ ["java", "Java"],
50672
+ ["javascript", "JavaScript"],
50673
+ ["json", "JSON"],
50674
+ ["kotlin", "Kotlin"],
50675
+ ["less", "Less"],
50676
+ ["lua", "Lua"],
50677
+ ["makefile", "Makefile"],
50678
+ ["markdown", "Markdown"],
50679
+ ["objectivec", "Objective-C"],
50680
+ ["perl", "Perl"],
50681
+ ["php", "PHP"],
50682
+ ["php-template", "PHP template"],
50683
+ ["python", "Python"],
50684
+ ["python-repl", "Python REPL"],
50685
+ ["r", "R"],
50686
+ ["ruby", "Ruby"],
50687
+ ["rust", "Rust"],
50688
+ ["scss", "SCSS"],
50689
+ ["shell", "Shell"],
50690
+ ["sql", "SQL"],
50691
+ ["swift", "Swift"],
50692
+ ["typescript", "TypeScript"],
50693
+ ["vbnet", "Visual Basic .NET"],
50694
+ ["wasm", "WebAssembly"],
50695
+ ["xml", "HTML / XML"],
50696
+ ["yaml", "YAML"]
50697
+ ];
50698
+ //#endregion
50699
+ //#region src/code-controls.ts
50700
+ var codeControlCtx = $ctx((operation) => operation(), "inkkitCodeControl");
50701
+ function codeControlsPlugin(visibleRect) {
50702
+ return $prose((ctx) => new Plugin({
50703
+ key: new PluginKey("codeControls"),
50704
+ props: { decorations(state) {
50705
+ const widgets = [];
50706
+ state.doc.descendants((node, pos) => {
50707
+ if (node.type.name !== "code_block") return true;
50708
+ const language = String(node.attrs.language ?? "");
50709
+ widgets.push(Decoration.widget(pos + 1, (view, getPos) => {
50710
+ const labels = editorLabels(ctx);
50711
+ const wrapper = document.createElement("span");
50712
+ wrapper.className = "inkkit-code-controls";
50713
+ const button = document.createElement("button");
50714
+ button.type = "button";
50715
+ button.tabIndex = 0;
50716
+ button.className = "inkkit-code-language";
50717
+ button.textContent = language === "text" || language === "plaintext" ? labels.codePlainText : codeLanguages.find(([value]) => value === language)?.[1] ?? (language || labels.codeAuto);
50718
+ button.setAttribute("aria-label", labels.codeLanguage);
50719
+ button.setAttribute("aria-haspopup", "dialog");
50720
+ button.setAttribute("aria-expanded", "false");
50721
+ wrapper.append(button);
50722
+ let popup;
50723
+ let restore;
50724
+ const close = (focus = false) => {
50725
+ popup?.remove();
50726
+ popup = void 0;
50727
+ restore?.();
50728
+ restore = void 0;
50729
+ button.setAttribute("aria-expanded", "false");
50730
+ if (focus) button.focus();
50731
+ };
50732
+ button.addEventListener("mousedown", (event) => event.preventDefault());
50733
+ button.addEventListener("click", () => {
50734
+ if (popup) {
50735
+ close();
50736
+ return;
50737
+ }
50738
+ const captured = view.state.doc;
50739
+ popup = document.createElement("span");
50740
+ popup.className = "inkkit-code-language-menu";
50741
+ popup.setAttribute("role", "dialog");
50742
+ popup.setAttribute("aria-label", labels.codeLanguage);
50743
+ popup.contentEditable = "false";
50744
+ const search = document.createElement("input");
50745
+ search.type = "search";
50746
+ search.placeholder = labels.codeLanguageSearch;
50747
+ search.setAttribute("aria-label", labels.codeLanguageSearch);
50748
+ const list = document.createElement("span");
50749
+ list.className = "inkkit-code-language-list";
50750
+ popup.append(search, list);
50751
+ const choices = [
50752
+ ["", labels.codeAuto],
50753
+ ["text", labels.codePlainText],
50754
+ ...codeLanguages
50755
+ ];
50756
+ if (language && !choices.some(([value]) => value === language)) choices.push([language, language]);
50757
+ const render = () => {
50758
+ list.replaceChildren();
50759
+ for (const [value, name] of choices) {
50760
+ if (!`${value} ${name}`.toLowerCase().includes(search.value.toLowerCase())) continue;
50761
+ const option = document.createElement("button");
50762
+ option.type = "button";
50763
+ option.textContent = name;
50764
+ option.setAttribute("aria-pressed", String(value === language));
50765
+ option.addEventListener("click", () => {
50766
+ close();
50767
+ if (view.state.doc !== captured) return;
50768
+ ctx.get(codeControlCtx.key)(() => {
50769
+ const at = getPos();
50770
+ if (at == null) return false;
50771
+ const position = at - 1;
50772
+ const current = view.state.doc.nodeAt(position);
50773
+ if (current?.type.name !== "code_block") return false;
50774
+ if (value === language) {
50775
+ view.focus();
50776
+ return false;
50777
+ }
50778
+ const transaction = view.state.tr.setNodeMarkup(position, void 0, {
50779
+ ...current.attrs,
50780
+ language: value,
50781
+ authoredFence: languageFence(current.attrs.authoredFence, value)
50782
+ });
50783
+ view.dispatch(closeHistory(transaction));
50784
+ view.dispatch(closeHistory(view.state.tr).setMeta("addToHistory", false));
50785
+ view.focus();
50786
+ return true;
50787
+ });
50788
+ });
50789
+ list.append(option);
50790
+ }
50791
+ };
50792
+ search.addEventListener("input", render);
50793
+ popup.addEventListener("keydown", (event) => {
50794
+ if (event.key === "Escape") {
50795
+ event.preventDefault();
50796
+ event.stopPropagation();
50797
+ close(true);
50798
+ }
50799
+ if (event.key === "ArrowDown") {
50800
+ event.preventDefault();
50801
+ const options = [...list.querySelectorAll("button")];
50802
+ options[(options.indexOf(document.activeElement) + 1) % options.length]?.focus();
50803
+ }
50804
+ if (event.key === "ArrowUp") {
50805
+ event.preventDefault();
50806
+ const options = [...list.querySelectorAll("button")];
50807
+ options[(options.indexOf(document.activeElement) - 1 + options.length) % options.length]?.focus();
50808
+ }
50809
+ if (event.key === "Enter" && event.target === search) {
50810
+ event.preventDefault();
50811
+ list.querySelector("button")?.click();
50812
+ }
50813
+ });
50814
+ wrapper.append(popup);
50815
+ render();
50816
+ const bounds = button.getBoundingClientRect();
50817
+ const safe = visibleRect();
50818
+ const left = Math.max(8, safe.left + 4);
50819
+ const right = Math.min(window.innerWidth - 8, safe.right - 4);
50820
+ const top = Math.max(8, safe.top + 4);
50821
+ const bottom = Math.min(window.innerHeight - 8, safe.bottom - 4);
50822
+ const below = Math.max(0, bottom - bounds.bottom - 4);
50823
+ const above = Math.max(0, bounds.top - top - 4);
50824
+ const height = Math.min(320, Math.max(below, above));
50825
+ popup.style.width = `${Math.max(0, Math.min(220, right - left))}px`;
50826
+ popup.style.maxHeight = `${height}px`;
50827
+ popup.style.left = `${Math.max(left, Math.min(bounds.right - 220, right - 220))}px`;
50828
+ const menuHeight = Math.min(height, popup.getBoundingClientRect().height);
50829
+ popup.style.top = `${Math.max(top, Math.min(below >= menuHeight ? bounds.bottom + 4 : bounds.top - menuHeight - 4, bottom - menuHeight))}px`;
50830
+ const outside = (event) => {
50831
+ if (!wrapper.contains(event.target)) close();
50832
+ };
50833
+ const scroll = (event) => {
50834
+ if (popup?.contains(event.target)) return;
50835
+ const current = button.getBoundingClientRect();
50836
+ if (current.top !== bounds.top || current.left !== bounds.left || current.bottom !== bounds.bottom || current.right !== bounds.right) close();
50837
+ };
50838
+ document.addEventListener("pointerdown", outside, true);
50839
+ window.addEventListener("scroll", scroll, true);
50840
+ restore = () => {
50841
+ document.removeEventListener("pointerdown", outside, true);
50842
+ window.removeEventListener("scroll", scroll, true);
50843
+ };
50844
+ button.setAttribute("aria-expanded", "true");
50845
+ render();
50846
+ search.focus({ preventScroll: true });
50847
+ });
50848
+ wrapper.cleanup = close;
50849
+ return wrapper;
50850
+ }, {
50851
+ side: -1,
50852
+ key: `language:${language}`,
50853
+ ignoreSelection: true,
50854
+ stopEvent: () => true,
50855
+ destroy: (dom) => dom.cleanup?.()
50856
+ }));
50857
+ return false;
50858
+ });
50859
+ return DecorationSet.create(state.doc, widgets);
50860
+ } },
50861
+ view: (view) => ({ update(next, previous) {
50862
+ if (next.state.doc !== previous.doc || !next.editable) view.dom.querySelectorAll(".inkkit-code-controls").forEach((element) => element.cleanup?.());
50863
+ } })
50864
+ }));
50865
+ }
50866
+ //#endregion
50867
+ //#region src/mode-selection.ts
50868
+ function modeSelectionMap(ctx, source, doc) {
50869
+ const points = [];
50870
+ const bom = source.startsWith("") ? 1 : 0;
50871
+ let text = "";
50872
+ const offsets = [];
50873
+ const ends = [];
50874
+ const append = (value, positions) => {
50875
+ for (let index = 0; index < value.length; index++) offsets[text.length + index] = positions[index];
50876
+ for (let index = 1; index <= value.length; index++) ends[text.length + index] = positions[index];
50877
+ text += value;
50878
+ };
50879
+ const visit = (node) => {
50880
+ if (node.type === "definition") return;
50881
+ if ("children" in node) {
50882
+ node.children.forEach((child) => visit(child));
50883
+ return;
50884
+ }
50885
+ const start = node.position?.start.offset;
50886
+ const end = node.position?.end.offset;
50887
+ if (start == null || end == null || !("value" in node)) return;
50888
+ const raw = source.slice(start + bom, end + bom);
50889
+ if (node.type === "text") {
50890
+ const decoded = decodedOffsets(raw, true);
50891
+ if (decoded) append(decoded.decoded, decoded.offsets.map((at) => at + start + bom));
50892
+ } else if (node.type === "inlineCode") {
50893
+ const exact = raw.indexOf(node.value);
50894
+ if (exact >= 0) {
50895
+ append(node.value, Array.from({ length: node.value.length + 1 }, (_, index) => start + bom + exact + index));
50896
+ return;
50897
+ }
50898
+ const ticks = /^`+/.exec(raw)?.[0].length ?? 0;
50899
+ let value = "";
50900
+ const positions = [];
50901
+ for (let index = ticks; index < raw.length - ticks; index++) {
50902
+ positions.push(start + bom + index);
50903
+ if (raw[index] === "\r" && raw[index + 1] === "\n") index++;
50904
+ value += /[\r\n]/.test(raw[index]) ? " " : raw[index];
50905
+ }
50906
+ positions.push(start + bom + raw.length - ticks);
50907
+ if (value.startsWith(" ") && value.endsWith(" ") && /[^ ]/.test(value)) {
50908
+ value = value.slice(1, -1);
50909
+ positions.shift();
50910
+ positions.pop();
50911
+ }
50912
+ append(value, positions);
50913
+ } else if (node.type === "code") {
50914
+ const lines = [...raw.matchAll(/([^\r\n]*)(\r\n|\r|\n|$)/g)];
50915
+ let line = /^(`{3,}|~{3,})/.test(raw) ? 1 : 0;
50916
+ const value = node.value.replace(/\r\n?/g, "\n");
50917
+ const positions = [];
50918
+ let offset = 0;
50919
+ for (const content of value.split("\n")) {
50920
+ while (line < lines.length && !lines[line][1].endsWith(content)) line++;
50921
+ const match = lines[line++];
50922
+ if (!match) return;
50923
+ const at = match.index + match[1].length - content.length;
50924
+ for (let index = 0; index <= content.length; index++) positions[offset + index] = start + bom + at + index;
50925
+ offset += content.length + 1;
50926
+ }
50927
+ append(value, positions);
50928
+ } else if (node.type === "html" || node.type === "inkkitLiteral") {
50929
+ const value = node.value;
50930
+ const at = raw.indexOf(value);
50931
+ if (at >= 0) append(value, Array.from({ length: value.length + 1 }, (_, index) => start + bom + at + index));
50932
+ }
50933
+ };
50934
+ const processor = ctx.get(remarkCtx);
50935
+ processor.runSync(processor.parse(source), { value: source }).children.forEach((node) => visit(node));
50936
+ let cursor = 0;
50937
+ doc.descendants((node, position) => {
50938
+ if ([
50939
+ "comment_inline",
50940
+ "comment_block",
50941
+ "reference_definition"
50942
+ ].includes(node.type.name)) return false;
50943
+ if (!node.isText) return true;
50944
+ const value = node.text;
50945
+ const at = text.indexOf(value, cursor);
50946
+ if (at < 0) return false;
50947
+ for (let index = 0; index <= value.length; index++) {
50948
+ const offset = index === value.length ? ends[at + index] : offsets[at + index];
50949
+ if (offset != null) points.push({
50950
+ formatted: position + index,
50951
+ source: offset
50952
+ });
50953
+ }
50954
+ cursor = at + value.length;
50955
+ return false;
50956
+ });
50957
+ const nearest = (position, from, to, bias = -1) => {
50958
+ let best;
50959
+ for (const point of points) if (!best || Math.abs(point[from] - position) < Math.abs(best[from] - position) || bias > 0 && Math.abs(point[from] - position) === Math.abs(best[from] - position)) best = point;
50960
+ return best?.[to] ?? (from === "formatted" ? 0 : 1);
50961
+ };
50962
+ return {
50963
+ source: (position, bias = -1) => nearest(position, "formatted", "source", bias),
50964
+ formatted: (offset) => nearest(offset, "source", "formatted")
50965
+ };
50966
+ }
50967
+ //#endregion
50352
50968
  //#region src/reference-clipboard.ts
50353
50969
  function key(node) {
50354
50970
  if (node.type.name === "reference_definition") return `link:${node.attrs.identifier}`;
@@ -55956,14 +56572,24 @@ var InkKitEditor = class InkKitEditor {
55956
56572
  expectOneOf(mode, editingModes, "mode");
55957
56573
  this.assertCurrent(expectedGeneration);
55958
56574
  if (this.formatType === "txt" || this.mode === mode) return false;
56575
+ const wasSource = this.literalSurface;
56576
+ const view = this.editor.ctx.get(editorViewCtx);
56577
+ const focused = wasSource ? this.plain.ownerDocument.activeElement === this.plain : view.hasFocus();
56578
+ const container = this.viewportContainer ?? this.root;
56579
+ const previousSurface = wasSource ? this.plain : container;
56580
+ const fraction = previousSurface.scrollTop / Math.max(1, previousSurface.scrollHeight - previousSurface.clientHeight);
56581
+ const left = previousSurface.scrollLeft;
56582
+ const sourceSelection = wasSource ? {
56583
+ anchor: this.plain.selectionDirection === "backward" ? this.plain.selectionEnd : this.plain.selectionStart,
56584
+ head: this.plain.selectionDirection === "backward" ? this.plain.selectionStart : this.plain.selectionEnd
56585
+ } : void 0;
55959
56586
  this.settleSource();
55960
56587
  this.undoneSteps = [];
55961
56588
  this.formattedUndone = 0;
55962
- const view = this.editor.ctx.get(editorViewCtx);
55963
- const focused = this.literalSurface ? this.plain.ownerDocument.activeElement === this.plain : view.hasFocus();
55964
- const container = this.viewportContainer ?? this.root;
55965
- const top = container.scrollTop, left = container.scrollLeft;
55966
56589
  const text = this.snapshot().text;
56590
+ const mapping = modeSelectionMap(this.editor.ctx, text, view.state.doc);
56591
+ const anchor = sourceSelection ? mapping.formatted(rawOffset(text, sourceSelection.anchor)) : normalizedOffset(text, mapping.source(view.state.selection.anchor, view.state.selection.anchor < view.state.selection.head ? 1 : -1));
56592
+ const head = sourceSelection ? mapping.formatted(rawOffset(text, sourceSelection.head)) : normalizedOffset(text, mapping.source(view.state.selection.head, view.state.selection.head < view.state.selection.anchor ? 1 : -1));
55967
56593
  this.mode = mode;
55968
56594
  this.session.modeChanged();
55969
56595
  this.cancelExports();
@@ -55971,8 +56597,8 @@ var InkKitEditor = class InkKitEditor {
55971
56597
  this.literalMatch = void 0;
55972
56598
  if (mode === "source") {
55973
56599
  this.plain.value = text;
55974
- this.plain.setSelectionRange(this.plain.value.length, this.plain.value.length);
55975
- }
56600
+ this.plain.setSelectionRange(Math.min(anchor, head), Math.max(anchor, head), anchor > head ? "backward" : "forward");
56601
+ } else view.dispatch(view.state.tr.setSelection(TextSelection.between(view.state.doc.resolve(Math.min(anchor, view.state.doc.content.size)), view.state.doc.resolve(Math.min(head, view.state.doc.content.size)))).setMeta("addToHistory", false));
55976
56602
  const suppressed = this.reportingSuppressed;
55977
56603
  this.reportingSuppressed = true;
55978
56604
  try {
@@ -55980,12 +56606,14 @@ var InkKitEditor = class InkKitEditor {
55980
56606
  } finally {
55981
56607
  this.reportingSuppressed = suppressed;
55982
56608
  }
56609
+ const surface = this.literalSurface ? this.plain : container;
56610
+ surface.scrollTop = fraction * Math.max(0, surface.scrollHeight - surface.clientHeight);
56611
+ surface.scrollLeft = left;
55983
56612
  if (focused) {
55984
56613
  if (this.literalSurface) this.plain.focus({ preventScroll: true });
55985
- else view.focus();
56614
+ else view.dom.focus({ preventScroll: true });
56615
+ this.revealCurrentSelection();
55986
56616
  }
55987
- container.scrollTop = top;
55988
- container.scrollLeft = left;
55989
56617
  this.publishCommandState();
55990
56618
  return true;
55991
56619
  }
@@ -56493,6 +57121,10 @@ var InkKitEditor = class InkKitEditor {
56493
57121
  ...defaultLabels,
56494
57122
  ...options.labels
56495
57123
  }));
57124
+ ctx.set(codeControlCtx.key, (operation) => instance.forPerson(() => instance.facadeCall(() => {
57125
+ instance.assertMutation();
57126
+ return !instance.literalSurface && operation();
57127
+ }), false));
56496
57128
  ctx.set(tableControlCtx.key, (operation) => instance.forPerson(() => instance.facadeCall(() => {
56497
57129
  instance.assertMutation();
56498
57130
  return !instance.literalSurface && operation();
@@ -56557,7 +57189,7 @@ var InkKitEditor = class InkKitEditor {
56557
57189
  return true;
56558
57190
  }
56559
57191
  }));
56560
- }).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(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-yHFoKkKz.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, {
57192
+ }).use(labelsCtx).use(instance.pasteController.plugin).use(visibleClipboard).use($prose(() => instance.suggestions.plugin())).use(instance.keymapPlugin).use(createDialect(Boolean(options.images || options.files), Boolean(options.wikiLinks), Boolean(options.files))).use(codeControlCtx).use(codeControlsPlugin(() => instance.viewport().rect)).use(tablePlugins).use(tableBlock).use(instance.changePlugin).use(instance.historyInputPlugin).use(history).use(clipboard).use(cursor).use(taskListPlugin).use(quoteBackspace).use(calloutEnter).use(literalEnter).use(literalReread).use(listItemBackspace).use(listItemShiftTab).use(nestedItemEnter).use(rejoinLists).use(movedListItems).use(textInputPolicy).use(events.copy ? codeCopyPlugin((text) => events.copy?.(text)) : []).use(placeholderPlugin).use(options.rendering?.codeHighlighting === false ? [] : (await import("./highlight-BIasnL-b.js")).highlightPlugin).use(options.rendering?.diagramPreview === false || !diagramsAvailable() ? [] : mermaidPreview((error) => instance.report(error))).use(selectionPlugin).use($prose(() => search())).use(instance.policyPlugin).use(options.wikiLinks ? wikiLinkView(options.wikiLinks, {
56561
57193
  capture: () => instance.adapterCapture(),
56562
57194
  active: () => instance.ready && !instance.destroyed && !instance.literalSurface,
56563
57195
  labels: {