@aicayzer/inkkit 0.0.9 → 0.0.11
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +6 -2
- package/dist/callouts.js +2 -1
- package/dist/clipboard.d.ts +3 -3
- package/dist/clipboard.js +64 -25
- package/dist/dialect.d.ts +1 -1
- package/dist/dialect.js +36 -2
- package/dist/editor.d.ts +12 -1
- package/dist/editor.js +144 -33
- package/dist/files.d.ts +40 -0
- package/dist/files.js +133 -0
- package/dist/images.d.ts +20 -4
- package/dist/images.js +242 -25
- package/dist/index.d.ts +1 -1
- package/dist/labels.d.ts +8 -0
- package/dist/labels.js +8 -0
- package/dist/linked-syntax.d.ts +48 -0
- package/dist/linked-syntax.js +308 -0
- package/dist/literals.js +40 -1
- package/dist/mermaid.js +1 -1
- package/dist/portable-files.d.ts +10 -0
- package/dist/portable-files.js +36 -0
- package/dist/preserve.js +7 -0
- package/dist/print.d.ts +2 -2
- package/dist/print.js +49 -17
- package/dist/style.css +41 -3
- package/dist/text-ranges.js +8 -3
- package/dist/types.d.ts +44 -1
- package/dist/wiki-view.d.ts +21 -0
- package/dist/wiki-view.js +78 -0
- package/package.json +1 -1
package/dist/editor.js
CHANGED
|
@@ -21,10 +21,11 @@ import { sourceAttribute, cleanSourceDoc, editedPlainSource, sourceSelection, ra
|
|
|
21
21
|
import { normaliseLabel, referenceDefinitions, footnoteDefinitions, } from './references.js';
|
|
22
22
|
import { selectionMarkdown, selectionContent, referenceMetadata, withReferenceMetadata, } from './reference-clipboard.js';
|
|
23
23
|
import { imageView } from './images.js';
|
|
24
|
+
import { wikiLinkView } from './wiki-view.js';
|
|
25
|
+
import { withFileSignal } from './files.js';
|
|
24
26
|
import { tablePlugins, tableCommand, tableCommands, tableAvailability, tableContext, configureTableMovement, } from './tables.js';
|
|
25
27
|
import { InkKitError, } from './types.js';
|
|
26
28
|
import { readableProjection, projectionPosition, projectionOffset, validateOffsets, formattedRects, literalRects, literalVisibleRanges, intersectsViewport, textRect, } from './text-ranges.js';
|
|
27
|
-
import { highlightPlugin } from './highlight.js';
|
|
28
29
|
import { highlightKeymap, toggleHighlightCommand } from './inline-highlight.js';
|
|
29
30
|
import { commentSelectionContent, setCommentVisibility } from './comments.js';
|
|
30
31
|
import { PasteController } from './paste.js';
|
|
@@ -251,12 +252,14 @@ export class InkKitEditor {
|
|
|
251
252
|
revision = 0;
|
|
252
253
|
documentEpoch = 0;
|
|
253
254
|
operationSequence = 0;
|
|
255
|
+
exportControllers = new Set();
|
|
254
256
|
plainComposing = false;
|
|
255
257
|
plainSearch = '';
|
|
256
258
|
literalMatch;
|
|
257
259
|
beforePlainInput;
|
|
258
260
|
mode = 'formatted';
|
|
259
261
|
sourcePreservations = new WeakMap();
|
|
262
|
+
sourceTexts = new WeakMap();
|
|
260
263
|
outlineEpoch = 0;
|
|
261
264
|
ready = false;
|
|
262
265
|
destroyed = false;
|
|
@@ -294,6 +297,8 @@ export class InkKitEditor {
|
|
|
294
297
|
if (this.loading || view.state.doc.eq(previous.doc))
|
|
295
298
|
return;
|
|
296
299
|
this.revision += 1;
|
|
300
|
+
this.cancelExports();
|
|
301
|
+
this.mediaPolicy();
|
|
297
302
|
this.literalMatch = undefined;
|
|
298
303
|
let markdown;
|
|
299
304
|
try {
|
|
@@ -335,6 +340,36 @@ export class InkKitEditor {
|
|
|
335
340
|
operationId: `${this.documentEpoch}:${this.generation}:${++this.operationSequence}`,
|
|
336
341
|
};
|
|
337
342
|
}
|
|
343
|
+
cancelExports() {
|
|
344
|
+
for (const controller of this.exportControllers)
|
|
345
|
+
controller.abort();
|
|
346
|
+
this.exportControllers.clear();
|
|
347
|
+
}
|
|
348
|
+
async exportContent(snapshot, operation) {
|
|
349
|
+
const epoch = this.documentEpoch;
|
|
350
|
+
const mode = this.editingMode;
|
|
351
|
+
const controller = new AbortController();
|
|
352
|
+
this.exportControllers.add(controller);
|
|
353
|
+
try {
|
|
354
|
+
return await withFileSignal(operation(controller.signal), controller.signal);
|
|
355
|
+
}
|
|
356
|
+
finally {
|
|
357
|
+
this.exportControllers.delete(controller);
|
|
358
|
+
this.assertCurrent(snapshot.generation);
|
|
359
|
+
if (controller.signal.aborted ||
|
|
360
|
+
epoch !== this.documentEpoch ||
|
|
361
|
+
snapshot.documentId !== this.documentId ||
|
|
362
|
+
snapshot.revision !== this.revision ||
|
|
363
|
+
mode !== this.editingMode)
|
|
364
|
+
throw new InkKitError('stale-document', 'Document changed while preparing output');
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
mediaPolicy() {
|
|
368
|
+
if (this.editor && this.root)
|
|
369
|
+
this.editor.ctx
|
|
370
|
+
.get(editorViewCtx)
|
|
371
|
+
.dom.dispatchEvent(new Event('inkkit-media-policy'));
|
|
372
|
+
}
|
|
338
373
|
assertAlive() {
|
|
339
374
|
if (this.destroyed)
|
|
340
375
|
throw new InkKitError('destroyed', 'The editor has been destroyed');
|
|
@@ -383,6 +418,7 @@ export class InkKitEditor {
|
|
|
383
418
|
.get(editorViewCtx)
|
|
384
419
|
.dom.dispatchEvent(new Event('inkkit-cancel-resize'));
|
|
385
420
|
this.applyInputPolicy();
|
|
421
|
+
this.mediaPolicy();
|
|
386
422
|
}
|
|
387
423
|
finally {
|
|
388
424
|
this.reportingSuppressed = suppressed;
|
|
@@ -566,6 +602,9 @@ export class InkKitEditor {
|
|
|
566
602
|
return this.literalSurface ? 'source' : 'formatted';
|
|
567
603
|
}
|
|
568
604
|
currentText(doc = this.editor.ctx.get(editorViewCtx).state.doc) {
|
|
605
|
+
const cached = this.sourceTexts.get(doc);
|
|
606
|
+
if (cached !== undefined)
|
|
607
|
+
return cached;
|
|
569
608
|
const provenance = doc.attrs[sourceAttribute];
|
|
570
609
|
if (this.formatType === 'txt')
|
|
571
610
|
return provenance?.text ?? this.originalSource;
|
|
@@ -577,7 +616,9 @@ export class InkKitEditor {
|
|
|
577
616
|
this.sourcePreservations.set(provenance, preservation);
|
|
578
617
|
}
|
|
579
618
|
}
|
|
580
|
-
|
|
619
|
+
const text = preservation.serialize(cleanSourceDoc(doc));
|
|
620
|
+
this.sourceTexts.set(doc, text);
|
|
621
|
+
return text;
|
|
581
622
|
}
|
|
582
623
|
setEditingMode(mode, expectedGeneration) {
|
|
583
624
|
this.assertCurrent(expectedGeneration);
|
|
@@ -593,6 +634,7 @@ export class InkKitEditor {
|
|
|
593
634
|
const top = container.scrollTop, left = container.scrollLeft;
|
|
594
635
|
const text = this.snapshot().text;
|
|
595
636
|
this.mode = mode;
|
|
637
|
+
this.cancelExports();
|
|
596
638
|
this.outlineEpoch += 1;
|
|
597
639
|
this.plainSearch = '';
|
|
598
640
|
this.literalMatch = undefined;
|
|
@@ -631,6 +673,7 @@ export class InkKitEditor {
|
|
|
631
673
|
: (this.options.labels?.sourceEditor ?? defaultLabels.sourceEditor));
|
|
632
674
|
this.editor.ctx.get(editorViewCtx).dom.parentElement.hidden =
|
|
633
675
|
this.literalSurface;
|
|
676
|
+
this.mediaPolicy();
|
|
634
677
|
}
|
|
635
678
|
replaceSource(text, expectedGeneration) {
|
|
636
679
|
this.assertMutation(expectedGeneration);
|
|
@@ -735,26 +778,17 @@ export class InkKitEditor {
|
|
|
735
778
|
}
|
|
736
779
|
async printableSnapshot(expectedGeneration) {
|
|
737
780
|
const snapshot = this.snapshot(expectedGeneration);
|
|
738
|
-
const epoch = this.documentEpoch;
|
|
739
781
|
const state = this.editor.ctx.get(editorViewCtx).state;
|
|
740
|
-
|
|
741
|
-
try {
|
|
782
|
+
const output = await this.exportContent(snapshot, async (signal) => {
|
|
742
783
|
if (snapshot.format === 'md' &&
|
|
743
784
|
!this.options.images &&
|
|
785
|
+
!this.options.files &&
|
|
744
786
|
hasAuthoredImagesInLiterals(state.doc.content, (source) => this.editor.ctx.get(remarkCtx).parse(source)))
|
|
745
787
|
throw new InkKitError('image-unavailable', 'No image adapter is configured for the authored images.');
|
|
746
|
-
|
|
747
|
-
snapshot.
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
}
|
|
751
|
-
finally {
|
|
752
|
-
this.assertCurrent(snapshot.generation);
|
|
753
|
-
if (snapshot.documentId !== this.documentId ||
|
|
754
|
-
epoch !== this.documentEpoch ||
|
|
755
|
-
snapshot.revision !== this.revision)
|
|
756
|
-
throw new InkKitError('stale-document', 'Document changed while preparing printable content');
|
|
757
|
-
}
|
|
788
|
+
return snapshot.format === 'txt'
|
|
789
|
+
? printableText(snapshot.text)
|
|
790
|
+
: printableMarkdown(state.doc.content, state.schema, this.options.images, this.context(), this.options.files, signal);
|
|
791
|
+
});
|
|
758
792
|
return Object.freeze({
|
|
759
793
|
documentId: snapshot.documentId,
|
|
760
794
|
generation: snapshot.generation,
|
|
@@ -827,7 +861,7 @@ export class InkKitEditor {
|
|
|
827
861
|
? error.message
|
|
828
862
|
: 'Cannot copy the selected Markdown');
|
|
829
863
|
}
|
|
830
|
-
const result = await portableClipboard(all || selectedDoc ? content : selectionContent(doc, content), schema, markdown, this.options.images, this.context(), completeDiagrams);
|
|
864
|
+
const result = await this.exportContent(snapshot, (signal) => portableClipboard(all || selectedDoc ? content : selectionContent(doc, content), schema, markdown, this.options.images, this.context(), completeDiagrams, this.options.files, signal));
|
|
831
865
|
const metadata = referenceMetadata(this.editor.ctx, selectedDoc ?? doc, content);
|
|
832
866
|
if (metadata != null)
|
|
833
867
|
result.html = withReferenceMetadata(result.html, metadata);
|
|
@@ -870,28 +904,41 @@ export class InkKitEditor {
|
|
|
870
904
|
}
|
|
871
905
|
else {
|
|
872
906
|
const { schema } = view.state;
|
|
907
|
+
const marks = view.state.storedMarks ??
|
|
908
|
+
(view.state.selection.empty
|
|
909
|
+
? view.state.selection.$from.marks()
|
|
910
|
+
: view.state.selection.$from.marksAcross(view.state.selection.$to)) ??
|
|
911
|
+
[];
|
|
873
912
|
const children = [];
|
|
874
|
-
text
|
|
875
|
-
|
|
876
|
-
.
|
|
877
|
-
.forEach((line, index) => {
|
|
913
|
+
const lines = text.replace(/\r\n?/g, '\n').split('\n');
|
|
914
|
+
lines.forEach((line, index) => {
|
|
915
|
+
const finalBreak = index === lines.length - 1 && line === '';
|
|
878
916
|
if (index)
|
|
879
917
|
children.push(schema.nodes.hardbreak.create({
|
|
880
|
-
isHTML:
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
918
|
+
isHTML: finalBreak,
|
|
919
|
+
}, null,
|
|
920
|
+
// A terminal HTML spacer must remain outside emphasis to reopen as a break.
|
|
921
|
+
finalBreak ? [] : marks));
|
|
884
922
|
if (line)
|
|
885
|
-
children.push(schema.text(line));
|
|
923
|
+
children.push(schema.text(line, marks));
|
|
886
924
|
});
|
|
887
925
|
if (!children.length) {
|
|
888
926
|
view.dispatch(view.state.tr.deleteSelection());
|
|
889
927
|
return;
|
|
890
928
|
}
|
|
891
929
|
const paragraph = schema.nodes.paragraph.create(null, children);
|
|
892
|
-
view.
|
|
930
|
+
const transaction = view.state.tr
|
|
893
931
|
.replaceSelection(new Slice(Fragment.from(paragraph), 1, 1))
|
|
894
|
-
.scrollIntoView()
|
|
932
|
+
.scrollIntoView();
|
|
933
|
+
try {
|
|
934
|
+
this.currentText(transaction.doc);
|
|
935
|
+
}
|
|
936
|
+
catch (error) {
|
|
937
|
+
throw new InkKitError('preservation', error instanceof Error
|
|
938
|
+
? error.message
|
|
939
|
+
: 'Cannot preserve inserted text');
|
|
940
|
+
}
|
|
941
|
+
view.dispatch(transaction);
|
|
895
942
|
}
|
|
896
943
|
}
|
|
897
944
|
}
|
|
@@ -906,6 +953,8 @@ export class InkKitEditor {
|
|
|
906
953
|
return;
|
|
907
954
|
this.destroyed = true;
|
|
908
955
|
this.ready = false;
|
|
956
|
+
this.cancelExports();
|
|
957
|
+
this.mediaPolicy();
|
|
909
958
|
this.pasteController.destroy();
|
|
910
959
|
if (this.clickHandler)
|
|
911
960
|
this.root.removeEventListener('click', this.clickHandler);
|
|
@@ -1052,7 +1101,7 @@ export class InkKitEditor {
|
|
|
1052
1101
|
.use(instance.pasteController.plugin)
|
|
1053
1102
|
.use(visibleClipboard)
|
|
1054
1103
|
.use(instance.keymapPlugin)
|
|
1055
|
-
.use(createDialect(Boolean(options.images)))
|
|
1104
|
+
.use(createDialect(Boolean(options.images || options.files), Boolean(options.wikiLinks), Boolean(options.files)))
|
|
1056
1105
|
.use(tablePlugins)
|
|
1057
1106
|
.use(instance.changePlugin)
|
|
1058
1107
|
.use(history)
|
|
@@ -1065,12 +1114,66 @@ export class InkKitEditor {
|
|
|
1065
1114
|
.use(dropControlCharacters)
|
|
1066
1115
|
.use(codeCopyPlugin((text) => events.copy(text)))
|
|
1067
1116
|
.use(placeholderPlugin)
|
|
1068
|
-
.use(
|
|
1069
|
-
|
|
1117
|
+
.use(options.rendering?.codeHighlighting === false
|
|
1118
|
+
? []
|
|
1119
|
+
: (await import('./highlight')).highlightPlugin)
|
|
1120
|
+
.use(options.rendering?.diagramPreview === false
|
|
1121
|
+
? []
|
|
1122
|
+
: mermaidPreview((error) => events.error?.(error)))
|
|
1070
1123
|
.use(selectionPlugin)
|
|
1071
1124
|
.use($prose(() => search()))
|
|
1072
1125
|
.use(instance.policyPlugin)
|
|
1073
|
-
.use(options.
|
|
1126
|
+
.use(options.wikiLinks
|
|
1127
|
+
? wikiLinkView(options.wikiLinks, {
|
|
1128
|
+
capture: () => {
|
|
1129
|
+
const epoch = instance.documentEpoch;
|
|
1130
|
+
const context = instance.context();
|
|
1131
|
+
return {
|
|
1132
|
+
context,
|
|
1133
|
+
isCurrent: () => !instance.destroyed &&
|
|
1134
|
+
epoch === instance.documentEpoch &&
|
|
1135
|
+
context.documentId === instance.documentId &&
|
|
1136
|
+
context.generation === instance.generation,
|
|
1137
|
+
};
|
|
1138
|
+
},
|
|
1139
|
+
active: () => instance.ready &&
|
|
1140
|
+
!instance.destroyed &&
|
|
1141
|
+
!instance.literalSurface,
|
|
1142
|
+
labels: { ...defaultLabels, ...options.labels },
|
|
1143
|
+
error: (error) => events.error?.(error),
|
|
1144
|
+
})
|
|
1145
|
+
: [])
|
|
1146
|
+
.use(options.images || options.files
|
|
1147
|
+
? imageView(options.images, options.files
|
|
1148
|
+
? {
|
|
1149
|
+
adapter: options.files,
|
|
1150
|
+
capture: () => {
|
|
1151
|
+
const epoch = instance.documentEpoch;
|
|
1152
|
+
const context = instance.context();
|
|
1153
|
+
return {
|
|
1154
|
+
context,
|
|
1155
|
+
isCurrent: () => !instance.destroyed &&
|
|
1156
|
+
epoch === instance.documentEpoch &&
|
|
1157
|
+
context.documentId === instance.documentId &&
|
|
1158
|
+
context.generation === instance.generation,
|
|
1159
|
+
};
|
|
1160
|
+
},
|
|
1161
|
+
active: () => instance.ready &&
|
|
1162
|
+
!instance.destroyed &&
|
|
1163
|
+
!instance.literalSurface,
|
|
1164
|
+
labels: { ...defaultLabels, ...options.labels },
|
|
1165
|
+
error: (error) => events.error?.(error),
|
|
1166
|
+
}
|
|
1167
|
+
: undefined, () => {
|
|
1168
|
+
try {
|
|
1169
|
+
instance.assertMutation();
|
|
1170
|
+
return true;
|
|
1171
|
+
}
|
|
1172
|
+
catch {
|
|
1173
|
+
return false;
|
|
1174
|
+
}
|
|
1175
|
+
})
|
|
1176
|
+
: [])
|
|
1074
1177
|
.create();
|
|
1075
1178
|
configureTableMovement(instance.editor.ctx, () => {
|
|
1076
1179
|
try {
|
|
@@ -1275,10 +1378,13 @@ export class InkKitEditor {
|
|
|
1275
1378
|
? this.plain.ownerDocument.activeElement === this.plain
|
|
1276
1379
|
: this.editor.ctx.get(editorViewCtx).hasFocus();
|
|
1277
1380
|
this.ready = false;
|
|
1381
|
+
this.cancelExports();
|
|
1382
|
+
this.mediaPolicy();
|
|
1278
1383
|
this.pasteController.cancelPending();
|
|
1279
1384
|
this.documentEpoch += 1;
|
|
1280
1385
|
this.outlineEpoch += 1;
|
|
1281
1386
|
this.sourcePreservations = new WeakMap();
|
|
1387
|
+
this.sourceTexts = new WeakMap();
|
|
1282
1388
|
this.mode = 'formatted';
|
|
1283
1389
|
this.plainComposing = false;
|
|
1284
1390
|
this.beforePlainInput = undefined;
|
|
@@ -1294,6 +1400,7 @@ export class InkKitEditor {
|
|
|
1294
1400
|
this.lastMarkdown = input.text;
|
|
1295
1401
|
this.updateSurface();
|
|
1296
1402
|
this.ready = true;
|
|
1403
|
+
this.mediaPolicy();
|
|
1297
1404
|
if (focused) {
|
|
1298
1405
|
if (this.literalSurface)
|
|
1299
1406
|
this.plain.focus({ preventScroll: true });
|
|
@@ -1794,6 +1901,10 @@ export class InkKitEditor {
|
|
|
1794
1901
|
this.replaceLiteralSelection(text, false);
|
|
1795
1902
|
return true;
|
|
1796
1903
|
}
|
|
1904
|
+
if (/[\r\n]/.test(text)) {
|
|
1905
|
+
this.pasteAsPlainText(text);
|
|
1906
|
+
return true;
|
|
1907
|
+
}
|
|
1797
1908
|
const view = this.editor.ctx.get(editorViewCtx);
|
|
1798
1909
|
if (view.composing)
|
|
1799
1910
|
return false;
|
package/dist/files.d.ts
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import type { DocumentContext, FileAdapter, FileContext, FilePresentation, FileReference } from './types.js';
|
|
2
|
+
import type { EditorLabels } from './labels.js';
|
|
3
|
+
export interface FileViewRuntime {
|
|
4
|
+
adapter: FileAdapter;
|
|
5
|
+
capture(): {
|
|
6
|
+
context: DocumentContext;
|
|
7
|
+
isCurrent(): boolean;
|
|
8
|
+
};
|
|
9
|
+
active(): boolean;
|
|
10
|
+
labels: Readonly<EditorLabels>;
|
|
11
|
+
error?(error: Error): void;
|
|
12
|
+
}
|
|
13
|
+
export declare function withFileSignal<T>(operation: Promise<T>, signal: AbortSignal): Promise<T>;
|
|
14
|
+
export declare function resolveFile(adapter: FileAdapter, reference: FileReference, context: FileContext): Promise<FilePresentation>;
|
|
15
|
+
export type FileResolutionState = {
|
|
16
|
+
status: 'idle';
|
|
17
|
+
} | {
|
|
18
|
+
status: 'loading';
|
|
19
|
+
} | {
|
|
20
|
+
status: 'resolved';
|
|
21
|
+
presentation: FilePresentation;
|
|
22
|
+
} | {
|
|
23
|
+
status: 'failed';
|
|
24
|
+
error: Error;
|
|
25
|
+
};
|
|
26
|
+
export declare class FileResolution {
|
|
27
|
+
private readonly runtime;
|
|
28
|
+
private readonly changed;
|
|
29
|
+
state: FileResolutionState;
|
|
30
|
+
private reference?;
|
|
31
|
+
private key;
|
|
32
|
+
private request?;
|
|
33
|
+
private dead;
|
|
34
|
+
constructor(runtime: FileViewRuntime, changed: () => void);
|
|
35
|
+
update(reference: FileReference): void;
|
|
36
|
+
retry(): void;
|
|
37
|
+
private current;
|
|
38
|
+
cancel(): void;
|
|
39
|
+
destroy(): void;
|
|
40
|
+
}
|
package/dist/files.js
ADDED
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
function abort(signal) {
|
|
2
|
+
if (signal.aborted)
|
|
3
|
+
throw new DOMException('File resolution cancelled', 'AbortError');
|
|
4
|
+
}
|
|
5
|
+
export function withFileSignal(operation, signal) {
|
|
6
|
+
return new Promise((resolve, reject) => {
|
|
7
|
+
const cleanup = () => signal.removeEventListener('abort', cancelled);
|
|
8
|
+
const cancelled = () => {
|
|
9
|
+
cleanup();
|
|
10
|
+
reject(new DOMException('File operation cancelled', 'AbortError'));
|
|
11
|
+
};
|
|
12
|
+
if (signal.aborted)
|
|
13
|
+
cancelled();
|
|
14
|
+
else
|
|
15
|
+
signal.addEventListener('abort', cancelled, { once: true });
|
|
16
|
+
operation.then((result) => {
|
|
17
|
+
cleanup();
|
|
18
|
+
if (signal.aborted)
|
|
19
|
+
cancelled();
|
|
20
|
+
else
|
|
21
|
+
resolve(result);
|
|
22
|
+
}, (error) => {
|
|
23
|
+
cleanup();
|
|
24
|
+
reject(error);
|
|
25
|
+
});
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
export async function resolveFile(adapter, reference, context) {
|
|
29
|
+
abort(context.signal);
|
|
30
|
+
const presentation = await withFileSignal(adapter.resolve(Object.freeze({ ...reference }), context), context.signal);
|
|
31
|
+
abort(context.signal);
|
|
32
|
+
if (!presentation ||
|
|
33
|
+
typeof presentation !== 'object' ||
|
|
34
|
+
!['image', 'audio', 'video', 'pdf', 'file', 'missing', 'error'].includes(presentation.kind))
|
|
35
|
+
throw new TypeError('File adapter returned an unsupported presentation');
|
|
36
|
+
if (presentation.label !== undefined &&
|
|
37
|
+
typeof presentation.label !== 'string')
|
|
38
|
+
throw new TypeError('File presentation label must be text');
|
|
39
|
+
if (presentation.kind === 'error' && typeof presentation.message !== 'string')
|
|
40
|
+
throw new TypeError('File presentation error must include a message');
|
|
41
|
+
if ('url' in presentation) {
|
|
42
|
+
if (typeof presentation.url !== 'string' || !presentation.url.trim())
|
|
43
|
+
throw new TypeError('File presentation needs a resource URL');
|
|
44
|
+
const url = new URL(presentation.url, document.baseURI);
|
|
45
|
+
if (['javascript:', 'vbscript:'].includes(url.protocol))
|
|
46
|
+
throw new TypeError('Executable file presentation URLs are not supported');
|
|
47
|
+
const prefix = presentation.kind === 'pdf' ? 'application/pdf' : `${presentation.kind}/`;
|
|
48
|
+
const mime = presentation.mimeType?.toLowerCase().split(';')[0]?.trim();
|
|
49
|
+
if (mime &&
|
|
50
|
+
(presentation.kind === 'pdf' ? mime !== prefix : !mime.startsWith(prefix)))
|
|
51
|
+
throw new TypeError('File presentation MIME type does not match its kind');
|
|
52
|
+
const dataMime = url.pathname.toLowerCase().split(/[;,]/)[0] ?? '';
|
|
53
|
+
if (url.protocol === 'data:' &&
|
|
54
|
+
(presentation.kind === 'pdf'
|
|
55
|
+
? dataMime !== prefix
|
|
56
|
+
: !dataMime.startsWith(prefix)))
|
|
57
|
+
throw new TypeError('File presentation data URL does not match its kind');
|
|
58
|
+
}
|
|
59
|
+
else if (['image', 'audio', 'video', 'pdf'].includes(presentation.kind)) {
|
|
60
|
+
throw new TypeError('File presentation needs a resource URL');
|
|
61
|
+
}
|
|
62
|
+
return Object.freeze({ ...presentation });
|
|
63
|
+
}
|
|
64
|
+
export class FileResolution {
|
|
65
|
+
runtime;
|
|
66
|
+
changed;
|
|
67
|
+
state = { status: 'idle' };
|
|
68
|
+
reference;
|
|
69
|
+
key = '';
|
|
70
|
+
request;
|
|
71
|
+
dead = false;
|
|
72
|
+
constructor(runtime, changed) {
|
|
73
|
+
this.runtime = runtime;
|
|
74
|
+
this.changed = changed;
|
|
75
|
+
}
|
|
76
|
+
update(reference) {
|
|
77
|
+
const key = JSON.stringify([
|
|
78
|
+
reference.reference,
|
|
79
|
+
reference.kind,
|
|
80
|
+
reference.fragment,
|
|
81
|
+
]);
|
|
82
|
+
this.reference = Object.freeze({ ...reference });
|
|
83
|
+
if (this.key === key && this.request?.isCurrent())
|
|
84
|
+
return;
|
|
85
|
+
this.key = key;
|
|
86
|
+
this.retry();
|
|
87
|
+
}
|
|
88
|
+
retry() {
|
|
89
|
+
this.cancel();
|
|
90
|
+
if (this.dead || !this.reference || !this.runtime.active())
|
|
91
|
+
return;
|
|
92
|
+
const captured = this.runtime.capture();
|
|
93
|
+
const request = {
|
|
94
|
+
controller: new AbortController(),
|
|
95
|
+
isCurrent: captured.isCurrent,
|
|
96
|
+
};
|
|
97
|
+
this.request = request;
|
|
98
|
+
this.state = { status: 'loading' };
|
|
99
|
+
this.changed();
|
|
100
|
+
void resolveFile(this.runtime.adapter, this.reference, {
|
|
101
|
+
...captured.context,
|
|
102
|
+
signal: request.controller.signal,
|
|
103
|
+
}).then((presentation) => {
|
|
104
|
+
if (!this.current(request))
|
|
105
|
+
return;
|
|
106
|
+
this.state = { status: 'resolved', presentation };
|
|
107
|
+
this.changed();
|
|
108
|
+
}, (failure) => {
|
|
109
|
+
if (!this.current(request))
|
|
110
|
+
return;
|
|
111
|
+
const error = failure instanceof Error ? failure : new Error(String(failure));
|
|
112
|
+
this.state = { status: 'failed', error };
|
|
113
|
+
this.changed();
|
|
114
|
+
this.runtime.error?.(error);
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
current(request) {
|
|
118
|
+
return (!this.dead &&
|
|
119
|
+
this.request === request &&
|
|
120
|
+
!request.controller.signal.aborted &&
|
|
121
|
+
this.runtime.active() &&
|
|
122
|
+
request.isCurrent());
|
|
123
|
+
}
|
|
124
|
+
cancel() {
|
|
125
|
+
this.request?.controller.abort();
|
|
126
|
+
this.request = undefined;
|
|
127
|
+
this.state = { status: 'idle' };
|
|
128
|
+
}
|
|
129
|
+
destroy() {
|
|
130
|
+
this.dead = true;
|
|
131
|
+
this.cancel();
|
|
132
|
+
}
|
|
133
|
+
}
|
package/dist/images.d.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { Node as ProseNode } from '@milkdown/kit/prose/model';
|
|
2
2
|
import type { EditorView, NodeView } from '@milkdown/kit/prose/view';
|
|
3
3
|
import type { ImageAdapter } from './types.js';
|
|
4
|
+
import { type FileViewRuntime } from './files.js';
|
|
4
5
|
/** A width rides in the alt text, as ``. Other Markdown readers
|
|
5
6
|
* show the picture and keep the number without requiring a private extension. */
|
|
6
7
|
export declare function splitAlt(alt: string): {
|
|
@@ -8,26 +9,41 @@ export declare function splitAlt(alt: string): {
|
|
|
8
9
|
width: number | null;
|
|
9
10
|
};
|
|
10
11
|
export declare function joinAlt(alt: string, width: number | null): string;
|
|
11
|
-
/** Draws the image and the handle that sizes it. The width is kept in the alt text, so the schema is the
|
|
12
|
-
* one commonmark gives us; a picture is never wider than its editor, whatever number the alt text holds. */
|
|
13
12
|
declare class ImageView implements NodeView {
|
|
14
13
|
private node;
|
|
15
14
|
private readonly view;
|
|
16
15
|
private readonly getPos;
|
|
17
16
|
private readonly adapter;
|
|
17
|
+
private readonly runtime?;
|
|
18
|
+
private readonly canMutate;
|
|
18
19
|
dom: HTMLElement;
|
|
19
20
|
private image?;
|
|
21
|
+
private media?;
|
|
22
|
+
private pdf?;
|
|
20
23
|
private width;
|
|
21
24
|
private stopResize?;
|
|
22
|
-
|
|
25
|
+
private resolution?;
|
|
26
|
+
private previewFailed;
|
|
27
|
+
constructor(node: ProseNode, view: EditorView, getPos: () => number | undefined, adapter: ImageAdapter | undefined, runtime?: FileViewRuntime | undefined, canMutate?: () => boolean);
|
|
28
|
+
private reference;
|
|
29
|
+
private active;
|
|
23
30
|
update(node: ProseNode): boolean;
|
|
24
31
|
stopEvent(event: Event): boolean;
|
|
25
32
|
ignoreMutation(): boolean;
|
|
33
|
+
private mediaPolicy;
|
|
26
34
|
private cancelResize;
|
|
35
|
+
private releaseMedia;
|
|
27
36
|
destroy(): void;
|
|
37
|
+
private button;
|
|
38
|
+
private activate;
|
|
39
|
+
private report;
|
|
40
|
+
private card;
|
|
41
|
+
private failPreview;
|
|
28
42
|
private render;
|
|
43
|
+
private renderImage;
|
|
29
44
|
private startResize;
|
|
30
45
|
private commit;
|
|
46
|
+
private canResize;
|
|
31
47
|
}
|
|
32
|
-
export declare function imageView(adapter?: ImageAdapter): import("@milkdown/kit/utils").$View<import("@milkdown/kit/utils").$Node, (node: ProseNode, view: EditorView, getPos: () => number | undefined) => ImageView>;
|
|
48
|
+
export declare function imageView(adapter?: ImageAdapter, runtime?: FileViewRuntime, canMutate?: () => boolean): import("@milkdown/kit/utils").$View<import("@milkdown/kit/utils").$Node, (node: ProseNode, view: EditorView, getPos: () => number | undefined) => ImageView>;
|
|
33
49
|
export {};
|