@aicayzer/inkkit 0.0.2 → 0.0.4

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.
@@ -0,0 +1,55 @@
1
+ import { Fragment, type Node as ProseNode } from '@milkdown/kit/prose/model';
2
+ import type { EditorView } from '@milkdown/kit/prose/view';
3
+ import type { Literal } from 'mdast';
4
+ export type CommentSyntax = 'html' | 'obsidian';
5
+ interface Comment extends Literal {
6
+ type: 'inkkitCommentInline' | 'inkkitCommentBlock';
7
+ syntax: CommentSyntax;
8
+ block?: boolean;
9
+ }
10
+ declare module 'mdast' {
11
+ interface PhrasingContentMap {
12
+ inkkitCommentInline: Comment & {
13
+ type: 'inkkitCommentInline';
14
+ };
15
+ }
16
+ interface BlockContentMap {
17
+ inkkitCommentBlock: Comment & {
18
+ type: 'inkkitCommentBlock';
19
+ };
20
+ }
21
+ interface RootContentMap {
22
+ inkkitCommentInline: Comment & {
23
+ type: 'inkkitCommentInline';
24
+ };
25
+ inkkitCommentBlock: Comment & {
26
+ type: 'inkkitCommentBlock';
27
+ };
28
+ }
29
+ }
30
+ declare module 'micromark-util-types' {
31
+ interface TokenTypeMap {
32
+ inkkitCommentInline: 'inkkitCommentInline';
33
+ inkkitCommentBlock: 'inkkitCommentBlock';
34
+ inkkitCommentData: 'inkkitCommentData';
35
+ }
36
+ }
37
+ export declare function commentSource(node: ProseNode): string;
38
+ export interface CommentSpan {
39
+ start: number;
40
+ end: number;
41
+ syntax: CommentSyntax;
42
+ value: string;
43
+ block?: boolean;
44
+ }
45
+ export declare function literalCommentSpans(value: string): CommentSpan[];
46
+ export declare const remarkCommentsPlugin: import("@milkdown/kit/utils").$Remark<"remarkComments", unknown>;
47
+ export declare const commentInline: import("@milkdown/kit/utils").$Node;
48
+ export declare const commentBlock: import("@milkdown/kit/utils").$Node;
49
+ export declare const commentVisibility: import("@milkdown/kit/utils").$Prose;
50
+ export declare function setCommentVisibility(view: EditorView, visible: boolean): void;
51
+ export declare const comments: (import("@milkdown/kit/utils").$Node | import("@milkdown/kit/utils").$Prose)[];
52
+ export declare function isComment(node: ProseNode): boolean;
53
+ export declare function shareableFragment(content: Fragment): Fragment;
54
+ export declare function commentSelectionContent(doc: ProseNode, from: number, to: number, content: Fragment): Fragment;
55
+ export {};
@@ -0,0 +1,487 @@
1
+ import { $node, $prose, $remark } from '@milkdown/kit/utils';
2
+ import { Fragment } from '@milkdown/kit/prose/model';
3
+ import { Plugin, PluginKey } from '@milkdown/kit/prose/state';
4
+ import { fromMarkdown } from 'mdast-util-from-markdown';
5
+ const lineEnding = (code) => code === -3 || code === -4 || code === -5;
6
+ const whitespace = (code) => code === 32 || code === -1 || code === -2;
7
+ function commentData(effects) {
8
+ let open = false;
9
+ return {
10
+ consume(code) {
11
+ if (!open) {
12
+ effects.enter('inkkitCommentData');
13
+ open = true;
14
+ }
15
+ effects.consume(code);
16
+ },
17
+ close() {
18
+ if (open) {
19
+ effects.exit('inkkitCommentData');
20
+ open = false;
21
+ }
22
+ },
23
+ };
24
+ }
25
+ const inlineComment = function (effects, ok, nok) {
26
+ const data = commentData(effects);
27
+ return start;
28
+ function start(code) {
29
+ effects.enter('inkkitCommentInline');
30
+ data.consume(code);
31
+ return second;
32
+ }
33
+ function second(code) {
34
+ if (code !== 37)
35
+ return nok(code);
36
+ data.consume(code);
37
+ return body;
38
+ }
39
+ function body(code) {
40
+ if (code === null)
41
+ return nok(code);
42
+ if (lineEnding(code)) {
43
+ data.close();
44
+ effects.enter('lineEnding');
45
+ effects.consume(code);
46
+ effects.exit('lineEnding');
47
+ return body;
48
+ }
49
+ data.consume(code);
50
+ return code === 37 ? closing : body;
51
+ }
52
+ function closing(code) {
53
+ if (code !== 37)
54
+ return body(code);
55
+ data.consume(code);
56
+ data.close();
57
+ effects.exit('inkkitCommentInline');
58
+ return ok;
59
+ }
60
+ };
61
+ // A flow construct owns blank lines and Markdown-looking text inside a block comment.
62
+ const blockComment = function (effects, ok, nok) {
63
+ const data = commentData(effects);
64
+ return start;
65
+ function start(code) {
66
+ effects.enter('inkkitCommentBlock');
67
+ data.consume(code);
68
+ return second;
69
+ }
70
+ function second(code) {
71
+ if (code !== 37)
72
+ return nok(code);
73
+ data.consume(code);
74
+ return openingEnd;
75
+ }
76
+ function openingEnd(code) {
77
+ if (whitespace(code)) {
78
+ data.consume(code);
79
+ return openingEnd;
80
+ }
81
+ if (!lineEnding(code))
82
+ return nok(code);
83
+ data.close();
84
+ effects.enter('lineEnding');
85
+ effects.consume(code);
86
+ effects.exit('lineEnding');
87
+ return lineStart;
88
+ }
89
+ function lineStart(code) {
90
+ if (whitespace(code)) {
91
+ data.consume(code);
92
+ return lineStart;
93
+ }
94
+ if (code === 37) {
95
+ data.consume(code);
96
+ return closingSecond;
97
+ }
98
+ return body(code);
99
+ }
100
+ function body(code) {
101
+ if (code === null)
102
+ return nok(code);
103
+ if (lineEnding(code)) {
104
+ data.close();
105
+ effects.enter('lineEnding');
106
+ effects.consume(code);
107
+ effects.exit('lineEnding');
108
+ return lineStart;
109
+ }
110
+ data.consume(code);
111
+ return lineEnding(code) ? lineStart : body;
112
+ }
113
+ function closingSecond(code) {
114
+ if (code !== 37)
115
+ return body(code);
116
+ data.consume(code);
117
+ return closingEnd;
118
+ }
119
+ function closingEnd(code) {
120
+ if (whitespace(code)) {
121
+ data.consume(code);
122
+ return closingEnd;
123
+ }
124
+ if (code !== null && !lineEnding(code))
125
+ return body(code);
126
+ data.close();
127
+ effects.exit('inkkitCommentBlock');
128
+ return ok(code);
129
+ }
130
+ };
131
+ const syntax = {
132
+ text: { 37: { name: 'inkkitCommentInline', tokenize: inlineComment } },
133
+ flow: {
134
+ 37: { name: 'inkkitCommentBlock', tokenize: blockComment, concrete: true },
135
+ },
136
+ };
137
+ const from = {
138
+ enter: {
139
+ inkkitCommentInline(token) {
140
+ this.enter({ type: 'inkkitCommentInline', syntax: 'obsidian', value: '' }, token);
141
+ },
142
+ inkkitCommentBlock(token) {
143
+ this.enter({ type: 'inkkitCommentBlock', syntax: 'obsidian', value: '' }, token);
144
+ },
145
+ },
146
+ exit: {
147
+ inkkitCommentInline(token) {
148
+ const node = this.stack.at(-1);
149
+ node.value = this.sliceSerialize(token).slice(2, -2);
150
+ this.exit(token);
151
+ },
152
+ inkkitCommentBlock(token) {
153
+ const node = this.stack.at(-1);
154
+ node.value = this.sliceSerialize(token)
155
+ .replace(/^%%[^\S\r\n]*\r?\n/, '')
156
+ .replace(/(?:\r?\n)?[^\S\r\n]*%%[^\S\r\n]*$/, '');
157
+ this.exit(token);
158
+ },
159
+ },
160
+ };
161
+ export function commentSource(node) {
162
+ const body = node.textContent;
163
+ return node.attrs.syntax === 'html'
164
+ ? `<!--${body}-->`
165
+ : node.type.name === 'comment_block' || node.attrs.block
166
+ ? `%%\n${body}\n%%`
167
+ : `%%${body}%%`;
168
+ }
169
+ const handler = (node) => {
170
+ const comment = node;
171
+ const value = comment.value ?? '';
172
+ return comment.syntax === 'html'
173
+ ? `<!--${value}-->`
174
+ : comment.type === 'inkkitCommentBlock' || comment.block
175
+ ? `%%\n${value}\n%%`
176
+ : `%%${value}%%`;
177
+ };
178
+ function htmlSpans(value) {
179
+ const spans = [];
180
+ let inTag = false;
181
+ let quote = '';
182
+ for (let index = 0; index < value.length; index++) {
183
+ const character = value[index];
184
+ if (quote) {
185
+ if (character === quote)
186
+ quote = '';
187
+ continue;
188
+ }
189
+ if (inTag && (character === '"' || character === "'")) {
190
+ quote = character;
191
+ continue;
192
+ }
193
+ if (value.startsWith('<!--', index)) {
194
+ const end = value.indexOf('-->', index + 4);
195
+ if (end < 0)
196
+ break;
197
+ spans.push({
198
+ start: index,
199
+ end: end + 3,
200
+ syntax: 'html',
201
+ value: value.slice(index + 4, end),
202
+ });
203
+ index = end + 2;
204
+ inTag = false;
205
+ }
206
+ else if (character === '<')
207
+ inTag = true;
208
+ else if (character === '>')
209
+ inTag = false;
210
+ }
211
+ return spans;
212
+ }
213
+ // Parse preserved source again rather than hiding delimiter-looking strings in code or escapes.
214
+ export function literalCommentSpans(value) {
215
+ const tree = fromMarkdown(value, {
216
+ extensions: [syntax],
217
+ mdastExtensions: [from],
218
+ });
219
+ const spans = [];
220
+ const visit = (node) => {
221
+ const start = node.position?.start.offset;
222
+ const end = node.position?.end.offset;
223
+ if (start != null && end != null) {
224
+ if (node.type === 'inkkitCommentInline' ||
225
+ node.type === 'inkkitCommentBlock')
226
+ spans.push({
227
+ start,
228
+ end,
229
+ syntax: node.syntax,
230
+ value: node.value,
231
+ block: node.type === 'inkkitCommentBlock',
232
+ });
233
+ else if (node.type === 'html')
234
+ spans.push(...htmlSpans(value.slice(start, end)).map((span) => ({
235
+ ...span,
236
+ start: start + span.start,
237
+ end: start + span.end,
238
+ })));
239
+ }
240
+ if ('children' in node)
241
+ node.children.forEach(visit);
242
+ };
243
+ visit(tree);
244
+ return spans.sort((left, right) => left.start - right.start);
245
+ }
246
+ function remarkComments() {
247
+ const data = this.data();
248
+ (data.micromarkExtensions ??= []).push(syntax);
249
+ (data.fromMarkdownExtensions ??= []).push(from);
250
+ (data.toMarkdownExtensions ??= []).push({
251
+ handlers: { inkkitCommentInline: handler, inkkitCommentBlock: handler },
252
+ unsafe: [{ character: '%', inConstruct: 'phrasing', after: '%' }],
253
+ });
254
+ return (tree) => {
255
+ const visit = (node, inline, parentType) => {
256
+ if (parentType === 'listItem' && node.type === 'inkkitCommentBlock')
257
+ return {
258
+ type: 'paragraph',
259
+ children: [{ ...node, type: 'inkkitCommentInline', block: true }],
260
+ position: node.position,
261
+ };
262
+ if (node.type === 'html') {
263
+ const spans = htmlSpans(node.value);
264
+ if (spans.length === 1 &&
265
+ spans[0].start === 0 &&
266
+ spans[0].end === node.value.length)
267
+ return parentType === 'listItem'
268
+ ? {
269
+ type: 'paragraph',
270
+ children: [
271
+ {
272
+ type: 'inkkitCommentInline',
273
+ syntax: 'html',
274
+ value: spans[0].value,
275
+ position: node.position,
276
+ },
277
+ ],
278
+ position: node.position,
279
+ }
280
+ : {
281
+ type: inline ? 'inkkitCommentInline' : 'inkkitCommentBlock',
282
+ syntax: 'html',
283
+ value: spans[0].value,
284
+ position: node.position,
285
+ };
286
+ }
287
+ if ('children' in node)
288
+ node.children = node.children.map((child) => visit(child, node.type === 'paragraph' ||
289
+ node.type === 'heading' ||
290
+ node.type === 'tableCell' ||
291
+ node.type === 'link' ||
292
+ node.type === 'linkReference' ||
293
+ inline, node.type));
294
+ if (node.type === 'paragraph' &&
295
+ parentType !== 'listItem' &&
296
+ node.children.length === 1 &&
297
+ node.children[0]?.type === 'inkkitCommentInline')
298
+ return {
299
+ ...node.children[0],
300
+ type: 'inkkitCommentBlock',
301
+ position: node.position,
302
+ };
303
+ return node;
304
+ };
305
+ tree.children = tree.children.map((node) => visit(node, false));
306
+ };
307
+ }
308
+ export const remarkCommentsPlugin = $remark('remarkComments', () => remarkComments);
309
+ function commentNode(inline) {
310
+ const name = inline ? 'comment_inline' : 'comment_block';
311
+ const mdastType = inline ? 'inkkitCommentInline' : 'inkkitCommentBlock';
312
+ return $node(name, () => ({
313
+ inline,
314
+ group: inline ? 'inline' : 'block',
315
+ content: 'text*',
316
+ marks: '',
317
+ code: true,
318
+ defining: true,
319
+ attrs: { syntax: { default: 'html' }, block: { default: !inline } },
320
+ parseDOM: [
321
+ {
322
+ tag: `${inline ? 'span' : 'div'}[data-inkkit-comment]`,
323
+ preserveWhitespace: 'full',
324
+ getAttrs: (dom) => ({
325
+ syntax: dom.getAttribute('data-inkkit-comment') === 'obsidian'
326
+ ? 'obsidian'
327
+ : 'html',
328
+ }),
329
+ },
330
+ ],
331
+ toDOM: (node) => [
332
+ inline ? 'span' : 'div',
333
+ {
334
+ class: `inkkit-comment inkkit-comment-${inline ? 'inline' : 'block'}`,
335
+ 'data-inkkit-comment': node.attrs.syntax,
336
+ 'aria-label': 'Author comment',
337
+ },
338
+ 0,
339
+ ],
340
+ parseMarkdown: {
341
+ match: (node) => node.type === mdastType,
342
+ runner: (state, node, type) => {
343
+ state.openNode(type, {
344
+ syntax: node.syntax,
345
+ block: inline ? Boolean(node.block) : true,
346
+ });
347
+ if (node.value)
348
+ state.addText(String(node.value));
349
+ state.closeNode();
350
+ },
351
+ },
352
+ toMarkdown: {
353
+ match: (node) => node.type.name === name,
354
+ runner: (state, node) => {
355
+ state.addNode(mdastType, undefined, node.textContent, {
356
+ syntax: node.attrs.syntax,
357
+ block: node.attrs.block,
358
+ });
359
+ },
360
+ },
361
+ }));
362
+ }
363
+ export const commentInline = commentNode(true);
364
+ export const commentBlock = commentNode(false);
365
+ const visibilityKey = new PluginKey('inkkit-comments-visible');
366
+ export const commentVisibility = $prose(() => new Plugin({
367
+ key: visibilityKey,
368
+ state: {
369
+ init: () => false,
370
+ apply: (transaction, value) => transaction.getMeta(visibilityKey) ?? value,
371
+ },
372
+ view(view) {
373
+ const update = () => view.dom.setAttribute('data-inkkit-comments-visible', String(visibilityKey.getState(view.state) ?? false));
374
+ update();
375
+ return { update };
376
+ },
377
+ }));
378
+ export function setCommentVisibility(view, visible) {
379
+ if (visibilityKey.getState(view.state) === visible)
380
+ return;
381
+ view.dispatch(view.state.tr
382
+ .setMeta(visibilityKey, visible)
383
+ .setMeta('addToHistory', false));
384
+ }
385
+ export const comments = [commentInline, commentBlock, commentVisibility].flat();
386
+ export function isComment(node) {
387
+ return (node.type.name === 'comment_inline' || node.type.name === 'comment_block');
388
+ }
389
+ // Authored reference metadata can retain comments after their visible nodes are removed.
390
+ function commentProvenance(value) {
391
+ if (typeof value !== 'string')
392
+ return false;
393
+ const visit = (value) => {
394
+ if (Array.isArray(value))
395
+ return value.some(visit);
396
+ if (!value || typeof value !== 'object')
397
+ return false;
398
+ const node = value;
399
+ return (node.type === 'inkkitCommentInline' ||
400
+ node.type === 'inkkitCommentBlock' ||
401
+ (node.type === 'html' &&
402
+ typeof node.value === 'string' &&
403
+ htmlSpans(node.value).length > 0) ||
404
+ Boolean(node.children?.some(visit)));
405
+ };
406
+ try {
407
+ return visit(JSON.parse(value));
408
+ }
409
+ catch {
410
+ return true;
411
+ }
412
+ }
413
+ export function shareableFragment(content) {
414
+ const filtered = [];
415
+ const labelHasComments = (label) => typeof label === 'string' &&
416
+ (label.includes('%%') || label.includes('<!--')) &&
417
+ literalCommentSpans(label).length > 0;
418
+ content.forEach((node) => {
419
+ if (isComment(node))
420
+ return;
421
+ if (node.type.name === 'reference_definition' &&
422
+ labelHasComments(node.attrs.label))
423
+ return;
424
+ node = node.mark(node.marks.map((mark) => {
425
+ if (mark.type.name !== 'link')
426
+ return mark;
427
+ const clearTarget = labelHasComments(mark.attrs.label);
428
+ if (!clearTarget && !commentProvenance(mark.attrs.referenceContent))
429
+ return mark;
430
+ return mark.type.create({
431
+ ...mark.attrs,
432
+ referenceContent: null,
433
+ ...(clearTarget
434
+ ? { identifier: null, label: null, referenceType: null }
435
+ : {}),
436
+ });
437
+ }));
438
+ if (!node.childCount) {
439
+ filtered.push(node);
440
+ return;
441
+ }
442
+ const inner = shareableFragment(node.content);
443
+ let whitespaceOnly = true, removedComment = false;
444
+ inner.forEach((child) => {
445
+ if (!(child.isText && !child.text.trim()) &&
446
+ child.type.name !== 'hardbreak')
447
+ whitespaceOnly = false;
448
+ });
449
+ node.content.descendants((child) => {
450
+ if (isComment(child)) {
451
+ removedComment = true;
452
+ return false;
453
+ }
454
+ });
455
+ const commentOnly = removedComment && whitespaceOnly;
456
+ const paragraph = () => Fragment.from(node.type.schema.nodes.paragraph.create());
457
+ if ((inner.size === 0 || commentOnly) && node.content.size > 0) {
458
+ if ([
459
+ 'footnote_definition',
460
+ 'list_item',
461
+ 'table_cell',
462
+ 'table_header',
463
+ 'inkkit_callout',
464
+ ].includes(node.type.name))
465
+ filtered.push(node.copy(paragraph()));
466
+ return;
467
+ }
468
+ if (!node.type.validContent(inner)) {
469
+ if (node.type.name === 'list_item')
470
+ filtered.push(node.copy(paragraph().append(inner)));
471
+ return;
472
+ }
473
+ filtered.push(node.copy(inner));
474
+ });
475
+ return Fragment.fromArray(filtered);
476
+ }
477
+ // ProseMirror omits a parent wrapper when a selection is wholly inside its text.
478
+ export function commentSelectionContent(doc, from, to, content) {
479
+ const start = doc.resolve(from);
480
+ const end = doc.resolve(to);
481
+ for (let depth = start.depth; depth > 0; depth--) {
482
+ const node = start.node(depth);
483
+ if (isComment(node) && end.depth >= depth && node === end.node(depth))
484
+ return Fragment.from(node.copy(node.content.cut(from - start.start(depth), to - start.start(depth))));
485
+ }
486
+ return content;
487
+ }
package/dist/dialect.js CHANGED
@@ -3,8 +3,12 @@ import { commonmark, hardbreakSchema, imageSchema, paragraphSchema, remarkInline
3
3
  import { extendListItemSchemaForTask, strikethroughAttr, strikethroughInputRule, strikethroughKeymap, strikethroughSchema, toggleStrikethroughCommand, wrapInTaskListInputRule, tableSchema, tableHeaderRowSchema, tableHeaderSchema, tableRowSchema, tableCellSchema, tableEditingPlugin, tableKeymap, goToNextTableCellCommand, goToPrevTableCellCommand, insertTableCommand, addRowBeforeCommand, addRowAfterCommand, addColBeforeCommand, addColAfterCommand, selectRowCommand, selectColCommand, deleteSelectedCellsCommand, setAlignCommand, exitTable, } from '@milkdown/kit/preset/gfm';
4
4
  import { $remark } from '@milkdown/kit/utils';
5
5
  import { autolinkInputRule } from './autolink.js';
6
+ import { preservingCodeBlocks, remarkCodeFences, fencedCodeHandler, } from './code-fences.js';
6
7
  import { literalBlock, createLiteralPreservation } from './literals.js';
7
8
  import { references, remarkReferencesPlugin } from './references.js';
9
+ import { callouts, remarkCalloutsPlugin } from './callouts.js';
10
+ import { inlineHighlight } from './inline-highlight.js';
11
+ import { comments, remarkCommentsPlugin } from './comments.js';
8
12
  import { gfmTableFromMarkdown, gfmTableToMarkdown } from 'mdast-util-gfm-table';
9
13
  import { gfmTable } from 'micromark-extension-gfm-table';
10
14
  import { defaultHandlers, } from 'mdast-util-to-markdown';
@@ -82,7 +86,7 @@ function remarkDialect() {
82
86
  add('toMarkdownExtensions', gfmTaskListItemToMarkdown());
83
87
  add('toMarkdownExtensions', gfmAutolinkLiteralToMarkdown());
84
88
  add('toMarkdownExtensions', gfmTableToMarkdown());
85
- add('toMarkdownExtensions', { handlers: { link } });
89
+ add('toMarkdownExtensions', { handlers: { link, code: fencedCodeHandler } });
86
90
  }
87
91
  export const remarkDialectPlugin = $remark('remarkDialect', () => remarkDialect);
88
92
  // Reference links retain their authored form instead of expanding to inline links.
@@ -251,11 +255,18 @@ export const tables = [
251
255
  ].flat();
252
256
  export function createDialect(images) {
253
257
  return [
258
+ remarkCodeFences,
254
259
  remarkReferencesPlugin,
260
+ remarkCalloutsPlugin,
261
+ remarkCommentsPlugin,
255
262
  // Protect unsupported inline HTML before the empty-line plugin consumes break nodes.
256
263
  createLiteralPreservation(images),
257
264
  commonmarkWithLiteralReferences,
258
265
  references,
266
+ callouts,
267
+ comments,
268
+ inlineHighlight,
269
+ preservingCodeBlocks,
259
270
  preserveSpacerParagraphs,
260
271
  visibleSoftbreaks,
261
272
  normalizedImages,
package/dist/editor.d.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  import { type TableCommand } from './tables.js';
2
2
  import { type DocumentInput, type DocumentSnapshot, type ImageAdapter, type ClipboardInput, type ClipboardOutput } from './types.js';
3
- export type Mark = 'bold' | 'italic' | 'strikethrough' | 'code' | 'link';
3
+ export type Mark = 'bold' | 'italic' | 'strikethrough' | 'highlight' | 'code' | 'link';
4
4
  export type Block = {
5
5
  type: 'paragraph';
6
6
  } | {
@@ -21,7 +21,7 @@ export interface CaretState {
21
21
  /** One quote holds the whole selection; the block is what sits inside it. */
22
22
  quoted: boolean;
23
23
  }
24
- export type FormatCommand = 'heading' | 'paragraph' | 'bold' | 'italic' | 'strikethrough' | 'code' | 'codeBlock' | 'quote' | 'bulletList' | 'orderedList' | 'taskList' | 'link';
24
+ export type FormatCommand = 'heading' | 'paragraph' | 'bold' | 'italic' | 'strikethrough' | 'highlight' | 'code' | 'codeBlock' | 'quote' | 'bulletList' | 'orderedList' | 'taskList' | 'link';
25
25
  /** The bindings the app sets, by shortcut name; each runs a format command. */
26
26
  export type Keymap = Record<string, string[]>;
27
27
  export interface EditorEvents {
@@ -67,6 +67,7 @@ export declare class InkKitEditor {
67
67
  snapshot(expectedGeneration?: number): DocumentSnapshot;
68
68
  clipboardSnapshot(all?: boolean): Promise<ClipboardOutput>;
69
69
  paste(input: ClipboardInput): Promise<void>;
70
+ setCommentsVisible(visible: boolean): void;
70
71
  pasteAsPlainText(text: string): void;
71
72
  table(command: TableCommand, options?: {
72
73
  rows?: number;