@aicayzer/inkkit 0.1.7 → 0.2.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.
package/README.md CHANGED
@@ -1,48 +1,32 @@
1
1
  # Inkkit
2
2
 
3
- A Markdown editor for the web and for apps that host a web view. It edits formatted text and keeps your Markdown as you wrote it: a document you open and leave is saved unchanged, and an edit rewrites only the blocks it touches.
3
+ A Markdown editor for web pages and for apps that host a web view. People edit formatted text, and your app saves the Markdown as it was written: an unedited document is saved unchanged, and an edit rewrites only the blocks it touches.
4
4
 
5
- - Formatted and source editing, with one undo history
6
- - Tables, task lists, footnotes, callouts, highlights and reference links
7
- - Mermaid diagram previews, offline
8
- - Copy as readable text or HTML, or copy the Markdown itself
9
- - Images and files through your own storage adapter
10
- - Your app owns the toolbar, menus, files and appearance
11
-
12
- ## How it works
5
+ <details>
6
+ <summary>How Inkkit preserves Markdown</summary>
13
7
 
14
8
  <picture>
15
- <source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/aicayzer/inkkit/main/docs/images/how-inkkit-works-dark.svg">
16
- <img src="https://raw.githubusercontent.com/aicayzer/inkkit/main/docs/images/how-inkkit-works.svg" width="800" alt="An ordinary Markdown editor rebuilds the whole file when it saves, so list markers, spacing and escaping change. Inkkit loads the file and edits it formatted with Milkdown. A preservation layer between Milkdown and the file saves a note you open and leave byte for byte unchanged. After an edit, it keeps unchanged blocks as their original bytes, writes only edited blocks with Milkdown, and reads the result back. If it reopens as the same document, the edit is kept and saved. If not, the edit is refused and the note stays as it was. Your app owns the files, storage, toolbar and appearance.">
9
+ <source media="(prefers-color-scheme: dark)" srcset="docs/images/how-inkkit-works-dark.svg">
10
+ <img src="docs/images/how-inkkit-works.svg" alt="Inkkit keeps unchanged source and checks edited blocks before your app saves them.">
17
11
  </picture>
18
12
 
