@aicayzer/inkkit 0.0.14 → 0.0.16
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 +0 -4
- package/README.md +27 -24
- package/dist/comments.js +7 -0
- package/dist/dialect.js +10 -19
- package/dist/editor.d.ts +3 -0
- package/dist/editor.js +52 -27
- package/dist/highlight.js +109 -22
- package/dist/image-blocks.d.ts +71 -0
- package/dist/image-blocks.js +286 -0
- package/dist/images.d.ts +3 -1
- package/dist/images.js +52 -23
- package/dist/mermaid.js +108 -6
- package/dist/paste-html.d.ts +23 -0
- package/dist/paste-html.js +441 -0
- package/dist/paste.js +39 -52
- package/dist/preserve.d.ts +1 -0
- package/dist/preserve.js +87 -26
- package/dist/search.d.ts +3 -3
- package/dist/search.js +2 -2
- package/dist/style.css +98 -4
- package/dist/text-ranges.js +10 -1
- package/package.json +1 -1
package/dist/preserve.js
CHANGED
|
@@ -323,27 +323,89 @@ function withoutHeadingId(node) {
|
|
|
323
323
|
? node.type.create({ ...node.attrs, id: '' }, node.content, node.marks)
|
|
324
324
|
: node;
|
|
325
325
|
}
|
|
326
|
-
|
|
327
|
-
//
|
|
328
|
-
|
|
326
|
+
const MATCHING_BUDGET = 1_000_000;
|
|
327
|
+
// Unchanged blocks are anchored first, so a long document with many scattered
|
|
328
|
+
// edits is matched as many short gaps rather than one wide span.
|
|
329
|
+
export function correspondence(before, after) {
|
|
329
330
|
const result = new Array(after.length);
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
331
|
+
const work = { budget: MATCHING_BUDGET };
|
|
332
|
+
const spans = [{ b: 0, a: 0, bEnd: before.length, aEnd: after.length }];
|
|
333
|
+
for (let span = spans.pop(); span; span = spans.pop()) {
|
|
334
|
+
let { b, a, bEnd, aEnd } = span;
|
|
335
|
+
while (b < bEnd && a < aEnd && before[b] === after[a])
|
|
336
|
+
result[a++] = b++;
|
|
337
|
+
while (b < bEnd && a < aEnd && before[bEnd - 1] === after[aEnd - 1])
|
|
338
|
+
result[--aEnd] = --bEnd;
|
|
339
|
+
if (b === bEnd || a === aEnd)
|
|
340
|
+
continue;
|
|
341
|
+
work.budget -= bEnd - b + (aEnd - a);
|
|
342
|
+
if (work.budget < 0)
|
|
343
|
+
throw tooManyBlocks();
|
|
344
|
+
const anchors = uniqueAnchors(before, after, b, bEnd, a, aEnd);
|
|
345
|
+
if (!anchors.length) {
|
|
346
|
+
align(before, after, b, bEnd, a, aEnd, result, work);
|
|
347
|
+
continue;
|
|
348
|
+
}
|
|
349
|
+
for (const anchor of anchors) {
|
|
350
|
+
result[anchor.a] = anchor.b;
|
|
351
|
+
spans.push({ b, a, bEnd: anchor.b, aEnd: anchor.a });
|
|
352
|
+
b = anchor.b + 1;
|
|
353
|
+
a = anchor.a + 1;
|
|
354
|
+
}
|
|
355
|
+
spans.push({ b, a, bEnd, aEnd });
|
|
356
|
+
}
|
|
357
|
+
return result;
|
|
358
|
+
}
|
|
359
|
+
function tooManyBlocks() {
|
|
360
|
+
return new PreservationError('The edit changes too many blocks to preserve safely');
|
|
361
|
+
}
|
|
362
|
+
// Blocks that occur exactly once on each side, in the longest subsequence whose
|
|
363
|
+
// order the two sides share. A repeated block is never an anchor: which copy it
|
|
364
|
+
// belongs to is not known until its neighbours are placed.
|
|
365
|
+
function uniqueAnchors(before, after, b, bEnd, a, aEnd) {
|
|
366
|
+
const once = (list, from, to) => {
|
|
367
|
+
const at = new Map();
|
|
368
|
+
for (let i = from; i < to; i++)
|
|
369
|
+
at.set(list[i], at.has(list[i]) ? -1 : i);
|
|
370
|
+
return at;
|
|
371
|
+
};
|
|
372
|
+
const beforeAt = once(before, b, bEnd);
|
|
373
|
+
const afterAt = once(after, a, aEnd);
|
|
374
|
+
const pairs = [];
|
|
375
|
+
for (let i = a; i < aEnd; i++) {
|
|
376
|
+
const match = beforeAt.get(after[i]);
|
|
377
|
+
if (match != null && match >= 0 && afterAt.get(after[i]) === i)
|
|
378
|
+
pairs.push({ b: match, a: i });
|
|
379
|
+
}
|
|
380
|
+
// Patience sorting: tails[k] ends the best increasing subsequence of length k + 1.
|
|
381
|
+
const tails = [];
|
|
382
|
+
const previous = new Array(pairs.length);
|
|
383
|
+
for (const [index, pair] of pairs.entries()) {
|
|
384
|
+
let low = 0;
|
|
385
|
+
let high = tails.length;
|
|
386
|
+
while (low < high) {
|
|
387
|
+
const middle = (low + high) >> 1;
|
|
388
|
+
if (pairs[tails[middle]].b < pair.b)
|
|
389
|
+
low = middle + 1;
|
|
390
|
+
else
|
|
391
|
+
high = middle;
|
|
392
|
+
}
|
|
393
|
+
previous[index] = low ? tails[low - 1] : -1;
|
|
394
|
+
tails[low] = index;
|
|
336
395
|
}
|
|
337
|
-
|
|
338
|
-
let
|
|
339
|
-
while (
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
result[--endAfter] = --endBefore;
|
|
396
|
+
const anchors = [];
|
|
397
|
+
let at = tails.length ? tails[tails.length - 1] : -1;
|
|
398
|
+
while (at >= 0) {
|
|
399
|
+
anchors.push(pairs[at]);
|
|
400
|
+
at = previous[at];
|
|
343
401
|
}
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
402
|
+
return anchors.reverse();
|
|
403
|
+
}
|
|
404
|
+
// Edit distance distinguishes replacements from insertions, so replacing a block
|
|
405
|
+
// retains its surrounding whitespace without assigning it a neighbour's source.
|
|
406
|
+
function align(before, after, b, bEnd, a, aEnd, result, work) {
|
|
407
|
+
const rows = bEnd - b + 1;
|
|
408
|
+
const columns = aEnd - a + 1;
|
|
347
409
|
let width = Math.max(1, Math.abs(rows - columns));
|
|
348
410
|
for (;;) {
|
|
349
411
|
const costs = [];
|
|
@@ -353,13 +415,13 @@ function correspondence(before, after) {
|
|
|
353
415
|
for (let i = 0; i < rows; i++) {
|
|
354
416
|
const from = Math.max(0, i - width);
|
|
355
417
|
const to = Math.min(columns - 1, i + width);
|
|
356
|
-
budget -= to - from + 1;
|
|
357
|
-
if (budget < 0)
|
|
358
|
-
throw
|
|
418
|
+
work.budget -= to - from + 1;
|
|
419
|
+
if (work.budget < 0)
|
|
420
|
+
throw tooManyBlocks();
|
|
359
421
|
const row = new Uint32Array(to - from + 1);
|
|
360
422
|
costs.push(row);
|
|
361
423
|
for (let j = from; j <= to; j++) {
|
|
362
|
-
const change = before[
|
|
424
|
+
const change = before[b + i - 1] === after[a + j - 1] ? 0 : 1;
|
|
363
425
|
row[j - from] =
|
|
364
426
|
i === 0
|
|
365
427
|
? j
|
|
@@ -377,9 +439,9 @@ function correspondence(before, after) {
|
|
|
377
439
|
let i = rows - 1;
|
|
378
440
|
let j = columns - 1;
|
|
379
441
|
while (i || j) {
|
|
380
|
-
const change = i && j && before[
|
|
442
|
+
const change = i && j && before[b + i - 1] === after[a + j - 1] ? 0 : 1;
|
|
381
443
|
if (i && j && get(i, j) === get(i - 1, j - 1) + change) {
|
|
382
|
-
result[
|
|
444
|
+
result[a + j - 1] = b + i - 1;
|
|
383
445
|
i--;
|
|
384
446
|
j--;
|
|
385
447
|
}
|
|
@@ -388,9 +450,8 @@ function correspondence(before, after) {
|
|
|
388
450
|
else
|
|
389
451
|
j--;
|
|
390
452
|
}
|
|
391
|
-
|
|
453
|
+
return;
|
|
392
454
|
}
|
|
393
|
-
return result;
|
|
394
455
|
}
|
|
395
456
|
/** Retains the loaded source of blocks the edit leaves alone. */
|
|
396
457
|
export class Preservation {
|
package/dist/search.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import
|
|
1
|
+
import { type Transaction } from '@milkdown/kit/prose/state';
|
|
2
2
|
import type { EditorView } from '@milkdown/kit/prose/view';
|
|
3
3
|
export interface TextMatch {
|
|
4
4
|
from: number;
|
|
@@ -11,5 +11,5 @@ export interface LiteralReplacement extends TextMatch {
|
|
|
11
11
|
export declare function findLiteral(text: string, search: string, from?: number): TextMatch | undefined;
|
|
12
12
|
export declare function replaceLiteral(text: string, search: string, replacement: string, selection: TextMatch, all?: boolean): LiteralReplacement;
|
|
13
13
|
export declare function findFormatted(view: EditorView, text: string): boolean;
|
|
14
|
-
export declare function replaceFormatted(view: EditorView, search: string, replacement: string, preflight: (
|
|
15
|
-
export declare function replaceFormattedRange(view: EditorView, from: number, to: number, replacement: string, preflight: (
|
|
14
|
+
export declare function replaceFormatted(view: EditorView, search: string, replacement: string, preflight: (tr: Transaction) => void, all?: boolean): number;
|
|
15
|
+
export declare function replaceFormattedRange(view: EditorView, from: number, to: number, replacement: string, preflight: (tr: Transaction) => void): boolean;
|
package/dist/search.js
CHANGED
|
@@ -162,7 +162,7 @@ export function replaceFormatted(view, search, replacement, preflight, all = fal
|
|
|
162
162
|
tr = tr.setSelection(TextSelection.create(tr.doc, match.from, tr.mapping.map(match.to, 1)));
|
|
163
163
|
}
|
|
164
164
|
try {
|
|
165
|
-
preflight(tr
|
|
165
|
+
preflight(tr);
|
|
166
166
|
}
|
|
167
167
|
catch (error) {
|
|
168
168
|
if (error instanceof InkKitError)
|
|
@@ -189,7 +189,7 @@ export function replaceFormattedRange(view, from, to, replacement, preflight) {
|
|
|
189
189
|
const tr = closeHistory(view.state.tr.replace(from, to, replacementSlice(view.state, from, to, value)));
|
|
190
190
|
tr.setSelection(TextSelection.create(tr.doc, from, tr.mapping.map(to, 1)));
|
|
191
191
|
try {
|
|
192
|
-
preflight(tr
|
|
192
|
+
preflight(tr);
|
|
193
193
|
}
|
|
194
194
|
catch (error) {
|
|
195
195
|
if (error instanceof InkKitError)
|
package/dist/style.css
CHANGED
|
@@ -201,6 +201,54 @@
|
|
|
201
201
|
max-width: 100%;
|
|
202
202
|
vertical-align: bottom;
|
|
203
203
|
}
|
|
204
|
+
/* A standalone image is its own block; the row is full width so a click beside the card lands on it. */
|
|
205
|
+
.inkkit-root .ProseMirror .inkkit-image-block {
|
|
206
|
+
display: block;
|
|
207
|
+
margin: 0 0
|
|
208
|
+
var(--inkkit-paragraph-spacing, calc(var(--inkkit-font-size, 15px) * 0.667));
|
|
209
|
+
line-height: 0;
|
|
210
|
+
}
|
|
211
|
+
.inkkit-root .ProseMirror .inkkit-image-block > .image {
|
|
212
|
+
line-height: normal;
|
|
213
|
+
}
|
|
214
|
+
/* A card that shares its paragraph with text sits on its own line within it. */
|
|
215
|
+
.inkkit-root .ProseMirror :not(.inkkit-image-block) > .image.inkkit-file {
|
|
216
|
+
display: block;
|
|
217
|
+
width: fit-content;
|
|
218
|
+
}
|
|
219
|
+
.inkkit-root .ProseMirror .inkkit-image-block.ProseMirror-selectednode {
|
|
220
|
+
outline: none;
|
|
221
|
+
}
|
|
222
|
+
.inkkit-root
|
|
223
|
+
.ProseMirror
|
|
224
|
+
.inkkit-image-block.ProseMirror-selectednode
|
|
225
|
+
> .image {
|
|
226
|
+
outline: 2px solid color-mix(in srgb, var(--_inkkit-accent) 60%, transparent);
|
|
227
|
+
outline-offset: 2px;
|
|
228
|
+
border-radius: 6px;
|
|
229
|
+
}
|
|
230
|
+
.inkkit-root .ProseMirror-gapcursor {
|
|
231
|
+
display: none;
|
|
232
|
+
pointer-events: none;
|
|
233
|
+
position: absolute;
|
|
234
|
+
}
|
|
235
|
+
.inkkit-root .ProseMirror-gapcursor::after {
|
|
236
|
+
content: '';
|
|
237
|
+
display: block;
|
|
238
|
+
position: absolute;
|
|
239
|
+
top: -2px;
|
|
240
|
+
width: 20px;
|
|
241
|
+
border-top: 2px solid var(--_inkkit-accent);
|
|
242
|
+
animation: inkkit-gapcursor-blink 1.1s steps(2, start) infinite;
|
|
243
|
+
}
|
|
244
|
+
@keyframes inkkit-gapcursor-blink {
|
|
245
|
+
to {
|
|
246
|
+
visibility: hidden;
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
.inkkit-root .ProseMirror-focused .ProseMirror-gapcursor {
|
|
250
|
+
display: block;
|
|
251
|
+
}
|
|
204
252
|
.inkkit-root .ProseMirror .image img {
|
|
205
253
|
display: block;
|
|
206
254
|
max-width: 100%;
|
|
@@ -222,6 +270,7 @@
|
|
|
222
270
|
position: absolute;
|
|
223
271
|
right: 0;
|
|
224
272
|
top: 50%;
|
|
273
|
+
z-index: 1;
|
|
225
274
|
transform: translateY(-50%);
|
|
226
275
|
width: 20px;
|
|
227
276
|
height: 32px;
|
|
@@ -255,7 +304,7 @@
|
|
|
255
304
|
vertical-align: middle;
|
|
256
305
|
border: 1px solid var(--_inkkit-rule);
|
|
257
306
|
border-radius: 6px;
|
|
258
|
-
padding:
|
|
307
|
+
padding: 6px;
|
|
259
308
|
white-space: normal;
|
|
260
309
|
}
|
|
261
310
|
|
|
@@ -263,13 +312,30 @@
|
|
|
263
312
|
display: flex;
|
|
264
313
|
flex-wrap: wrap;
|
|
265
314
|
align-items: center;
|
|
266
|
-
gap: 0.5em;
|
|
315
|
+
gap: 0.25em 0.5em;
|
|
267
316
|
font-size: 0.9em;
|
|
268
317
|
line-height: 1.4;
|
|
269
318
|
color: var(--_inkkit-muted);
|
|
270
319
|
}
|
|
271
320
|
.inkkit-root .ProseMirror .inkkit-file-footer:not(:first-child) {
|
|
272
|
-
margin-block-start: 0.
|
|
321
|
+
margin-block-start: 0.25em;
|
|
322
|
+
}
|
|
323
|
+
/* Below a picture, the name takes the picture's width rather than widening the card, so the grip
|
|
324
|
+
stays at the picture's edge; a short picture still leaves room for a few characters. */
|
|
325
|
+
.inkkit-root .ProseMirror .inkkit-image-preview ~ .inkkit-file-footer {
|
|
326
|
+
width: 0;
|
|
327
|
+
min-width: 100%;
|
|
328
|
+
}
|
|
329
|
+
.inkkit-root .ProseMirror .image.inkkit-file:has(> .inkkit-image-preview) {
|
|
330
|
+
min-width: min(100%, 8em);
|
|
331
|
+
}
|
|
332
|
+
/* The name truncates; the open arrow stays visible right after it. */
|
|
333
|
+
.inkkit-root .ProseMirror .inkkit-file-name {
|
|
334
|
+
display: flex;
|
|
335
|
+
flex: 1 1 auto;
|
|
336
|
+
align-items: center;
|
|
337
|
+
gap: 0.25em;
|
|
338
|
+
min-width: 0;
|
|
273
339
|
}
|
|
274
340
|
|
|
275
341
|
.inkkit-root .ProseMirror .inkkit-file audio,
|
|
@@ -290,7 +356,7 @@
|
|
|
290
356
|
}
|
|
291
357
|
|
|
292
358
|
.inkkit-root .ProseMirror .inkkit-file-label {
|
|
293
|
-
flex:
|
|
359
|
+
flex: 0 1 auto;
|
|
294
360
|
min-width: 0;
|
|
295
361
|
overflow: hidden;
|
|
296
362
|
text-overflow: ellipsis;
|
|
@@ -313,6 +379,34 @@
|
|
|
313
379
|
border-radius: 3px;
|
|
314
380
|
cursor: pointer;
|
|
315
381
|
}
|
|
382
|
+
.inkkit-root .ProseMirror .inkkit-file-open {
|
|
383
|
+
display: inline-flex;
|
|
384
|
+
align-items: center;
|
|
385
|
+
justify-content: center;
|
|
386
|
+
min-width: 1.5em;
|
|
387
|
+
min-height: 1.5em;
|
|
388
|
+
padding: 0;
|
|
389
|
+
color: var(--_inkkit-muted);
|
|
390
|
+
background: transparent;
|
|
391
|
+
border-color: transparent;
|
|
392
|
+
}
|
|
393
|
+
.inkkit-root .ProseMirror .inkkit-file-open:hover {
|
|
394
|
+
color: var(--_inkkit-text);
|
|
395
|
+
background: var(--_inkkit-code-bg);
|
|
396
|
+
}
|
|
397
|
+
.inkkit-root .ProseMirror .inkkit-file-open:focus-visible {
|
|
398
|
+
outline: 2px solid var(--_inkkit-accent);
|
|
399
|
+
outline-offset: 1px;
|
|
400
|
+
}
|
|
401
|
+
.inkkit-root .ProseMirror .inkkit-file-open svg {
|
|
402
|
+
width: 0.85em;
|
|
403
|
+
height: 0.85em;
|
|
404
|
+
fill: none;
|
|
405
|
+
stroke: currentColor;
|
|
406
|
+
stroke-width: 1.5;
|
|
407
|
+
stroke-linecap: round;
|
|
408
|
+
stroke-linejoin: round;
|
|
409
|
+
}
|
|
316
410
|
|
|
317
411
|
.inkkit-root .ProseMirror {
|
|
318
412
|
/* The viewport, not the wrapper Milkdown adds, so a click below the text still places the caret. */
|
package/dist/text-ranges.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { InkKitError } from './types.js';
|
|
2
2
|
import { fileReference } from './linked-syntax.js';
|
|
3
|
+
import { imageBlockName } from './image-blocks.js';
|
|
3
4
|
export function readableProjection(doc) {
|
|
4
5
|
const result = { text: '', spans: [] };
|
|
5
6
|
let blocks = 0;
|
|
@@ -24,6 +25,11 @@ export function readableProjection(doc) {
|
|
|
24
25
|
String(node.attrs.kind).replace(/^./, (value) => value.toUpperCase()), position, position + 1, 'embed');
|
|
25
26
|
return true;
|
|
26
27
|
}
|
|
28
|
+
if (name === imageBlockName) {
|
|
29
|
+
block(position);
|
|
30
|
+
append(fileReference(node).label || 'Image', position, position + node.nodeSize, 'embed');
|
|
31
|
+
return false;
|
|
32
|
+
}
|
|
27
33
|
if (node.isTextblock) {
|
|
28
34
|
block(position + 1);
|
|
29
35
|
if (!node.content.size)
|
|
@@ -128,9 +134,12 @@ export function formattedRects(view, projection, from, to) {
|
|
|
128
134
|
if (span.kind === 'embed' || span.kind === 'break') {
|
|
129
135
|
const node = view.nodeDOM(span.start);
|
|
130
136
|
if (node instanceof Element) {
|
|
137
|
+
// A standalone image's node is its full-width row; the card is what shows.
|
|
131
138
|
const label = node.matches('.inkkit-callout')
|
|
132
139
|
? (node.querySelector(':scope > .inkkit-callout-header > .inkkit-callout-title') ?? node)
|
|
133
|
-
: node
|
|
140
|
+
: node.matches('.inkkit-image-block')
|
|
141
|
+
? (node.firstElementChild ?? node)
|
|
142
|
+
: node;
|
|
134
143
|
rects.push(...Array.from(label.getClientRects(), textRect));
|
|
135
144
|
}
|
|
136
145
|
continue;
|