@aicayzer/inkkit 0.0.0-stage → 0.0.2
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/LICENSE +21 -0
- package/NOTICE +6 -0
- package/README.md +41 -2
- package/dist/autolink.d.ts +1 -0
- package/dist/autolink.js +18 -0
- package/dist/clipboard.d.ts +11 -0
- package/dist/clipboard.js +161 -0
- package/dist/decorations.d.ts +2 -0
- package/dist/decorations.js +66 -0
- package/dist/dialect.d.ts +9 -0
- package/dist/dialect.js +292 -0
- package/dist/editor.d.ts +110 -0
- package/dist/editor.js +1149 -0
- package/dist/highlight.d.ts +1 -0
- package/dist/highlight.js +49 -0
- package/dist/images.d.ts +32 -0
- package/dist/images.js +129 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.js +2 -0
- package/dist/literals.d.ts +16 -0
- package/dist/literals.js +142 -0
- package/dist/paste.d.ts +23 -0
- package/dist/paste.js +417 -0
- package/dist/preserve.d.ts +16 -0
- package/dist/preserve.js +403 -0
- package/dist/reference-clipboard.d.ts +8 -0
- package/dist/reference-clipboard.js +197 -0
- package/dist/references.d.ts +14 -0
- package/dist/references.js +367 -0
- package/dist/selection.d.ts +1 -0
- package/dist/selection.js +47 -0
- package/dist/style.css +564 -0
- package/dist/tables.d.ts +7 -0
- package/dist/tables.js +80 -0
- package/dist/tasks.d.ts +3 -0
- package/dist/tasks.js +49 -0
- package/dist/types.d.ts +60 -0
- package/dist/types.js +8 -0
- package/package.json +74 -4
package/dist/paste.js
ADDED
|
@@ -0,0 +1,417 @@
|
|
|
1
|
+
import { editorViewCtx, parserCtx } from '@milkdown/kit/core';
|
|
2
|
+
import { DOMParser as ProseParser, Fragment, Slice, } from '@milkdown/kit/prose/model';
|
|
3
|
+
import { Plugin, PluginKey } from '@milkdown/kit/prose/state';
|
|
4
|
+
import { $prose } from '@milkdown/kit/utils';
|
|
5
|
+
import { avoidReferenceCollisions, markdownFromHTML, } from './reference-clipboard.js';
|
|
6
|
+
import { InkKitError, } from './types.js';
|
|
7
|
+
export class PasteController {
|
|
8
|
+
options;
|
|
9
|
+
active;
|
|
10
|
+
dead = false;
|
|
11
|
+
get pending() {
|
|
12
|
+
return this.active != null;
|
|
13
|
+
}
|
|
14
|
+
plugin;
|
|
15
|
+
constructor(options) {
|
|
16
|
+
this.options = options;
|
|
17
|
+
this.plugin = $prose(() => new Plugin({
|
|
18
|
+
key: new PluginKey('inkkitPaste'),
|
|
19
|
+
state: {
|
|
20
|
+
init: () => null,
|
|
21
|
+
apply: (transaction) => {
|
|
22
|
+
if (this.active) {
|
|
23
|
+
const empty = this.active.from === this.active.to;
|
|
24
|
+
this.active.from = transaction.mapping.map(this.active.from, 1);
|
|
25
|
+
this.active.to = transaction.mapping.map(this.active.to, empty ? 1 : -1);
|
|
26
|
+
}
|
|
27
|
+
return null;
|
|
28
|
+
},
|
|
29
|
+
},
|
|
30
|
+
props: {
|
|
31
|
+
handlePaste: (view, event) => {
|
|
32
|
+
const data = event.clipboardData;
|
|
33
|
+
if (!data || !view.editable)
|
|
34
|
+
return false;
|
|
35
|
+
if (view.state.selection.$from.parent.type.spec.code)
|
|
36
|
+
return false;
|
|
37
|
+
const text = data.getData('text/plain');
|
|
38
|
+
const html = data.getData('text/html');
|
|
39
|
+
const metadata = data.getData('vscode-editor-data');
|
|
40
|
+
if (metadata) {
|
|
41
|
+
try {
|
|
42
|
+
const value = JSON.parse(metadata);
|
|
43
|
+
if (!value ||
|
|
44
|
+
typeof value !== 'object' ||
|
|
45
|
+
!('mode' in value) ||
|
|
46
|
+
typeof value.mode !== 'string')
|
|
47
|
+
throw new Error('Invalid VS Code clipboard metadata');
|
|
48
|
+
// Retain the provider's code-language behaviour for valid metadata.
|
|
49
|
+
return false;
|
|
50
|
+
}
|
|
51
|
+
catch {
|
|
52
|
+
void this.paste({ text, plainText: true }).catch((error) => options.onError?.(error));
|
|
53
|
+
return true;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
const files = [...data.files].filter((file) => file.type.startsWith('image/'));
|
|
57
|
+
void this.run({
|
|
58
|
+
text,
|
|
59
|
+
html,
|
|
60
|
+
plainText: this.options.literalText?.() ?? false,
|
|
61
|
+
}, files).catch((error) => options.onError?.(error));
|
|
62
|
+
return true;
|
|
63
|
+
},
|
|
64
|
+
},
|
|
65
|
+
}));
|
|
66
|
+
}
|
|
67
|
+
paste(input) {
|
|
68
|
+
return this.run(input);
|
|
69
|
+
}
|
|
70
|
+
cancelPending() {
|
|
71
|
+
this.active = undefined;
|
|
72
|
+
}
|
|
73
|
+
destroy() {
|
|
74
|
+
this.dead = true;
|
|
75
|
+
this.cancelPending();
|
|
76
|
+
}
|
|
77
|
+
assertContext(context, operation) {
|
|
78
|
+
if (this.dead)
|
|
79
|
+
throw new InkKitError('destroyed', 'The editor was destroyed.');
|
|
80
|
+
const current = this.options.context();
|
|
81
|
+
if (this.active !== operation ||
|
|
82
|
+
current.documentId !== context.documentId ||
|
|
83
|
+
current.generation !== context.generation)
|
|
84
|
+
throw new InkKitError('stale-document', 'The document changed while images were importing.');
|
|
85
|
+
}
|
|
86
|
+
async run(input, files = []) {
|
|
87
|
+
if (this.dead)
|
|
88
|
+
throw new InkKitError('destroyed', 'The editor was destroyed.');
|
|
89
|
+
if (this.pending)
|
|
90
|
+
throw new InkKitError('operation-pending', 'An image paste is still pending.');
|
|
91
|
+
const context = { ...this.options.context() };
|
|
92
|
+
const ctx = this.options.ctx();
|
|
93
|
+
const view = ctx.get(editorViewCtx);
|
|
94
|
+
if (!view.editable || this.options.editable?.() === false)
|
|
95
|
+
return;
|
|
96
|
+
if (view.composing)
|
|
97
|
+
throw new InkKitError('composition', 'Finish composing text before pasting.');
|
|
98
|
+
const operation = {
|
|
99
|
+
from: view.state.selection.from,
|
|
100
|
+
to: view.state.selection.to,
|
|
101
|
+
};
|
|
102
|
+
this.active = operation;
|
|
103
|
+
try {
|
|
104
|
+
const images = [...(input.images ?? [])];
|
|
105
|
+
for (const file of files)
|
|
106
|
+
images.push({
|
|
107
|
+
bytes: new Uint8Array(await file.arrayBuffer()),
|
|
108
|
+
mimeType: file.type,
|
|
109
|
+
filename: file.name,
|
|
110
|
+
});
|
|
111
|
+
this.assertContext(context, operation);
|
|
112
|
+
const schema = view.state.schema;
|
|
113
|
+
let slice;
|
|
114
|
+
const plainText = input.plainText ||
|
|
115
|
+
this.options.literalText?.() ||
|
|
116
|
+
view.state.selection.$from.parent.type.spec.code;
|
|
117
|
+
const markdown = plainText
|
|
118
|
+
? undefined
|
|
119
|
+
: (input.markdown ??
|
|
120
|
+
(input.html ? markdownFromHTML(input.html) : undefined));
|
|
121
|
+
if (plainText) {
|
|
122
|
+
if (view.state.selection.$from.parent.type.spec.code) {
|
|
123
|
+
const anchor = operation;
|
|
124
|
+
view.dispatch(view.state.tr.insertText(input.text, anchor.from, anchor.to));
|
|
125
|
+
return;
|
|
126
|
+
}
|
|
127
|
+
const paragraphs = input.text
|
|
128
|
+
.replace(/\r\n?/g, '\n')
|
|
129
|
+
.split('\n')
|
|
130
|
+
.map((line) => schema.nodes.paragraph.create(null, line ? schema.text(line) : undefined));
|
|
131
|
+
slice = new Slice(Fragment.fromArray(paragraphs), 1, 1);
|
|
132
|
+
}
|
|
133
|
+
else if (input.html) {
|
|
134
|
+
// Template contents remain inert while unsupported source is replaced
|
|
135
|
+
// and managed image references are imported.
|
|
136
|
+
const template = document.createElement('template');
|
|
137
|
+
template.innerHTML = input.html;
|
|
138
|
+
const container = template.content;
|
|
139
|
+
if (markdown == null) {
|
|
140
|
+
// Native destinations can keep node metadata after dropping the
|
|
141
|
+
// Markdown wrapper and its definitions. Import their visible HTML.
|
|
142
|
+
for (const element of container.querySelectorAll('[data-inkkit-reference],[data-inkkit-footnote-reference],[data-inkkit-footnote-definition]')) {
|
|
143
|
+
for (const name of [
|
|
144
|
+
'data-inkkit-reference',
|
|
145
|
+
'data-inkkit-footnote-reference',
|
|
146
|
+
'data-inkkit-footnote-definition',
|
|
147
|
+
'data-inkkit-label',
|
|
148
|
+
'data-inkkit-reference-type',
|
|
149
|
+
'data-inkkit-reference-content',
|
|
150
|
+
])
|
|
151
|
+
element.removeAttribute(name);
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
// Native HTML clipboards add encoding metadata. Obsidian's reading
|
|
155
|
+
// view also copies its title and collapse controls with document HTML.
|
|
156
|
+
for (const metadata of container.querySelectorAll('meta[charset]'))
|
|
157
|
+
metadata.remove();
|
|
158
|
+
for (const control of container.querySelectorAll('div.mod-header.mod-ui,div.mod-footer.mod-ui')) {
|
|
159
|
+
if (control.querySelector('.inline-title[contenteditable],.embedded-backlinks'))
|
|
160
|
+
control.remove();
|
|
161
|
+
}
|
|
162
|
+
for (const heading of container.querySelectorAll('h1[data-heading],h2[data-heading],h3[data-heading],h4[data-heading],h5[data-heading],h6[data-heading]')) {
|
|
163
|
+
for (const control of heading.querySelectorAll(':scope > span.heading-collapse-indicator.collapse-indicator.collapse-icon')) {
|
|
164
|
+
if (control.querySelector(':scope > svg.svg-icon.right-triangle') &&
|
|
165
|
+
!control.textContent?.trim())
|
|
166
|
+
control.remove();
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
for (const control of container.querySelectorAll('pre > button.copy-code-button')) {
|
|
170
|
+
if (control.querySelector('svg.svg-icon'))
|
|
171
|
+
control.remove();
|
|
172
|
+
}
|
|
173
|
+
const supported = new Set([
|
|
174
|
+
'P',
|
|
175
|
+
'H1',
|
|
176
|
+
'H2',
|
|
177
|
+
'H3',
|
|
178
|
+
'H4',
|
|
179
|
+
'H5',
|
|
180
|
+
'H6',
|
|
181
|
+
'STRONG',
|
|
182
|
+
'B',
|
|
183
|
+
'EM',
|
|
184
|
+
'I',
|
|
185
|
+
'S',
|
|
186
|
+
'DEL',
|
|
187
|
+
'STRIKE',
|
|
188
|
+
'CODE',
|
|
189
|
+
'PRE',
|
|
190
|
+
'BLOCKQUOTE',
|
|
191
|
+
'A',
|
|
192
|
+
'BR',
|
|
193
|
+
'HR',
|
|
194
|
+
'UL',
|
|
195
|
+
'OL',
|
|
196
|
+
'LI',
|
|
197
|
+
'TABLE',
|
|
198
|
+
'THEAD',
|
|
199
|
+
'TBODY',
|
|
200
|
+
'TFOOT',
|
|
201
|
+
'TR',
|
|
202
|
+
'TH',
|
|
203
|
+
'TD',
|
|
204
|
+
'IMG',
|
|
205
|
+
'SPAN',
|
|
206
|
+
'SUP',
|
|
207
|
+
'DIV',
|
|
208
|
+
'FONT',
|
|
209
|
+
'SECTION',
|
|
210
|
+
'ARTICLE',
|
|
211
|
+
'MAIN',
|
|
212
|
+
'HEADER',
|
|
213
|
+
'FOOTER',
|
|
214
|
+
]);
|
|
215
|
+
for (const element of [...container.querySelectorAll('*')]) {
|
|
216
|
+
if (!container.contains(element) || supported.has(element.tagName))
|
|
217
|
+
continue;
|
|
218
|
+
const literal = document.createElement('pre');
|
|
219
|
+
literal.className = 'literal-markdown';
|
|
220
|
+
literal.textContent = element.outerHTML;
|
|
221
|
+
element.replaceWith(literal);
|
|
222
|
+
}
|
|
223
|
+
const walker = document.createTreeWalker(container, NodeFilter.SHOW_COMMENT);
|
|
224
|
+
const comments = [];
|
|
225
|
+
while (walker.nextNode())
|
|
226
|
+
comments.push(walker.currentNode);
|
|
227
|
+
for (const comment of comments) {
|
|
228
|
+
const literal = document.createElement('pre');
|
|
229
|
+
literal.className = 'literal-markdown';
|
|
230
|
+
literal.textContent = `<!--${comment.data}-->`;
|
|
231
|
+
comment.replaceWith(literal);
|
|
232
|
+
}
|
|
233
|
+
for (const table of [...container.querySelectorAll('table')]) {
|
|
234
|
+
if (table.parentElement?.closest('table'))
|
|
235
|
+
continue;
|
|
236
|
+
const rows = [...table.querySelectorAll('tr')];
|
|
237
|
+
const widths = rows.map((row) => row.querySelectorAll(':scope > th,:scope > td').length);
|
|
238
|
+
const unsupported = !schema.nodes.table ||
|
|
239
|
+
widths.length === 0 ||
|
|
240
|
+
widths.some((width) => !width || width !== widths[0]) ||
|
|
241
|
+
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');
|
|
242
|
+
if (!unsupported && !table.querySelector('th')) {
|
|
243
|
+
for (const cell of rows[0]?.querySelectorAll(':scope > td') ?? []) {
|
|
244
|
+
const heading = document.createElement('th');
|
|
245
|
+
for (const attribute of [...cell.attributes])
|
|
246
|
+
heading.setAttribute(attribute.name, attribute.value);
|
|
247
|
+
heading.append(...cell.childNodes);
|
|
248
|
+
cell.replaceWith(heading);
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
if (unsupported) {
|
|
252
|
+
const literal = document.createElement('pre');
|
|
253
|
+
literal.className = 'literal-markdown';
|
|
254
|
+
literal.textContent = table.outerHTML;
|
|
255
|
+
table.replaceWith(literal);
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
const imported = new Map();
|
|
259
|
+
const elements = [...container.querySelectorAll('img')];
|
|
260
|
+
const uniqueSources = new Set(elements.map((element) => element.getAttribute('src') ?? ''));
|
|
261
|
+
// A host-supplied array explicitly associates unnamed bytes by unique
|
|
262
|
+
// preview order. A browser FileList has no such association, except the
|
|
263
|
+
// unambiguous single-file/single-source case.
|
|
264
|
+
const positional = (files.length === 0 || (files.length === 1 && uniqueSources.size === 1)
|
|
265
|
+
? images
|
|
266
|
+
: (input.images ?? [])).filter((image) => !image.source);
|
|
267
|
+
let index = 0;
|
|
268
|
+
for (const element of elements) {
|
|
269
|
+
const source = element.getAttribute('src') ?? '';
|
|
270
|
+
const alt = element.getAttribute('alt') ?? 'Image';
|
|
271
|
+
try {
|
|
272
|
+
let reference = imported.get(source);
|
|
273
|
+
if (!reference) {
|
|
274
|
+
const matched = images.find((image) => image.source ===
|
|
275
|
+
source);
|
|
276
|
+
const captured = matched ?? dataImage(source) ?? positional[index++];
|
|
277
|
+
if (!captured || !this.options.adapter)
|
|
278
|
+
throw new Error('The pasted image is unavailable.');
|
|
279
|
+
reference = (await this.options.adapter.importImage(captured, { ...context })).reference;
|
|
280
|
+
this.assertContext(context, operation);
|
|
281
|
+
imported.set(source, reference);
|
|
282
|
+
}
|
|
283
|
+
element.setAttribute('src', reference);
|
|
284
|
+
element.removeAttribute('srcset');
|
|
285
|
+
}
|
|
286
|
+
catch (error) {
|
|
287
|
+
if (error instanceof InkKitError)
|
|
288
|
+
throw error;
|
|
289
|
+
element.replaceWith(document.createTextNode(`[${alt}: image unavailable]`));
|
|
290
|
+
this.options.onError?.(new InkKitError('image-unavailable', error instanceof Error
|
|
291
|
+
? error.message
|
|
292
|
+
: 'The pasted image is unavailable.'));
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
if (markdown != null) {
|
|
296
|
+
const parsed = ctx.get(parserCtx)(markdown);
|
|
297
|
+
let imageIndex = 0;
|
|
298
|
+
const hydrate = (node) => {
|
|
299
|
+
if (node.type.name === 'image') {
|
|
300
|
+
const slot = /^inkkit-clipboard-image:(\d+)$/.exec(String(node.attrs.src));
|
|
301
|
+
const position = slot ? Number(slot[1]) : imageIndex++;
|
|
302
|
+
const element = elements.some((element) => element.hasAttribute('data-inkkit-image-slot'))
|
|
303
|
+
? elements.find((element) => Number(element.getAttribute('data-inkkit-image-slot')) ===
|
|
304
|
+
position)
|
|
305
|
+
: elements[position];
|
|
306
|
+
if (!element || !container.contains(element))
|
|
307
|
+
return schema.text(`[${node.attrs.alt || 'Image'}: image unavailable]`);
|
|
308
|
+
return node.type.create({ ...node.attrs, src: element.getAttribute('src') }, null, node.marks);
|
|
309
|
+
}
|
|
310
|
+
if (node.isText)
|
|
311
|
+
return node;
|
|
312
|
+
const children = [];
|
|
313
|
+
node.forEach((child) => children.push(hydrate(child)));
|
|
314
|
+
return node.copy(Fragment.fromArray(children));
|
|
315
|
+
};
|
|
316
|
+
slice = new Slice(hydrate(parsed).content, 0, 0);
|
|
317
|
+
}
|
|
318
|
+
else
|
|
319
|
+
slice = ProseParser.fromSchema(schema).parseSlice(container, {
|
|
320
|
+
preserveWhitespace: true,
|
|
321
|
+
});
|
|
322
|
+
}
|
|
323
|
+
else if (images.length) {
|
|
324
|
+
const nodes = [];
|
|
325
|
+
if (input.text)
|
|
326
|
+
nodes.push(schema.nodes.paragraph.create(null, schema.text(input.text)));
|
|
327
|
+
for (const image of images) {
|
|
328
|
+
try {
|
|
329
|
+
if (!this.options.adapter)
|
|
330
|
+
throw new InkKitError('image-unavailable', 'No image adapter is configured.');
|
|
331
|
+
const imported = await this.options.adapter.importImage(image, {
|
|
332
|
+
...context,
|
|
333
|
+
});
|
|
334
|
+
this.assertContext(context, operation);
|
|
335
|
+
const imageNode = schema.nodes.image?.create({
|
|
336
|
+
src: imported.reference,
|
|
337
|
+
alt: image.filename ?? 'Image',
|
|
338
|
+
});
|
|
339
|
+
if (!imageNode)
|
|
340
|
+
throw new InkKitError('image-unavailable', 'Images are disabled for this document.');
|
|
341
|
+
nodes.push(schema.nodes.paragraph.create(null, imageNode));
|
|
342
|
+
}
|
|
343
|
+
catch (error) {
|
|
344
|
+
if (error instanceof InkKitError &&
|
|
345
|
+
error.code !== 'image-unavailable')
|
|
346
|
+
throw error;
|
|
347
|
+
nodes.push(schema.nodes.paragraph.create(null, schema.text(`[${image.filename ?? 'Image'}: image unavailable]`)));
|
|
348
|
+
this.options.onError?.(new InkKitError('image-unavailable', error instanceof Error
|
|
349
|
+
? error.message
|
|
350
|
+
: 'The pasted image is unavailable.'));
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
slice = new Slice(Fragment.fromArray(nodes), 1, 1);
|
|
354
|
+
}
|
|
355
|
+
else {
|
|
356
|
+
const selection = view.state.selection;
|
|
357
|
+
const text = input.text.trim();
|
|
358
|
+
if (markdown == null &&
|
|
359
|
+
!selection.empty &&
|
|
360
|
+
/^https?:\/\/\S+$/.test(text) &&
|
|
361
|
+
schema.marks.link) {
|
|
362
|
+
const anchor = operation;
|
|
363
|
+
const mark = schema.marks.link.create({ href: text, title: '' });
|
|
364
|
+
view.dispatch(view.state.tr
|
|
365
|
+
.addMark(anchor.from, anchor.to, mark)
|
|
366
|
+
.removeStoredMark(mark));
|
|
367
|
+
return;
|
|
368
|
+
}
|
|
369
|
+
const doc = ctx.get(parserCtx)(markdown ?? input.text);
|
|
370
|
+
slice = new Slice(doc.content, 0, 0);
|
|
371
|
+
}
|
|
372
|
+
this.assertContext(context, operation);
|
|
373
|
+
if (!view.editable || this.options.editable?.() === false)
|
|
374
|
+
return;
|
|
375
|
+
const anchor = operation;
|
|
376
|
+
if (!plainText) {
|
|
377
|
+
const valid = slice.content.firstChild?.isInline
|
|
378
|
+
? Fragment.from(schema.nodes.paragraph.create(null, slice.content))
|
|
379
|
+
: slice.content;
|
|
380
|
+
const remapped = avoidReferenceCollisions(schema.topNodeType.create(null, valid), view.state.doc);
|
|
381
|
+
slice = new Slice(slice.content.firstChild?.isInline
|
|
382
|
+
? remapped.firstChild.content
|
|
383
|
+
: remapped.content, slice.openStart, slice.openEnd);
|
|
384
|
+
}
|
|
385
|
+
const insertion = view.state.doc.resolve(anchor.from);
|
|
386
|
+
// An isolated block such as a table cannot fit inside an empty paragraph.
|
|
387
|
+
// Replace that empty block rather than retaining a spurious spacer.
|
|
388
|
+
if (anchor.from === anchor.to &&
|
|
389
|
+
insertion.parent.type.name === 'paragraph' &&
|
|
390
|
+
insertion.parent.content.size === 0 &&
|
|
391
|
+
slice.content.firstChild?.isBlock &&
|
|
392
|
+
slice.content.firstChild.type.spec.isolating) {
|
|
393
|
+
view.dispatch(view.state.tr
|
|
394
|
+
.replaceRange(insertion.before(), insertion.after(), new Slice(slice.content, 0, 0))
|
|
395
|
+
.scrollIntoView());
|
|
396
|
+
}
|
|
397
|
+
else
|
|
398
|
+
view.dispatch(view.state.tr
|
|
399
|
+
.replaceRange(anchor.from, anchor.to, slice)
|
|
400
|
+
.scrollIntoView());
|
|
401
|
+
}
|
|
402
|
+
finally {
|
|
403
|
+
if (this.active === operation)
|
|
404
|
+
this.active = undefined;
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
function dataImage(source) {
|
|
409
|
+
const match = /^data:(image\/(?:png|jpeg|gif|webp|avif|bmp|tiff));base64,([A-Za-z0-9+/=\s]+)$/.exec(source);
|
|
410
|
+
if (!match)
|
|
411
|
+
return undefined;
|
|
412
|
+
const binary = atob(match[2]);
|
|
413
|
+
return {
|
|
414
|
+
mimeType: match[1],
|
|
415
|
+
bytes: Uint8Array.from(binary, (character) => character.charCodeAt(0)),
|
|
416
|
+
};
|
|
417
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { Ctx } from '@milkdown/kit/ctx';
|
|
2
|
+
import type { Node as ProseNode } from '@milkdown/kit/prose/model';
|
|
3
|
+
export declare class PreservationError extends Error {
|
|
4
|
+
constructor(message: string, options?: ErrorOptions);
|
|
5
|
+
}
|
|
6
|
+
/** Retains the loaded source of blocks the edit leaves alone. */
|
|
7
|
+
export declare class Preservation {
|
|
8
|
+
private readonly ctx;
|
|
9
|
+
private readonly source;
|
|
10
|
+
private readonly baseline;
|
|
11
|
+
private readonly blocks;
|
|
12
|
+
private readonly lineEnding;
|
|
13
|
+
private readonly forms;
|
|
14
|
+
constructor(ctx: Ctx, source: string);
|
|
15
|
+
serialize(doc: ProseNode): string;
|
|
16
|
+
}
|