@aicayzer/inkkit 0.0.7 → 0.0.9
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 +25 -2
- package/README.md +16 -16
- package/dist/callouts.js +6 -3
- package/dist/decorations.js +7 -6
- package/dist/editor.d.ts +86 -5
- package/dist/editor.js +767 -73
- package/dist/images.d.ts +1 -0
- package/dist/images.js +11 -0
- package/dist/index.d.ts +3 -2
- package/dist/labels.d.ts +28 -0
- package/dist/labels.js +26 -0
- package/dist/mermaid.js +6 -3
- package/dist/paste.d.ts +1 -0
- package/dist/paste.js +5 -1
- package/dist/references.js +5 -4
- package/dist/search.d.ts +2 -1
- package/dist/search.js +53 -22
- package/dist/selection.js +22 -10
- package/dist/source.d.ts +0 -1
- package/dist/source.js +0 -60
- package/dist/style.css +377 -299
- package/dist/tables.d.ts +10 -1
- package/dist/tables.js +87 -11
- package/dist/tasks.js +2 -0
- package/dist/text-ranges.d.ts +26 -0
- package/dist/text-ranges.js +228 -0
- package/dist/types.d.ts +40 -1
- package/package.json +4 -2
package/dist/editor.js
CHANGED
|
@@ -1,33 +1,36 @@
|
|
|
1
|
-
import { Editor, defaultValueCtx, editorViewCtx, parserCtx, editorViewOptionsCtx, remarkCtx, remarkStringifyOptionsCtx, rootCtx, } from '@milkdown/kit/core';
|
|
1
|
+
import { Editor, commandsCtx, defaultValueCtx, editorViewCtx, parserCtx, 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
|
-
import { history } from '@milkdown/kit/plugin/history';
|
|
4
|
+
import { history, historyKeymap } from '@milkdown/kit/plugin/history';
|
|
5
5
|
import { closeHistory, undo as undoHistory, redo as redoHistory, } from '@milkdown/kit/prose/history';
|
|
6
6
|
import { cursor } from '@milkdown/kit/plugin/cursor';
|
|
7
7
|
import { blockquoteKeymap, blockquoteSchema, bulletListKeymap, codeBlockKeymap, createCodeBlockCommand, emphasisKeymap, headingKeymap, inlineCodeKeymap, inlineCodeSchema, linkSchema, listItemKeymap, orderedListKeymap, paragraphKeymap, strongKeymap, toggleEmphasisCommand, toggleInlineCodeCommand, toggleLinkCommand, toggleStrongCommand, turnIntoTextCommand, wrapInBulletListCommand, wrapInHeadingCommand, wrapInOrderedListCommand, } from '@milkdown/kit/preset/commonmark';
|
|
8
|
-
import { strikethroughKeymap, toggleStrikethroughCommand, } from '@milkdown/kit/preset/gfm';
|
|
8
|
+
import { strikethroughKeymap, tableKeymap, toggleStrikethroughCommand, } from '@milkdown/kit/preset/gfm';
|
|
9
9
|
import { NodeRange, Fragment, Slice, } from '@milkdown/kit/prose/model';
|
|
10
10
|
import { keydownHandler, keymap } from '@milkdown/kit/prose/keymap';
|
|
11
|
+
import { goToNextCell } from '@milkdown/kit/prose/tables';
|
|
11
12
|
import { liftListItem } from '@milkdown/kit/prose/schema-list';
|
|
12
13
|
import { findWrapping, liftTarget } from '@milkdown/kit/prose/transform';
|
|
13
14
|
import { AllSelection, NodeSelection, Plugin, PluginKey, Selection, TextSelection, } from '@milkdown/kit/prose/state';
|
|
14
15
|
import { $prose, callCommand, replaceAll, } from '@milkdown/kit/utils';
|
|
15
16
|
import { codeCopyPlugin, placeholderPlugin } from './decorations.js';
|
|
17
|
+
import { labelsCtx, defaultLabels } from './labels.js';
|
|
16
18
|
import { createDialect, stringifyOptions } from './dialect.js';
|
|
17
19
|
import { Preservation } from './preserve.js';
|
|
18
|
-
import { sourceAttribute, cleanSourceDoc, editedPlainSource, sourceSelection,
|
|
20
|
+
import { sourceAttribute, cleanSourceDoc, editedPlainSource, sourceSelection, rawOffset, normalizedOffset, } from './source.js';
|
|
19
21
|
import { normaliseLabel, referenceDefinitions, footnoteDefinitions, } from './references.js';
|
|
20
22
|
import { selectionMarkdown, selectionContent, referenceMetadata, withReferenceMetadata, } from './reference-clipboard.js';
|
|
21
23
|
import { imageView } from './images.js';
|
|
22
|
-
import { tablePlugins, tableCommand, configureTableMovement, } from './tables.js';
|
|
24
|
+
import { tablePlugins, tableCommand, tableCommands, tableAvailability, tableContext, configureTableMovement, } from './tables.js';
|
|
23
25
|
import { InkKitError, } from './types.js';
|
|
26
|
+
import { readableProjection, projectionPosition, projectionOffset, validateOffsets, formattedRects, literalRects, literalVisibleRanges, intersectsViewport, textRect, } from './text-ranges.js';
|
|
24
27
|
import { highlightPlugin } from './highlight.js';
|
|
25
28
|
import { highlightKeymap, toggleHighlightCommand } from './inline-highlight.js';
|
|
26
29
|
import { commentSelectionContent, setCommentVisibility } from './comments.js';
|
|
27
30
|
import { PasteController } from './paste.js';
|
|
28
31
|
import { selectionPlugin } from './selection.js';
|
|
29
32
|
import { search, SearchQuery, setSearchState } from 'prosemirror-search';
|
|
30
|
-
import { findFormatted, findLiteral, replaceFormatted, replaceLiteral, } from './search.js';
|
|
33
|
+
import { findFormatted, findLiteral, replaceFormatted, replaceLiteral, replaceFormattedRange, } from './search.js';
|
|
31
34
|
import { collectFormattedHeadings, collectSourceHeadings, navigateFormattedHeading, sourceHeadingPosition, } from './outline.js';
|
|
32
35
|
import { taskListPlugin, toggleTaskList } from './tasks.js';
|
|
33
36
|
import { isMermaid, mermaidPreview } from './mermaid.js';
|
|
@@ -36,6 +39,9 @@ const shortcutCommands = {
|
|
|
36
39
|
heading1: ['heading', 1],
|
|
37
40
|
heading2: ['heading', 2],
|
|
38
41
|
heading3: ['heading', 3],
|
|
42
|
+
heading4: ['heading', 4],
|
|
43
|
+
heading5: ['heading', 5],
|
|
44
|
+
heading6: ['heading', 6],
|
|
39
45
|
paragraph: ['paragraph'],
|
|
40
46
|
bold: ['bold'],
|
|
41
47
|
italic: ['italic'],
|
|
@@ -234,6 +240,7 @@ function joinLists(tr, start, index) {
|
|
|
234
240
|
if (before && after && isList(before) && after.type === before.type)
|
|
235
241
|
tr.join($start.posAtIndex(index));
|
|
236
242
|
}
|
|
243
|
+
let editorSequence = 0;
|
|
237
244
|
export class InkKitEditor {
|
|
238
245
|
events;
|
|
239
246
|
options;
|
|
@@ -257,12 +264,29 @@ export class InkKitEditor {
|
|
|
257
264
|
pasteController;
|
|
258
265
|
plain;
|
|
259
266
|
root;
|
|
267
|
+
ownsRootClass = false;
|
|
260
268
|
clickHandler;
|
|
261
269
|
footnoteKeyHandler;
|
|
262
270
|
copyHandler;
|
|
263
271
|
originalSource = '';
|
|
264
272
|
generation = 0;
|
|
273
|
+
reportingSuppressed = false;
|
|
265
274
|
loading = false;
|
|
275
|
+
textIdentity = ++editorSequence;
|
|
276
|
+
textScope;
|
|
277
|
+
viewportContainer;
|
|
278
|
+
viewportInsets = {
|
|
279
|
+
top: 0,
|
|
280
|
+
right: 0,
|
|
281
|
+
bottom: 0,
|
|
282
|
+
left: 0,
|
|
283
|
+
};
|
|
284
|
+
writable = true;
|
|
285
|
+
inputPreferences = {};
|
|
286
|
+
policyEpoch = 0;
|
|
287
|
+
literalKeys = () => false;
|
|
288
|
+
configuredKeys = {};
|
|
289
|
+
readOnlyKeys = () => false;
|
|
266
290
|
changePlugin = $prose(() => new Plugin({
|
|
267
291
|
key: new PluginKey('documentChanges'),
|
|
268
292
|
view: () => ({
|
|
@@ -287,7 +311,7 @@ export class InkKitEditor {
|
|
|
287
311
|
this.plain.value = markdown;
|
|
288
312
|
if (!this.plainComposing) {
|
|
289
313
|
this.plain.setSelectionRange(Math.min(from, this.plain.value.length), Math.min(to, this.plain.value.length));
|
|
290
|
-
|
|
314
|
+
this.revealLiteralSelection();
|
|
291
315
|
}
|
|
292
316
|
}
|
|
293
317
|
if (markdown === this.lastMarkdown)
|
|
@@ -301,6 +325,8 @@ export class InkKitEditor {
|
|
|
301
325
|
constructor(events, options) {
|
|
302
326
|
this.events = events;
|
|
303
327
|
this.options = options;
|
|
328
|
+
this.writable = options.editable ?? true;
|
|
329
|
+
this.inputPreferences = { ...options.textInput };
|
|
304
330
|
}
|
|
305
331
|
context() {
|
|
306
332
|
return {
|
|
@@ -326,6 +352,212 @@ export class InkKitEditor {
|
|
|
326
352
|
if (this.pasteController.pending)
|
|
327
353
|
throw new InkKitError('operation-pending', 'An image import is in progress');
|
|
328
354
|
}
|
|
355
|
+
assertMutation(generation = this.generation) {
|
|
356
|
+
this.assertCurrent(generation);
|
|
357
|
+
if (!this.writable)
|
|
358
|
+
throw new InkKitError('read-only', 'The editor is read-only');
|
|
359
|
+
}
|
|
360
|
+
get composing() {
|
|
361
|
+
return this.literalSurface
|
|
362
|
+
? this.plainComposing
|
|
363
|
+
: this.editor.ctx.get(editorViewCtx).composing;
|
|
364
|
+
}
|
|
365
|
+
get editable() {
|
|
366
|
+
this.assertAlive();
|
|
367
|
+
return this.writable;
|
|
368
|
+
}
|
|
369
|
+
setEditable(editable) {
|
|
370
|
+
this.assertAlive();
|
|
371
|
+
if (this.composing)
|
|
372
|
+
throw new InkKitError('composition', 'Text composition is in progress');
|
|
373
|
+
if (this.writable === editable)
|
|
374
|
+
return;
|
|
375
|
+
const suppressed = this.reportingSuppressed;
|
|
376
|
+
this.reportingSuppressed = true;
|
|
377
|
+
try {
|
|
378
|
+
this.writable = editable;
|
|
379
|
+
this.policyEpoch += 1;
|
|
380
|
+
if (!editable)
|
|
381
|
+
this.pasteController.cancelPending();
|
|
382
|
+
this.editor.ctx
|
|
383
|
+
.get(editorViewCtx)
|
|
384
|
+
.dom.dispatchEvent(new Event('inkkit-cancel-resize'));
|
|
385
|
+
this.applyInputPolicy();
|
|
386
|
+
}
|
|
387
|
+
finally {
|
|
388
|
+
this.reportingSuppressed = suppressed;
|
|
389
|
+
}
|
|
390
|
+
this.publishCommandState();
|
|
391
|
+
}
|
|
392
|
+
setTextInputPreferences(preferences) {
|
|
393
|
+
this.assertAlive();
|
|
394
|
+
if (this.composing)
|
|
395
|
+
throw new InkKitError('composition', 'Text composition is in progress');
|
|
396
|
+
const suppressed = this.reportingSuppressed;
|
|
397
|
+
this.reportingSuppressed = true;
|
|
398
|
+
try {
|
|
399
|
+
this.inputPreferences = { ...preferences };
|
|
400
|
+
this.applyInputPolicy();
|
|
401
|
+
}
|
|
402
|
+
finally {
|
|
403
|
+
this.reportingSuppressed = suppressed;
|
|
404
|
+
}
|
|
405
|
+
this.publishCommandState();
|
|
406
|
+
}
|
|
407
|
+
applyInputPolicy() {
|
|
408
|
+
const view = this.editor.ctx.get(editorViewCtx);
|
|
409
|
+
const attributes = typeof view.props.attributes === 'function'
|
|
410
|
+
? view.props.attributes(view.state)
|
|
411
|
+
: view.props.attributes;
|
|
412
|
+
view.setProps({
|
|
413
|
+
editable: () => this.writable && !this.literalSurface,
|
|
414
|
+
attributes: {
|
|
415
|
+
...attributes,
|
|
416
|
+
role: 'textbox',
|
|
417
|
+
'aria-multiline': 'true',
|
|
418
|
+
'aria-label': this.options.labels?.formattedEditor ?? defaultLabels.formattedEditor,
|
|
419
|
+
},
|
|
420
|
+
});
|
|
421
|
+
this.plain.readOnly = !this.writable;
|
|
422
|
+
for (const surface of [view.dom, this.plain]) {
|
|
423
|
+
for (const name of [
|
|
424
|
+
'spellcheck',
|
|
425
|
+
'autocorrect',
|
|
426
|
+
'autocapitalize',
|
|
427
|
+
]) {
|
|
428
|
+
const value = this.inputPreferences[name];
|
|
429
|
+
if (value == null)
|
|
430
|
+
surface.removeAttribute(name);
|
|
431
|
+
else
|
|
432
|
+
surface.setAttribute(name, name === 'autocorrect' ? (value ? 'on' : 'off') : String(value));
|
|
433
|
+
}
|
|
434
|
+
}
|
|
435
|
+
}
|
|
436
|
+
publishCommandState() {
|
|
437
|
+
if (!this.ready ||
|
|
438
|
+
this.loading ||
|
|
439
|
+
this.reportingSuppressed ||
|
|
440
|
+
this.destroyed)
|
|
441
|
+
return;
|
|
442
|
+
this.events.commandStateChanged?.(this.commandState());
|
|
443
|
+
}
|
|
444
|
+
commandState(expectedGeneration = this.generation) {
|
|
445
|
+
this.assertAlive();
|
|
446
|
+
if (!this.ready)
|
|
447
|
+
throw new InkKitError('not-ready', 'No document has been loaded');
|
|
448
|
+
if (expectedGeneration !== this.generation)
|
|
449
|
+
throw new InkKitError('stale-document', 'Document changed');
|
|
450
|
+
const view = this.editor.ctx.get(editorViewCtx);
|
|
451
|
+
const { state } = view;
|
|
452
|
+
const mutable = this.writable && !this.composing && !this.pasteController.pending;
|
|
453
|
+
const formatted = mutable && !this.literalSurface;
|
|
454
|
+
const manager = this.editor.ctx.get(commandsCtx);
|
|
455
|
+
const check = (key, payload) => formatted && manager.get(key)(payload)(state);
|
|
456
|
+
const format = {
|
|
457
|
+
heading: check(wrapInHeadingCommand.key, 1) || check(turnIntoTextCommand.key),
|
|
458
|
+
paragraph: check(turnIntoTextCommand.key),
|
|
459
|
+
bold: check(toggleStrongCommand.key),
|
|
460
|
+
italic: check(toggleEmphasisCommand.key),
|
|
461
|
+
strikethrough: check(toggleStrikethroughCommand.key),
|
|
462
|
+
highlight: check(toggleHighlightCommand.key),
|
|
463
|
+
code: formatted &&
|
|
464
|
+
(state.selection.empty
|
|
465
|
+
? state.selection.$from.parent.inlineContent &&
|
|
466
|
+
state.selection.$from.parent.type.allowsMarkType(inlineCodeSchema.type(this.editor.ctx))
|
|
467
|
+
: check(toggleInlineCodeCommand.key)),
|
|
468
|
+
codeBlock: check(createCodeBlockCommand.key, '') || check(turnIntoTextCommand.key),
|
|
469
|
+
quote: formatted && this.canQuote(state),
|
|
470
|
+
bulletList: formatted &&
|
|
471
|
+
(innermostListDepth(textBounds(state).$from) != null ||
|
|
472
|
+
check(wrapInBulletListCommand.key)),
|
|
473
|
+
orderedList: formatted &&
|
|
474
|
+
(innermostListDepth(textBounds(state).$from) != null ||
|
|
475
|
+
check(wrapInOrderedListCommand.key)),
|
|
476
|
+
taskList: formatted &&
|
|
477
|
+
(innermostListDepth(textBounds(state).$from) != null ||
|
|
478
|
+
check(wrapInBulletListCommand.key)),
|
|
479
|
+
link: check(toggleLinkCommand.key, {}),
|
|
480
|
+
};
|
|
481
|
+
const tables = formatted
|
|
482
|
+
? tableAvailability(this.editor.ctx)
|
|
483
|
+
: Object.fromEntries(tableCommands.map((name) => [name, false]));
|
|
484
|
+
return {
|
|
485
|
+
documentId: this.documentId,
|
|
486
|
+
generation: this.generation,
|
|
487
|
+
revision: this.revision,
|
|
488
|
+
format: this.formatType,
|
|
489
|
+
mode: this.editingMode,
|
|
490
|
+
editable: this.writable,
|
|
491
|
+
composing: this.composing,
|
|
492
|
+
pending: this.pasteController.pending,
|
|
493
|
+
caret: this.literalSurface
|
|
494
|
+
? { marks: [], block: { type: 'paragraph' }, quoted: false }
|
|
495
|
+
: caretState(state),
|
|
496
|
+
table: this.literalSurface ? undefined : tableContext(state),
|
|
497
|
+
commands: {
|
|
498
|
+
undo: mutable && undoHistory(state),
|
|
499
|
+
redo: mutable && redoHistory(state),
|
|
500
|
+
insertText: mutable,
|
|
501
|
+
paste: mutable,
|
|
502
|
+
replace: mutable,
|
|
503
|
+
replaceSource: mutable,
|
|
504
|
+
insertImages: formatted && !!this.options.images,
|
|
505
|
+
insertPaths: mutable,
|
|
506
|
+
insertFootnote: formatted,
|
|
507
|
+
editReferenceDefinition: formatted && referenceDefinitions(state.doc).size > 0,
|
|
508
|
+
format,
|
|
509
|
+
table: tables,
|
|
510
|
+
},
|
|
511
|
+
};
|
|
512
|
+
}
|
|
513
|
+
canQuote(state) {
|
|
514
|
+
const { $from, $to } = textBounds(state);
|
|
515
|
+
if (quoted($from, $to)) {
|
|
516
|
+
const range = $from.blockRange($to, (node) => node.type.name === 'blockquote');
|
|
517
|
+
return !!range && liftTarget(range) != null;
|
|
518
|
+
}
|
|
519
|
+
const type = blockquoteSchema.type(this.editor.ctx);
|
|
520
|
+
let range = $from.blockRange($to);
|
|
521
|
+
if (range && findWrapping(range, type))
|
|
522
|
+
return true;
|
|
523
|
+
const depth = outermostListDepth($from);
|
|
524
|
+
if (depth == null)
|
|
525
|
+
return false;
|
|
526
|
+
range = new NodeRange(state.doc.resolve($from.before(depth)), state.doc.resolve($from.after(depth)), depth - 1);
|
|
527
|
+
return !!findWrapping(range, type);
|
|
528
|
+
}
|
|
529
|
+
policyPlugin = $prose(() => new Plugin({
|
|
530
|
+
key: new PluginKey('inkkitInputPolicy'),
|
|
531
|
+
filterTransaction: (tr) => this.loading ||
|
|
532
|
+
this.writable ||
|
|
533
|
+
(!tr.docChanged && !tr.storedMarksSet),
|
|
534
|
+
props: {
|
|
535
|
+
editable: () => this.writable && !this.literalSurface,
|
|
536
|
+
handleDOMEvents: {
|
|
537
|
+
compositionstart: () => {
|
|
538
|
+
queueMicrotask(() => this.publishCommandState());
|
|
539
|
+
return false;
|
|
540
|
+
},
|
|
541
|
+
compositionend: () => {
|
|
542
|
+
queueMicrotask(() => this.publishCommandState());
|
|
543
|
+
return false;
|
|
544
|
+
},
|
|
545
|
+
beforeinput: (_view, event) => {
|
|
546
|
+
if (this.writable)
|
|
547
|
+
return false;
|
|
548
|
+
event.preventDefault();
|
|
549
|
+
return true;
|
|
550
|
+
},
|
|
551
|
+
drop: (_view, event) => {
|
|
552
|
+
if (this.writable)
|
|
553
|
+
return false;
|
|
554
|
+
event.preventDefault();
|
|
555
|
+
return true;
|
|
556
|
+
},
|
|
557
|
+
},
|
|
558
|
+
},
|
|
559
|
+
view: () => ({ update: () => this.publishCommandState() }),
|
|
560
|
+
}));
|
|
329
561
|
get literalSurface() {
|
|
330
562
|
return this.formatType === 'txt' || this.mode === 'source';
|
|
331
563
|
}
|
|
@@ -353,6 +585,12 @@ export class InkKitEditor {
|
|
|
353
585
|
throw new RangeError('Editing mode must be source or formatted');
|
|
354
586
|
if (this.formatType === 'txt' || this.mode === mode)
|
|
355
587
|
return false;
|
|
588
|
+
const view = this.editor.ctx.get(editorViewCtx);
|
|
589
|
+
const focused = this.literalSurface
|
|
590
|
+
? this.plain.ownerDocument.activeElement === this.plain
|
|
591
|
+
: view.hasFocus();
|
|
592
|
+
const container = this.viewportContainer ?? this.root;
|
|
593
|
+
const top = container.scrollTop, left = container.scrollLeft;
|
|
356
594
|
const text = this.snapshot().text;
|
|
357
595
|
this.mode = mode;
|
|
358
596
|
this.outlineEpoch += 1;
|
|
@@ -362,23 +600,45 @@ export class InkKitEditor {
|
|
|
362
600
|
this.plain.value = text;
|
|
363
601
|
this.plain.setSelectionRange(this.plain.value.length, this.plain.value.length);
|
|
364
602
|
}
|
|
365
|
-
this.
|
|
603
|
+
const suppressed = this.reportingSuppressed;
|
|
604
|
+
this.reportingSuppressed = true;
|
|
605
|
+
try {
|
|
606
|
+
this.updateSurface();
|
|
607
|
+
}
|
|
608
|
+
finally {
|
|
609
|
+
this.reportingSuppressed = suppressed;
|
|
610
|
+
}
|
|
611
|
+
if (focused) {
|
|
612
|
+
if (this.literalSurface)
|
|
613
|
+
this.plain.focus({ preventScroll: true });
|
|
614
|
+
else
|
|
615
|
+
view.focus();
|
|
616
|
+
}
|
|
617
|
+
container.scrollTop = top;
|
|
618
|
+
container.scrollLeft = left;
|
|
366
619
|
this.events.stateChanged(this.literalSurface
|
|
367
620
|
? { marks: [], block: { type: 'paragraph' }, quoted: false }
|
|
368
621
|
: caretState(this.editor.ctx.get(editorViewCtx).state));
|
|
622
|
+
this.publishCommandState();
|
|
369
623
|
return true;
|
|
370
624
|
}
|
|
371
625
|
updateSurface() {
|
|
372
626
|
this.plain.hidden = !this.literalSurface;
|
|
373
|
-
this.
|
|
627
|
+
this.applyInputPolicy();
|
|
628
|
+
this.plain.setAttribute('aria-label', this.formatType === 'txt'
|
|
629
|
+
? (this.options.labels?.plainTextEditor ??
|
|
630
|
+
defaultLabels.plainTextEditor)
|
|
631
|
+
: (this.options.labels?.sourceEditor ?? defaultLabels.sourceEditor));
|
|
374
632
|
this.editor.ctx.get(editorViewCtx).dom.parentElement.hidden =
|
|
375
633
|
this.literalSurface;
|
|
376
634
|
}
|
|
377
635
|
replaceSource(text, expectedGeneration) {
|
|
378
|
-
this.
|
|
636
|
+
this.assertMutation(expectedGeneration);
|
|
379
637
|
return this.applySource(text, true);
|
|
380
638
|
}
|
|
381
639
|
applySource(text, isolated, from, to) {
|
|
640
|
+
if (!this.writable)
|
|
641
|
+
throw new InkKitError('read-only', 'The editor is read-only');
|
|
382
642
|
if (typeof text !== 'string')
|
|
383
643
|
throw new TypeError('Source must be a string');
|
|
384
644
|
if (text === this.currentText())
|
|
@@ -433,12 +693,12 @@ export class InkKitEditor {
|
|
|
433
693
|
return true;
|
|
434
694
|
}
|
|
435
695
|
undo(expectedGeneration) {
|
|
436
|
-
this.
|
|
696
|
+
this.assertMutation(expectedGeneration);
|
|
437
697
|
const view = this.editor.ctx.get(editorViewCtx);
|
|
438
698
|
return undoHistory(view.state, view.dispatch);
|
|
439
699
|
}
|
|
440
700
|
redo(expectedGeneration) {
|
|
441
|
-
this.
|
|
701
|
+
this.assertMutation(expectedGeneration);
|
|
442
702
|
const view = this.editor.ctx.get(editorViewCtx);
|
|
443
703
|
return redoHistory(view.state, view.dispatch);
|
|
444
704
|
}
|
|
@@ -583,7 +843,7 @@ export class InkKitEditor {
|
|
|
583
843
|
return result;
|
|
584
844
|
}
|
|
585
845
|
async paste(input) {
|
|
586
|
-
this.
|
|
846
|
+
this.assertMutation();
|
|
587
847
|
if (this.literalSurface || input.plainText) {
|
|
588
848
|
this.pasteAsPlainText(this.mode === 'source' && this.formatType === 'md'
|
|
589
849
|
? (input.markdown ?? input.text)
|
|
@@ -597,7 +857,7 @@ export class InkKitEditor {
|
|
|
597
857
|
setCommentVisibility(this.editor.ctx.get(editorViewCtx), visible);
|
|
598
858
|
}
|
|
599
859
|
pasteAsPlainText(text) {
|
|
600
|
-
this.
|
|
860
|
+
this.assertMutation();
|
|
601
861
|
if (this.literalSurface) {
|
|
602
862
|
this.replaceLiteralSelection(text, true, this.formatType === 'md');
|
|
603
863
|
}
|
|
@@ -636,7 +896,7 @@ export class InkKitEditor {
|
|
|
636
896
|
}
|
|
637
897
|
}
|
|
638
898
|
table(command, options) {
|
|
639
|
-
this.
|
|
899
|
+
this.assertMutation();
|
|
640
900
|
if (this.literalSurface)
|
|
641
901
|
return false;
|
|
642
902
|
return tableCommand(this.editor.ctx, command, options);
|
|
@@ -657,6 +917,8 @@ export class InkKitEditor {
|
|
|
657
917
|
}
|
|
658
918
|
this.plain.remove();
|
|
659
919
|
await this.editor.destroy();
|
|
920
|
+
if (this.ownsRootClass)
|
|
921
|
+
this.root.classList.remove('inkkit-root');
|
|
660
922
|
}
|
|
661
923
|
// The app's bindings, replaced whole whenever they change; the plugin stays.
|
|
662
924
|
keymapPlugin = $prose(() => new Plugin({
|
|
@@ -683,7 +945,8 @@ export class InkKitEditor {
|
|
|
683
945
|
if (node.type.name === 'image' || isMermaid(node))
|
|
684
946
|
portable = true;
|
|
685
947
|
});
|
|
686
|
-
const cut = event.type === 'cut';
|
|
948
|
+
const cut = event.type === 'cut' && this.writable;
|
|
949
|
+
const policyEpoch = this.policyEpoch;
|
|
687
950
|
const epoch = this.documentEpoch;
|
|
688
951
|
if (portable && this.events.clipboard) {
|
|
689
952
|
void this.clipboardSnapshot(false)
|
|
@@ -694,8 +957,9 @@ export class InkKitEditor {
|
|
|
694
957
|
throw new InkKitError('image-unavailable', 'The selected content could not be cut safely');
|
|
695
958
|
await this.events.clipboard(content);
|
|
696
959
|
if (cut) {
|
|
697
|
-
this.
|
|
960
|
+
this.assertMutation(snapshot.generation);
|
|
698
961
|
if (epoch !== this.documentEpoch ||
|
|
962
|
+
policyEpoch !== this.policyEpoch ||
|
|
699
963
|
snapshot.revision !== this.revision)
|
|
700
964
|
throw new InkKitError('stale-document', 'Document changed before cutting');
|
|
701
965
|
this.plain.setSelectionRange(from, to);
|
|
@@ -725,17 +989,24 @@ export class InkKitEditor {
|
|
|
725
989
|
static async mount(root, events, options = {}) {
|
|
726
990
|
const instance = new InkKitEditor(events, options);
|
|
727
991
|
instance.root = root;
|
|
992
|
+
instance.ownsRootClass = !root.classList.contains('inkkit-root');
|
|
993
|
+
root.classList.add('inkkit-root');
|
|
728
994
|
instance.pasteController = new PasteController({
|
|
729
995
|
ctx: () => instance.editor.ctx,
|
|
730
996
|
context: () => instance.context(),
|
|
731
997
|
adapter: options.images,
|
|
732
998
|
onError: (error) => events.error?.(error instanceof Error ? error : new Error(String(error))),
|
|
733
999
|
literalText: () => instance.literalSurface,
|
|
734
|
-
editable: () => !instance.literalSurface,
|
|
1000
|
+
editable: () => instance.writable && !instance.literalSurface,
|
|
1001
|
+
onPendingChanged: () => instance.publishCommandState(),
|
|
735
1002
|
});
|
|
736
1003
|
instance.editor = await Editor.make()
|
|
737
1004
|
.config((ctx) => {
|
|
738
1005
|
ctx.set(rootCtx, root);
|
|
1006
|
+
ctx.update(labelsCtx.key, () => ({
|
|
1007
|
+
...defaultLabels,
|
|
1008
|
+
...options.labels,
|
|
1009
|
+
}));
|
|
739
1010
|
ctx.set(defaultValueCtx, '');
|
|
740
1011
|
ctx.set(remarkStringifyOptionsCtx, stringifyOptions);
|
|
741
1012
|
// The preset also binds Mod-[ and Mod-] here; the app uses those for back and forward.
|
|
@@ -754,6 +1025,8 @@ export class InkKitEditor {
|
|
|
754
1025
|
}
|
|
755
1026
|
return cleared;
|
|
756
1027
|
});
|
|
1028
|
+
unbind(tableKeymap, ['NextCell', 'PrevCell']);
|
|
1029
|
+
unbind(historyKeymap);
|
|
757
1030
|
unbind(strongKeymap);
|
|
758
1031
|
unbind(emphasisKeymap);
|
|
759
1032
|
unbind(inlineCodeKeymap);
|
|
@@ -765,14 +1038,17 @@ export class InkKitEditor {
|
|
|
765
1038
|
unbind(codeBlockKeymap);
|
|
766
1039
|
unbind(bulletListKeymap);
|
|
767
1040
|
unbind(orderedListKeymap);
|
|
768
|
-
// The caret is kept above the fade under the formatting bar.
|
|
769
1041
|
ctx.update(editorViewOptionsCtx, (options) => ({
|
|
770
1042
|
...options,
|
|
771
|
-
|
|
772
|
-
|
|
1043
|
+
handleScrollToSelection: () => {
|
|
1044
|
+
if (!instance.loading && instance.ready)
|
|
1045
|
+
instance.revealCurrentSelection();
|
|
1046
|
+
return true;
|
|
1047
|
+
},
|
|
773
1048
|
}));
|
|
774
1049
|
})
|
|
775
|
-
.use(
|
|
1050
|
+
.use(labelsCtx)
|
|
1051
|
+
.use(caretStatePlugin(events, () => !instance.loading && !instance.literalSurface))
|
|
776
1052
|
.use(instance.pasteController.plugin)
|
|
777
1053
|
.use(visibleClipboard)
|
|
778
1054
|
.use(instance.keymapPlugin)
|
|
@@ -793,12 +1069,13 @@ export class InkKitEditor {
|
|
|
793
1069
|
.use(mermaidPreview((error) => events.error?.(error)))
|
|
794
1070
|
.use(selectionPlugin)
|
|
795
1071
|
.use($prose(() => search()))
|
|
1072
|
+
.use(instance.policyPlugin)
|
|
796
1073
|
.use(options.images ? imageView(options.images) : [])
|
|
797
1074
|
.create();
|
|
798
1075
|
configureTableMovement(instance.editor.ctx, () => {
|
|
799
1076
|
try {
|
|
800
1077
|
instance.assertCurrent();
|
|
801
|
-
return !instance.literalSurface;
|
|
1078
|
+
return instance.writable && !instance.literalSurface;
|
|
802
1079
|
}
|
|
803
1080
|
catch {
|
|
804
1081
|
return false;
|
|
@@ -809,7 +1086,11 @@ export class InkKitEditor {
|
|
|
809
1086
|
instance.plain.setAttribute('aria-label', 'Plain text editor');
|
|
810
1087
|
instance.plain.hidden = true;
|
|
811
1088
|
root.append(instance.plain);
|
|
812
|
-
instance.plain.addEventListener('beforeinput', () => {
|
|
1089
|
+
instance.plain.addEventListener('beforeinput', (event) => {
|
|
1090
|
+
if (!instance.writable) {
|
|
1091
|
+
event.preventDefault();
|
|
1092
|
+
return;
|
|
1093
|
+
}
|
|
813
1094
|
if (instance.ready && instance.literalSurface)
|
|
814
1095
|
instance.beforePlainInput = {
|
|
815
1096
|
from: instance.plain.selectionStart,
|
|
@@ -819,6 +1100,10 @@ export class InkKitEditor {
|
|
|
819
1100
|
instance.plain.addEventListener('input', () => {
|
|
820
1101
|
if (!instance.ready || !instance.literalSurface)
|
|
821
1102
|
return;
|
|
1103
|
+
if (!instance.writable) {
|
|
1104
|
+
instance.plain.value = instance.currentText();
|
|
1105
|
+
return;
|
|
1106
|
+
}
|
|
822
1107
|
try {
|
|
823
1108
|
instance.applySource(editedPlainSource(instance.currentText(), instance.plain.value), false, instance.plain.selectionStart, instance.plain.selectionEnd);
|
|
824
1109
|
}
|
|
@@ -830,22 +1115,18 @@ export class InkKitEditor {
|
|
|
830
1115
|
}
|
|
831
1116
|
});
|
|
832
1117
|
instance.plain.addEventListener('keydown', (event) => {
|
|
833
|
-
if (instance.plainComposing
|
|
834
|
-
return;
|
|
835
|
-
if (event.key.toLowerCase() !== 'z' && event.key.toLowerCase() !== 'y')
|
|
1118
|
+
if (instance.plainComposing)
|
|
836
1119
|
return;
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
if (event.key.toLowerCase() === 'y' || event.shiftKey)
|
|
840
|
-
instance.redo();
|
|
841
|
-
else
|
|
842
|
-
instance.undo();
|
|
843
|
-
}
|
|
844
|
-
catch (error) {
|
|
845
|
-
events.error?.(error instanceof Error ? error : new Error(String(error)));
|
|
846
|
-
}
|
|
1120
|
+
if (instance.literalKeys(instance.editor.ctx.get(editorViewCtx), event))
|
|
1121
|
+
event.preventDefault();
|
|
847
1122
|
});
|
|
1123
|
+
for (const event of ['select', 'keyup', 'click'])
|
|
1124
|
+
instance.plain.addEventListener(event, () => instance.publishCommandState());
|
|
848
1125
|
instance.plain.addEventListener('paste', (event) => {
|
|
1126
|
+
if (!instance.writable) {
|
|
1127
|
+
event.preventDefault();
|
|
1128
|
+
return;
|
|
1129
|
+
}
|
|
849
1130
|
if (!event.clipboardData)
|
|
850
1131
|
return;
|
|
851
1132
|
event.preventDefault();
|
|
@@ -858,9 +1139,11 @@ export class InkKitEditor {
|
|
|
858
1139
|
});
|
|
859
1140
|
instance.plain.addEventListener('compositionstart', () => {
|
|
860
1141
|
instance.plainComposing = true;
|
|
1142
|
+
instance.publishCommandState();
|
|
861
1143
|
});
|
|
862
1144
|
instance.plain.addEventListener('compositionend', () => {
|
|
863
1145
|
instance.plainComposing = false;
|
|
1146
|
+
instance.publishCommandState();
|
|
864
1147
|
});
|
|
865
1148
|
const activateFootnote = (event) => {
|
|
866
1149
|
const element = event.target.closest('[data-inkkit-footnote-reference]');
|
|
@@ -936,7 +1219,7 @@ export class InkKitEditor {
|
|
|
936
1219
|
event.clipboardData.setData('text/html', metadata != null
|
|
937
1220
|
? withReferenceMetadata(output.html, metadata)
|
|
938
1221
|
: output.html);
|
|
939
|
-
if (event.type === 'cut')
|
|
1222
|
+
if (event.type === 'cut' && instance.writable)
|
|
940
1223
|
view.dispatch(view.state.tr.deleteSelection().scrollIntoView());
|
|
941
1224
|
}
|
|
942
1225
|
catch (error) {
|
|
@@ -949,7 +1232,8 @@ export class InkKitEditor {
|
|
|
949
1232
|
if (events.clipboard) {
|
|
950
1233
|
const epoch = instance.documentEpoch, revision = instance.revision;
|
|
951
1234
|
const { from, to } = view.state.selection;
|
|
952
|
-
const cut = event.type === 'cut';
|
|
1235
|
+
const cut = event.type === 'cut' && instance.writable;
|
|
1236
|
+
const policyEpoch = instance.policyEpoch;
|
|
953
1237
|
void instance
|
|
954
1238
|
.clipboardSnapshot(false)
|
|
955
1239
|
.then(async (content) => {
|
|
@@ -959,8 +1243,9 @@ export class InkKitEditor {
|
|
|
959
1243
|
throw new InkKitError('image-unavailable', 'The selected content could not be cut safely');
|
|
960
1244
|
await events.clipboard(content);
|
|
961
1245
|
if (cut) {
|
|
962
|
-
instance.
|
|
1246
|
+
instance.assertMutation();
|
|
963
1247
|
if (epoch !== instance.documentEpoch ||
|
|
1248
|
+
policyEpoch !== instance.policyEpoch ||
|
|
964
1249
|
revision !== instance.revision)
|
|
965
1250
|
throw new InkKitError('stale-document', 'Document changed before cutting');
|
|
966
1251
|
view.dispatch(view.state.tr.deleteRange(from, to).scrollIntoView());
|
|
@@ -978,10 +1263,18 @@ export class InkKitEditor {
|
|
|
978
1263
|
};
|
|
979
1264
|
root.addEventListener('copy', instance.copyHandler, true);
|
|
980
1265
|
root.addEventListener('cut', instance.copyHandler, true);
|
|
1266
|
+
instance.applyInputPolicy();
|
|
1267
|
+
instance.setKeymap(options.keymap ?? {});
|
|
981
1268
|
return instance;
|
|
982
1269
|
}
|
|
983
1270
|
loadDocument(input) {
|
|
984
1271
|
this.assertAlive();
|
|
1272
|
+
const container = this.viewportContainer ?? this.root;
|
|
1273
|
+
const top = container.scrollTop, left = container.scrollLeft;
|
|
1274
|
+
const focused = this.literalSurface
|
|
1275
|
+
? this.plain.ownerDocument.activeElement === this.plain
|
|
1276
|
+
: this.editor.ctx.get(editorViewCtx).hasFocus();
|
|
1277
|
+
this.ready = false;
|
|
985
1278
|
this.pasteController.cancelPending();
|
|
986
1279
|
this.documentEpoch += 1;
|
|
987
1280
|
this.outlineEpoch += 1;
|
|
@@ -995,11 +1288,24 @@ export class InkKitEditor {
|
|
|
995
1288
|
this.formatType = input.format;
|
|
996
1289
|
this.revision = 0;
|
|
997
1290
|
this.plain.value = input.text;
|
|
1291
|
+
this.plain.setSelectionRange(this.plain.value.length, this.plain.value.length);
|
|
998
1292
|
this.load(input.format === 'txt' ? '' : input.text, input.generation);
|
|
999
1293
|
this.originalSource = input.text;
|
|
1000
1294
|
this.lastMarkdown = input.text;
|
|
1001
|
-
this.ready = true;
|
|
1002
1295
|
this.updateSurface();
|
|
1296
|
+
this.ready = true;
|
|
1297
|
+
if (focused) {
|
|
1298
|
+
if (this.literalSurface)
|
|
1299
|
+
this.plain.focus({ preventScroll: true });
|
|
1300
|
+
else
|
|
1301
|
+
this.editor.ctx.get(editorViewCtx).focus();
|
|
1302
|
+
}
|
|
1303
|
+
container.scrollTop = top;
|
|
1304
|
+
container.scrollLeft = left;
|
|
1305
|
+
this.events.stateChanged(this.literalSurface
|
|
1306
|
+
? { marks: [], block: { type: 'paragraph' }, quoted: false }
|
|
1307
|
+
: caretState(this.editor.ctx.get(editorViewCtx).state));
|
|
1308
|
+
this.publishCommandState();
|
|
1003
1309
|
}
|
|
1004
1310
|
reloadDocument(input) {
|
|
1005
1311
|
this.assertAlive();
|
|
@@ -1007,26 +1313,350 @@ export class InkKitEditor {
|
|
|
1007
1313
|
const wasLiteral = this.literalSurface;
|
|
1008
1314
|
const previousMode = this.editingMode;
|
|
1009
1315
|
const previousFormat = this.formatType;
|
|
1316
|
+
const selectedNode = !wasLiteral && view.state.selection instanceof NodeSelection
|
|
1317
|
+
? view.state.selection.node
|
|
1318
|
+
: undefined;
|
|
1010
1319
|
const at = wasLiteral
|
|
1011
1320
|
? this.plain.selectionStart
|
|
1012
|
-
: view.state.selection.
|
|
1013
|
-
const end = wasLiteral ? this.plain.selectionEnd :
|
|
1014
|
-
const
|
|
1321
|
+
: view.state.selection.anchor;
|
|
1322
|
+
const end = wasLiteral ? this.plain.selectionEnd : view.state.selection.head;
|
|
1323
|
+
const direction = this.plain.selectionDirection;
|
|
1324
|
+
const container = this.viewportContainer ?? this.root;
|
|
1325
|
+
const top = container.scrollTop, left = container.scrollLeft;
|
|
1326
|
+
const focused = wasLiteral
|
|
1327
|
+
? document.activeElement === this.plain
|
|
1328
|
+
: view.hasFocus();
|
|
1015
1329
|
const plainTop = this.plain.scrollTop, plainLeft = this.plain.scrollLeft;
|
|
1016
|
-
this.
|
|
1017
|
-
|
|
1018
|
-
input
|
|
1019
|
-
|
|
1020
|
-
|
|
1330
|
+
this.reportingSuppressed = true;
|
|
1331
|
+
try {
|
|
1332
|
+
this.loadDocument(input);
|
|
1333
|
+
if (previousFormat === 'md' &&
|
|
1334
|
+
input.format === 'md' &&
|
|
1335
|
+
previousMode === 'source')
|
|
1336
|
+
this.setEditingMode('source', input.generation);
|
|
1337
|
+
if (this.literalSurface)
|
|
1338
|
+
this.plain.setSelectionRange(Math.min(at, this.plain.value.length), Math.min(end, this.plain.value.length), direction);
|
|
1339
|
+
else {
|
|
1340
|
+
const anchor = Math.min(at, view.state.doc.content.size);
|
|
1341
|
+
const node = view.state.doc.nodeAt(anchor);
|
|
1342
|
+
view.dispatch(view.state.tr.setSelection(selectedNode &&
|
|
1343
|
+
node?.type === selectedNode.type &&
|
|
1344
|
+
NodeSelection.isSelectable(node)
|
|
1345
|
+
? NodeSelection.create(view.state.doc, anchor)
|
|
1346
|
+
: TextSelection.between(view.state.doc.resolve(anchor), view.state.doc.resolve(Math.min(end, view.state.doc.content.size)))));
|
|
1347
|
+
}
|
|
1348
|
+
if (focused) {
|
|
1349
|
+
if (this.literalSurface)
|
|
1350
|
+
this.plain.focus({ preventScroll: true });
|
|
1351
|
+
else
|
|
1352
|
+
view.focus();
|
|
1353
|
+
}
|
|
1354
|
+
container.scrollTop = top;
|
|
1355
|
+
container.scrollLeft = left;
|
|
1356
|
+
if (wasLiteral && this.literalSurface) {
|
|
1357
|
+
this.plain.scrollTop = plainTop;
|
|
1358
|
+
this.plain.scrollLeft = plainLeft;
|
|
1359
|
+
}
|
|
1360
|
+
}
|
|
1361
|
+
finally {
|
|
1362
|
+
this.reportingSuppressed = false;
|
|
1363
|
+
this.publishCommandState();
|
|
1364
|
+
}
|
|
1365
|
+
}
|
|
1366
|
+
setViewport(options) {
|
|
1367
|
+
this.assertAlive();
|
|
1368
|
+
const container = options.scrollContainer ?? this.root;
|
|
1369
|
+
if (container !== this.root && !container.contains(this.root))
|
|
1370
|
+
throw new RangeError('The scroll container must be the editor root or an ancestor');
|
|
1371
|
+
const insets = { top: 0, right: 0, bottom: 0, left: 0, ...options.insets };
|
|
1372
|
+
if (Object.values(insets).some((value) => !Number.isFinite(value) || value < 0))
|
|
1373
|
+
throw new RangeError('Viewport insets must be finite non-negative CSS pixel values');
|
|
1374
|
+
this.viewportContainer = container;
|
|
1375
|
+
this.viewportInsets = insets;
|
|
1376
|
+
}
|
|
1377
|
+
viewport() {
|
|
1378
|
+
this.assertAlive();
|
|
1379
|
+
const container = this.viewportContainer ?? this.root;
|
|
1380
|
+
const bounds = container.getBoundingClientRect();
|
|
1381
|
+
const root = this.root.getBoundingClientRect();
|
|
1382
|
+
const insets = this.viewportInsets;
|
|
1383
|
+
const window = this.root.ownerDocument.defaultView;
|
|
1384
|
+
const left = Math.max(bounds.left + container.clientLeft, root.left, 0);
|
|
1385
|
+
const top = Math.max(bounds.top + container.clientTop, root.top, 0);
|
|
1386
|
+
const right = Math.max(left, Math.min(bounds.left + container.clientLeft + container.clientWidth, root.right, window.innerWidth));
|
|
1387
|
+
const bottom = Math.max(top, Math.min(bounds.top + container.clientTop + container.clientHeight, root.bottom, window.innerHeight));
|
|
1388
|
+
let rect = textRect({ left, top, right, bottom });
|
|
1389
|
+
if (this.literalSurface) {
|
|
1390
|
+
const plain = this.plain.getBoundingClientRect();
|
|
1391
|
+
const literalLeft = Math.max(rect.left, plain.left + this.plain.clientLeft);
|
|
1392
|
+
const literalTop = Math.max(rect.top, plain.top + this.plain.clientTop);
|
|
1393
|
+
rect = textRect({
|
|
1394
|
+
left: literalLeft,
|
|
1395
|
+
top: literalTop,
|
|
1396
|
+
right: Math.max(literalLeft, Math.min(rect.right, plain.left + this.plain.clientLeft + this.plain.clientWidth)),
|
|
1397
|
+
bottom: Math.max(literalTop, Math.min(rect.bottom, plain.top + this.plain.clientTop + this.plain.clientHeight)),
|
|
1398
|
+
});
|
|
1399
|
+
}
|
|
1400
|
+
const insetLeft = Math.min(rect.right, rect.left + insets.left);
|
|
1401
|
+
const insetTop = Math.min(rect.bottom, rect.top + insets.top);
|
|
1402
|
+
rect = textRect({
|
|
1403
|
+
left: insetLeft,
|
|
1404
|
+
top: insetTop,
|
|
1405
|
+
right: Math.max(insetLeft, rect.right - insets.right),
|
|
1406
|
+
bottom: Math.max(insetTop, rect.bottom - insets.bottom),
|
|
1407
|
+
});
|
|
1408
|
+
return Object.freeze({
|
|
1409
|
+
rect: Object.freeze(rect),
|
|
1410
|
+
insets: Object.freeze({ ...insets }),
|
|
1411
|
+
scrollTop: container.scrollTop + (this.literalSurface ? this.plain.scrollTop : 0),
|
|
1412
|
+
scrollLeft: container.scrollLeft +
|
|
1413
|
+
(this.literalSurface ? this.plain.scrollLeft : 0),
|
|
1414
|
+
});
|
|
1415
|
+
}
|
|
1416
|
+
readableScope() {
|
|
1417
|
+
const key = `${this.documentEpoch}:${this.revision}:${this.outlineEpoch}:${this.editingMode}`;
|
|
1418
|
+
if (this.textScope?.key !== key)
|
|
1419
|
+
this.textScope = {
|
|
1420
|
+
key,
|
|
1421
|
+
id: `${this.textIdentity}:${key}`,
|
|
1422
|
+
projection: this.literalSurface
|
|
1423
|
+
? { text: this.plain.value, spans: [] }
|
|
1424
|
+
: readableProjection(this.editor.ctx.get(editorViewCtx).state.doc),
|
|
1425
|
+
};
|
|
1426
|
+
return this.textScope;
|
|
1427
|
+
}
|
|
1428
|
+
textSnapshot(expectedGeneration) {
|
|
1429
|
+
this.assertCurrent(expectedGeneration);
|
|
1430
|
+
const scope = this.readableScope();
|
|
1431
|
+
const selection = this.editor.ctx.get(editorViewCtx).state.selection;
|
|
1432
|
+
return Object.freeze({
|
|
1433
|
+
snapshotId: scope.id,
|
|
1434
|
+
documentId: this.documentId,
|
|
1435
|
+
generation: this.generation,
|
|
1436
|
+
revision: this.revision,
|
|
1437
|
+
format: this.formatType,
|
|
1438
|
+
mode: this.editingMode,
|
|
1439
|
+
text: scope.projection.text,
|
|
1440
|
+
selection: Object.freeze({
|
|
1441
|
+
snapshotId: scope.id,
|
|
1442
|
+
from: this.literalSurface
|
|
1443
|
+
? this.plain.selectionStart
|
|
1444
|
+
: projectionOffset(scope.projection, selection.from),
|
|
1445
|
+
to: this.literalSurface
|
|
1446
|
+
? this.plain.selectionEnd
|
|
1447
|
+
: projectionOffset(scope.projection, selection.to),
|
|
1448
|
+
}),
|
|
1449
|
+
});
|
|
1450
|
+
}
|
|
1451
|
+
validateTextRange(range) {
|
|
1452
|
+
this.assertCurrent();
|
|
1453
|
+
const scope = this.readableScope();
|
|
1454
|
+
if (range.snapshotId !== scope.id)
|
|
1455
|
+
throw new InkKitError('stale-document', 'The readable text snapshot changed');
|
|
1456
|
+
validateOffsets(scope.projection.text, range.from, range.to);
|
|
1457
|
+
return scope.projection;
|
|
1458
|
+
}
|
|
1459
|
+
selectTextRange(range, options = {}) {
|
|
1460
|
+
const projection = this.validateTextRange(range);
|
|
1021
1461
|
if (this.literalSurface)
|
|
1022
|
-
this.plain.setSelectionRange(
|
|
1023
|
-
else
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1462
|
+
this.plain.setSelectionRange(range.from, range.to);
|
|
1463
|
+
else {
|
|
1464
|
+
for (const span of projection.spans)
|
|
1465
|
+
if (span.kind === 'embed' &&
|
|
1466
|
+
((range.from > span.from && range.from < span.to) ||
|
|
1467
|
+
(range.to > span.from && range.to < span.to)))
|
|
1468
|
+
throw new InkKitError('invalid-range', 'Embedded labels must be selected as complete ranges');
|
|
1469
|
+
const view = this.editor.ctx.get(editorViewCtx);
|
|
1470
|
+
const from = projectionPosition(projection, range.from, 1);
|
|
1471
|
+
const to = projectionPosition(projection, range.to, range.from === range.to ? 1 : -1);
|
|
1472
|
+
const embed = projection.spans.find((span) => span.kind === 'embed' &&
|
|
1473
|
+
span.from === range.from &&
|
|
1474
|
+
span.to === range.to);
|
|
1475
|
+
view.dispatch(view.state.tr.setSelection(embed && view.state.doc.nodeAt(embed.start)
|
|
1476
|
+
? NodeSelection.create(view.state.doc, embed.start)
|
|
1477
|
+
: TextSelection.between(view.state.doc.resolve(from), view.state.doc.resolve(to))));
|
|
1478
|
+
}
|
|
1479
|
+
if (options.focus) {
|
|
1480
|
+
if (this.literalSurface)
|
|
1481
|
+
this.plain.focus({ preventScroll: true });
|
|
1482
|
+
else
|
|
1483
|
+
this.editor.ctx.get(editorViewCtx).focus();
|
|
1484
|
+
}
|
|
1485
|
+
if (options.reveal)
|
|
1486
|
+
this.revealTextRange(range);
|
|
1487
|
+
this.publishCommandState();
|
|
1488
|
+
}
|
|
1489
|
+
replaceTextRange(range, text) {
|
|
1490
|
+
const projection = this.validateTextRange(range);
|
|
1491
|
+
this.assertMutation();
|
|
1492
|
+
if (typeof text !== 'string')
|
|
1493
|
+
throw new TypeError('Replacement must be a string');
|
|
1494
|
+
if (this.literalSurface) {
|
|
1495
|
+
const raw = this.currentText();
|
|
1496
|
+
const from = rawOffset(raw, range.from), to = rawOffset(raw, range.to);
|
|
1497
|
+
const ending = /\r\n|\r|\n/.exec(raw)?.[0] ?? '\n';
|
|
1498
|
+
const value = text.replace(/\r\n?/g, '\n');
|
|
1499
|
+
const replaced = raw.slice(0, from) + value.replaceAll('\n', ending) + raw.slice(to);
|
|
1500
|
+
return this.applySource(replaced, true, range.from, range.from + value.length);
|
|
1029
1501
|
}
|
|
1502
|
+
if (projection.spans.some((span) => (span.kind === 'embed' &&
|
|
1503
|
+
range.from > span.from &&
|
|
1504
|
+
range.from < span.to) ||
|
|
1505
|
+
(span.kind === 'embed' &&
|
|
1506
|
+
range.to > span.from &&
|
|
1507
|
+
range.to < span.to) ||
|
|
1508
|
+
(span.to > range.from &&
|
|
1509
|
+
span.from < range.to &&
|
|
1510
|
+
span.kind !== 'text')))
|
|
1511
|
+
throw new InkKitError('invalid-range', 'Replacement cannot remove embedded content or structural separators');
|
|
1512
|
+
const from = projectionPosition(projection, range.from, 1);
|
|
1513
|
+
const to = projectionPosition(projection, range.to, range.from === range.to ? 1 : -1);
|
|
1514
|
+
return replaceFormattedRange(this.editor.ctx.get(editorViewCtx), from, to, text, (doc) => {
|
|
1515
|
+
this.currentText(doc);
|
|
1516
|
+
});
|
|
1517
|
+
}
|
|
1518
|
+
textRangeRects(range) {
|
|
1519
|
+
const projection = this.validateTextRange(range);
|
|
1520
|
+
const rects = this.literalSurface
|
|
1521
|
+
? literalRects(this.plain, range.from, range.to)
|
|
1522
|
+
: formattedRects(this.editor.ctx.get(editorViewCtx), projection, range.from, range.to);
|
|
1523
|
+
return Object.freeze(rects.map((rect) => Object.freeze(rect)));
|
|
1524
|
+
}
|
|
1525
|
+
visibleTextRanges(snapshotId) {
|
|
1526
|
+
const snapshot = this.textSnapshot();
|
|
1527
|
+
this.validateTextRange({ snapshotId, from: 0, to: 0 });
|
|
1528
|
+
const viewport = this.viewport().rect;
|
|
1529
|
+
if (this.literalSurface)
|
|
1530
|
+
return Object.freeze(literalVisibleRanges(this.plain, viewport).map((range) => Object.freeze({ snapshotId, ...range })));
|
|
1531
|
+
const visible = (rect) => intersectsViewport(rect, viewport);
|
|
1532
|
+
const ranges = [];
|
|
1533
|
+
const projection = this.readableScope().projection;
|
|
1534
|
+
const view = this.editor.ctx.get(editorViewCtx);
|
|
1535
|
+
for (const span of projection.spans) {
|
|
1536
|
+
const current = { text: projection.text, spans: [span] };
|
|
1537
|
+
if (span.kind === 'separator' ||
|
|
1538
|
+
!formattedRects(view, current, span.from, span.to).some(visible))
|
|
1539
|
+
continue;
|
|
1540
|
+
for (let from = span.from; from < span.to;) {
|
|
1541
|
+
const to = from + (snapshot.text.codePointAt(from) > 0xffff ? 2 : 1);
|
|
1542
|
+
if (formattedRects(view, current, from, to).some(visible)) {
|
|
1543
|
+
const previous = ranges.at(-1);
|
|
1544
|
+
if (previous?.to === from)
|
|
1545
|
+
previous.to = to;
|
|
1546
|
+
else
|
|
1547
|
+
ranges.push({ snapshotId, from, to });
|
|
1548
|
+
}
|
|
1549
|
+
from = to;
|
|
1550
|
+
}
|
|
1551
|
+
}
|
|
1552
|
+
return Object.freeze(ranges.map((range) => Object.freeze(range)));
|
|
1553
|
+
}
|
|
1554
|
+
revealTextRange(range) {
|
|
1555
|
+
const projection = this.validateTextRange(range);
|
|
1556
|
+
if (!this.literalSurface) {
|
|
1557
|
+
const view = this.editor.ctx.get(editorViewCtx);
|
|
1558
|
+
const position = projectionPosition(projection, range.from, 1);
|
|
1559
|
+
const at = view.domAtPos(position).node;
|
|
1560
|
+
let element = at instanceof Element ? at : at.parentElement;
|
|
1561
|
+
while (element && this.root.contains(element)) {
|
|
1562
|
+
if (element.matches('.inkkit-callout[data-inkkit-folded="true"]'))
|
|
1563
|
+
element
|
|
1564
|
+
.querySelector(':scope > .inkkit-callout-header [data-inkkit-callout-toggle]')
|
|
1565
|
+
?.click();
|
|
1566
|
+
element = element.parentElement;
|
|
1567
|
+
}
|
|
1568
|
+
}
|
|
1569
|
+
this.revealRects(this.textRangeRects(range));
|
|
1570
|
+
}
|
|
1571
|
+
revealRects(rects) {
|
|
1572
|
+
if (!rects.length)
|
|
1573
|
+
return;
|
|
1574
|
+
const viewport = this.viewport().rect;
|
|
1575
|
+
const leading = rects[0];
|
|
1576
|
+
const envelope = {
|
|
1577
|
+
top: leading.top,
|
|
1578
|
+
bottom: leading.bottom,
|
|
1579
|
+
left: leading.left,
|
|
1580
|
+
right: leading.right,
|
|
1581
|
+
};
|
|
1582
|
+
for (const rect of rects) {
|
|
1583
|
+
envelope.top = Math.min(envelope.top, rect.top);
|
|
1584
|
+
envelope.bottom = Math.max(envelope.bottom, rect.bottom);
|
|
1585
|
+
envelope.left = Math.min(envelope.left, rect.left);
|
|
1586
|
+
envelope.right = Math.max(envelope.right, rect.right);
|
|
1587
|
+
}
|
|
1588
|
+
const fitsHeight = envelope.bottom - envelope.top <= viewport.height;
|
|
1589
|
+
const fitsWidth = envelope.right - envelope.left <= viewport.width;
|
|
1590
|
+
const rect = {
|
|
1591
|
+
top: fitsHeight ? envelope.top : leading.top,
|
|
1592
|
+
bottom: fitsHeight
|
|
1593
|
+
? envelope.bottom
|
|
1594
|
+
: Math.min(leading.bottom, leading.top + viewport.height),
|
|
1595
|
+
left: fitsWidth ? envelope.left : leading.left,
|
|
1596
|
+
right: fitsWidth
|
|
1597
|
+
? envelope.right
|
|
1598
|
+
: Math.min(leading.right, leading.left + viewport.width),
|
|
1599
|
+
};
|
|
1600
|
+
const vertical = viewport.height <= 0
|
|
1601
|
+
? 0
|
|
1602
|
+
: rect.top < viewport.top
|
|
1603
|
+
? rect.top - viewport.top
|
|
1604
|
+
: rect.bottom > viewport.bottom
|
|
1605
|
+
? rect.bottom - viewport.bottom
|
|
1606
|
+
: 0;
|
|
1607
|
+
const horizontal = viewport.width <= 0
|
|
1608
|
+
? 0
|
|
1609
|
+
: rect.left < viewport.left
|
|
1610
|
+
? rect.left - viewport.left
|
|
1611
|
+
: rect.right > viewport.right
|
|
1612
|
+
? rect.right - viewport.right
|
|
1613
|
+
: 0;
|
|
1614
|
+
let remainingTop = vertical, remainingLeft = horizontal;
|
|
1615
|
+
if (this.literalSurface) {
|
|
1616
|
+
const top = this.plain.scrollTop, left = this.plain.scrollLeft;
|
|
1617
|
+
this.plain.scrollTop += vertical;
|
|
1618
|
+
this.plain.scrollLeft += horizontal;
|
|
1619
|
+
remainingTop -= this.plain.scrollTop - top;
|
|
1620
|
+
remainingLeft -= this.plain.scrollLeft - left;
|
|
1621
|
+
}
|
|
1622
|
+
let container = this.viewportContainer ?? this.root;
|
|
1623
|
+
const document = this.root.ownerDocument;
|
|
1624
|
+
while (container &&
|
|
1625
|
+
container !== document.body &&
|
|
1626
|
+
container !== document.documentElement) {
|
|
1627
|
+
const top = container.scrollTop, left = container.scrollLeft;
|
|
1628
|
+
container.scrollTop += remainingTop;
|
|
1629
|
+
container.scrollLeft += remainingLeft;
|
|
1630
|
+
remainingTop -= container.scrollTop - top;
|
|
1631
|
+
remainingLeft -= container.scrollLeft - left;
|
|
1632
|
+
container = container.parentElement;
|
|
1633
|
+
}
|
|
1634
|
+
if (remainingTop || remainingLeft)
|
|
1635
|
+
document.defaultView.scrollBy(remainingLeft, remainingTop);
|
|
1636
|
+
}
|
|
1637
|
+
revealCurrentSelection() {
|
|
1638
|
+
if (this.loading || !this.ready)
|
|
1639
|
+
return;
|
|
1640
|
+
if (this.literalSurface) {
|
|
1641
|
+
this.revealLiteralSelection();
|
|
1642
|
+
return;
|
|
1643
|
+
}
|
|
1644
|
+
const view = this.editor.ctx.get(editorViewCtx);
|
|
1645
|
+
const projection = this.readableScope().projection;
|
|
1646
|
+
const from = projectionOffset(projection, view.state.selection.from);
|
|
1647
|
+
const to = projectionOffset(projection, view.state.selection.to);
|
|
1648
|
+
if (this.composing || this.pasteController.pending) {
|
|
1649
|
+
this.revealRects(formattedRects(view, projection, from, to));
|
|
1650
|
+
return;
|
|
1651
|
+
}
|
|
1652
|
+
this.revealTextRange({
|
|
1653
|
+
snapshotId: this.readableScope().id,
|
|
1654
|
+
from,
|
|
1655
|
+
to,
|
|
1656
|
+
});
|
|
1657
|
+
}
|
|
1658
|
+
revealLiteralSelection() {
|
|
1659
|
+
this.revealRects(literalRects(this.plain, this.plain.selectionStart, this.plain.selectionEnd));
|
|
1030
1660
|
}
|
|
1031
1661
|
load(markdown, generation) {
|
|
1032
1662
|
this.generation = generation;
|
|
@@ -1047,8 +1677,7 @@ export class InkKitEditor {
|
|
|
1047
1677
|
const { doc } = view.state;
|
|
1048
1678
|
const end = Selection.findFrom(doc.resolve(doc.content.size), -1, true) ??
|
|
1049
1679
|
Selection.atEnd(doc);
|
|
1050
|
-
view.dispatch(setSearchState(view.state.tr.setSelection(end)
|
|
1051
|
-
this.events.stateChanged(caretState(view.state));
|
|
1680
|
+
view.dispatch(setSearchState(view.state.tr.setSelection(end), new SearchQuery({ search: '' })));
|
|
1052
1681
|
}
|
|
1053
1682
|
find(text, expectedGeneration) {
|
|
1054
1683
|
this.assertCurrent(expectedGeneration);
|
|
@@ -1078,17 +1707,18 @@ export class InkKitEditor {
|
|
|
1078
1707
|
else if (this.plainSearch)
|
|
1079
1708
|
this.plain.setSelectionRange(this.plain.selectionEnd, this.plain.selectionEnd);
|
|
1080
1709
|
this.plainSearch = text;
|
|
1081
|
-
|
|
1710
|
+
this.revealLiteralSelection();
|
|
1082
1711
|
return;
|
|
1083
1712
|
}
|
|
1084
|
-
findFormatted(this.editor.ctx.get(editorViewCtx), text)
|
|
1713
|
+
if (findFormatted(this.editor.ctx.get(editorViewCtx), text))
|
|
1714
|
+
this.revealCurrentSelection();
|
|
1085
1715
|
}
|
|
1086
1716
|
replace(search, replacement, expectedGeneration) {
|
|
1087
|
-
this.
|
|
1717
|
+
this.assertMutation(expectedGeneration);
|
|
1088
1718
|
return this.replaceMatches(search, replacement, false) > 0;
|
|
1089
1719
|
}
|
|
1090
1720
|
replaceAll(search, replacement, expectedGeneration) {
|
|
1091
|
-
this.
|
|
1721
|
+
this.assertMutation(expectedGeneration);
|
|
1092
1722
|
return this.replaceMatches(search, replacement, true);
|
|
1093
1723
|
}
|
|
1094
1724
|
replaceMatches(search, replacement, all) {
|
|
@@ -1146,7 +1776,7 @@ export class InkKitEditor {
|
|
|
1146
1776
|
const position = sourceHeadingPosition(this.editor.ctx, this.plain.value, heading, this.outlineContext());
|
|
1147
1777
|
this.plain.setSelectionRange(position, position);
|
|
1148
1778
|
this.plain.focus({ preventScroll: true });
|
|
1149
|
-
|
|
1779
|
+
this.revealLiteralSelection();
|
|
1150
1780
|
return true;
|
|
1151
1781
|
}
|
|
1152
1782
|
return navigateFormattedHeading(this.editor.ctx.get(editorViewCtx), heading, this.outlineContext());
|
|
@@ -1159,7 +1789,7 @@ export class InkKitEditor {
|
|
|
1159
1789
|
: this.editor.ctx.get(editorViewCtx).composing) &&
|
|
1160
1790
|
generation === this.generation)
|
|
1161
1791
|
return false;
|
|
1162
|
-
this.
|
|
1792
|
+
this.assertMutation(generation);
|
|
1163
1793
|
if (this.literalSurface) {
|
|
1164
1794
|
this.replaceLiteralSelection(text, false);
|
|
1165
1795
|
return true;
|
|
@@ -1183,11 +1813,38 @@ export class InkKitEditor {
|
|
|
1183
1813
|
generation === this.generation)
|
|
1184
1814
|
return false;
|
|
1185
1815
|
this.assertCurrent(generation);
|
|
1816
|
+
const configuredEvent = new KeyboardEvent('keydown', {
|
|
1817
|
+
key,
|
|
1818
|
+
code,
|
|
1819
|
+
metaKey,
|
|
1820
|
+
ctrlKey,
|
|
1821
|
+
altKey,
|
|
1822
|
+
shiftKey,
|
|
1823
|
+
bubbles: true,
|
|
1824
|
+
cancelable: true,
|
|
1825
|
+
});
|
|
1826
|
+
if (!this.literalSurface && altKey && key === 'Enter')
|
|
1827
|
+
return this.navigateFootnote(shiftKey ? 'reference' : 'definition');
|
|
1828
|
+
if (!this.writable &&
|
|
1829
|
+
!this.literalSurface &&
|
|
1830
|
+
key === 'Tab' &&
|
|
1831
|
+
!metaKey &&
|
|
1832
|
+
!ctrlKey &&
|
|
1833
|
+
!altKey) {
|
|
1834
|
+
const view = this.editor.ctx.get(editorViewCtx);
|
|
1835
|
+
return goToNextCell(shiftKey ? -1 : 1)(view.state, view.dispatch);
|
|
1836
|
+
}
|
|
1837
|
+
if (!this.writable) {
|
|
1838
|
+
if (['Enter', 'Tab', 'Backspace', 'Delete'].includes(key))
|
|
1839
|
+
this.assertMutation(generation);
|
|
1840
|
+
this.readOnlyKeys(this.editor.ctx.get(editorViewCtx), configuredEvent);
|
|
1841
|
+
}
|
|
1186
1842
|
if (this.literalSurface) {
|
|
1187
|
-
if ((
|
|
1188
|
-
return
|
|
1189
|
-
if (
|
|
1190
|
-
|
|
1843
|
+
if (this.literalKeys(this.editor.ctx.get(editorViewCtx), configuredEvent))
|
|
1844
|
+
return true;
|
|
1845
|
+
if (!this.writable &&
|
|
1846
|
+
['Enter', 'Tab', 'Backspace', 'Delete'].includes(key))
|
|
1847
|
+
throw new InkKitError('read-only', 'The editor is read-only');
|
|
1191
1848
|
if (metaKey || ctrlKey || altKey)
|
|
1192
1849
|
return false;
|
|
1193
1850
|
if (key === 'Enter' || key === 'Tab') {
|
|
@@ -1237,21 +1894,58 @@ export class InkKitEditor {
|
|
|
1237
1894
|
* order, so a key given to two shortcuts lands the same way every time. */
|
|
1238
1895
|
setKeymap(keymap) {
|
|
1239
1896
|
this.assertAlive();
|
|
1897
|
+
this.configuredKeys = {
|
|
1898
|
+
undo: ['Mod-z'],
|
|
1899
|
+
redo: ['Mod-y', 'Shift-Mod-z'],
|
|
1900
|
+
tableExit: ['Mod-Enter', 'Enter'],
|
|
1901
|
+
...keymap,
|
|
1902
|
+
};
|
|
1240
1903
|
const bindings = {};
|
|
1904
|
+
// Consuming unbound history keys prevents the browser's separate undo stack
|
|
1905
|
+
// from changing source/TXT outside the shared document history.
|
|
1906
|
+
const historyBindings = Object.fromEntries(['Mod-z', 'Mod-y', 'Shift-Mod-z'].map((key) => [key, () => true]));
|
|
1907
|
+
for (const name of ['undo', 'redo']) {
|
|
1908
|
+
for (const key of this.configuredKeys[name] ?? []) {
|
|
1909
|
+
historyBindings[key] = () => {
|
|
1910
|
+
if (!this.ready || !this.commandState().commands[name])
|
|
1911
|
+
return true;
|
|
1912
|
+
return this[name]();
|
|
1913
|
+
};
|
|
1914
|
+
}
|
|
1915
|
+
}
|
|
1916
|
+
Object.assign(bindings, historyBindings);
|
|
1241
1917
|
for (const [name, command] of Object.entries(shortcutCommands)) {
|
|
1242
1918
|
for (const key of keymap[name] ?? []) {
|
|
1243
1919
|
bindings[key] = () => {
|
|
1920
|
+
if (!this.ready || !this.commandState().commands.format[command[0]])
|
|
1921
|
+
return true;
|
|
1244
1922
|
this.format(...command);
|
|
1245
1923
|
return true;
|
|
1246
1924
|
};
|
|
1247
1925
|
}
|
|
1248
1926
|
}
|
|
1927
|
+
for (const command of tableCommands) {
|
|
1928
|
+
const name = 'table' + command[0].toUpperCase() + command.slice(1);
|
|
1929
|
+
for (const key of this.configuredKeys[name] ?? [])
|
|
1930
|
+
bindings[key] = () => {
|
|
1931
|
+
if (!this.ready || !this.commandState().commands.table[command])
|
|
1932
|
+
return false;
|
|
1933
|
+
return this.table(command);
|
|
1934
|
+
};
|
|
1935
|
+
}
|
|
1936
|
+
this.readOnlyKeys = keydownHandler(Object.fromEntries(Object.keys(bindings).map((key) => [
|
|
1937
|
+
key,
|
|
1938
|
+
() => {
|
|
1939
|
+
throw new InkKitError('read-only', 'The editor is read-only');
|
|
1940
|
+
},
|
|
1941
|
+
])));
|
|
1249
1942
|
this.keys = keydownHandler(bindings);
|
|
1943
|
+
this.literalKeys = keydownHandler(historyBindings);
|
|
1250
1944
|
}
|
|
1251
1945
|
/** Dropped files land as one paragraph per path at the drop point: in place of an empty block, after
|
|
1252
1946
|
* the top-level block otherwise, so a list or quote is not opened up by them. */
|
|
1253
1947
|
insertPaths(paths, x, y) {
|
|
1254
|
-
this.
|
|
1948
|
+
this.assertMutation();
|
|
1255
1949
|
if (paths.length === 0)
|
|
1256
1950
|
return;
|
|
1257
1951
|
if (this.literalSurface) {
|
|
@@ -1262,7 +1956,7 @@ export class InkKitEditor {
|
|
|
1262
1956
|
this.insertBlocks(paths.map((path) => schema.nodes.paragraph.create(null, schema.text(path))), x, y);
|
|
1263
1957
|
}
|
|
1264
1958
|
insertImages(images, x, y) {
|
|
1265
|
-
this.
|
|
1959
|
+
this.assertMutation();
|
|
1266
1960
|
if (!images.length)
|
|
1267
1961
|
return;
|
|
1268
1962
|
if (!this.options.images || this.literalSurface)
|
|
@@ -1420,7 +2114,7 @@ export class InkKitEditor {
|
|
|
1420
2114
|
view.dispatch(view.state.tr.lift(range, target));
|
|
1421
2115
|
}
|
|
1422
2116
|
format(command, arg) {
|
|
1423
|
-
this.
|
|
2117
|
+
this.assertMutation();
|
|
1424
2118
|
if (this.literalSurface)
|
|
1425
2119
|
return;
|
|
1426
2120
|
const run = (cmd, payload) => this.editor.action(callCommand(cmd, payload));
|
|
@@ -1494,7 +2188,7 @@ export class InkKitEditor {
|
|
|
1494
2188
|
this.focus();
|
|
1495
2189
|
}
|
|
1496
2190
|
editReferenceDefinition(label, destination, title) {
|
|
1497
|
-
this.
|
|
2191
|
+
this.assertMutation();
|
|
1498
2192
|
if (this.literalSurface)
|
|
1499
2193
|
return false;
|
|
1500
2194
|
const view = this.editor.ctx.get(editorViewCtx);
|
|
@@ -1511,7 +2205,7 @@ export class InkKitEditor {
|
|
|
1511
2205
|
return true;
|
|
1512
2206
|
}
|
|
1513
2207
|
insertFootnote(label) {
|
|
1514
|
-
this.
|
|
2208
|
+
this.assertMutation();
|
|
1515
2209
|
if (this.literalSurface)
|
|
1516
2210
|
return;
|
|
1517
2211
|
const view = this.editor.ctx.get(editorViewCtx);
|