19
- A formatted Markdown editor usually rebuilds the whole file when it saves, so opening and saving a note can change list markers, spacing and escaping that you never touched. Inkkit adds a preservation layer between [Milkdown](https://milkdown.dev) and the file. A note that you open and leave is saved byte for byte unchanged, and after an edit only the blocks you edited are rewritten. Inkkit reads the result of each edit back and refuses the edit if it would not reopen as the same document.
20
-
21
- For the exact guarantees, see [Preservation](https://github.com/aicayzer/inkkit/blob/main/docs/preservation.md).
13
+ </details>
22
14
 
23
- ## Install
15
+ ## Get started
24
16
 
25
17
  ```sh
26
18
  npm install @aicayzer/inkkit
27
19
  ```
28
20
 
29
- An app that does not render diagrams can import from `@aicayzer/inkkit/core` instead. That entry leaves Mermaid out of the app's bundle and keeps diagrams as editable source. See [Builds without diagrams](https://github.com/aicayzer/inkkit/blob/main/docs/api.md#builds-without-diagrams).
30
-
31
- ## Supported engines
32
-
33
- Inkkit supports Safari and `WKWebView` 17.5 or later (iOS 17.5, macOS 14.5), Chrome and Edge 123 or later, and Firefox 125 or later. The host page must declare UTF-8. See [Supported engines](https://github.com/aicayzer/inkkit/blob/main/docs/integration.md#supported-engines) for the APIs behind these versions.
34
-
35
- ## Use
36
-
37
21
  ```ts
38
22
  import { InkKitEditor } from '@aicayzer/inkkit'
39
23
  import '@aicayzer/inkkit/style.css'
40
24
 
41
25
  const editor = await InkKitEditor.mount(document.getElementById('editor')!, {
42
- changed({ generation, revision, text }) {},
43
- commandStateChanged({ caret }) {},
44
- openLink(href) {},
45
- copy(text) {},
26
+ changed() {},
27
+ error(error) {
28
+ console.error(error.code, error.message)
29
+ },
46
30
  })
47
31
 
48
32
  editor.loadDocument({
@@ -55,17 +39,9 @@ editor.loadDocument({
55
39
  const { text } = editor.snapshot(1)
56
40
  ```
57
41
 
58
- ## Built with Inkkit
59
-
60
- - [Memos](https://github.com/aicayzer/memos): a small Mac app for memos, in one floating window
61
- - [PadPad](https://github.com/aicayzer/padpad): a small Mac app for quick text and files
62
-
63
- ## Learn more
42
+ ## Documentation
64
43
 
65
- - [Supported syntax](https://github.com/aicayzer/inkkit/blob/main/docs/supported-syntax.md)
66
- - [Integration guide](https://github.com/aicayzer/inkkit/blob/main/docs/integration.md)
67
- - [API reference](https://github.com/aicayzer/inkkit/blob/main/docs/api.md)
68
- - [All documentation](https://github.com/aicayzer/inkkit/blob/main/docs/README.md)
44
+ [inkkit.cyzr.me](https://inkkit.cyzr.me) covers how Inkkit works, compatibility, integration guides and the API reference.
69
45
 
70
46
  ## License
71
47
 
@@ -35008,7 +35008,7 @@ var associationAttrs = {
35008
35008
  identifier: { default: "" }
35009
35009
  };
35010
35010
  /** Whether an element reads as `text`, with whitespace collapsed as shown. */
35011
- function showsText$1(dom, text) {
35011
+ function showsText(dom, text) {
35012
35012
  const shown = (value) => value.replace(/\s+/g, " ").trim();
35013
35013
  return shown(dom.textContent ?? "") === shown(text);
35014
35014
  }
@@ -35033,7 +35033,7 @@ var references = [
35033
35033
  const url = dom.getAttribute("data-inkkit-url") ?? "";
35034
35034
  const label = dom.getAttribute("data-inkkit-label") ?? "";
35035
35035
  const title = dom.getAttribute("data-inkkit-title");
35036
- if (!isSafeLinkTarget(url) || !showsText$1(dom, definitionText(label, url, title))) return false;
35036
+ if (!isSafeLinkTarget(url) || !showsText(dom, definitionText(label, url, title))) return false;
35037
35037
  return {
35038
35038
  label,
35039
35039
  identifier: dom.getAttribute("data-inkkit-reference-definition") ?? "",
@@ -35085,7 +35085,7 @@ var references = [
35085
35085
  tag: "sup[data-inkkit-footnote-reference]",
35086
35086
  getAttrs: (dom) => {
35087
35087
  const label = dom.getAttribute("data-inkkit-label") ?? "";
35088
- return showsText$1(dom, label) ? {
35088
+ return showsText(dom, label) ? {
35089
35089
  label,
35090
35090
  identifier: dom.getAttribute("data-inkkit-footnote-reference") ?? ""
35091
35091
  } : false;
@@ -35529,7 +35529,7 @@ var wikiLinkSchema = $node("inkkit_wiki_link", () => ({
35529
35529
  tag: "span[data-inkkit-wiki]",
35530
35530
  getAttrs(dom) {
35531
35531
  const parsed = parseLinkedSyntax(dom.getAttribute("data-inkkit-wiki-raw") ?? "", dom.getAttribute("data-inkkit-wiki-table") === "true");
35532
- return parsed && !parsed.embed && showsWikiTarget(parsed) && showsText$1(dom, parsed.label) ? {
35532
+ return parsed && !parsed.embed && showsWikiTarget(parsed) && showsText(dom, parsed.label) ? {
35533
35533
  raw: parsed.raw,
35534
35534
  target: parsed.target,
35535
35535
  fragment: parsed.fragment,
@@ -43736,6 +43736,7 @@ function commentNode(inline) {
43736
43736
  marks: "",
43737
43737
  code: true,
43738
43738
  defining: true,
43739
+ createGapCursor: !inline,
43739
43740
  attrs: {
43740
43741
  syntax: { default: "html" },
43741
43742
  block: { default: !inline }
@@ -44924,50 +44925,63 @@ function blockDropRange(view, x, y) {
44924
44925
  * callout, where typing would change text the reader can't see or didn't
44925
44926
  * choose.
44926
44927
  */
44927
- function caretAfter(doc, pos) {
44928
+ function caretAfter(doc, pos, folds = /* @__PURE__ */ new Map()) {
44929
+ const hides = (node) => isComment(node) || node.type.name === "inkkit_callout" && (folds.get(node) ?? node.attrs.fold === "-");
44930
+ const concealedDepth = ($pos) => {
44931
+ for (let depth = 1; depth <= $pos.depth; depth++) if (hides($pos.node(depth))) return depth;
44932
+ return -1;
44933
+ };
44934
+ const concealed = ($pos) => concealedDepth($pos) >= 0;
44928
44935
  const $pos = doc.resolve(pos);
44929
44936
  const before = $pos.nodeBefore;
44930
44937
  if (before?.isTextblock && !concealed(doc.resolve(pos - 1))) return TextSelection.create(doc, pos - 1);
44931
- if (GapCursor.valid($pos)) return new GapCursor($pos);
44938
+ const gap = GapCursor;
44939
+ const forward = before && !showsText(before) ? shownText(doc, pos, 1) : null;
44940
+ if (forward) return forward;
44941
+ if (gap.valid($pos)) return new GapCursor($pos);
44932
44942
  const [first, second] = before && !showsText(before) ? [1, -1] : [-1, 1];
44933
44943
  return shownText(doc, pos, first) ?? shownText(doc, pos, second) ?? Selection.near($pos);
44934
- }
44935
- function hides(node) {
44936
- return isComment(node) || node.type.name === "inkkit_callout" && node.attrs.fold === "-";
44937
- }
44938
- function concealedDepth($pos) {
44939
- for (let depth = 1; depth <= $pos.depth; depth++) if (hides($pos.node(depth))) return depth;
44940
- return -1;
44941
- }
44942
- function concealed($pos) {
44943
- return concealedDepth($pos) >= 0;
44944
- }
44945
- function showsText(node) {
44946
- if (hides(node)) return false;
44947
- if (node.isTextblock) return true;
44948
- let found = false;
44949
- node.descendants((child) => {
44950
- if (found || hides(child)) return false;
44951
- if (child.isTextblock) found = true;
44952
- return !found;
44953
- });
44954
- return found;
44955
- }
44956
- function shownText(doc, pos, dir) {
44957
- let $from = doc.resolve(pos);
44958
- for (;;) {
44959
- const found = TextSelection.findFrom($from, dir, true);
44960
- if (!found) return null;
44961
- const depth = concealedDepth(found.$head);
44962
- if (depth < 0) return found;
44963
- $from = doc.resolve(dir > 0 ? found.$head.after(depth) : found.$head.before(depth));
44944
+ function showsText(node) {
44945
+ if (hides(node)) return false;
44946
+ if (node.isTextblock) return true;
44947
+ let found = false;
44948
+ node.descendants((child) => {
44949
+ if (found || hides(child)) return false;
44950
+ if (child.isTextblock) found = true;
44951
+ return !found;
44952
+ });
44953
+ return found;
44964
44954
  }
44955
+ function shownText(doc, pos, dir) {
44956
+ let $from = doc.resolve(pos);
44957
+ for (;;) {
44958
+ const found = TextSelection.findFrom($from, dir, true);
44959
+ if (!found) return null;
44960
+ const depth = concealedDepth(found.$head);
44961
+ if (depth < 0) return found;
44962
+ $from = doc.resolve(dir > 0 ? found.$head.after(depth) : found.$head.before(depth));
44963
+ }
44964
+ }
44965
+ }
44966
+ /** Capture reader folding before dispatch, then map it to the new document. */
44967
+ function calloutFolds(view, tr) {
44968
+ const folds = /* @__PURE__ */ new Map();
44969
+ view.state.doc.descendants((node, pos) => {
44970
+ if (node.type.name !== "inkkit_callout") return;
44971
+ const dom = view.nodeDOM(pos);
44972
+ if (!(dom instanceof HTMLElement)) return;
44973
+ const mapped = tr.mapping.mapResult(pos, 1);
44974
+ if (mapped.deleted) return;
44975
+ const next = tr.doc.nodeAt(mapped.pos);
44976
+ if (next?.type === node.type) folds.set(next, dom.getAttribute("data-inkkit-folded") === "true");
44977
+ });
44978
+ return folds;
44965
44979
  }
44966
44980
  /**
44967
44981
  * Puts the caret just after what the transaction's last step inserted, so
44968
44982
  * typing continues after pasted or dropped content instead of replacing it.
44969
44983
  */
44970
- function caretAfterInsertion(tr) {
44984
+ function caretAfterInsertion(tr, view) {
44971
44985
  const map = tr.mapping.maps[tr.mapping.maps.length - 1];
44972
44986
  let end;
44973
44987
  map?.forEach((_from, _to, _newFrom, newTo) => {
@@ -44975,7 +44989,7 @@ function caretAfterInsertion(tr) {
44975
44989
  });
44976
44990
  if (end == null) return tr;
44977
44991
  const $end = tr.doc.resolve(end);
44978
- return tr.setSelection($end.parent.inlineContent ? TextSelection.create(tr.doc, end) : caretAfter(tr.doc, end));
44992
+ return tr.setSelection($end.parent.inlineContent ? TextSelection.create(tr.doc, end) : caretAfter(tr.doc, end, view && calloutFolds(view, tr)));
44979
44993
  }
44980
44994
  //#endregion
44981
44995
  //#region src/code-fences.ts
@@ -48200,6 +48214,7 @@ var calloutSchema = $node("inkkit_callout", (ctx) => ({
48200
48214
  group: "block",
48201
48215
  content: "block+",
48202
48216
  defining: true,
48217
+ createGapCursor: true,
48203
48218
  attrs: {
48204
48219
  kind: { default: "NOTE" },
48205
48220
  marker: { default: "[!NOTE]" },
@@ -53851,7 +53866,7 @@ function pasteSpreadsheet(ctx, input, at) {
53851
53866
  }
53852
53867
  let grid;
53853
53868
  try {
53854
- grid = input.html?.match(/<table[\s>]/i) ? htmlGrid(view.state.schema, input.html) : input.text.includes(" ") ? textGrid(view.state.schema, input.text) : void 0;
53869
+ grid = input.html?.match(/<table[\s>]/i) ? htmlGrid(view.state.schema, input.html) : !input.html && input.text.includes(" ") && (inTable || looksLikeGrid(input.text)) ? textGrid(view.state.schema, input.text) : void 0;
53855
53870
  } catch (error) {
53856
53871
  if (inTable || !input.html) throw error;
53857
53872
  if (!standaloneTable(input.html)) return false;
@@ -53869,7 +53884,7 @@ function pasteSpreadsheet(ctx, input, at) {
53869
53884
  if (cursor < input.html.length) children.push(schema.text(input.html.slice(cursor)));
53870
53885
  const content = importableSlice(ctx, new Slice(Fragment.from(schema.nodes.literal_markdown.create(null, children)), 0, 0)).content;
53871
53886
  const selection = view.state.selection;
53872
- dispatchTableOperation(view, at ? caretAfterInsertion(view.state.tr.replaceWith(at.from, at.to, content)) : selection.$from.parent.type.name === "paragraph" && selection.empty && selection.$from.parent.content.size === 0 ? view.state.tr.replaceWith(selection.$from.before(), selection.$from.after(), content) : content.childCount === 1 ? view.state.tr.replaceSelectionWith(content.firstChild) : view.state.tr.replaceSelection(new Slice(content, 0, 0)));
53887
+ dispatchTableOperation(view, at ? caretAfterInsertion(view.state.tr.replaceWith(at.from, at.to, content), view) : selection.$from.parent.type.name === "paragraph" && selection.empty && selection.$from.parent.content.size === 0 ? view.state.tr.replaceWith(selection.$from.before(), selection.$from.after(), content) : content.childCount === 1 ? view.state.tr.replaceSelectionWith(content.firstChild) : view.state.tr.replaceSelection(new Slice(content, 0, 0)));
53873
53888
  return true;
53874
53889
  }
53875
53890
  if (!grid) return false;
@@ -53884,7 +53899,7 @@ function pasteSpreadsheet(ctx, input, at) {
53884
53899
  if (node === table) tableStart = pos + 1;
53885
53900
  });
53886
53901
  if (tableStart < 0) reject();
53887
- tr.setSelection(caretAfter(tr.doc, tableStart - 1 + table.nodeSize));
53902
+ tr.setSelection(caretAfter(tr.doc, tableStart - 1 + table.nodeSize, calloutFolds(view, tr)));
53888
53903
  dispatchTableOperation(view, tr);
53889
53904
  return true;
53890
53905
  }
@@ -53929,6 +53944,12 @@ function standaloneTable(html) {
53929
53944
  function reject() {
53930
53945
  throw new InkKitError("preservation", "This spreadsheet cannot be represented safely in a Markdown table. Paste as plain text outside the table to retain its source.");
53931
53946
  }
53947
+ function looksLikeGrid(input) {
53948
+ const rows = input.replace(/\r\n?/g, "\n").replace(/\n$/, "").split("\n");
53949
+ if (rows.length < 2) return false;
53950
+ const widths = rows.map((row) => row.split(" ").length);
53951
+ return widths[0] > 1 && widths.every((width) => width === widths[0]) && rows.some((row) => row.split(" ")[0].trim().length > 0);
53952
+ }
53932
53953
  function textGrid(schema, input) {
53933
53954
  const text = input.replace(/\r\n?/g, "\n").replace(/\n$/, "");
53934
53955
  const rows = [[]];
@@ -54119,7 +54140,10 @@ var PasteController = class {
54119
54140
  return mapped?.operation === operation ? mapped : operation;
54120
54141
  }
54121
54142
  cancelPending() {
54143
+ const operation = this.active;
54144
+ if (!operation) return;
54122
54145
  this.active = void 0;
54146
+ operation.controller.abort();
54123
54147
  this.options.onPendingChanged?.();
54124
54148
  }
54125
54149
  destroy() {
@@ -54131,6 +54155,26 @@ var PasteController = class {
54131
54155
  const current = this.options.context();
54132
54156
  if (this.active !== operation || current.documentId !== context.documentId || current.generation !== context.generation) throw new InkKitError("stale-document", "The document changed while images were importing.");
54133
54157
  }
54158
+ async importImage(image, context, operation) {
54159
+ this.assertContext(context, operation);
54160
+ const signal = operation.controller.signal;
54161
+ let abort;
54162
+ const cancelled = new Promise((_, reject) => {
54163
+ abort = () => reject(new InkKitError(this.dead ? "destroyed" : "stale-document", "The image import was cancelled."));
54164
+ signal.addEventListener("abort", abort, { once: true });
54165
+ });
54166
+ try {
54167
+ return await Promise.race([new Promise((resolve) => {
54168
+ resolve(this.options.adapter.importImage(image, {
54169
+ ...context,
54170
+ signal
54171
+ }));
54172
+ }), cancelled]);
54173
+ } finally {
54174
+ signal.removeEventListener("abort", abort);
54175
+ this.assertContext(context, operation);
54176
+ }
54177
+ }
54134
54178
  commit(apply) {
54135
54179
  return this.options.commit ? this.options.commit(apply) : apply();
54136
54180
  }
@@ -54145,7 +54189,7 @@ var PasteController = class {
54145
54189
  const blocks = Fragment.fromArray(topLevel(view.state.schema, slice.content));
54146
54190
  const tr = view.state.tr.replaceWith(range.from, range.to, blocks);
54147
54191
  const end = tr.mapping.map(range.to);
54148
- this.dispatch(view, tr.setSelection(caretAfter(tr.doc, end)).scrollIntoView());
54192
+ this.dispatch(view, tr.setSelection(caretAfter(tr.doc, end, calloutFolds(view, tr))).scrollIntoView());
54149
54193
  }
54150
54194
  /** Imports the input at the selection, or in place of `target`. */
54151
54195
  async run(input, files = [], target) {
@@ -54162,9 +54206,12 @@ var PasteController = class {
54162
54206
  from: exit,
54163
54207
  to: exit
54164
54208
  };
54165
- const operation = target ? { ...target } : {
54166
- from: view.state.selection.from,
54167
- to: view.state.selection.to
54209
+ const operation = {
54210
+ ...target ?? {
54211
+ from: view.state.selection.from,
54212
+ to: view.state.selection.to
54213
+ },
54214
+ controller: new AbortController()
54168
54215
  };
54169
54216
  const inCode = () => view.state.doc.resolve(this.anchor(operation).from).parent.type.spec.code;
54170
54217
  this.active = operation;
@@ -54213,14 +54260,14 @@ var PasteController = class {
54213
54260
  if (!reference) {
54214
54261
  const captured = images.find((image) => image.source === source) ?? dataImage(source) ?? positional[index++];
54215
54262
  if (!captured || !this.options.adapter) throw new Error("The pasted image is unavailable.");
54216
- reference = (await this.options.adapter.importImage(captured, { ...context })).reference;
54263
+ reference = (await this.importImage(captured, context, operation)).reference;
54217
54264
  this.assertContext(context, operation);
54218
54265
  imported.set(source, reference);
54219
54266
  }
54220
54267
  element.setAttribute("src", reference);
54221
54268
  element.removeAttribute("srcset");
54222
54269
  } catch (error) {
54223
- if (error instanceof InkKitError) throw error;
54270
+ if (error instanceof InkKitError && error.code !== "image-unavailable") throw error;
54224
54271
  element.replaceWith(document.createTextNode(`[${alt}: image unavailable]`));
54225
54272
  this.options.onError?.(coded(error, "image-unavailable", error instanceof Error ? error.message : "The pasted image is unavailable."));
54226
54273
  }
@@ -54265,7 +54312,7 @@ var PasteController = class {
54265
54312
  if (input.text) nodes.push(schema.nodes.paragraph.create(null, schema.text(input.text)));
54266
54313
  for (const image of images) try {
54267
54314
  if (!this.options.adapter) throw new InkKitError("image-unavailable", "No image adapter is configured.");
54268
- const imported = await this.options.adapter.importImage(image, { ...context });
54315
+ const imported = await this.importImage(image, context, operation);
54269
54316
  this.assertContext(context, operation);
54270
54317
  const imageNode = schema.nodes.image?.create({
54271
54318
  src: imported.reference,
@@ -54312,8 +54359,8 @@ var PasteController = class {
54312
54359
  });
54313
54360
  return;
54314
54361
  }
54315
- if (anchor.from === anchor.to && insertion.parent.type.name === "paragraph" && insertion.parent.content.size === 0 && slice.content.firstChild?.isBlock && slice.content.firstChild.type.spec.isolating) this.dispatch(view, caretAfterInsertion(view.state.tr.replaceRange(insertion.before(), insertion.after(), new Slice(slice.content, 0, 0))).scrollIntoView());
54316
- else this.dispatch(view, caretAfterInsertion(view.state.tr.replaceRange(anchor.from, anchor.to, source == null ? slice : withEdgeSpaces(slice, source, view.state.doc.resolve(anchor.from), view.state.doc.resolve(anchor.to)))).scrollIntoView());
54362
+ if (anchor.from === anchor.to && insertion.parent.type.name === "paragraph" && insertion.parent.content.size === 0 && slice.content.firstChild?.isBlock && slice.content.firstChild.type.spec.isolating) this.dispatch(view, caretAfterInsertion(view.state.tr.replaceRange(insertion.before(), insertion.after(), new Slice(slice.content, 0, 0)), view).scrollIntoView());
54363
+ else this.dispatch(view, caretAfterInsertion(view.state.tr.replaceRange(anchor.from, anchor.to, source == null ? slice : withEdgeSpaces(slice, source, view.state.doc.resolve(anchor.from), view.state.doc.resolve(anchor.to))), view).scrollIntoView());
54317
54364
  } finally {
54318
54365
  if (this.active === operation) {
54319
54366
  this.active = void 0;
@@ -55315,17 +55362,35 @@ function navigateFormattedHeading(view, requested, context) {
55315
55362
  }
55316
55363
  //#endregion
55317
55364
  //#region src/tasks.ts
55365
+ function onTaskMarker(event) {
55366
+ const target = event.target;
55367
+ if (!(target instanceof HTMLElement) || !target.matches("li[data-item-type=\"task\"]")) return false;
55368
+ const marker = getComputedStyle(target, "::before");
55369
+ const rect = target.getBoundingClientRect();
55370
+ const left = rect.left + parseFloat(marker.left);
55371
+ const top = rect.top + parseFloat(marker.top);
55372
+ return event.clientX >= left && event.clientX <= left + parseFloat(marker.width) && event.clientY >= top && event.clientY <= top + parseFloat(marker.height);
55373
+ }
55318
55374
  var taskListPlugin = $prose(() => new Plugin({
55319
55375
  key: new PluginKey("taskToggle"),
55320
- props: { handleClickOn(view, _pos, node, nodePos, event) {
55321
- if (!view.editable) return false;
55322
- if (node.type.name !== "list_item" || node.attrs.checked == null) return false;
55323
- if (!event.target.matches("li[data-item-type=\"task\"]")) return false;
55324
- view.dispatch(view.state.tr.setNodeMarkup(nodePos, void 0, {
55325
- ...node.attrs,
55326
- checked: !node.attrs.checked
55327
- }));
55328
- return true;
55376
+ props: { handleDOMEvents: {
55377
+ mousedown(view, event) {
55378
+ if (!view.editable || event.button !== 0 || !onTaskMarker(event)) return false;
55379
+ event.preventDefault();
55380
+ return true;
55381
+ },
55382
+ click(view, event) {
55383
+ if (!view.editable || event.button !== 0 || !onTaskMarker(event)) return false;
55384
+ const nodePos = view.posAtDOM(event.target, 0) - 1;
55385
+ const node = view.state.doc.nodeAt(nodePos);
55386
+ if (node?.type.name !== "list_item" || node.attrs.checked == null) return false;
55387
+ event.preventDefault();
55388
+ view.dispatch(view.state.tr.setNodeMarkup(nodePos, void 0, {
55389
+ ...node.attrs,
55390
+ checked: !node.attrs.checked
55391
+ }));
55392
+ return true;
55393
+ }
55329
55394
  } }
55330
55395
  }));
55331
55396
  function listItemsInSelection(view) {
@@ -56219,7 +56284,7 @@ var sourceGroupDelay = 500;
56219
56284
  var sourceStepLimit = 100;
56220
56285
  /**
56221
56286
  * The editor. Create it with `InkKitEditor.mount`, load a document, and read it back with
56222
- * `snapshot`. The reference is https://github.com/aicayzer/inkkit/blob/main/docs/api.md.
56287
+ * `snapshot`. The reference is https://inkkit.cyzr.me/reference/editor.
56223
56288
  */
56224
56289
  var InkKitEditor = class InkKitEditor {
56225
56290
  options;
@@ -57157,6 +57222,11 @@ var InkKitEditor = class InkKitEditor {
57157
57222
  ...result
57158
57223
  };
57159
57224
  }
57225
+ /** Cancels the pending image paste without changing the document or editability. */
57226
+ cancelPendingImports(expectedGeneration) {
57227
+ this.assertReady(expectedGeneration);
57228
+ this.pasteController.cancelPending();
57229
+ }
57160
57230
  /** Pastes content the host read from the native clipboard. Capture it once; don't read the
57161
57231
  * clipboard again while this runs. */
57162
57232
  async paste(input, expectedGeneration) {
@@ -57440,7 +57510,7 @@ var InkKitEditor = class InkKitEditor {
57440
57510
  return true;
57441
57511
  }
57442
57512
  }));
57443
- }).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-TSUdUlMi.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, {
57513
+ }).use(labelsCtx).use(instance.pasteController.plugin).use(visibleClipboard).use($prose(() => instance.suggestions.plugin())).use(instance.keymapPlugin).use(createDialect(Boolean(options.images || options.files), Boolean(options.wikiLinks), Boolean(options.files))).use(codeControlCtx).use(codeControlsPlugin(() => instance.viewport().rect)).use(tablePlugins).use(tableBlock).use(instance.changePlugin).use(instance.historyInputPlugin).use(history).use(clipboard).use(cursor).use(taskListPlugin).use(quoteBackspace).use(calloutEnter).use(literalEnter).use(literalReread).use(listItemBackspace).use(listItemShiftTab).use(nestedItemEnter).use(rejoinLists).use(movedListItems).use(textInputPolicy).use(events.copy ? codeCopyPlugin((text) => events.copy?.(text)) : []).use(placeholderPlugin).use(options.rendering?.codeHighlighting === false ? [] : (await import("./highlight-AI86evan.js")).highlightPlugin).use(options.rendering?.diagramPreview === false || !diagramsAvailable() ? [] : mermaidPreview((error) => instance.report(error))).use(selectionPlugin).use($prose(() => search())).use(instance.policyPlugin).use(options.wikiLinks ? wikiLinkView(options.wikiLinks, {
57444
57514
  capture: () => instance.adapterCapture(),
57445
57515
  active: () => instance.ready && !instance.destroyed && !instance.literalSurface,
57446
57516
  labels: {
@@ -58786,6 +58856,7 @@ var InkKitEditor = class InkKitEditor {
58786
58856
  "printableSnapshot",
58787
58857
  "clipboardSnapshot",
58788
58858
  "paste",
58859
+ "cancelPendingImports",
58789
58860
  "pasteAsPlainText",
58790
58861
  "commandState",
58791
58862
  "setEditable",
@@ -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-DkL-wDqy.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-C612uj1Q.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
@@ -1,5 +1,5 @@
1
1
  export { InkKitEditor } from './editor.js';
2
2
  export type { EditorEvents, EditorOptions, TextInputPreferences, CommandState, EditingMode, CaretState, Mark, Block, FormatCommand, FormatOptions, KeyInput, ImageInsertion, ShortcutName, Keymap, } from './editor.js';
3
3
  export { InkKitError } from './types.js';
4
- export type { DocumentFormat, EditorLabels, Heading, TableCommand, TableOptions, BlockMoveDirection, DocumentContext, DocumentInput, DocumentSnapshot, DocumentVersion, EditorErrorCode, ImageAdapter, 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';
4
+ export type { DocumentFormat, 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';
package/dist/core.js CHANGED
@@ -1,2 +1,2 @@
1
- import { r as InkKitError, t as InkKitEditor } from "./chunks/core-DkL-wDqy.js";
1
+ import { r as InkKitError, t as InkKitEditor } from "./chunks/core-C612uj1Q.js";
2
2
  export { InkKitEditor, InkKitError };
package/dist/editor.d.ts CHANGED
@@ -143,7 +143,7 @@ export interface EditorEvents {
143
143
  export declare const editorEvents: readonly ["changed", "commandStateChanged", "openLink", "copy", "error", "clipboard"];
144
144
  /**
145
145
  * The editor. Create it with `InkKitEditor.mount`, load a document, and read it back with
146
- * `snapshot`. The reference is https://github.com/aicayzer/inkkit/blob/main/docs/api.md.
146
+ * `snapshot`. The reference is https://inkkit.cyzr.me/reference/editor.
147
147
  */
148
148
  export declare class InkKitEditor {
149
149
  private readonly options;
@@ -321,6 +321,8 @@ export declare class InkKitEditor {
321
321
  /** Prepares the document or the selection for the native clipboard. Rejects with `stale-document`
322
322
  * if the document changes first. */
323
323
  clipboardSnapshot(scope?: ClipboardScope, expectedGeneration?: number): Promise<ClipboardOutput>;
324
+ /** Cancels the pending image paste without changing the document or editability. */
325
+ cancelPendingImports(expectedGeneration?: number): void;
324
326
  /** Pastes content the host read from the native clipboard. Capture it once; don't read the
325
327
  * clipboard again while this runs. */
326
328
  paste(input: ClipboardInput, expectedGeneration?: number): Promise<void>;
package/dist/index.js CHANGED
@@ -1,4 +1,4 @@
1
- import { n as registerMermaid, r as InkKitError, t as InkKitEditor } from "./chunks/core-DkL-wDqy.js";
1
+ import { n as registerMermaid, r as InkKitError, t as InkKitEditor } from "./chunks/core-C612uj1Q.js";
2
2
  //#region src/index.ts
3
3
  registerMermaid(async () => (await import("mermaid")).default);
4
4
  //#endregion
package/dist/style.css CHANGED
@@ -154,9 +154,11 @@
154
154
  transform: translate(-50%, -50%);
155
155
  border-radius: 4px;
156
156
  box-shadow: 0 0 0 1px var(--_inkkit-rule);
157
- cursor: grab;
158
157
  touch-action: none;
159
158
  }
159
+ .inkkit-root .inkkit-table-controls .inkkit-table-grip:not(:disabled) {
160
+ cursor: grab;
161
+ }
160
162
  .inkkit-root .inkkit-table-grip[data-inkkit-axis='column'] {
161
163
  width: 24px;
162
164
  height: 14px;
@@ -252,6 +254,10 @@
252
254
  outline-offset: 2px;
253
255
  border-radius: 6px;
254
256
  }
257
+ /* A gap or node selection draws its own indicator, not a native text caret. */
258
+ .inkkit-root .ProseMirror.ProseMirror-hideselection {
259
+ caret-color: transparent;
260
+ }
255
261
  .inkkit-root .ProseMirror-gapcursor {
256
262
  display: none;
257
263
  pointer-events: none;
package/dist/types.d.ts CHANGED
@@ -51,6 +51,10 @@ export interface PortableImage {
51
51
  mimeType: string;
52
52
  filename?: string;
53
53
  }
54
+ /** The document and cancellation signal for one image import. */
55
+ export interface ImageImportContext extends DocumentContext {
56
+ signal: AbortSignal;
57
+ }
54
58
  /** Stores, shows and exports the images a host owns. References stay opaque to the editor. */
55
59
  export interface ImageAdapter {
56
60
  /** The display URL for `reference`, or `undefined` when the image is unavailable. */
@@ -58,7 +62,7 @@ export interface ImageAdapter {
58
62
  url: string;
59
63
  } | undefined;
60
64
  /** Stores the bytes and returns the reference to write into the Markdown. */
61
- importImage(input: CapturedImage, context: DocumentContext): Promise<{
65
+ importImage(input: CapturedImage, context: ImageImportContext): Promise<{
62
66
  reference: string;
63
67
  }>;
64
68
  /** The stored bytes and type of `reference`, for portable clipboard and print output. */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aicayzer/inkkit",
3
- "version": "0.1.7",
3
+ "version": "0.2.0",
4
4
  "description": "A preserving Markdown editor for offline web views and the web",
5
5
  "license": "MIT",
6
6
  "type": "module",