@aicayzer/inkkit 0.0.18 → 0.1.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/NOTICE +4 -2
- package/README.md +11 -7
- package/dist/THIRD-PARTY-NOTICES.txt +2742 -0
- package/dist/chunks/core-vtCr4MBS.js +56503 -0
- package/dist/chunks/highlight-Daj2fv_u.js +14122 -0
- package/dist/core.d.ts +2 -5
- package/dist/core.js +2 -2
- package/dist/editor.d.ts +229 -47
- package/dist/index.js +5 -3
- package/dist/style.css +231 -202
- package/dist/types.d.ts +143 -18
- package/package.json +16 -13
- package/dist/autolink.d.ts +0 -1
- package/dist/autolink.js +0 -18
- package/dist/callouts.d.ts +0 -25
- package/dist/callouts.js +0 -288
- package/dist/clipboard.d.ts +0 -11
- package/dist/clipboard.js +0 -261
- package/dist/code-fences.d.ts +0 -8
- package/dist/code-fences.js +0 -171
- package/dist/comments.d.ts +0 -76
- package/dist/comments.js +0 -545
- package/dist/cursor.d.ts +0 -2
- package/dist/cursor.js +0 -40
- package/dist/decorations.d.ts +0 -2
- package/dist/decorations.js +0 -67
- package/dist/dialect.d.ts +0 -9
- package/dist/dialect.js +0 -446
- package/dist/drop-placement.d.ts +0 -19
- package/dist/drop-placement.js +0 -47
- package/dist/editor.js +0 -2673
- package/dist/files.d.ts +0 -40
- package/dist/files.js +0 -133
- package/dist/highlight.d.ts +0 -1
- package/dist/highlight.js +0 -136
- package/dist/html-import.d.ts +0 -13
- package/dist/html-import.js +0 -477
- package/dist/image-blocks.d.ts +0 -86
- package/dist/image-blocks.js +0 -329
- package/dist/images.d.ts +0 -51
- package/dist/images.js +0 -398
- package/dist/inline-highlight.d.ts +0 -6
- package/dist/inline-highlight.js +0 -112
- package/dist/labels.d.ts +0 -36
- package/dist/labels.js +0 -34
- package/dist/link-safety.d.ts +0 -17
- package/dist/link-safety.js +0 -29
- package/dist/linked-syntax.d.ts +0 -48
- package/dist/linked-syntax.js +0 -320
- package/dist/literals.d.ts +0 -20
- package/dist/literals.js +0 -216
- package/dist/mermaid.d.ts +0 -28
- package/dist/mermaid.js +0 -404
- package/dist/outline.d.ts +0 -22
- package/dist/outline.js +0 -79
- package/dist/paste-html.d.ts +0 -31
- package/dist/paste-html.js +0 -462
- package/dist/paste.d.ts +0 -33
- package/dist/paste.js +0 -591
- package/dist/portable-files.d.ts +0 -10
- package/dist/portable-files.js +0 -36
- package/dist/preserve.d.ts +0 -38
- package/dist/preserve.js +0 -1396
- package/dist/print.d.ts +0 -7
- package/dist/print.js +0 -406
- package/dist/reference-clipboard.d.ts +0 -8
- package/dist/reference-clipboard.js +0 -201
- package/dist/references.d.ts +0 -16
- package/dist/references.js +0 -418
- package/dist/scroll.d.ts +0 -15
- package/dist/scroll.js +0 -27
- package/dist/search.d.ts +0 -15
- package/dist/search.js +0 -202
- package/dist/selection.d.ts +0 -1
- package/dist/selection.js +0 -59
- package/dist/source.d.ts +0 -16
- package/dist/source.js +0 -57
- package/dist/spreadsheet.d.ts +0 -8
- package/dist/spreadsheet.js +0 -289
- package/dist/table-alignment.d.ts +0 -2
- package/dist/table-alignment.js +0 -46
- package/dist/tables.d.ts +0 -27
- package/dist/tables.js +0 -344
- package/dist/tasks.d.ts +0 -3
- package/dist/tasks.js +0 -51
- package/dist/text-ranges.d.ts +0 -29
- package/dist/text-ranges.js +0 -256
- package/dist/types.js +0 -8
- package/dist/wiki-view.d.ts +0 -21
- package/dist/wiki-view.js +0 -78
package/dist/paste.js
DELETED
|
@@ -1,591 +0,0 @@
|
|
|
1
|
-
import { editorViewCtx, parserCtx } from '@milkdown/kit/core';
|
|
2
|
-
import { DOMParser as ProseParser, DOMSerializer, 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 { pasteSpreadsheet } from './spreadsheet.js';
|
|
6
|
-
import { importableSlice, topLevel } from './html-import.js';
|
|
7
|
-
import { blockDropRange, caretAfter } from './drop-placement.js';
|
|
8
|
-
import { isImage } from './image-blocks.js';
|
|
9
|
-
import { collapseClipboardWhitespace, isBlockElement, isRewrittenElement, isRichElement, normalizeClipboardHTML, } from './paste-html.js';
|
|
10
|
-
import { avoidReferenceCollisions, markdownFromHTML, } from './reference-clipboard.js';
|
|
11
|
-
import { InkKitError, } from './types.js';
|
|
12
|
-
export class PasteController {
|
|
13
|
-
options;
|
|
14
|
-
active;
|
|
15
|
-
key = new PluginKey('inkkitPaste');
|
|
16
|
-
dead = false;
|
|
17
|
-
claimed = false;
|
|
18
|
-
get pending() {
|
|
19
|
-
return this.active != null;
|
|
20
|
-
}
|
|
21
|
-
plugin;
|
|
22
|
-
constructor(options) {
|
|
23
|
-
this.options = options;
|
|
24
|
-
this.plugin = $prose(() => new Plugin({
|
|
25
|
-
key: this.key,
|
|
26
|
-
state: {
|
|
27
|
-
init: () => null,
|
|
28
|
-
apply: (transaction, previous) => {
|
|
29
|
-
if (!this.active)
|
|
30
|
-
return null;
|
|
31
|
-
const anchor = previous?.operation === this.active ? previous : this.active;
|
|
32
|
-
return {
|
|
33
|
-
operation: this.active,
|
|
34
|
-
from: transaction.mapping.map(anchor.from, 1),
|
|
35
|
-
to: transaction.mapping.map(anchor.to, anchor.from === anchor.to ? 1 : -1),
|
|
36
|
-
};
|
|
37
|
-
},
|
|
38
|
-
},
|
|
39
|
-
props: {
|
|
40
|
-
handleDOMEvents: {
|
|
41
|
-
paste: (view, event) => {
|
|
42
|
-
// ProseMirror parses the clipboard before asking handlePaste,
|
|
43
|
-
// and this controller then imports it again itself.
|
|
44
|
-
this.claimed = claimsPaste(view, event) !== false;
|
|
45
|
-
queueMicrotask(() => {
|
|
46
|
-
this.claimed = false;
|
|
47
|
-
});
|
|
48
|
-
return false;
|
|
49
|
-
},
|
|
50
|
-
// A drop of HTML from outside the editor imports as a paste of
|
|
51
|
-
// whole blocks, placed as dropped images are. ProseMirror keeps
|
|
52
|
-
// moves within the editor and drops of plain text.
|
|
53
|
-
drop: (view, event) => {
|
|
54
|
-
const data = event.dataTransfer;
|
|
55
|
-
if (!data?.getData('text/html') ||
|
|
56
|
-
!view.editable ||
|
|
57
|
-
view.dragging)
|
|
58
|
-
return false;
|
|
59
|
-
event.preventDefault();
|
|
60
|
-
const target = blockDropRange(view, event.clientX, event.clientY);
|
|
61
|
-
view.focus();
|
|
62
|
-
void this.run({
|
|
63
|
-
text: data.getData('text/plain'),
|
|
64
|
-
html: data.getData('text/html'),
|
|
65
|
-
plainText: this.options.literalText?.() ?? false,
|
|
66
|
-
}, [...data.files].filter((file) => file.type.startsWith('image/')), target).catch((error) => options.onError?.(error));
|
|
67
|
-
return true;
|
|
68
|
-
},
|
|
69
|
-
},
|
|
70
|
-
// Pastes and drops that ProseMirror inserts itself.
|
|
71
|
-
transformPasted: (slice, view, plain) => this.claimed || plain || view.dragging
|
|
72
|
-
? slice
|
|
73
|
-
: importableSlice(this.options.ctx(), slice),
|
|
74
|
-
handlePaste: (view, event) => {
|
|
75
|
-
this.claimed = false;
|
|
76
|
-
const claim = claimsPaste(view, event);
|
|
77
|
-
if (claim === false)
|
|
78
|
-
return false;
|
|
79
|
-
const data = event.clipboardData;
|
|
80
|
-
const text = data.getData('text/plain');
|
|
81
|
-
const html = data.getData('text/html');
|
|
82
|
-
if (claim === 'plain') {
|
|
83
|
-
void this.paste({ text, plainText: true }).catch((error) => options.onError?.(error));
|
|
84
|
-
return true;
|
|
85
|
-
}
|
|
86
|
-
const files = [...data.files].filter((file) => file.type.startsWith('image/'));
|
|
87
|
-
void this.run({
|
|
88
|
-
text,
|
|
89
|
-
html,
|
|
90
|
-
plainText: this.options.literalText?.() ?? false,
|
|
91
|
-
}, files).catch((error) => options.onError?.(error));
|
|
92
|
-
return true;
|
|
93
|
-
},
|
|
94
|
-
},
|
|
95
|
-
}));
|
|
96
|
-
}
|
|
97
|
-
paste(input) {
|
|
98
|
-
return this.run(input);
|
|
99
|
-
}
|
|
100
|
-
anchor(operation) {
|
|
101
|
-
const mapped = this.key.getState(this.options.ctx().get(editorViewCtx).state);
|
|
102
|
-
return mapped?.operation === operation ? mapped : operation;
|
|
103
|
-
}
|
|
104
|
-
cancelPending() {
|
|
105
|
-
this.active = undefined;
|
|
106
|
-
this.options.onPendingChanged?.();
|
|
107
|
-
}
|
|
108
|
-
destroy() {
|
|
109
|
-
this.dead = true;
|
|
110
|
-
this.cancelPending();
|
|
111
|
-
}
|
|
112
|
-
assertContext(context, operation) {
|
|
113
|
-
if (this.dead)
|
|
114
|
-
throw new InkKitError('destroyed', 'The editor was destroyed.');
|
|
115
|
-
const current = this.options.context();
|
|
116
|
-
if (this.active !== operation ||
|
|
117
|
-
current.documentId !== context.documentId ||
|
|
118
|
-
current.generation !== context.generation)
|
|
119
|
-
throw new InkKitError('stale-document', 'The document changed while images were importing.');
|
|
120
|
-
}
|
|
121
|
-
/**
|
|
122
|
-
* Inserts dropped content as whole blocks, so none joins the block beside
|
|
123
|
-
* it, and puts the caret after them.
|
|
124
|
-
*/
|
|
125
|
-
insertBlocks(view, slice, range) {
|
|
126
|
-
const blocks = Fragment.fromArray(topLevel(view.state.schema, slice.content));
|
|
127
|
-
const tr = view.state.tr.replaceWith(range.from, range.to, blocks);
|
|
128
|
-
const end = tr.mapping.map(range.to);
|
|
129
|
-
view.dispatch(tr.setSelection(caretAfter(tr.doc, end)).scrollIntoView());
|
|
130
|
-
}
|
|
131
|
-
/** Imports the input at the selection, or in place of `target`. */
|
|
132
|
-
async run(input, files = [], target) {
|
|
133
|
-
if (this.dead)
|
|
134
|
-
throw new InkKitError('destroyed', 'The editor was destroyed.');
|
|
135
|
-
if (this.pending)
|
|
136
|
-
throw new InkKitError('operation-pending', 'An image paste is still pending.');
|
|
137
|
-
const context = { ...this.options.context() };
|
|
138
|
-
const ctx = this.options.ctx();
|
|
139
|
-
const view = ctx.get(editorViewCtx);
|
|
140
|
-
if (!view.editable || this.options.editable?.() === false)
|
|
141
|
-
return;
|
|
142
|
-
if (view.composing)
|
|
143
|
-
throw new InkKitError('composition', 'Finish composing text before pasting.');
|
|
144
|
-
const operation = target
|
|
145
|
-
? { ...target }
|
|
146
|
-
: { from: view.state.selection.from, to: view.state.selection.to };
|
|
147
|
-
const inCode = () => view.state.doc.resolve(this.anchor(operation).from).parent.type.spec.code;
|
|
148
|
-
this.active = operation;
|
|
149
|
-
this.options.onPendingChanged?.();
|
|
150
|
-
try {
|
|
151
|
-
const images = [...(input.images ?? [])];
|
|
152
|
-
for (const file of files)
|
|
153
|
-
images.push({
|
|
154
|
-
bytes: new Uint8Array(await file.arrayBuffer()),
|
|
155
|
-
mimeType: file.type,
|
|
156
|
-
filename: file.name,
|
|
157
|
-
});
|
|
158
|
-
this.assertContext(context, operation);
|
|
159
|
-
const schema = view.state.schema;
|
|
160
|
-
let slice;
|
|
161
|
-
const plainText = input.plainText || this.options.literalText?.() || inCode();
|
|
162
|
-
if (!plainText &&
|
|
163
|
-
input.markdown == null &&
|
|
164
|
-
pasteSpreadsheet(ctx, { ...input, images }, target && dropRange(view.state.doc, this.anchor(operation))))
|
|
165
|
-
return;
|
|
166
|
-
const markdown = plainText
|
|
167
|
-
? undefined
|
|
168
|
-
: (input.markdown ??
|
|
169
|
-
(input.html ? markdownFromHTML(input.html) : undefined));
|
|
170
|
-
if (plainText) {
|
|
171
|
-
if (inCode()) {
|
|
172
|
-
const anchor = this.anchor(operation);
|
|
173
|
-
view.dispatch(view.state.tr.insertText(input.text, anchor.from, anchor.to));
|
|
174
|
-
return;
|
|
175
|
-
}
|
|
176
|
-
const paragraphs = input.text
|
|
177
|
-
.replace(/\r\n?/g, '\n')
|
|
178
|
-
.split('\n')
|
|
179
|
-
.map((line) => schema.nodes.paragraph.create(null, line ? schema.text(line) : undefined));
|
|
180
|
-
slice = new Slice(Fragment.fromArray(paragraphs), 1, 1);
|
|
181
|
-
}
|
|
182
|
-
else if (input.html) {
|
|
183
|
-
// Template contents remain inert while unsupported source is replaced
|
|
184
|
-
// and managed image references are imported.
|
|
185
|
-
const template = document.createElement('template');
|
|
186
|
-
template.innerHTML = input.html;
|
|
187
|
-
const container = template.content;
|
|
188
|
-
// Preserve whole unsupported tables before rewriting their descendants,
|
|
189
|
-
// so comments retain their source and ordinary-copy privacy.
|
|
190
|
-
for (const table of [...container.querySelectorAll('table')]) {
|
|
191
|
-
if (table.parentElement?.closest('table'))
|
|
192
|
-
continue;
|
|
193
|
-
// Inside an element that stays literal source, the whole element is
|
|
194
|
-
// kept as written, so its tables are not rewritten or judged here.
|
|
195
|
-
if (!insideRewrittenContent(table))
|
|
196
|
-
continue;
|
|
197
|
-
if (schema.nodes.table) {
|
|
198
|
-
// Judge the table as it will import, so wrappers such as `u` or
|
|
199
|
-
// `label` in a cell do not make it literal. A table that is still
|
|
200
|
-
// unsupported keeps its original source. An input in a cell would
|
|
201
|
-
// be dropped with its state, so that table stays source too.
|
|
202
|
-
const trial = table.cloneNode(true);
|
|
203
|
-
const holder = table.ownerDocument.createDocumentFragment();
|
|
204
|
-
holder.append(trial);
|
|
205
|
-
normalizeClipboardHTML(holder);
|
|
206
|
-
if (!table.querySelector('td input,th input') &&
|
|
207
|
-
!unsupportedClipboardTable(trial)) {
|
|
208
|
-
table.replaceWith(trial);
|
|
209
|
-
continue;
|
|
210
|
-
}
|
|
211
|
-
}
|
|
212
|
-
const preserved = ctx.get(parserCtx)(table.outerHTML);
|
|
213
|
-
table.replaceWith(DOMSerializer.fromSchema(schema).serializeFragment(preserved.content));
|
|
214
|
-
}
|
|
215
|
-
for (const warning of container.querySelectorAll('[data-inkkit-diagram-error]'))
|
|
216
|
-
warning.remove();
|
|
217
|
-
for (const diagram of container.querySelectorAll('figure[data-inkkit-mermaid]')) {
|
|
218
|
-
const source = diagram.getAttribute('data-inkkit-mermaid') ?? '';
|
|
219
|
-
if (source.length > 30000)
|
|
220
|
-
continue;
|
|
221
|
-
const pre = document.createElement('pre');
|
|
222
|
-
pre.dataset.language = 'mermaid';
|
|
223
|
-
const fence = diagram.getAttribute('data-inkkit-fence');
|
|
224
|
-
if (fence)
|
|
225
|
-
pre.setAttribute('data-inkkit-fence', fence);
|
|
226
|
-
const code = document.createElement('code');
|
|
227
|
-
code.className = 'language-mermaid';
|
|
228
|
-
code.textContent = source;
|
|
229
|
-
pre.append(code);
|
|
230
|
-
diagram.replaceWith(pre);
|
|
231
|
-
}
|
|
232
|
-
if (markdown == null) {
|
|
233
|
-
// Native destinations can keep node metadata after dropping the
|
|
234
|
-
// Markdown wrapper and its definitions. Import their visible HTML.
|
|
235
|
-
for (const element of container.querySelectorAll('[data-inkkit-reference],[data-inkkit-footnote-reference],[data-inkkit-footnote-definition]')) {
|
|
236
|
-
for (const name of [
|
|
237
|
-
'data-inkkit-reference',
|
|
238
|
-
'data-inkkit-footnote-reference',
|
|
239
|
-
'data-inkkit-footnote-definition',
|
|
240
|
-
'data-inkkit-label',
|
|
241
|
-
'data-inkkit-reference-type',
|
|
242
|
-
'data-inkkit-reference-content',
|
|
243
|
-
])
|
|
244
|
-
element.removeAttribute(name);
|
|
245
|
-
}
|
|
246
|
-
}
|
|
247
|
-
// Native HTML clipboards add encoding metadata and fragment markers.
|
|
248
|
-
normalizeClipboardHTML(container);
|
|
249
|
-
// Obsidian's reading view also copies its title and collapse controls
|
|
250
|
-
// with document HTML.
|
|
251
|
-
for (const control of container.querySelectorAll('div.mod-header.mod-ui,div.mod-footer.mod-ui')) {
|
|
252
|
-
if (control.querySelector('.inline-title[contenteditable],.embedded-backlinks'))
|
|
253
|
-
control.remove();
|
|
254
|
-
}
|
|
255
|
-
for (const heading of container.querySelectorAll('h1[data-heading],h2[data-heading],h3[data-heading],h4[data-heading],h5[data-heading],h6[data-heading]')) {
|
|
256
|
-
for (const control of heading.querySelectorAll(':scope > span.heading-collapse-indicator.collapse-indicator.collapse-icon')) {
|
|
257
|
-
if (control.querySelector(':scope > svg.svg-icon.right-triangle') &&
|
|
258
|
-
!control.textContent?.trim())
|
|
259
|
-
control.remove();
|
|
260
|
-
}
|
|
261
|
-
}
|
|
262
|
-
for (const control of container.querySelectorAll('pre > button.copy-code-button')) {
|
|
263
|
-
if (control.querySelector('svg.svg-icon'))
|
|
264
|
-
control.remove();
|
|
265
|
-
}
|
|
266
|
-
for (const element of [...container.querySelectorAll('*')]) {
|
|
267
|
-
if (!container.contains(element) || isRichElement(element))
|
|
268
|
-
continue;
|
|
269
|
-
const literal = document.createElement('pre');
|
|
270
|
-
literal.className = 'literal-markdown';
|
|
271
|
-
literal.textContent = element.outerHTML;
|
|
272
|
-
element.replaceWith(literal);
|
|
273
|
-
}
|
|
274
|
-
for (const table of [...container.querySelectorAll('table')]) {
|
|
275
|
-
if (table.parentElement?.closest('table'))
|
|
276
|
-
continue;
|
|
277
|
-
const rows = [...table.querySelectorAll('tr')];
|
|
278
|
-
const unsupported = !schema.nodes.table || unsupportedClipboardTable(table);
|
|
279
|
-
if (!unsupported && !table.querySelector('th')) {
|
|
280
|
-
for (const cell of rows[0]?.querySelectorAll(':scope > td') ?? []) {
|
|
281
|
-
const heading = document.createElement('th');
|
|
282
|
-
for (const attribute of [...cell.attributes])
|
|
283
|
-
heading.setAttribute(attribute.name, attribute.value);
|
|
284
|
-
heading.append(...cell.childNodes);
|
|
285
|
-
cell.replaceWith(heading);
|
|
286
|
-
}
|
|
287
|
-
}
|
|
288
|
-
if (unsupported) {
|
|
289
|
-
const literal = document.createElement('pre');
|
|
290
|
-
literal.className = 'literal-markdown';
|
|
291
|
-
literal.textContent = table.outerHTML;
|
|
292
|
-
table.replaceWith(literal);
|
|
293
|
-
}
|
|
294
|
-
}
|
|
295
|
-
collapseClipboardWhitespace(container);
|
|
296
|
-
// An authored comment is read in Markdown as an InkKit comment, so it
|
|
297
|
-
// imports as one, after whitespace collapse has passed it by.
|
|
298
|
-
const walker = document.createTreeWalker(container, NodeFilter.SHOW_COMMENT);
|
|
299
|
-
const comments = [];
|
|
300
|
-
while (walker.nextNode())
|
|
301
|
-
comments.push(walker.currentNode);
|
|
302
|
-
for (const comment of comments) {
|
|
303
|
-
const element = document.createElement(inlineComment(comment) ? 'span' : 'div');
|
|
304
|
-
element.setAttribute('data-inkkit-comment', 'html');
|
|
305
|
-
element.textContent = comment.data;
|
|
306
|
-
comment.replaceWith(element);
|
|
307
|
-
}
|
|
308
|
-
const imported = new Map();
|
|
309
|
-
const elements = [...container.querySelectorAll('img')];
|
|
310
|
-
const uniqueSources = new Set(elements.map((element) => element.getAttribute('src') ?? ''));
|
|
311
|
-
// A host-supplied array explicitly associates unnamed bytes by unique
|
|
312
|
-
// preview order. A browser FileList has no such association, except the
|
|
313
|
-
// unambiguous single-file/single-source case.
|
|
314
|
-
const positional = (files.length === 0 || (files.length === 1 && uniqueSources.size === 1)
|
|
315
|
-
? images
|
|
316
|
-
: (input.images ?? [])).filter((image) => !image.source);
|
|
317
|
-
let index = 0;
|
|
318
|
-
for (const element of elements) {
|
|
319
|
-
const source = element.getAttribute('src') ?? '';
|
|
320
|
-
const alt = element.getAttribute('alt') ?? 'Image';
|
|
321
|
-
try {
|
|
322
|
-
let reference = imported.get(source);
|
|
323
|
-
if (!reference) {
|
|
324
|
-
const matched = images.find((image) => image.source ===
|
|
325
|
-
source);
|
|
326
|
-
const captured = matched ?? dataImage(source) ?? positional[index++];
|
|
327
|
-
if (!captured || !this.options.adapter)
|
|
328
|
-
throw new Error('The pasted image is unavailable.');
|
|
329
|
-
reference = (await this.options.adapter.importImage(captured, { ...context })).reference;
|
|
330
|
-
this.assertContext(context, operation);
|
|
331
|
-
imported.set(source, reference);
|
|
332
|
-
}
|
|
333
|
-
element.setAttribute('src', reference);
|
|
334
|
-
element.removeAttribute('srcset');
|
|
335
|
-
}
|
|
336
|
-
catch (error) {
|
|
337
|
-
if (error instanceof InkKitError)
|
|
338
|
-
throw error;
|
|
339
|
-
element.replaceWith(document.createTextNode(`[${alt}: image unavailable]`));
|
|
340
|
-
this.options.onError?.(new InkKitError('image-unavailable', error instanceof Error
|
|
341
|
-
? error.message
|
|
342
|
-
: 'The pasted image is unavailable.'));
|
|
343
|
-
}
|
|
344
|
-
}
|
|
345
|
-
if (markdown != null) {
|
|
346
|
-
const parsed = ctx.get(parserCtx)(markdown);
|
|
347
|
-
let imageIndex = 0;
|
|
348
|
-
const hydrate = (node) => {
|
|
349
|
-
if (isImage(node)) {
|
|
350
|
-
const slot = /^inkkit-clipboard-image:(\d+)$/.exec(String(node.attrs.src));
|
|
351
|
-
const position = slot ? Number(slot[1]) : imageIndex++;
|
|
352
|
-
const element = elements.some((element) => element.hasAttribute('data-inkkit-image-slot'))
|
|
353
|
-
? elements.find((element) => Number(element.getAttribute('data-inkkit-authored-image-slot') ?? element.getAttribute('data-inkkit-image-slot')) === position)
|
|
354
|
-
: elements[position];
|
|
355
|
-
if (!element || !container.contains(element)) {
|
|
356
|
-
const text = schema.text(`[${node.attrs.alt || 'Image'}: image unavailable]`);
|
|
357
|
-
return node.isBlock
|
|
358
|
-
? schema.nodes.paragraph.create(null, text)
|
|
359
|
-
: text;
|
|
360
|
-
}
|
|
361
|
-
return node.type.create({ ...node.attrs, src: element.getAttribute('src') }, null, node.marks);
|
|
362
|
-
}
|
|
363
|
-
if (node.isText)
|
|
364
|
-
return node;
|
|
365
|
-
const children = [];
|
|
366
|
-
node.forEach((child) => children.push(hydrate(child)));
|
|
367
|
-
return node.copy(Fragment.fromArray(children));
|
|
368
|
-
};
|
|
369
|
-
slice = new Slice(hydrate(parsed).content, 0, 0);
|
|
370
|
-
}
|
|
371
|
-
else
|
|
372
|
-
slice = importableSlice(ctx, ProseParser.fromSchema(schema).parseSlice(container, {
|
|
373
|
-
preserveWhitespace: true,
|
|
374
|
-
}));
|
|
375
|
-
}
|
|
376
|
-
else if (images.length) {
|
|
377
|
-
const nodes = [];
|
|
378
|
-
if (input.text)
|
|
379
|
-
nodes.push(schema.nodes.paragraph.create(null, schema.text(input.text)));
|
|
380
|
-
for (const image of images) {
|
|
381
|
-
try {
|
|
382
|
-
if (!this.options.adapter)
|
|
383
|
-
throw new InkKitError('image-unavailable', 'No image adapter is configured.');
|
|
384
|
-
const imported = await this.options.adapter.importImage(image, {
|
|
385
|
-
...context,
|
|
386
|
-
});
|
|
387
|
-
this.assertContext(context, operation);
|
|
388
|
-
const imageNode = schema.nodes.image?.create({
|
|
389
|
-
src: imported.reference,
|
|
390
|
-
alt: image.filename ?? 'Image',
|
|
391
|
-
});
|
|
392
|
-
if (!imageNode)
|
|
393
|
-
throw new InkKitError('image-unavailable', 'Images are disabled for this document.');
|
|
394
|
-
nodes.push(schema.nodes.paragraph.create(null, imageNode));
|
|
395
|
-
}
|
|
396
|
-
catch (error) {
|
|
397
|
-
if (error instanceof InkKitError &&
|
|
398
|
-
error.code !== 'image-unavailable')
|
|
399
|
-
throw error;
|
|
400
|
-
nodes.push(schema.nodes.paragraph.create(null, schema.text(`[${image.filename ?? 'Image'}: image unavailable]`)));
|
|
401
|
-
this.options.onError?.(new InkKitError('image-unavailable', error instanceof Error
|
|
402
|
-
? error.message
|
|
403
|
-
: 'The pasted image is unavailable.'));
|
|
404
|
-
}
|
|
405
|
-
}
|
|
406
|
-
slice = new Slice(Fragment.fromArray(nodes), 1, 1);
|
|
407
|
-
}
|
|
408
|
-
else {
|
|
409
|
-
const selection = view.state.selection;
|
|
410
|
-
const text = input.text.trim();
|
|
411
|
-
if (markdown == null &&
|
|
412
|
-
!selection.empty &&
|
|
413
|
-
/^https?:\/\/\S+$/.test(text) &&
|
|
414
|
-
schema.marks.link) {
|
|
415
|
-
const anchor = this.anchor(operation);
|
|
416
|
-
const mark = schema.marks.link.create({ href: text });
|
|
417
|
-
view.dispatch(view.state.tr
|
|
418
|
-
.addMark(anchor.from, anchor.to, mark)
|
|
419
|
-
.removeStoredMark(mark));
|
|
420
|
-
return;
|
|
421
|
-
}
|
|
422
|
-
const doc = ctx.get(parserCtx)(markdown ?? input.text);
|
|
423
|
-
slice = new Slice(doc.content, 0, 0);
|
|
424
|
-
}
|
|
425
|
-
this.assertContext(context, operation);
|
|
426
|
-
if (!view.editable || this.options.editable?.() === false)
|
|
427
|
-
return;
|
|
428
|
-
const anchor = this.anchor(operation);
|
|
429
|
-
if (!plainText) {
|
|
430
|
-
const valid = slice.content.firstChild?.isInline
|
|
431
|
-
? Fragment.from(schema.nodes.paragraph.create(null, slice.content))
|
|
432
|
-
: slice.content;
|
|
433
|
-
const remapped = avoidReferenceCollisions(schema.topNodeType.create(null, valid), view.state.doc);
|
|
434
|
-
slice = new Slice(slice.content.firstChild?.isInline
|
|
435
|
-
? remapped.firstChild.content
|
|
436
|
-
: remapped.content, slice.openStart, slice.openEnd);
|
|
437
|
-
}
|
|
438
|
-
if (target) {
|
|
439
|
-
this.insertBlocks(view, slice, dropRange(view.state.doc, anchor));
|
|
440
|
-
return;
|
|
441
|
-
}
|
|
442
|
-
const insertion = view.state.doc.resolve(anchor.from);
|
|
443
|
-
// Blocks pasted into an empty top-level paragraph take its place whole,
|
|
444
|
-
// as a drop does; joining the first of them into it could lose a
|
|
445
|
-
// wrapper whose only content was empty.
|
|
446
|
-
if (anchor.from === anchor.to &&
|
|
447
|
-
insertion.depth === 1 &&
|
|
448
|
-
insertion.parent.type.name === 'paragraph' &&
|
|
449
|
-
insertion.parent.content.size === 0 &&
|
|
450
|
-
slice.content.firstChild?.isBlock) {
|
|
451
|
-
this.insertBlocks(view, slice, {
|
|
452
|
-
from: insertion.before(),
|
|
453
|
-
to: insertion.after(),
|
|
454
|
-
});
|
|
455
|
-
return;
|
|
456
|
-
}
|
|
457
|
-
// An isolated block such as a table cannot fit inside an empty paragraph.
|
|
458
|
-
// Replace that empty block rather than retaining a spurious spacer.
|
|
459
|
-
if (anchor.from === anchor.to &&
|
|
460
|
-
insertion.parent.type.name === 'paragraph' &&
|
|
461
|
-
insertion.parent.content.size === 0 &&
|
|
462
|
-
slice.content.firstChild?.isBlock &&
|
|
463
|
-
slice.content.firstChild.type.spec.isolating) {
|
|
464
|
-
view.dispatch(view.state.tr
|
|
465
|
-
.replaceRange(insertion.before(), insertion.after(), new Slice(slice.content, 0, 0))
|
|
466
|
-
.scrollIntoView());
|
|
467
|
-
}
|
|
468
|
-
else
|
|
469
|
-
view.dispatch(view.state.tr
|
|
470
|
-
.replaceRange(anchor.from, anchor.to, slice)
|
|
471
|
-
.scrollIntoView());
|
|
472
|
-
}
|
|
473
|
-
finally {
|
|
474
|
-
if (this.active === operation) {
|
|
475
|
-
this.active = undefined;
|
|
476
|
-
this.options.onPendingChanged?.();
|
|
477
|
-
}
|
|
478
|
-
}
|
|
479
|
-
}
|
|
480
|
-
}
|
|
481
|
-
/**
|
|
482
|
-
* The range a drop replaces, mapped to the current document. It replaces an
|
|
483
|
-
* empty paragraph only while that is still empty, so text typed there while
|
|
484
|
-
* images import is kept, and the drop then follows it.
|
|
485
|
-
*/
|
|
486
|
-
function dropRange(doc, range) {
|
|
487
|
-
const node = doc.nodeAt(range.from);
|
|
488
|
-
return range.from < range.to &&
|
|
489
|
-
node?.type.name === 'paragraph' &&
|
|
490
|
-
node.content.size === 0 &&
|
|
491
|
-
range.from + node.nodeSize === range.to
|
|
492
|
-
? range
|
|
493
|
-
: { from: range.to, to: range.to };
|
|
494
|
-
}
|
|
495
|
-
/**
|
|
496
|
-
* Whether `handlePaste` takes the paste, and as what. Valid VS Code metadata
|
|
497
|
-
* keeps the provider's code-language behaviour; invalid metadata pastes plain
|
|
498
|
-
* text.
|
|
499
|
-
*/
|
|
500
|
-
function claimsPaste(view, event) {
|
|
501
|
-
const data = event.clipboardData;
|
|
502
|
-
if (!data || !view.editable)
|
|
503
|
-
return false;
|
|
504
|
-
if (view.state.selection.$from.parent.type.spec.code)
|
|
505
|
-
return false;
|
|
506
|
-
const metadata = data.getData('vscode-editor-data');
|
|
507
|
-
if (!metadata)
|
|
508
|
-
return 'import';
|
|
509
|
-
try {
|
|
510
|
-
const value = JSON.parse(metadata);
|
|
511
|
-
return value &&
|
|
512
|
-
typeof value === 'object' &&
|
|
513
|
-
'mode' in value &&
|
|
514
|
-
typeof value.mode === 'string'
|
|
515
|
-
? false
|
|
516
|
-
: 'plain';
|
|
517
|
-
}
|
|
518
|
-
catch {
|
|
519
|
-
return 'plain';
|
|
520
|
-
}
|
|
521
|
-
}
|
|
522
|
-
// Inside text or an element that holds text, a comment is part of a line.
|
|
523
|
-
function inlineComment(comment) {
|
|
524
|
-
const parent = comment.parentElement;
|
|
525
|
-
if (parent && /^(?:P|H[1-6]|TD|TH|LI|DT|DD)$/.test(parent.tagName))
|
|
526
|
-
return true;
|
|
527
|
-
if (parent && isRichElement(parent) && !isBlockElement(parent))
|
|
528
|
-
return true;
|
|
529
|
-
for (const sibling of comment.parentNode?.childNodes ?? [])
|
|
530
|
-
if ((sibling.nodeType === Node.TEXT_NODE && sibling.textContent?.trim()) ||
|
|
531
|
-
(sibling.nodeType === Node.ELEMENT_NODE &&
|
|
532
|
-
isRichElement(sibling) &&
|
|
533
|
-
!isBlockElement(sibling)))
|
|
534
|
-
return true;
|
|
535
|
-
return false;
|
|
536
|
-
}
|
|
537
|
-
function insideRewrittenContent(element) {
|
|
538
|
-
for (let parent = element.parentElement; parent; parent = parent.parentElement)
|
|
539
|
-
if (!isRewrittenElement(parent))
|
|
540
|
-
return false;
|
|
541
|
-
return true;
|
|
542
|
-
}
|
|
543
|
-
function unsupportedClipboardTable(table) {
|
|
544
|
-
const rows = [...table.querySelectorAll('tr')];
|
|
545
|
-
const widths = rows.map((row) => row.querySelectorAll(':scope > th,:scope > td').length);
|
|
546
|
-
const supported = new Set([
|
|
547
|
-
'THEAD',
|
|
548
|
-
'TBODY',
|
|
549
|
-
'TFOOT',
|
|
550
|
-
'TR',
|
|
551
|
-
'TH',
|
|
552
|
-
'TD',
|
|
553
|
-
'P',
|
|
554
|
-
'STRONG',
|
|
555
|
-
'B',
|
|
556
|
-
'EM',
|
|
557
|
-
'I',
|
|
558
|
-
'S',
|
|
559
|
-
'DEL',
|
|
560
|
-
'STRIKE',
|
|
561
|
-
'CODE',
|
|
562
|
-
'A',
|
|
563
|
-
'MARK',
|
|
564
|
-
'SPAN',
|
|
565
|
-
'SUP',
|
|
566
|
-
'FONT',
|
|
567
|
-
'IMG',
|
|
568
|
-
]);
|
|
569
|
-
return (widths.length === 0 ||
|
|
570
|
-
widths.some((width) => !width || width !== widths[0]) ||
|
|
571
|
-
!!document.createTreeWalker(table, NodeFilter.SHOW_COMMENT).nextNode() ||
|
|
572
|
-
[...table.querySelectorAll('*')].some((element) => !supported.has(element.tagName)) ||
|
|
573
|
-
[...table.querySelectorAll('p')].some((paragraph) => {
|
|
574
|
-
const cell = paragraph.parentElement;
|
|
575
|
-
return (!cell ||
|
|
576
|
-
!['TH', 'TD'].includes(cell.tagName) ||
|
|
577
|
-
cell.children.length !== 1 ||
|
|
578
|
-
[...cell.childNodes].some((node) => node !== paragraph && !!node.textContent?.trim()));
|
|
579
|
-
}) ||
|
|
580
|
-
!!table.querySelector('[rowspan]:not([rowspan="1"]),[colspan]:not([colspan="1"])'));
|
|
581
|
-
}
|
|
582
|
-
function dataImage(source) {
|
|
583
|
-
const match = /^data:(image\/(?:png|jpeg|gif|webp|avif|bmp|tiff));base64,([A-Za-z0-9+/=\s]+)$/.exec(source);
|
|
584
|
-
if (!match)
|
|
585
|
-
return undefined;
|
|
586
|
-
const binary = atob(match[2]);
|
|
587
|
-
return {
|
|
588
|
-
mimeType: match[1],
|
|
589
|
-
bytes: Uint8Array.from(binary, (character) => character.charCodeAt(0)),
|
|
590
|
-
};
|
|
591
|
-
}
|
package/dist/portable-files.d.ts
DELETED
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
import type { Node } from '@milkdown/kit/prose/model';
|
|
2
|
-
import type { DocumentContext, FileAdapter, FilePresentation, FileReference, ImageAdapter, PortableImage } from './types.js';
|
|
3
|
-
export interface PortableFile {
|
|
4
|
-
reference: FileReference;
|
|
5
|
-
presentation: FilePresentation;
|
|
6
|
-
label: string;
|
|
7
|
-
description: string;
|
|
8
|
-
}
|
|
9
|
-
export declare function portableFile(node: Node, adapter: FileAdapter | undefined, context: DocumentContext, signal: AbortSignal): Promise<PortableFile | undefined>;
|
|
10
|
-
export declare function portableFileImage(file: PortableFile, files: FileAdapter, legacy: ImageAdapter | undefined, context: DocumentContext, signal: AbortSignal): Promise<PortableImage>;
|
package/dist/portable-files.js
DELETED
|
@@ -1,36 +0,0 @@
|
|
|
1
|
-
import { resolveFile, withFileSignal } from './files.js';
|
|
2
|
-
import { fileReference } from './linked-syntax.js';
|
|
3
|
-
export async function portableFile(node, adapter, context, signal) {
|
|
4
|
-
if (!adapter)
|
|
5
|
-
return undefined;
|
|
6
|
-
const reference = fileReference(node);
|
|
7
|
-
let presentation;
|
|
8
|
-
try {
|
|
9
|
-
presentation = await resolveFile(adapter, reference, { ...context, signal });
|
|
10
|
-
}
|
|
11
|
-
catch (error) {
|
|
12
|
-
signal.throwIfAborted();
|
|
13
|
-
presentation = {
|
|
14
|
-
kind: 'error',
|
|
15
|
-
message: error instanceof Error ? error.message : 'The file is unavailable.',
|
|
16
|
-
};
|
|
17
|
-
}
|
|
18
|
-
const label = presentation.label || reference.label || reference.reference || 'File';
|
|
19
|
-
const kind = presentation.kind;
|
|
20
|
-
const description = kind === 'missing' || kind === 'error'
|
|
21
|
-
? `[File: ${label} (${kind === 'missing' ? 'unavailable' : 'error'})]`
|
|
22
|
-
: `[${kind === 'pdf' ? 'PDF' : kind[0].toUpperCase() + kind.slice(1)}: ${label}]`;
|
|
23
|
-
return { reference, presentation, label, description };
|
|
24
|
-
}
|
|
25
|
-
export async function portableFileImage(file, files, legacy, context, signal) {
|
|
26
|
-
signal.throwIfAborted();
|
|
27
|
-
const image = files.exportImage
|
|
28
|
-
? await withFileSignal(files.exportImage(file.reference, { ...context, signal }), signal)
|
|
29
|
-
: file.reference.kind === 'path' && legacy
|
|
30
|
-
? await withFileSignal(legacy.exportImage(file.reference.reference, { ...context }), signal)
|
|
31
|
-
: undefined;
|
|
32
|
-
signal.throwIfAborted();
|
|
33
|
-
if (!image)
|
|
34
|
-
throw new Error('The image has no portable export adapter.');
|
|
35
|
-
return image;
|
|
36
|
-
}
|
package/dist/preserve.d.ts
DELETED
|
@@ -1,38 +0,0 @@
|
|
|
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
|
-
export declare function semanticSignature(doc: ProseNode): string;
|
|
7
|
-
export declare function correspondence(before: string[], after: string[]): Array<number | undefined>;
|
|
8
|
-
/** Retains the loaded source of blocks the edit leaves alone. */
|
|
9
|
-
export declare class Preservation {
|
|
10
|
-
private readonly ctx;
|
|
11
|
-
private readonly source;
|
|
12
|
-
private readonly baseline;
|
|
13
|
-
private readonly blocks;
|
|
14
|
-
private readonly lineEnding;
|
|
15
|
-
private readonly originalForms;
|
|
16
|
-
private readonly forms;
|
|
17
|
-
private readonly blockEdits;
|
|
18
|
-
private readonly itemEdits;
|
|
19
|
-
private readonly definitionNodes;
|
|
20
|
-
private readonly definitionSources?;
|
|
21
|
-
constructor(ctx: Ctx, source: string);
|
|
22
|
-
private retainBlock;
|
|
23
|
-
private localEdits;
|
|
24
|
-
private pairedInPlace;
|
|
25
|
-
private definitions;
|
|
26
|
-
private inContext;
|
|
27
|
-
private parseLocally;
|
|
28
|
-
private canonicalTree;
|
|
29
|
-
private static plainInline;
|
|
30
|
-
private separated;
|
|
31
|
-
private localBlock;
|
|
32
|
-
private formOf;
|
|
33
|
-
private localItems;
|
|
34
|
-
private localItem;
|
|
35
|
-
private separateLists;
|
|
36
|
-
private relabelList;
|
|
37
|
-
serialize(doc: ProseNode): string;
|
|
38
|
-
}
|