@aicayzer/inkkit 0.4.1 → 0.5.0

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.
@@ -28436,6 +28436,24 @@ var formatLossCodes = [
28436
28436
  "mark-spaces",
28437
28437
  "empty-line"
28438
28438
  ];
28439
+ /** The closed list of failure codes. The API reference describes each one. */
28440
+ var editorErrorCodes = [
28441
+ "not-ready",
28442
+ "destroyed",
28443
+ "stale-document",
28444
+ "composition",
28445
+ "operation-pending",
28446
+ "read-only",
28447
+ "invalid-argument",
28448
+ "invalid-range",
28449
+ "preservation",
28450
+ "image-unavailable",
28451
+ "diagram-unavailable",
28452
+ "file-unavailable",
28453
+ "clipboard-unavailable",
28454
+ "slash-command-failed",
28455
+ "internal"
28456
+ ];
28439
28457
  /** Every failure InkKit reports. Hosts branch on `code`; `message` is for logs. When the failure
28440
28458
  * started elsewhere, such as in an adapter, the host clipboard or the renderer, `cause` holds the
28441
28459
  * original error. */
@@ -28793,6 +28811,208 @@ function mermaidPreview(store, onError) {
28793
28811
  });
28794
28812
  }
28795
28813
  //#endregion
28814
+ //#region src/preserve-error.ts
28815
+ var PreservationError = class extends Error {
28816
+ constructor(message, options) {
28817
+ super(message, options);
28818
+ this.name = "PreservationError";
28819
+ }
28820
+ };
28821
+ var PreservationRefusal = class extends PreservationError {
28822
+ reason;
28823
+ constructor(reason, message) {
28824
+ super(message);
28825
+ this.reason = reason;
28826
+ }
28827
+ };
28828
+ var MarkdownParseRefusal = class extends InkKitError {
28829
+ constructor() {
28830
+ super("preservation", "The Markdown cannot be shown without losing part of it");
28831
+ }
28832
+ };
28833
+ function isPreservationRefusal(error) {
28834
+ return error instanceof PreservationRefusal || error instanceof MarkdownParseRefusal;
28835
+ }
28836
+ //#endregion
28837
+ //#region src/diagnostics.ts
28838
+ var observers = /* @__PURE__ */ new WeakMap();
28839
+ function observePreservation(ctx, observer) {
28840
+ if (observer) observers.set(ctx, observer);
28841
+ else observers.delete(ctx);
28842
+ }
28843
+ function tracePreservation(ctx, detail) {
28844
+ observers.get(ctx)?.(detail);
28845
+ }
28846
+ var Diagnostics = class {
28847
+ state;
28848
+ enabled = false;
28849
+ sessionId = randomSession();
28850
+ started = performance.now();
28851
+ sequence = 0;
28852
+ events = [];
28853
+ incident;
28854
+ dispose;
28855
+ latestState = {
28856
+ ready: false,
28857
+ revision: 0,
28858
+ format: "md",
28859
+ mode: "formatted",
28860
+ editable: false,
28861
+ composing: false,
28862
+ pending: false
28863
+ };
28864
+ currentState() {
28865
+ try {
28866
+ return this.latestState = this.state();
28867
+ } catch {
28868
+ return this.latestState;
28869
+ }
28870
+ }
28871
+ capture(detail) {
28872
+ try {
28873
+ this.record(detail);
28874
+ } catch {}
28875
+ }
28876
+ constructor(state) {
28877
+ this.state = state;
28878
+ }
28879
+ setEnabled(enabled, root, ctx) {
28880
+ if (enabled === this.enabled) return;
28881
+ this.dispose?.();
28882
+ this.dispose = void 0;
28883
+ observePreservation(ctx);
28884
+ this.enabled = enabled;
28885
+ this.events = [];
28886
+ this.incident = void 0;
28887
+ this.sequence = 0;
28888
+ if (!enabled) return;
28889
+ this.sessionId = randomSession();
28890
+ this.started = performance.now();
28891
+ const listeners = [];
28892
+ const listen = (target, name, run) => {
28893
+ target.addEventListener(name, run, true);
28894
+ listeners.push(() => target.removeEventListener(name, run, true));
28895
+ };
28896
+ for (const name of ["beforeinput", "input"]) listen(root, name, (event) => {
28897
+ const inputType = event.inputType ?? "";
28898
+ const input = inputType.includes("Composition") ? "composition" : inputType.startsWith("delete") ? "delete" : inputType.includes("Paste") ? "paste" : inputType.includes("Drop") ? "drop" : inputType.startsWith("history") ? "history" : inputType.startsWith("format") ? "format" : inputType === "insertText" ? "text" : "other";
28899
+ this.capture({
28900
+ kind: "input",
28901
+ phase: name === "beforeinput" ? "before" : "after",
28902
+ input
28903
+ });
28904
+ });
28905
+ for (const name of ["compositionstart", "compositionend"]) listen(root, name, () => this.capture({
28906
+ kind: "composition",
28907
+ phase: name === "compositionstart" ? "start" : "end"
28908
+ }));
28909
+ for (const name of ["focusin", "focusout"]) listen(root, name, () => this.capture({
28910
+ kind: "focus",
28911
+ phase: name === "focusin" ? "in" : "out"
28912
+ }));
28913
+ listen(root.ownerDocument, "selectionchange", () => {
28914
+ const document = root.ownerDocument;
28915
+ if (root.contains(document.activeElement) || root.contains(document.getSelection()?.anchorNode ?? null)) this.capture({ kind: "selection" });
28916
+ });
28917
+ this.dispose = () => listeners.forEach((remove) => remove());
28918
+ observePreservation(ctx, (detail) => this.capture(detail));
28919
+ this.capture({ kind: "enabled" });
28920
+ }
28921
+ record(detail) {
28922
+ if (!this.enabled) return;
28923
+ const safe = { ...detail };
28924
+ if (safe.code && !editorErrorCodes.includes(safe.code)) safe.code = "internal";
28925
+ if (safe.reason && ![
28926
+ "matching-budget",
28927
+ "source-location",
28928
+ "reopen-mismatch",
28929
+ "parse-refusal",
28930
+ "unexpected"
28931
+ ].includes(safe.reason)) safe.reason = "unexpected";
28932
+ const event = {
28933
+ ...safe,
28934
+ sequence: ++this.sequence,
28935
+ elapsedMs: Math.round(performance.now() - this.started),
28936
+ state: this.currentState()
28937
+ };
28938
+ this.events.push(event);
28939
+ if (this.events.length > 200) this.events.shift();
28940
+ if (this.incident) {
28941
+ if (this.incident.tail.length < 40) this.incident.tail.push(event);
28942
+ } else if (safe.code === "preservation" || safe.code === "internal" || safe.kind === "preservation" && safe.phase === "refused") this.incident = {
28943
+ triggerSequence: event.sequence,
28944
+ events: this.events.slice(),
28945
+ tail: []
28946
+ };
28947
+ }
28948
+ report(presentation) {
28949
+ return structuredClone({
28950
+ schemaVersion: 1,
28951
+ enabled: this.enabled,
28952
+ sessionId: this.sessionId,
28953
+ state: this.currentState(),
28954
+ events: this.events,
28955
+ ...this.incident ? { incident: this.incident } : {},
28956
+ ...presentation ? { presentation } : {}
28957
+ });
28958
+ }
28959
+ destroy(ctx) {
28960
+ this.dispose?.();
28961
+ observePreservation(ctx);
28962
+ this.dispose = void 0;
28963
+ this.enabled = false;
28964
+ this.events = [];
28965
+ this.incident = void 0;
28966
+ }
28967
+ };
28968
+ function diagnosticOperation(name) {
28969
+ if (name === "snapshot") return "snapshot";
28970
+ if (name === "undo" || name === "redo") return "history";
28971
+ if (name === "loadDocument" || name === "reloadDocument") return "load";
28972
+ if (name.startsWith("set")) return "configuration";
28973
+ if (name.includes("Snapshot") || name === "convertDocument") return "output";
28974
+ if (name.includes("TextRange") || name === "focus" || name === "blur") return "selection";
28975
+ if (name === "destroy") return "lifecycle";
28976
+ if ([
28977
+ "insertText",
28978
+ "replaceSource",
28979
+ "paste",
28980
+ "pasteAsPlainText",
28981
+ "format",
28982
+ "table",
28983
+ "moveBlock",
28984
+ "replace",
28985
+ "replaceAll",
28986
+ "insertImages",
28987
+ "appendImages",
28988
+ "insertFootnote",
28989
+ "insertPaths",
28990
+ "editReferenceDefinition"
28991
+ ].includes(name)) return "edit";
28992
+ return "other";
28993
+ }
28994
+ var diagnosticNumber = (value) => Number.isFinite(value) ? Math.round(Math.max(-1e7, Math.min(1e7, value)) * 100) / 100 : 0;
28995
+ function diagnosticReason(error) {
28996
+ try {
28997
+ for (let depth = 0; depth < 8 && error instanceof Error; depth++) {
28998
+ if (error instanceof MarkdownParseRefusal) return "parse-refusal";
28999
+ if (error instanceof PreservationRefusal) {
29000
+ const reason = error.reason;
29001
+ return reason === "matching-budget" || reason === "source-location" || reason === "reopen-mismatch" ? reason : "unexpected";
29002
+ }
29003
+ error = Object.getOwnPropertyDescriptor(error, "cause")?.value;
29004
+ }
29005
+ } catch {}
29006
+ return "unexpected";
29007
+ }
29008
+ function randomSession() {
29009
+ const bytes = crypto.getRandomValues(/* @__PURE__ */ new Uint8Array(16));
29010
+ bytes[6] = bytes[6] & 15 | 64;
29011
+ bytes[8] = bytes[8] & 63 | 128;
29012
+ const hex = Array.from(bytes, (byte) => byte.toString(16).padStart(2, "0")).join("");
29013
+ return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20)}`;
29014
+ }
29015
+ //#endregion
28796
29016
  //#region src/bom.ts
28797
29017
  function leadingBOMLength(text) {
28798
29018
  return text.startsWith("") ? 1 : 0;
@@ -44160,29 +44380,6 @@ function languageFence(value, language) {
44160
44380
  };
44161
44381
  }
44162
44382
  //#endregion
44163
- //#region src/preserve-error.ts
44164
- var PreservationError = class extends Error {
44165
- constructor(message, options) {
44166
- super(message, options);
44167
- this.name = "PreservationError";
44168
- }
44169
- };
44170
- var PreservationRefusal = class extends PreservationError {
44171
- reason;
44172
- constructor(reason, message) {
44173
- super(message);
44174
- this.reason = reason;
44175
- }
44176
- };
44177
- var MarkdownParseRefusal = class extends InkKitError {
44178
- constructor() {
44179
- super("preservation", "The Markdown cannot be shown without losing part of it");
44180
- }
44181
- };
44182
- function isPreservationRefusal(error) {
44183
- return error instanceof PreservationRefusal || error instanceof MarkdownParseRefusal;
44184
- }
44185
- //#endregion
44186
44383
  //#region src/parser.ts
44187
44384
  /**
44188
44385
  * Milkdown's parser state, except that it throws where Milkdown would drop content. Milkdown
@@ -44309,6 +44506,10 @@ async function resolveFile(adapter, reference, context) {
44309
44506
  "error"
44310
44507
  ].includes(presentation.kind)) throw new TypeError("File adapter returned an unsupported presentation");
44311
44508
  if (presentation.label !== void 0 && typeof presentation.label !== "string") throw new TypeError("File presentation label must be text");
44509
+ if (presentation.kind === "file") {
44510
+ if (presentation.layout !== void 0 && presentation.layout !== "fill") throw new TypeError("File presentation layout must be fill");
44511
+ if (presentation.activation !== void 0 && presentation.activation !== "card") throw new TypeError("File presentation activation must be card");
44512
+ }
44312
44513
  if (presentation.kind === "error" && typeof presentation.message !== "string") throw new TypeError("File presentation error must include a message");
44313
44514
  if ("url" in presentation) {
44314
44515
  if (typeof presentation.url !== "string" || !presentation.url.trim()) throw new TypeError("File presentation needs a resource URL");
@@ -51050,6 +51251,12 @@ var Preservation = class Preservation {
51050
51251
  try {
51051
51252
  return this.serializeWith(doc);
51052
51253
  } catch (cause) {
51254
+ tracePreservation(this.ctx, {
51255
+ kind: "preservation",
51256
+ phase: "refused",
51257
+ code: "preservation",
51258
+ reason: cause instanceof PreservationRefusal ? cause.reason : "unexpected"
51259
+ });
51053
51260
  if (cause instanceof PreservationError || isPreservationRefusal(cause)) throw cause;
51054
51261
  throw new PreservationError("Cannot preserve the edited Markdown", { cause });
51055
51262
  } finally {
@@ -51057,14 +51264,26 @@ var Preservation = class Preservation {
51057
51264
  }
51058
51265
  }
51059
51266
  serializeWith(doc) {
51060
- if (this.unchanged(doc)) return this.source;
51267
+ if (this.unchanged(doc)) {
51268
+ tracePreservation(this.ctx, {
51269
+ kind: "preservation",
51270
+ path: "unchanged"
51271
+ });
51272
+ return this.source;
51273
+ }
51061
51274
  let local = this.localEdits(doc);
51275
+ let path = "local";
51062
51276
  if (local === void 0) try {
51277
+ path = "regions";
51063
51278
  local = this.regions(doc);
51064
51279
  } catch (error) {
51065
51280
  if (!isPreservationRefusal(error)) throw error;
51066
51281
  local = void 0;
51067
51282
  }
51283
+ tracePreservation(this.ctx, {
51284
+ kind: "preservation",
51285
+ path: local === void 0 ? "whole" : path
51286
+ });
51068
51287
  return local === void 0 ? this.wholeDocument(doc) : this.checked(doc, local);
51069
51288
  }
51070
51289
  unchanged(doc) {
@@ -56472,6 +56691,8 @@ var ImageView = class {
56472
56691
  resolution;
56473
56692
  previewFailed = false;
56474
56693
  suspended = false;
56694
+ /** True while the card is itself the Open control (`activation: 'card'`). */
56695
+ cardKeys = false;
56475
56696
  /** Present when the host sets `files`, which then shows every image. */
56476
56697
  runtime;
56477
56698
  constructor(node, view, getPos, adapter, host, files, cache, canMutate = () => true) {
@@ -56527,7 +56748,7 @@ var ImageView = class {
56527
56748
  return true;
56528
56749
  }
56529
56750
  stopEvent(event) {
56530
- return event.target instanceof Element && !!event.target.closest(".inkkit-image-handle, .inkkit-file-control, audio, video, iframe");
56751
+ return event.target instanceof Element && (!!event.target.closest(".inkkit-image-handle, .inkkit-file-control, audio, video, iframe") || event.type === "keydown" && event.target === this.element && this.cardKeys);
56531
56752
  }
56532
56753
  ignoreMutation() {
56533
56754
  return true;
@@ -56644,7 +56865,25 @@ var ImageView = class {
56644
56865
  const selection = NodeSelection.create(state.doc, pos);
56645
56866
  if (!state.selection.eq(selection)) this.view.dispatch(state.tr.setSelection(selection));
56646
56867
  }
56647
- card(label, status, retry = false) {
56868
+ cardKey = (event) => {
56869
+ if (event.target !== this.element || event.isComposing) return;
56870
+ if (event.key !== "Enter" && event.key !== " ") return;
56871
+ event.preventDefault();
56872
+ this.activate();
56873
+ };
56874
+ cardClick = () => this.activate();
56875
+ /** Makes the whole card the one control that opens the file, in place of the arrow button. */
56876
+ activateCard(label) {
56877
+ const { element } = this;
56878
+ element.setAttribute("role", "button");
56879
+ element.setAttribute("aria-label", `${this.runtime.labels.fileOpen} ${label}`);
56880
+ element.tabIndex = 0;
56881
+ element.dataset.inkkitFileActivation = "card";
56882
+ this.cardKeys = true;
56883
+ element.addEventListener("click", this.cardClick);
56884
+ element.addEventListener("keydown", this.cardKey);
56885
+ }
56886
+ card(label, status, retry = false, activation = false) {
56648
56887
  if (this.resolution?.state.status !== "loading" && !this.handle) this.restoreResizeFocus = false;
56649
56888
  const footer = document.createElement("span");
56650
56889
  footer.className = "inkkit-file-footer";
@@ -56655,7 +56894,8 @@ var ImageView = class {
56655
56894
  text.textContent = label;
56656
56895
  text.title = label;
56657
56896
  name.append(text);
56658
- if (this.runtime?.adapter.open) {
56897
+ if (activation && this.runtime?.adapter.open) this.activateCard(label);
56898
+ else if (this.runtime?.adapter.open) {
56659
56899
  const open = this.button("", this.activate);
56660
56900
  open.classList.add("inkkit-file-open");
56661
56901
  open.setAttribute("aria-label", `${this.runtime.labels.fileOpen} ${label}`);
@@ -56691,6 +56931,12 @@ var ImageView = class {
56691
56931
  } : splitAlt(String(this.node.attrs.alt ?? ""));
56692
56932
  this.width = width;
56693
56933
  this.element.textContent = "";
56934
+ this.element.removeEventListener("click", this.cardClick);
56935
+ this.element.removeEventListener("keydown", this.cardKey);
56936
+ this.element.removeAttribute("tabindex");
56937
+ this.element.removeAttribute("data-inkkit-file-activation");
56938
+ this.element.removeAttribute("data-inkkit-file-layout");
56939
+ this.cardKeys = false;
56694
56940
  this.image = void 0;
56695
56941
  this.handle = void 0;
56696
56942
  if (this.runtime) {
@@ -56754,6 +57000,9 @@ var ImageView = class {
56754
57000
  this.pdf = pdf;
56755
57001
  this.element.append(pdf);
56756
57002
  this.card(label, this.runtime.labels.filePDFPreview);
57003
+ } else if (presentation.kind === "file") {
57004
+ if (presentation.layout === "fill") this.element.dataset.inkkitFileLayout = "fill";
57005
+ this.card(label, void 0, false, presentation.activation === "card");
56757
57006
  } else this.card(label);
56758
57007
  this.restoreHandleFocus();
56759
57008
  return;
@@ -56970,7 +57219,7 @@ async function createMilkdownEditor(root, options, runtime) {
56970
57219
  return true;
56971
57220
  }
56972
57221
  }));
56973
- }).use(labelsCtx).use(linkSchemesCtx).use(formatInputCtx).use(richTextGuard).use(runtime.plugins.paste).use(visibleClipboard).use(runtime.plugins.suggestions).use(runtime.plugins.keymap).use(createDialect(Boolean(options.images || options.files), Boolean(options.wikiLinks), Boolean(options.files))).use(codeControlCtx).use(codeControlsPlugin(() => runtime.visibleRect())).use(tablePlugins).use(tableBlock).use(runtime.plugins.change).use(runtime.plugins.historyInput).use(history).use(clipboard).use(cursor).use(taskListPlugin((operation) => runtime.mutate(operation))).use(undoTypedRule).use(quoteBackspace).use(calloutEnter).use(literalEnter).use(literalReread).use(listItemBackspace).use(nestedItemEnter).use(rejoinLists).use(movedListItems).use(textInputPolicy).use(runtime.copy ? codeCopyPlugin((text) => runtime.copy?.(text)) : []).use(placeholderPlugin).use(options.rendering?.codeHighlighting === false ? [] : (await import("./highlight-CqtvWKCp.js")).highlightPlugin).use(options.rendering?.diagramPreview === false || !diagramsAvailable() ? [] : mermaidPreview(runtime.diagrams, (error) => runtime.report(error))).use(selectionPlugin).use($prose(() => search())).use(runtime.plugins.policy).use(options.wikiLinks ? wikiLinkView(options.wikiLinks, {
57222
+ }).use(labelsCtx).use(linkSchemesCtx).use(formatInputCtx).use(richTextGuard).use(runtime.plugins.paste).use(visibleClipboard).use(runtime.plugins.suggestions).use(runtime.plugins.keymap).use(createDialect(Boolean(options.images || options.files), Boolean(options.wikiLinks), Boolean(options.files))).use(codeControlCtx).use(codeControlsPlugin(() => runtime.visibleRect())).use(tablePlugins).use(tableBlock).use(runtime.plugins.change).use(runtime.plugins.historyInput).use(history).use(clipboard).use(cursor).use(taskListPlugin((operation) => runtime.mutate(operation))).use(undoTypedRule).use(quoteBackspace).use(calloutEnter).use(literalEnter).use(literalReread).use(listItemBackspace).use(nestedItemEnter).use(rejoinLists).use(movedListItems).use(textInputPolicy).use(runtime.copy ? codeCopyPlugin((text) => runtime.copy?.(text)) : []).use(placeholderPlugin).use(options.rendering?.codeHighlighting === false ? [] : (await import("./highlight-D3LQRB1-.js")).highlightPlugin).use(options.rendering?.diagramPreview === false || !diagramsAvailable() ? [] : mermaidPreview(runtime.diagrams, (error) => runtime.report(error))).use(selectionPlugin).use($prose(() => search())).use(runtime.plugins.policy).use(options.wikiLinks ? wikiLinkView(options.wikiLinks, {
56974
57223
  capture: () => runtime.capture(),
56975
57224
  active: () => runtime.active(),
56976
57225
  labels: {
@@ -59576,6 +59825,7 @@ var stateEvents = ["changed", "commandStateChanged"];
59576
59825
  var InkKitEditor = class InkKitEditor {
59577
59826
  options;
59578
59827
  editor;
59828
+ diagnostics = new Diagnostics(() => this.diagnosticState());
59579
59829
  document = new EditorDocument({
59580
59830
  ctx: () => this.editor.ctx,
59581
59831
  literal: () => this.literalSurface,
@@ -59710,7 +59960,7 @@ var InkKitEditor = class InkKitEditor {
59710
59960
  try {
59711
59961
  markdown = this.document.currentText(view.state.doc);
59712
59962
  } catch (error) {
59713
- this.events.error?.(coded(error, "preservation"));
59963
+ this.report(coded(error, "preservation"));
59714
59964
  return;
59715
59965
  }
59716
59966
  if (this.literalSurface) this.source.reconcileFormattedChange(markdown, view.state.doc.attrs[sourceAttribute]);
@@ -59909,12 +60159,23 @@ var InkKitEditor = class InkKitEditor {
59909
60159
  }
59910
60160
  /** Reports a refused edit to the facade call that made it, or else to `events.error`. */
59911
60161
  refuse(error) {
60162
+ this.diagnostics.capture({
60163
+ kind: "error",
60164
+ code: error.code,
60165
+ ...error.code === "preservation" ? { reason: diagnosticReason(error) } : {}
60166
+ });
59912
60167
  if (this.call) this.call.refusal ??= error;
59913
60168
  else this.events.error?.(error);
59914
60169
  }
59915
60170
  /** Reports a failure no call is waiting for. */
59916
60171
  report(error) {
59917
- this.events.error?.(coded(error));
60172
+ const failure = coded(error);
60173
+ this.diagnostics.capture({
60174
+ kind: "error",
60175
+ code: failure.code,
60176
+ ...failure.code === "preservation" ? { reason: diagnosticReason(failure) } : {}
60177
+ });
60178
+ this.events.error?.(failure);
59918
60179
  }
59919
60180
  /** Runs a facade method for something the person did, such as a shortcut. Its failure goes to
59920
60181
  * `events.error`, unless a host call such as `keyDown` is waiting for it. */
@@ -59930,6 +60191,93 @@ var InkKitEditor = class InkKitEditor {
59930
60191
  get composing() {
59931
60192
  return this.literalSurface ? this.source.composing : this.editor.ctx.get(editorViewCtx).composing;
59932
60193
  }
60194
+ diagnosticState() {
60195
+ const view = this.editor?.ctx.get(editorViewCtx);
60196
+ const literal = this.source && this.literalSurface;
60197
+ return {
60198
+ ready: this.ready,
60199
+ revision: this.session.revision,
60200
+ generation: this.session.generation,
60201
+ format: this.document.format,
60202
+ mode: literal ? "source" : "formatted",
60203
+ editable: this.writable,
60204
+ composing: Boolean(literal ? this.source.composing : view?.composing),
60205
+ pending: Boolean(this.pasteController?.pending),
60206
+ ...view ? { selection: literal ? {
60207
+ from: this.source.element.selectionStart,
60208
+ to: this.source.element.selectionEnd
60209
+ } : {
60210
+ from: view.state.selection.from,
60211
+ to: view.state.selection.to
60212
+ } } : {}
60213
+ };
60214
+ }
60215
+ /** Starts or clears content-free local tracing. Disabled by default. */
60216
+ setDiagnosticsEnabled(enabled) {
60217
+ this.assertAlive();
60218
+ if (typeof enabled !== "boolean") throw invalidArgument("enabled must be a boolean");
60219
+ this.diagnostics.setEnabled(enabled, this.root, this.editor.ctx);
60220
+ }
60221
+ /** Reads diagnostics without serializing the document or requiring an editable surface. */
60222
+ diagnosticReport() {
60223
+ this.assertAlive();
60224
+ let presentation;
60225
+ if (this.diagnostics.enabled) try {
60226
+ const view = this.editor.ctx.get(editorViewCtx);
60227
+ const literal = this.literalSurface;
60228
+ const selection = view.state.selection;
60229
+ const dom = literal ? this.source.element : view.domAtPos(selection.from).node;
60230
+ const element = dom.nodeType === 1 ? dom : dom.parentElement ?? view.dom;
60231
+ const window = this.root.ownerDocument.defaultView;
60232
+ const style = window.getComputedStyle(element);
60233
+ const fontFamily = style.fontFamily.slice(0, 160).replace(/[^\p{L}\p{N} ,"'_-]/gu, "");
60234
+ const type = selection.$from.parent.type.name;
60235
+ const block = literal ? "literal" : type === "paragraph" ? "paragraph" : type === "heading" ? "heading" : type === "code_block" ? "code" : "other";
60236
+ const markNames = [
60237
+ "strong",
60238
+ "emphasis",
60239
+ "inlineCode",
60240
+ "strike_through",
60241
+ "link",
60242
+ "highlight"
60243
+ ];
60244
+ const marks = literal ? [] : (view.state.storedMarks ?? selection.$from.marks()).slice(0, 12).map((mark) => markNames.find((name) => name === mark.type.name) ?? "other");
60245
+ const bounds = literal ? void 0 : view.coordsAtPos(selection.from);
60246
+ presentation = {
60247
+ fontFamily,
60248
+ fontSize: diagnosticNumber(Number.parseFloat(style.fontSize)),
60249
+ fontWeight: diagnosticNumber(style.fontWeight === "normal" ? 400 : style.fontWeight === "bold" ? 700 : Number.parseFloat(style.fontWeight)),
60250
+ fontStyle: style.fontStyle === "normal" || style.fontStyle === "italic" || style.fontStyle === "oblique" ? style.fontStyle : "other",
60251
+ ...!literal && type === "heading" && [
60252
+ 1,
60253
+ 2,
60254
+ 3,
60255
+ 4,
60256
+ 5,
60257
+ 6
60258
+ ].includes(selection.$from.parent.attrs.level) ? { headingLevel: selection.$from.parent.attrs.level } : {},
60259
+ ...style.lineHeight !== "normal" ? { lineHeight: diagnosticNumber(Number.parseFloat(style.lineHeight)) } : {},
60260
+ block,
60261
+ marks,
60262
+ caretColor: /^(?:rgba?\([\d.,% ]+\)|#[\da-f]{3,8}|auto|transparent|currentcolor)$/i.test(style.caretColor) ? style.caretColor.slice(0, 80) : "other",
60263
+ ...bounds ? { caret: {
60264
+ left: diagnosticNumber(bounds.left),
60265
+ top: diagnosticNumber(bounds.top),
60266
+ right: diagnosticNumber(bounds.right),
60267
+ bottom: diagnosticNumber(bounds.bottom)
60268
+ } } : {},
60269
+ viewport: {
60270
+ width: diagnosticNumber(window.innerWidth),
60271
+ height: diagnosticNumber(window.innerHeight)
60272
+ },
60273
+ scroll: {
60274
+ left: diagnosticNumber(this.geometry.container.scrollLeft),
60275
+ top: diagnosticNumber(this.geometry.container.scrollTop)
60276
+ }
60277
+ };
60278
+ } catch {}
60279
+ return this.diagnostics.report(presentation);
60280
+ }
59933
60281
  /** The formats the host offered at mount, in its order, with what saving each promises. */
59934
60282
  get formats() {
59935
60283
  this.assertAlive();
@@ -60263,6 +60611,7 @@ var InkKitEditor = class InkKitEditor {
60263
60611
  * throws `destroyed`. */
60264
60612
  async destroy() {
60265
60613
  if (this.destroyed) return;
60614
+ this.diagnostics.destroy(this.editor.ctx);
60266
60615
  this.destroyed = true;
60267
60616
  this.clipboard.clear();
60268
60617
  this.ready = false;
@@ -60971,12 +61320,46 @@ var InkKitEditor = class InkKitEditor {
60971
61320
  "viewport",
60972
61321
  "headings",
60973
61322
  "navigateHeading",
60974
- "destroy"
61323
+ "destroy",
61324
+ "setDiagnosticsEnabled",
61325
+ "diagnosticReport"
60975
61326
  ]) {
60976
61327
  const method = InkKitEditor.prototype[name];
60977
61328
  Object.defineProperty(InkKitEditor.prototype, name, {
60978
61329
  value(...args) {
60979
- return this.facadeCall(() => method.apply(this, args));
61330
+ if (name === "setDiagnosticsEnabled" || name === "diagnosticReport") return method.apply(this, args);
61331
+ if (!this.diagnostics.enabled) return this.facadeCall(() => method.apply(this, args));
61332
+ const operation = diagnosticOperation(name);
61333
+ this.diagnostics.capture({
61334
+ kind: "operation",
61335
+ phase: "begin",
61336
+ operation
61337
+ });
61338
+ const success = (value) => {
61339
+ this.diagnostics.capture({
61340
+ kind: "operation",
61341
+ phase: "success",
61342
+ operation
61343
+ });
61344
+ return value;
61345
+ };
61346
+ const refused = (error) => {
61347
+ const failure = coded(error);
61348
+ this.diagnostics.capture({
61349
+ kind: "operation",
61350
+ phase: "refused",
61351
+ operation,
61352
+ code: failure.code,
61353
+ ...failure.code === "preservation" ? { reason: diagnosticReason(failure) } : {}
61354
+ });
61355
+ throw failure;
61356
+ };
61357
+ try {
61358
+ const result = this.facadeCall(() => method.apply(this, args));
61359
+ return result instanceof Promise ? result.then(success, refused) : success(result);
61360
+ } catch (error) {
61361
+ return refused(error);
61362
+ }
60980
61363
  },
60981
61364
  writable: true,
60982
61365
  configurable: true
@@ -1,4 +1,4 @@
1
- import { a as Decoration, c as PluginKey, d as __toESM, i as $prose, o as DecorationSet, s as Plugin, u as __commonJSMin } from "./core-CisgnAp-.js";
1
+ import { a as Decoration, c as PluginKey, d as __toESM, i as $prose, o as DecorationSet, s as Plugin, u as __commonJSMin } from "./core-fEBASTpF.js";
2
2
  //#region node_modules/.pnpm/highlight.js@11.11.2/node_modules/highlight.js/es/languages/arduino.js
3
3
  /** @type LanguageFn */
4
4
  function cPlusPlus(hljs) {
package/dist/core.d.ts CHANGED
@@ -3,3 +3,4 @@ export type { EditorEvents, EditorOptions, TextInputPreferences, CommandState, E
3
3
  export { InkKitError } from './types.js';
4
4
  export type { DocumentFormat, TextDocumentFormat, TextDocumentInput, RichTextDocumentInput, TextDocumentSnapshot, RichTextDocumentSnapshot, RichTextDocument, RichTextBlock, RichTextListItem, RichTextTableCell, RichTextInline, RichTextMark, FormatLoss, FormatLossCode, ConvertedDocument, FormatPromise, OfferedFormat, EditorLabels, Heading, TableCommand, TableOptions, BlockMoveDirection, DocumentContext, DocumentInput, DocumentSnapshot, DocumentVersion, EditorErrorCode, ImageAdapter, ImageImportContext, WikiLinkReference, WikiLinkAdapter, FileReference, ClientPoint, FileContext, FilePresentation, FileAdapter, CapturedImage, PortableImage, ClipboardInput, ClipboardOutput, ClipboardScope, ClipboardImage, ClipboardDiagram, PrintableDocument, PrintableWarning, TextRange, ReadableTextSnapshot, TextRect, ViewportInsets, ViewportOptions, ViewportSnapshot, } from './types.js';
5
5
  export type { SlashMenuOptions, SlashCommandName, SlashCommand } from './types.js';
6
+ export type { DiagnosticReport, DiagnosticEvent, DiagnosticState, DiagnosticPresentation, } from './diagnostics.js';
package/dist/core.js CHANGED
@@ -1,2 +1,2 @@
1
- import { r as InkKitError, t as InkKitEditor } from "./chunks/core-CisgnAp-.js";
1
+ import { r as InkKitError, t as InkKitEditor } from "./chunks/core-fEBASTpF.js";
2
2
  export { InkKitEditor, InkKitError };
@@ -0,0 +1,94 @@
1
+ import { type DocumentFormat, type EditorErrorCode } from './types.js';
2
+ /** Content-free editor counters and input guards. */
3
+ export type DiagnosticState = {
4
+ ready: boolean;
5
+ revision: number;
6
+ generation?: number;
7
+ format: DocumentFormat;
8
+ mode: 'formatted' | 'source';
9
+ editable: boolean;
10
+ composing: boolean;
11
+ pending: boolean;
12
+ selection?: {
13
+ from: number;
14
+ to: number;
15
+ };
16
+ };
17
+ /** Closed categories, never raw browser input, method arguments or error messages. */
18
+ export type DiagnosticEvent = {
19
+ sequence: number;
20
+ elapsedMs: number;
21
+ kind: 'input' | 'composition' | 'focus' | 'selection' | 'operation' | 'preservation' | 'error' | 'enabled';
22
+ state: DiagnosticState;
23
+ phase?: 'before' | 'after' | 'start' | 'end' | 'in' | 'out' | 'begin' | 'success' | 'refused';
24
+ input?: 'text' | 'composition' | 'delete' | 'paste' | 'drop' | 'history' | 'format' | 'other';
25
+ operation?: 'snapshot' | 'edit' | 'history' | 'load' | 'configuration' | 'output' | 'selection' | 'lifecycle' | 'other';
26
+ code?: EditorErrorCode;
27
+ path?: 'unchanged' | 'local' | 'regions' | 'whole';
28
+ reason?: 'matching-budget' | 'source-location' | 'reopen-mismatch' | 'parse-refusal' | 'unexpected';
29
+ };
30
+ /** Declared CSS configuration and geometry, measured only when a report is requested. */
31
+ export type DiagnosticPresentation = {
32
+ fontFamily: string;
33
+ fontSize: number;
34
+ fontWeight: number;
35
+ fontStyle: 'normal' | 'italic' | 'oblique' | 'other';
36
+ headingLevel?: 1 | 2 | 3 | 4 | 5 | 6;
37
+ lineHeight?: number;
38
+ block: 'paragraph' | 'heading' | 'code' | 'other' | 'literal';
39
+ marks: ('strong' | 'emphasis' | 'inlineCode' | 'strike_through' | 'link' | 'highlight' | 'other')[];
40
+ caretColor: string;
41
+ caret?: {
42
+ left: number;
43
+ top: number;
44
+ right: number;
45
+ bottom: number;
46
+ };
47
+ viewport: {
48
+ width: number;
49
+ height: number;
50
+ };
51
+ scroll: {
52
+ left: number;
53
+ top: number;
54
+ };
55
+ };
56
+ /** An opt-in local report. Disabling diagnostics clears all retained events and the incident. */
57
+ export type DiagnosticReport = {
58
+ schemaVersion: 1;
59
+ enabled: boolean;
60
+ sessionId: string;
61
+ state: DiagnosticState;
62
+ events: DiagnosticEvent[];
63
+ incident?: {
64
+ triggerSequence: number;
65
+ events: DiagnosticEvent[];
66
+ tail: DiagnosticEvent[];
67
+ };
68
+ presentation?: DiagnosticPresentation;
69
+ };
70
+ type Detail = Omit<DiagnosticEvent, 'sequence' | 'elapsedMs' | 'state'>;
71
+ export declare function observePreservation(ctx: object, observer?: (detail: Detail) => void): void;
72
+ export declare function tracePreservation(ctx: object, detail: Detail): void;
73
+ export declare class Diagnostics {
74
+ private readonly state;
75
+ enabled: boolean;
76
+ private sessionId;
77
+ private started;
78
+ private sequence;
79
+ private events;
80
+ private incident?;
81
+ private dispose?;
82
+ private latestState;
83
+ private currentState;
84
+ capture(detail: Detail): void;
85
+ constructor(state: () => DiagnosticState);
86
+ setEnabled(enabled: boolean, root: HTMLElement, ctx: object): void;
87
+ private record;
88
+ report(presentation?: DiagnosticPresentation): DiagnosticReport;
89
+ destroy(ctx: object): void;
90
+ }
91
+ export declare function diagnosticOperation(name: string): DiagnosticEvent['operation'];
92
+ export declare const diagnosticNumber: (value: number) => number;
93
+ export declare function diagnosticReason(error: unknown): DiagnosticEvent['reason'];
94
+ export {};
package/dist/editor.d.ts CHANGED
@@ -1,3 +1,4 @@
1
+ import { type DiagnosticReport } from './diagnostics.js';
1
2
  import type { SlashMenuOptions } from './types.js';
2
3
  import { InkKitError, type ClientPoint, type DocumentFormat, type DocumentInput, type DocumentSnapshot, type ConvertedDocument, type TextDocumentFormat, type ImageAdapter, type FileAdapter, type WikiLinkAdapter, type ClipboardInput, type ClipboardOutput, type ClipboardScope, type DocumentVersion, type PrintableDocument, type TextRange, type ReadableTextSnapshot, type TextRect, type ViewportOptions, type ViewportSnapshot, type EditorLabels, type Heading, type TableCommand, type TableOptions, type BlockMoveDirection, type OfferedFormat } from './types.js';
3
4
  /** An inline format that `CaretState` can report. */
@@ -157,6 +158,7 @@ export declare const editorEvents: readonly ["changed", "commandStateChanged", "
157
158
  export declare class InkKitEditor<F extends DocumentFormat = TextDocumentFormat> {
158
159
  private readonly options;
159
160
  private editor;
161
+ private readonly diagnostics;
160
162
  private readonly document;
161
163
  private readonly history;
162
164
  private readonly text;
@@ -248,6 +250,11 @@ export declare class InkKitEditor<F extends DocumentFormat = TextDocumentFormat>
248
250
  * `events.error`, unless a host call such as `keyDown` is waiting for it. */
249
251
  private forPerson;
250
252
  private get composing();
253
+ private diagnosticState;
254
+ /** Starts or clears content-free local tracing. Disabled by default. */
255
+ setDiagnosticsEnabled(enabled: boolean): void;
256
+ /** Reads diagnostics without serializing the document or requiring an editable surface. */
257
+ diagnosticReport(): DiagnosticReport;
251
258
  /** The formats the host offered at mount, in its order, with what saving each promises. */
252
259
  get formats(): readonly OfferedFormat[];
253
260
  /** Whether the person can edit. A read-only editor still permits selection, copying and find. */
package/dist/index.js CHANGED
@@ -1,4 +1,4 @@
1
- import { n as registerDiagram, r as InkKitError, t as InkKitEditor } from "./chunks/core-CisgnAp-.js";
1
+ import { n as registerDiagram, r as InkKitError, t as InkKitEditor } from "./chunks/core-fEBASTpF.js";
2
2
  //#region node_modules/.pnpm/dompurify@3.4.17/node_modules/dompurify/dist/purify.es.mjs
3
3
  /*! @license DOMPurify 3.4.17 | (c) Cure53 and other contributors | Released under the Apache license 2.0 and Mozilla Public License 2.0 | github.com/cure53/DOMPurify/blob/3.4.17/LICENSE */
4
4
  function _OverloadYield(e, d) {
package/dist/style.css CHANGED
@@ -376,6 +376,22 @@
376
376
  white-space: normal;
377
377
  }
378
378
 
379
+ /* A file presentation with `layout: 'fill'` takes the column's width instead of its label's. */
380
+ .inkkit-root
381
+ .ProseMirror
382
+ .inkkit-image.inkkit-file[data-inkkit-file-layout='fill'] {
383
+ display: block;
384
+ width: 100%;
385
+ }
386
+ .inkkit-root .ProseMirror .inkkit-file[data-inkkit-file-activation='card'] {
387
+ cursor: pointer;
388
+ }
389
+ .inkkit-root
390
+ .ProseMirror
391
+ .inkkit-file[data-inkkit-file-activation='card']:hover {
392
+ background: var(--_inkkit-code-bg);
393
+ }
394
+
379
395
  .inkkit-root .ProseMirror .inkkit-file-footer {
380
396
  display: flex;
381
397
  flex-wrap: wrap;
@@ -1103,6 +1119,7 @@
1103
1119
  /* One ring for every control InkKit draws; the editing surfaces can be set apart. */
1104
1120
  .inkkit-root button:focus-visible,
1105
1121
  .inkkit-root .inkkit-image-handle:focus-visible,
1122
+ .inkkit-root .inkkit-file[data-inkkit-file-activation='card']:focus-visible,
1106
1123
  .inkkit-root .inkkit-file audio:focus-visible,
1107
1124
  .inkkit-root .inkkit-file video:focus-visible {
1108
1125
  outline: var(--inkkit-focus-width, 2px) solid
package/dist/types.d.ts CHANGED
@@ -212,7 +212,14 @@ export type FilePresentation = {
212
212
  label?: string;
213
213
  mimeType?: string;
214
214
  } | {
215
- kind: 'file' | 'missing';
215
+ kind: 'file';
216
+ label?: string;
217
+ /** `'fill'` stretches the card to the full width of the column. */
218
+ layout?: 'fill';
219
+ /** `'card'` makes the whole card the one control that calls `FileAdapter.open`. */
220
+ activation?: 'card';
221
+ } | {
222
+ kind: 'missing';
216
223
  label?: string;
217
224
  } | {
218
225
  kind: 'error';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aicayzer/inkkit",
3
- "version": "0.4.1",
3
+ "version": "0.5.0",
4
4
  "description": "A preserving Markdown editor for offline web views and the web",
5
5
  "license": "MIT",
6
6
  "type": "module",