@aicayzer/inkkit 0.0.4 → 0.0.6

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
@@ -33,10 +33,14 @@ editor.loadDocument({
33
33
  const snapshot = editor.snapshot(1)
34
34
  ```
35
35
 
36
- Markdown includes CommonMark, task lists, strikethrough, autolinks, GFM tables, editable footnotes and reference-style links, bounded callouts, highlights and author comments. Unsupported constructs remain literal Markdown. TXT mode treats all text literally. Images require a host adapter. RTF files remain outside the package scope.
36
+ Markdown includes CommonMark, task lists, strikethrough, autolinks, GFM tables, editable footnotes and reference-style links, bounded callouts, highlights, author comments and offline Mermaid diagrams. Unsupported constructs remain literal Markdown. TXT mode treats all text literally. Images require a host adapter. RTF files remain outside the package scope.
37
+
38
+ Table controls include complete row/column movement and explicit stable text or numeric sorting. Ordinary spreadsheet paste preserves cell boundaries, replaces selected cells and grows supported tables. Hosts call these operations through the public facade; each is undoable.
39
+
40
+ `await editor.printableSnapshot(1)` returns a complete printable HTML document, print styles and portable image bytes. It excludes author comments, includes folded content and reports unavailable assets or a changed document. Client apps own Print/PDF commands, dialogs and file generation.
37
41
 
38
42
  See [supported syntax](docs/supported-syntax.md), [the host API](docs/api.md), [preservation and clipboard behavior](docs/preservation.md), [development](docs/development.md), and [releases](docs/releasing.md).
39
43
 
40
- Planned work and release scope are tracked in the [InkKit Project](https://github.com/users/aicayzer/projects/3) and [milestones](https://github.com/aicayzer/inkkit/milestones). Release 0.0.2 is published and verified; 0.0.3 delivery is authorised. Targets from 0.0.4 remain provisional.
44
+ Delivery scope and release status are tracked in the [InkKit Project](https://github.com/users/aicayzer/projects/3) and [milestones](https://github.com/aicayzer/inkkit/milestones).
41
45
 
42
46
  MIT licensed. InkKit builds on the public PadPad and Memos editors; see [NOTICE](NOTICE).
package/dist/dialect.js CHANGED
@@ -3,6 +3,7 @@ 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 { normalizeTableAlignment, normalizeTableColumnAlignment, } from './table-alignment.js';
6
7
  import { preservingCodeBlocks, remarkCodeFences, fencedCodeHandler, } from './code-fences.js';
7
8
  import { literalBlock, createLiteralPreservation } from './literals.js';
8
9
  import { references, remarkReferencesPlugin } from './references.js';
@@ -86,6 +87,17 @@ function remarkDialect() {
86
87
  add('toMarkdownExtensions', gfmTaskListItemToMarkdown());
87
88
  add('toMarkdownExtensions', gfmAutolinkLiteralToMarkdown());
88
89
  add('toMarkdownExtensions', gfmTableToMarkdown());
90
+ add('toMarkdownExtensions', {
91
+ handlers: {
92
+ text(node, parent, state, info) {
93
+ const value = defaultHandlers.text(node, parent, state, info);
94
+ // GFM trims literal whitespace at cell edges on reopening.
95
+ return state.stack.includes('tableCell')
96
+ ? value.replace(/^[ \t]+|[ \t]+$/g, (spaces) => spaces.replaceAll(' ', ' ').replaceAll('\t', '	'))
97
+ : value;
98
+ },
99
+ },
100
+ });
89
101
  add('toMarkdownExtensions', { handlers: { link, code: fencedCodeHandler } });
90
102
  }
91
103
  export const remarkDialectPlugin = $remark('remarkDialect', () => remarkDialect);
@@ -209,7 +221,7 @@ export const tables = [
209
221
  return false;
210
222
  return {
211
223
  ...attrs,
212
- alignment: dom.style.textAlign || dom.getAttribute('align') || null,
224
+ alignment: normalizeTableAlignment(dom),
213
225
  };
214
226
  },
215
227
  }
@@ -231,7 +243,7 @@ export const tables = [
231
243
  return false;
232
244
  return {
233
245
  ...attrs,
234
- alignment: dom.style.textAlign || dom.getAttribute('align') || null,
246
+ alignment: normalizeTableColumnAlignment(dom),
235
247
  };
236
248
  },
237
249
  }
package/dist/editor.d.ts CHANGED
@@ -1,5 +1,5 @@
1
- import { type TableCommand } from './tables.js';
2
- import { type DocumentInput, type DocumentSnapshot, type ImageAdapter, type ClipboardInput, type ClipboardOutput } from './types.js';
1
+ import { type TableCommand, type TableOptions } from './tables.js';
2
+ import { type DocumentInput, type DocumentSnapshot, type ImageAdapter, type ClipboardInput, type ClipboardOutput, type PrintableDocument } from './types.js';
3
3
  export type Mark = 'bold' | 'italic' | 'strikethrough' | 'highlight' | 'code' | 'link';
4
4
  export type Block = {
5
5
  type: 'paragraph';
@@ -65,14 +65,12 @@ export declare class InkKitEditor {
65
65
  private assertCurrent;
66
66
  private plainSource;
67
67
  snapshot(expectedGeneration?: number): DocumentSnapshot;
68
+ printableSnapshot(expectedGeneration?: number): Promise<PrintableDocument>;
68
69
  clipboardSnapshot(all?: boolean): Promise<ClipboardOutput>;
69
70
  paste(input: ClipboardInput): Promise<void>;
70
71
  setCommentsVisible(visible: boolean): void;
71
72
  pasteAsPlainText(text: string): void;
72
- table(command: TableCommand, options?: {
73
- rows?: number;
74
- columns?: number;
75
- }): boolean;
73
+ table(command: TableCommand, options?: TableOptions): boolean;
76
74
  destroy(): Promise<void>;
77
75
  private keymapPlugin;
78
76
  static mount(root: HTMLElement, events: EditorEvents, options?: {
package/dist/editor.js CHANGED
@@ -1,4 +1,4 @@
1
- import { Editor, defaultValueCtx, editorViewCtx, editorViewOptionsCtx, remarkStringifyOptionsCtx, rootCtx, } from '@milkdown/kit/core';
1
+ import { Editor, defaultValueCtx, editorViewCtx, editorViewOptionsCtx, remarkCtx, remarkStringifyOptionsCtx, rootCtx, } from '@milkdown/kit/core';
2
2
  import { visibleClipboard, portableClipboard, clipboardText, clipboardContent, } from './clipboard.js';
3
3
  import { clipboard } from '@milkdown/kit/plugin/clipboard';
4
4
  import { history } from '@milkdown/kit/plugin/history';
@@ -17,7 +17,7 @@ import { Preservation } from './preserve.js';
17
17
  import { normaliseLabel, referenceDefinitions, footnoteDefinitions, } from './references.js';
18
18
  import { selectionMarkdown, selectionContent, referenceMetadata, withReferenceMetadata, } from './reference-clipboard.js';
19
19
  import { imageView } from './images.js';
20
- import { tablePlugins, tableCommand } from './tables.js';
20
+ import { tablePlugins, tableCommand, configureTableMovement, } from './tables.js';
21
21
  import { InkKitError, } from './types.js';
22
22
  import { highlightPlugin } from './highlight.js';
23
23
  import { highlightKeymap, toggleHighlightCommand } from './inline-highlight.js';
@@ -27,6 +27,7 @@ import { selectionPlugin } from './selection.js';
27
27
  import { search, SearchQuery, getSearchState, setSearchState, } from 'prosemirror-search';
28
28
  import { taskListPlugin, toggleTaskList } from './tasks.js';
29
29
  import { isMermaid, mermaidPreview } from './mermaid.js';
30
+ import { hasAuthoredImagesInLiterals, printableMarkdown, printableText, } from './print.js';
30
31
  const shortcutCommands = {
31
32
  heading1: ['heading', 1],
32
33
  heading2: ['heading', 2],
@@ -334,6 +335,42 @@ export class InkKitEditor {
334
335
  dirty: text !== this.originalSource,
335
336
  };
336
337
  }
338
+ async printableSnapshot(expectedGeneration) {
339
+ const snapshot = this.snapshot(expectedGeneration);
340
+ const epoch = this.documentEpoch;
341
+ const state = this.editor.ctx.get(editorViewCtx).state;
342
+ let output;
343
+ try {
344
+ if (snapshot.format === 'md' &&
345
+ !this.options.images &&
346
+ hasAuthoredImagesInLiterals(state.doc.content, (source) => this.editor.ctx.get(remarkCtx).parse(source)))
347
+ throw new InkKitError('image-unavailable', 'No image adapter is configured for the authored images.');
348
+ output =
349
+ snapshot.format === 'txt'
350
+ ? printableText(snapshot.text)
351
+ : await printableMarkdown(state.doc.content, state.schema, this.options.images, this.context());
352
+ }
353
+ finally {
354
+ this.assertCurrent(snapshot.generation);
355
+ if (snapshot.documentId !== this.documentId ||
356
+ epoch !== this.documentEpoch ||
357
+ snapshot.revision !== this.revision)
358
+ throw new InkKitError('stale-document', 'Document changed while preparing printable content');
359
+ }
360
+ return Object.freeze({
361
+ documentId: snapshot.documentId,
362
+ generation: snapshot.generation,
363
+ revision: snapshot.revision,
364
+ format: snapshot.format,
365
+ html: output.html,
366
+ styles: output.styles,
367
+ assets: Object.freeze(output.assets.map((asset) => Object.freeze({
368
+ ...asset,
369
+ bytes: new Uint8Array(asset.bytes),
370
+ }))),
371
+ warnings: Object.freeze(output.warnings.map((warning) => Object.freeze({ ...warning }))),
372
+ });
373
+ }
337
374
  async clipboardSnapshot(all = true) {
338
375
  const snapshot = this.snapshot();
339
376
  const epoch = this.documentEpoch;
@@ -552,6 +589,15 @@ export class InkKitEditor {
552
589
  .use($prose(() => search()))
553
590
  .use(options.images ? imageView(options.images) : [])
554
591
  .create();
592
+ configureTableMovement(instance.editor.ctx, () => {
593
+ try {
594
+ instance.assertCurrent();
595
+ return instance.formatType === 'md';
596
+ }
597
+ catch {
598
+ return false;
599
+ }
600
+ });
555
601
  instance.plain = document.createElement('textarea');
556
602
  instance.plain.className = 'inkkit-plain';
557
603
  instance.plain.setAttribute('aria-label', 'Plain text editor');
package/dist/index.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  export { InkKitEditor } from './editor.js';
2
2
  export type { EditorEvents, CaretState, Mark, Block, FormatCommand, Keymap, } from './editor.js';
3
- export type { TableCommand } from './tables.js';
3
+ export type { TableCommand, TableOptions } from './tables.js';
4
4
  export { InkKitError } from './types.js';
5
- export type { DocumentFormat, DocumentContext, DocumentInput, DocumentSnapshot, EditorErrorCode, ImageAdapter, CapturedImage, PortableImage, ClipboardInput, ClipboardOutput, ClipboardImage, ClipboardDiagram, } from './types.js';
5
+ export type { DocumentFormat, DocumentContext, DocumentInput, DocumentSnapshot, EditorErrorCode, ImageAdapter, CapturedImage, PortableImage, ClipboardInput, ClipboardOutput, ClipboardImage, ClipboardDiagram, PrintableDocument, PrintableWarning, } from './types.js';
@@ -12,6 +12,7 @@ declare module 'mdast' {
12
12
  inkkitLiteral: InkKitLiteral;
13
13
  }
14
14
  }
15
+ export declare function sourceFrontmatter(source: string): string | undefined;
15
16
  export declare function createLiteralPreservation(images: boolean): import("@milkdown/kit/utils").$Remark<"preserveLiterals", unknown>;
16
17
  export declare const preserveLiterals: import("@milkdown/kit/utils").$Remark<"preserveLiterals", unknown>;
17
18
  export declare const literalBlock: import("@milkdown/kit/utils").$Node;
package/dist/literals.js CHANGED
@@ -1,6 +1,9 @@
1
1
  import { $node, $remark } from '@milkdown/kit/utils';
2
2
  import { commentSource, literalCommentSpans, } from './comments.js';
3
3
  const unsupported = new Set(['html', 'image', 'imageReference']);
4
+ export function sourceFrontmatter(source) {
5
+ return /^(---|\+\+\+)\r?\n[\s\S]*?\r?\n\1(?:\r?\n|$)/.exec(source)?.[0];
6
+ }
4
7
  function restoreInlineBreaks(node) {
5
8
  if ((node.type === 'paragraph' || node.type === 'heading') &&
6
9
  node.children.length > 1) {
@@ -51,8 +54,8 @@ export function createLiteralPreservation(images) {
51
54
  restoreInlineBreaks(tree);
52
55
  // Micromark's positions exclude an initial byte-order mark.
53
56
  const source = String(file.value).replace(/^\uFEFF/, '');
54
- const frontmatter = /^(---|\+\+\+)\r?\n[\s\S]*?\r?\n\1(?:\r?\n|$)/.exec(source);
55
- const frontmatterEnd = frontmatter?.[0].length ?? 0;
57
+ const frontmatter = sourceFrontmatter(source);
58
+ const frontmatterEnd = frontmatter?.length ?? 0;
56
59
  const preserveBlock = (node, depth = 0) => {
57
60
  if (node.type === 'footnoteDefinition') {
58
61
  node.children = node.children.map((child) => preserveBlock(child, depth + 1));
@@ -80,13 +83,13 @@ export function createLiteralPreservation(images) {
80
83
  if (frontmatter)
81
84
  blocks.push({
82
85
  type: 'inkkitLiteral',
83
- value: frontmatter[0].trimEnd(),
86
+ value: frontmatter.trimEnd(),
84
87
  position: {
85
88
  start: { line: 1, column: 1, offset: 0 },
86
89
  end: {
87
90
  line: 1,
88
91
  column: 1,
89
- offset: frontmatter[0].trimEnd().length,
92
+ offset: frontmatter.trimEnd().length,
90
93
  },
91
94
  },
92
95
  });
package/dist/paste.js CHANGED
@@ -1,7 +1,8 @@
1
1
  import { editorViewCtx, parserCtx } from '@milkdown/kit/core';
2
- import { DOMParser as ProseParser, Fragment, Slice, } from '@milkdown/kit/prose/model';
2
+ import { DOMParser as ProseParser, DOMSerializer, Fragment, Slice, } from '@milkdown/kit/prose/model';
3
3
  import { Plugin, PluginKey } from '@milkdown/kit/prose/state';
4
4
  import { $prose } from '@milkdown/kit/utils';
5
+ import { pasteSpreadsheet } from './spreadsheet.js';
5
6
  import { avoidReferenceCollisions, markdownFromHTML, } from './reference-clipboard.js';
6
7
  import { InkKitError, } from './types.js';
7
8
  export class PasteController {
@@ -114,6 +115,10 @@ export class PasteController {
114
115
  const plainText = input.plainText ||
115
116
  this.options.literalText?.() ||
116
117
  view.state.selection.$from.parent.type.spec.code;
118
+ if (!plainText &&
119
+ input.markdown == null &&
120
+ pasteSpreadsheet(ctx, { ...input, images }))
121
+ return;
117
122
  const markdown = plainText
118
123
  ? undefined
119
124
  : (input.markdown ??
@@ -136,6 +141,16 @@ export class PasteController {
136
141
  const template = document.createElement('template');
137
142
  template.innerHTML = input.html;
138
143
  const container = template.content;
144
+ // Preserve whole unsupported tables before rewriting their descendants,
145
+ // so comments retain their source and ordinary-copy privacy.
146
+ for (const table of [...container.querySelectorAll('table')]) {
147
+ if (table.parentElement?.closest('table'))
148
+ continue;
149
+ if (schema.nodes.table && !unsupportedClipboardTable(table))
150
+ continue;
151
+ const preserved = ctx.get(parserCtx)(table.outerHTML);
152
+ table.replaceWith(DOMSerializer.fromSchema(schema).serializeFragment(preserved.content));
153
+ }
139
154
  for (const warning of container.querySelectorAll('[data-inkkit-diagram-error]'))
140
155
  warning.remove();
141
156
  for (const diagram of container.querySelectorAll('figure[data-inkkit-mermaid]')) {
@@ -252,11 +267,7 @@ export class PasteController {
252
267
  if (table.parentElement?.closest('table'))
253
268
  continue;
254
269
  const rows = [...table.querySelectorAll('tr')];
255
- const widths = rows.map((row) => row.querySelectorAll(':scope > th,:scope > td').length);
256
- const unsupported = !schema.nodes.table ||
257
- widths.length === 0 ||
258
- widths.some((width) => !width || width !== widths[0]) ||
259
- table.querySelector('table,[rowspan]:not([rowspan="1"]),[colspan]:not([colspan="1"]),td > p ~ p,th > p ~ p,td > div,th > div,td > ul,td > ol,td > pre,td > blockquote,th > ul,th > ol,th > pre,th > blockquote');
270
+ const unsupported = !schema.nodes.table || unsupportedClipboardTable(table);
260
271
  if (!unsupported && !table.querySelector('th')) {
261
272
  for (const cell of rows[0]?.querySelectorAll(':scope > td') ?? []) {
262
273
  const heading = document.createElement('th');
@@ -422,6 +433,45 @@ export class PasteController {
422
433
  }
423
434
  }
424
435
  }
436
+ function unsupportedClipboardTable(table) {
437
+ const rows = [...table.querySelectorAll('tr')];
438
+ const widths = rows.map((row) => row.querySelectorAll(':scope > th,:scope > td').length);
439
+ const supported = new Set([
440
+ 'THEAD',
441
+ 'TBODY',
442
+ 'TFOOT',
443
+ 'TR',
444
+ 'TH',
445
+ 'TD',
446
+ 'P',
447
+ 'STRONG',
448
+ 'B',
449
+ 'EM',
450
+ 'I',
451
+ 'S',
452
+ 'DEL',
453
+ 'STRIKE',
454
+ 'CODE',
455
+ 'A',
456
+ 'MARK',
457
+ 'SPAN',
458
+ 'SUP',
459
+ 'FONT',
460
+ 'IMG',
461
+ ]);
462
+ return (widths.length === 0 ||
463
+ widths.some((width) => !width || width !== widths[0]) ||
464
+ !!document.createTreeWalker(table, NodeFilter.SHOW_COMMENT).nextNode() ||
465
+ [...table.querySelectorAll('*')].some((element) => !supported.has(element.tagName)) ||
466
+ [...table.querySelectorAll('p')].some((paragraph) => {
467
+ const cell = paragraph.parentElement;
468
+ return (!cell ||
469
+ !['TH', 'TD'].includes(cell.tagName) ||
470
+ cell.children.length !== 1 ||
471
+ [...cell.childNodes].some((node) => node !== paragraph && !!node.textContent?.trim()));
472
+ }) ||
473
+ !!table.querySelector('[rowspan]:not([rowspan="1"]),[colspan]:not([colspan="1"])'));
474
+ }
425
475
  function dataImage(source) {
426
476
  const match = /^data:(image\/(?:png|jpeg|gif|webp|avif|bmp|tiff));base64,([A-Za-z0-9+/=\s]+)$/.exec(source);
427
477
  if (!match)
package/dist/preserve.js CHANGED
@@ -2,16 +2,14 @@ import { parserCtx, remarkCtx } from '@milkdown/kit/core';
2
2
  import { serialize } from './dialect.js';
3
3
  import { restoreCodeEndings, finalCodeEnding } from './code-fences.js';
4
4
  function signature(node) {
5
- return JSON.stringify(node, (key, value) => ['position', 'data', 'url', 'title', 'marker'].includes(key)
6
- ? undefined
7
- : value);
5
+ return JSON.stringify(node, (key, value) => ['position', 'data', 'marker'].includes(key) ? undefined : value);
8
6
  }
9
7
  function ownShape(node) {
10
8
  return JSON.stringify(node, (key, value) => ['position', 'data', 'children', 'value', 'marker'].includes(key)
11
9
  ? undefined
12
10
  : value);
13
11
  }
14
- function retainText(raw, before, after, quoteIndent = false) {
12
+ function retainText(raw, before, after, quoteIndent = false, inTable = false) {
15
13
  before = before.replace(/\r\n?/g, '\n');
16
14
  after = after.replace(/\r\n?/g, '\n');
17
15
  let prefix = 0;
@@ -64,7 +62,7 @@ function retainText(raw, before, after, quoteIndent = false) {
64
62
  }
65
63
  if (decoded !== before) {
66
64
  // Continuation indentation is excluded from a quoted paragraph's text.
67
- return quoteIndent ? after : retainText(raw, before, after, true);
65
+ return quoteIndent ? after : retainText(raw, before, after, true, inTable);
68
66
  }
69
67
  const inserted = after
70
68
  .slice(prefix, after.length - suffix)
@@ -73,7 +71,9 @@ function retainText(raw, before, after, quoteIndent = false) {
73
71
  const at = prefix + index;
74
72
  const delimiter = (character === '%' || character === '=') &&
75
73
  (after[at - 1] === character || after[at + 1] === character);
76
- return delimiter || /[\\`*_[\]<>]/.test(character)
74
+ return delimiter ||
75
+ /[\\`*_[\]<>]/.test(character) ||
76
+ (inTable && character === '|')
77
77
  ? `\\${character}`
78
78
  : character;
79
79
  })
@@ -108,7 +108,7 @@ function retainBoundaryWhitespace(raw, canonical) {
108
108
  return raw;
109
109
  }
110
110
  // Patch changed leaves so an adjacent edit retains authored delimiters and labels.
111
- function retainSource(before, after, source, canonical, lineEnding) {
111
+ function retainSource(before, after, source, canonical, lineEnding, inTable = false) {
112
112
  const start = before.position?.start.offset;
113
113
  const end = before.position?.end.offset;
114
114
  const newStart = after.position?.start.offset;
@@ -163,7 +163,7 @@ function retainSource(before, after, source, canonical, lineEnding) {
163
163
  destination[3]);
164
164
  }
165
165
  if (before.type === 'text' && after.type === 'text')
166
- return retainBoundaryWhitespace(retainText(raw, before.value, after.value), canonical.slice(newStart, newEnd));
166
+ return retainBoundaryWhitespace(retainText(raw, before.value, after.value, false, inTable), canonical.slice(newStart, newEnd));
167
167
  if (signature(before) === signature(after) &&
168
168
  ownShape(before) === ownShape(after))
169
169
  return raw;
@@ -173,13 +173,40 @@ function retainSource(before, after, source, canonical, lineEnding) {
173
173
  after.children &&
174
174
  before.children.length === after.children.length) {
175
175
  let output = '', previous = start;
176
+ const retained = new Set();
176
177
  before.children.forEach((child, index) => {
178
+ const incoming = after.children[index];
179
+ const movedIndex = before.type === 'table' || before.type === 'tableRow'
180
+ ? before.children.findIndex((candidate, candidateIndex) => !retained.has(candidateIndex) &&
181
+ signature(candidate) === signature(incoming) &&
182
+ ownShape(candidate) === ownShape(incoming))
183
+ : -1;
184
+ const original = movedIndex < 0 ? child : before.children[movedIndex];
185
+ if (movedIndex >= 0)
186
+ retained.add(movedIndex);
177
187
  const childStart = child.position?.start.offset, childEnd = child.position?.end.offset;
178
188
  if (childStart == null || childEnd == null)
179
189
  throw new PreservationError('A source token has no location');
180
- output +=
181
- source.slice(previous, childStart) +
182
- retainSource(child, after.children[index], source, canonical, lineEnding);
190
+ let retainedSource = retainSource(original, incoming, source, canonical, lineEnding, inTable || before.type === 'tableCell');
191
+ if (before.type === 'tableRow' && movedIndex >= 0) {
192
+ // Cell positions own different pipe boundaries at the row edges.
193
+ const originalFrom = original.children?.[0]?.position?.start.offset;
194
+ const originalTo = original.children?.at(-1)?.position?.end.offset;
195
+ const from = child.children?.[0]?.position?.start.offset;
196
+ const to = child.children?.at(-1)?.position?.end.offset;
197
+ retainedSource =
198
+ originalFrom != null &&
199
+ originalTo != null &&
200
+ from != null &&
201
+ to != null
202
+ ? source.slice(childStart, from) +
203
+ source.slice(originalFrom, originalTo) +
204
+ source.slice(to, childEnd)
205
+ : canonical
206
+ .slice(incoming.position.start.offset, incoming.position.end.offset)
207
+ .replaceAll('\n', lineEnding);
208
+ }
209
+ output += source.slice(previous, childStart) + retainedSource;
183
210
  previous = childEnd;
184
211
  });
185
212
  return output + source.slice(previous, end);
@@ -0,0 +1,7 @@
1
+ import type { Fragment, Schema } from '@milkdown/kit/prose/model';
2
+ import { type DocumentContext, type ImageAdapter, type PrintableDocument } from './types.js';
3
+ type PrintableContent = Pick<PrintableDocument, 'html' | 'styles' | 'assets' | 'warnings'>;
4
+ export declare function printableText(text: string): PrintableContent;
5
+ export declare function hasAuthoredImagesInLiterals(content: Fragment, parse: (source: string) => unknown): boolean;
6
+ export declare function printableMarkdown(content: Fragment, schema: Schema, adapter: ImageAdapter | undefined, context: DocumentContext): Promise<PrintableContent>;
7
+ export {};