@codingame/monaco-vscode-markdown-language-features-default-extension 36.2.7 → 37.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/index.js +237 -23
- package/package.json +2 -2
- package/resources/abnfDiagram-VCTEODGH-MFZBS7QV.js +1 -0
- package/resources/architecture-7GRP2DOG-KMESPJ5A.js +1 -0
- package/resources/architectureDiagram-5GKGNRK7-MTTZPDNG.js +36 -0
- package/resources/blockDiagram-NRAW4CY4-UDRMVB5I.js +129 -0
- package/resources/c4Diagram-UCG6FXSJ-CNUYFC6N.js +38 -0
- package/resources/chunk-2KBWIZHB.js +14 -0
- package/resources/chunk-3CEAP2V5.js +1 -0
- package/resources/chunk-43RBMH4Y.js +1 -0
- package/resources/chunk-4AUIAGYT.js +1 -0
- package/resources/chunk-4PRM3W7S.js +88 -0
- package/resources/chunk-5DJUZA4F.js +1 -0
- package/resources/chunk-6LQNQPBM.js +1 -0
- package/resources/chunk-A3DHMJKP.js +1 -0
- package/resources/chunk-ATDVZUJ2.js +231 -0
- package/resources/chunk-CBQW2LLN.js +1 -0
- package/resources/chunk-CCQNES6G.js +1 -0
- package/resources/chunk-CD5RYVNW.js +1 -0
- package/resources/chunk-CQFFNO72.js +1 -0
- package/resources/chunk-EWILYK3H.js +1 -0
- package/resources/chunk-EWVD6AUZ.js +15 -0
- package/resources/chunk-EXFMYL6U.js +1 -0
- package/resources/chunk-FH3BATM5.js +1 -0
- package/resources/chunk-FK4OKHO7.js +178 -0
- package/resources/chunk-FZLE27CN.js +1 -0
- package/resources/chunk-HRJ5UBVC.js +1 -0
- package/resources/chunk-IEMKBRIG.js +339 -0
- package/resources/chunk-IRNO36C3.js +1 -0
- package/resources/chunk-J4SQIVOP.js +1 -0
- package/resources/chunk-JAORNH2M.js +2 -0
- package/resources/chunk-JBQZ5UCE.js +1 -0
- package/resources/chunk-JFI5BOLY.js +1 -0
- package/resources/chunk-JXCTOEX6.js +1 -0
- package/resources/chunk-K2JFJOPP.js +1 -0
- package/resources/chunk-LK6KML2S.js +10 -0
- package/resources/chunk-MNHOAW4R.js +3 -0
- package/resources/chunk-NLCJ5AKJ.js +132 -0
- package/resources/chunk-OOJM4CTU.js +1 -0
- package/resources/chunk-R7CM4BQN.js +2 -0
- package/resources/chunk-RBL2QVA3.js +1 -0
- package/resources/chunk-SNGMH3JW.js +2 -0
- package/resources/chunk-TGD3YKTU.js +32 -0
- package/resources/chunk-TGYBLZFN.js +1 -0
- package/resources/chunk-U3E5RSXE.js +62 -0
- package/resources/chunk-U7TCJVVP.js +1 -0
- package/resources/chunk-WSHCWW4Q.js +2 -0
- package/resources/chunk-WVYKIHVG.js +1 -0
- package/resources/chunk-XCMS6RVA.js +1 -0
- package/resources/chunk-XM54GAH3.js +206 -0
- package/resources/chunk-YICKNY6Y.js +1 -0
- package/resources/chunk-YNKYB66X.js +257 -0
- package/resources/chunk-ZMBQGVIF.js +1 -0
- package/resources/chunk-ZNREFWKG.js +1 -0
- package/resources/chunk-ZU7JKVUR.js +168 -0
- package/resources/chunk-ZYRITDEB.js +72 -0
- package/resources/classDiagram-DTDB5LWJ-TBJLBGTZ.js +1 -0
- package/resources/codicon-R6HEZFPR.ttf +0 -0
- package/resources/cose-bilkent-JH36ORCC-7TCRTQWC.js +1 -0
- package/resources/cynefin-OW5HDTMX-QMEUXFXO.js +1 -0
- package/resources/cynefinDiagram-5FMLGOSQ-J745EYTI.js +62 -0
- package/resources/dagre-3AP2YEHR-CUFPL4A5.js +4 -0
- package/resources/diagram-S7CK7UJ4-SC5AFKXH.js +30 -0
- package/resources/diagram-UQ7AKVKN-3O7WQJXE.js +41 -0
- package/resources/diagram-VSXAHHWV-SN3VCCS5.js +3 -0
- package/resources/diagram-VX7I27RA-7PQJWMN5.js +24 -0
- package/resources/diagram-Z3DM3KII-QZ45TJFK.js +24 -0
- package/resources/ebnfDiagram-PWID7BFC-CBJPGG56.js +1 -0
- package/resources/editor.css.txt +1 -1
- package/resources/editor.js +137 -3431
- package/resources/editor.ts +587 -46
- package/resources/erDiagram-SSCWMZ5O-TUKYG42P.js +99 -0
- package/resources/eventmodeling-NTZA5JFV-MGOPW7BD.js +1 -0
- package/resources/extension.js +179 -172
- package/resources/extension.js.map +4 -4
- package/resources/flowDiagram-A5DVABFB-M6K5X26V.js +1 -0
- package/resources/ganttDiagram-EL5Y4UJY-JCSKUU2V.js +292 -0
- package/resources/gitGraph-4MIJSDKK-ITDCTFJT.js +1 -0
- package/resources/gitGraphDiagram-WWUBYQGX-RXRB5JA2.js +106 -0
- package/resources/index.js +3 -3
- package/resources/index2.js +17 -17
- package/resources/info-A6RAGUB7-VXIEHQAI.js +1 -0
- package/resources/infoDiagram-RXCK75RN-2T6V4JS2.js +2 -0
- package/resources/ishikawaDiagram-5VMMS53U-6WC4IGXZ.js +70 -0
- package/resources/journeyDiagram-EYS64GPL-PZQYT6UF.js +139 -0
- package/resources/kanban-definition-3QL26DDD-VYBV345J.js +89 -0
- package/resources/katex-QJ7O5CJ5.js +1 -0
- package/resources/linkPresentationProvider.ts +179 -0
- package/resources/markdown.css.txt +39 -0
- package/resources/markdownEditor.css.txt +13 -0
- package/resources/mermaid.core-S5KCHHL2.js +18 -0
- package/resources/mindmap-definition-FBJOCRG2-H3I5F4WB.js +96 -0
- package/resources/package.json +1 -1
- package/resources/package.nls.json +1 -1
- package/resources/package.schema.json +1 -1
- package/resources/packet-AYTQ26CC-WLFKOXIZ.js +1 -0
- package/resources/pegDiagram-XKGWAZYB-HUV4U5RU.js +1 -0
- package/resources/pie-WAS4IAKB-GZIMD664.js +1 -0
- package/resources/pieDiagram-E7YTZNPT-XOJEVCEO.js +39 -0
- package/resources/pre.js +1 -1
- package/resources/preview-dark.svg +1 -1
- package/resources/preview-light.svg +1 -1
- package/resources/quadrantDiagram-AXDQQJYC-YPPZYAGP.js +7 -0
- package/resources/radar-RG4KPBEZ-URG5LVGS.js +1 -0
- package/resources/railroad-74A4TZTK-3ZMXCKLP.js +1 -0
- package/resources/railroad-abnf-HS5TGJTU-LBR5MV33.js +1 -0
- package/resources/railroad-ebnf-LZEXJU2U-YEVF3EJR.js +1 -0
- package/resources/railroad-peg-WCYAUIDC-CV774YSK.js +1 -0
- package/resources/railroadDiagram-O6MQD6OU-YZF7JNDT.js +1 -0
- package/resources/requirementDiagram-EFPCY7ZU-PZ7BYTDZ.js +84 -0
- package/resources/sankeyDiagram-P5KCCOFB-3P5DWREA.js +40 -0
- package/resources/sequenceDiagram-WJ2MYXX4-O52K5NPU.js +162 -0
- package/resources/sizeCapture-X5ZJPWSS-PRFE7SKK.js +1 -0
- package/resources/stateDiagram-HBIQ2CUA-KDPYCQOT.js +1 -0
- package/resources/stateDiagram-v2-4QOOHH4V-QBQE56JB.js +1 -0
- package/resources/swimlanes-XN3QIQJK-YCLJ3KIH.js +1 -0
- package/resources/swimlanesDiagram-VK2B7HYN-RS7VRH6T.js +8 -0
- package/resources/syntaxHighlighter.ts +2 -2
- package/resources/timeline-definition-24CTP7MA-GQ3MCHCF.js +120 -0
- package/resources/treeView-Q6P3EWNA-UEXUSAGC.js +1 -0
- package/resources/treemap-WGGIJYW6-NFXJPDAT.js +1 -0
- package/resources/tsconfig.json +1 -1
- package/resources/vennDiagram-4TSXK5OY-PNMXQJF6.js +34 -0
- package/resources/wardley-WFR3VGLG-MQ6DEZYA.js +1 -0
- package/resources/wardleyDiagram-VM6X3IG4-46KJTB57.js +78 -0
- package/resources/xychartDiagram-S5SC5T6Z-NSN5W7VL.js +7 -0
package/resources/editor.ts
CHANGED
|
@@ -3,14 +3,17 @@
|
|
|
3
3
|
* Licensed under the MIT License. See License.txt in the project root for license information.
|
|
4
4
|
*--------------------------------------------------------------------------------------------*/
|
|
5
5
|
|
|
6
|
-
import { EditorController, EditorModel, EditorView, GutterMarker, OffsetRange, StringEdit, StringValue, findNodeOffsetById,
|
|
7
|
-
import {
|
|
8
|
-
import
|
|
6
|
+
import { AsyncClipboardStrategy, CommentModeController, CommentsModel, CommentsView, EditorController, EditorModel, EditorView, GutterMarker, OffsetRange, Selection, StringEdit, StringReplacement, StringValue, commands, findNodeOffsetById, vscodeHostKeyboardProfile, vscodeLocalKeyboardProfile, type CodeBlockAstNode, type LinkPresentationKind } from '@vscode/markdown-editor';
|
|
7
|
+
import { VirtualizedIframeEmbeddedEditorFactory, type IframeEmbeddedEditorHostTransport, type IframeEmbeddedEditorProvider, type IframeEmbeddedEditorProviderSelector, type ResolvedIframeEmbeddedEditor } from '@vscode/markdown-editor/web-editors';
|
|
8
|
+
import { Disposable, autorun, observableValue } from '@vscode/observables';
|
|
9
9
|
import 'katex/dist/katex.min.css';
|
|
10
10
|
import '@vscode/markdown-editor/editor.css';
|
|
11
|
-
import '@vscode/markdown-editor/themes/vscode.css';
|
|
11
|
+
import '@vscode/markdown-editor/themes/vscode-default.css';
|
|
12
|
+
import '@vscode/markdown-editor/commentInput.css';
|
|
13
|
+
import '@vscode/markdown-editor/commentWidget.css';
|
|
12
14
|
import './markdownEditor.css';
|
|
13
15
|
import { WebviewSyntaxHighlighter } from './syntaxHighlighter';
|
|
16
|
+
import { WebviewLinkPresentationProvider } from './linkPresentationProvider';
|
|
14
17
|
|
|
15
18
|
interface VsCodeApi {
|
|
16
19
|
postMessage(message: unknown): void;
|
|
@@ -20,40 +23,179 @@ interface VsCodeApi {
|
|
|
20
23
|
|
|
21
24
|
declare function acquireVsCodeApi(): VsCodeApi;
|
|
22
25
|
|
|
26
|
+
/**
|
|
27
|
+
* The editor's view state, persisted as webview state (`getState`/`setState`) so
|
|
28
|
+
* the scroll and cursor position are restored when the webview is reloaded or the
|
|
29
|
+
* custom editor is re-created (e.g. after switching sessions and back).
|
|
30
|
+
*/
|
|
31
|
+
interface PersistedViewState {
|
|
32
|
+
scrollTop?: number;
|
|
33
|
+
selection?: { anchor: number; active: number };
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
interface CodeBlockEditorProviderDefinition {
|
|
37
|
+
readonly id: string;
|
|
38
|
+
readonly selector: IframeEmbeddedEditorProviderSelector;
|
|
39
|
+
readonly source: { readonly kind: 'static'; readonly descriptor: ResolvedIframeEmbeddedEditor } | { readonly kind: 'exportApi' };
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
interface InitialState {
|
|
43
|
+
readonly content: string;
|
|
44
|
+
readonly documentVersion: number;
|
|
45
|
+
readonly readonly: boolean;
|
|
46
|
+
readonly richLinksEnabled: boolean;
|
|
47
|
+
readonly linkPresentationRules: readonly { id: string; source: string; flags: string; kind: LinkPresentationKind }[];
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
class CodeBlockEditorHostTransport implements IframeEmbeddedEditorHostTransport {
|
|
51
|
+
readonly #listeners = new Set<(message: unknown) => void>();
|
|
52
|
+
readonly #pendingMessages: unknown[] = [];
|
|
53
|
+
readonly #postMessage: (message: unknown) => void;
|
|
54
|
+
readonly #onDispose: () => void;
|
|
55
|
+
#activated = false;
|
|
56
|
+
#disposed = false;
|
|
57
|
+
|
|
58
|
+
readonly onMessage: IframeEmbeddedEditorHostTransport['onMessage'] = (listener: (message: unknown) => void) => {
|
|
59
|
+
if (this.#disposed) {
|
|
60
|
+
throw new Error('Code block editor host transport is disposed');
|
|
61
|
+
}
|
|
62
|
+
this.#listeners.add(listener);
|
|
63
|
+
if (!this.#activated) {
|
|
64
|
+
this.#activated = true;
|
|
65
|
+
for (const message of this.#pendingMessages.splice(0)) {
|
|
66
|
+
listener(message);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
return { dispose: () => this.#listeners.delete(listener) };
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
constructor(
|
|
73
|
+
readonly runtimeId: string,
|
|
74
|
+
postMessage: (message: unknown) => void,
|
|
75
|
+
onDispose: () => void,
|
|
76
|
+
) {
|
|
77
|
+
this.#postMessage = postMessage;
|
|
78
|
+
this.#onDispose = onDispose;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
sendMessage(message: unknown): void {
|
|
82
|
+
if (this.#disposed) {
|
|
83
|
+
throw new Error('Code block editor host transport is disposed');
|
|
84
|
+
}
|
|
85
|
+
this.#postMessage(message);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
acceptMessage(message: unknown): void {
|
|
89
|
+
if (this.#disposed) {
|
|
90
|
+
return;
|
|
91
|
+
}
|
|
92
|
+
if (!this.#activated) {
|
|
93
|
+
this.#pendingMessages.push(message);
|
|
94
|
+
return;
|
|
95
|
+
}
|
|
96
|
+
for (const listener of this.#listeners) {
|
|
97
|
+
listener(message);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
dispose(): void {
|
|
102
|
+
if (this.#disposed) {
|
|
103
|
+
return;
|
|
104
|
+
}
|
|
105
|
+
this.#disposed = true;
|
|
106
|
+
this.#pendingMessages.length = 0;
|
|
107
|
+
this.#listeners.clear();
|
|
108
|
+
this.#onDispose();
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
23
112
|
class Editor extends Disposable {
|
|
24
113
|
readonly model = new EditorModel();
|
|
25
114
|
isUpdatingFromExtension = false;
|
|
115
|
+
#isUpdatingComments = false;
|
|
26
116
|
#mermaidCounter = 0;
|
|
27
|
-
#
|
|
117
|
+
#codeBlockEditorProviders: readonly CodeBlockEditorProviderDefinition[] = [];
|
|
118
|
+
#nextCodeBlockEditorRequestId = 1;
|
|
119
|
+
#nextCodeBlockEditorRuntimeId = 1;
|
|
120
|
+
readonly #codeBlockEditorRequests = new Map<number, (descriptor: ResolvedIframeEmbeddedEditor | undefined) => void>();
|
|
121
|
+
readonly #codeBlockEditorHostTransports = new Map<string, CodeBlockEditorHostTransport>();
|
|
122
|
+
#controller: EditorController | undefined;
|
|
123
|
+
#view: EditorView | undefined;
|
|
124
|
+
#embeddedCodeEditorFactory: VirtualizedIframeEmbeddedEditorFactory | undefined;
|
|
28
125
|
|
|
126
|
+
readonly #comments = new CommentsModel();
|
|
127
|
+
#commentsView: CommentsView | undefined;
|
|
128
|
+
/** Whether the workbench feedback store currently accepts new comments for this resource. */
|
|
129
|
+
readonly #acceptsComments = observableValue<boolean>('acceptsComments', false);
|
|
130
|
+
// the message secret allows to distinguish vscode sending us a message vs a nested iframe
|
|
131
|
+
readonly #messageSecret: string;
|
|
29
132
|
readonly #vscode = acquireVsCodeApi();
|
|
30
133
|
readonly #syntaxHighlighter = new WebviewSyntaxHighlighter((message) => this.#vscode.postMessage(message));
|
|
134
|
+
readonly #linkPresentationProvider: WebviewLinkPresentationProvider | undefined;
|
|
31
135
|
|
|
32
|
-
constructor(host: HTMLElement) {
|
|
136
|
+
constructor(host: HTMLElement, initialState: InitialState) {
|
|
33
137
|
super();
|
|
34
138
|
|
|
35
|
-
|
|
139
|
+
const messageSecret = document.querySelector<HTMLMetaElement>('meta[name="vscode-markdown-editor-message-secret"]')?.content;
|
|
140
|
+
if (!messageSecret) {
|
|
141
|
+
throw new Error('Missing Markdown editor message secret');
|
|
142
|
+
}
|
|
143
|
+
this.#messageSecret = messageSecret;
|
|
144
|
+
this.#linkPresentationProvider = initialState.richLinksEnabled
|
|
145
|
+
? this._register(new WebviewLinkPresentationProvider(
|
|
146
|
+
initialState.linkPresentationRules,
|
|
147
|
+
message => this.#vscode.postMessage(message),
|
|
148
|
+
))
|
|
149
|
+
: undefined;
|
|
150
|
+
|
|
151
|
+
this.model.sourceText.set(new StringValue(initialState.content), undefined);
|
|
152
|
+
this.model.readonlyMode.set(initialState.readonly, undefined);
|
|
36
153
|
|
|
37
154
|
window.addEventListener('message', (event) => {
|
|
38
155
|
const message = event.data;
|
|
156
|
+
if (!message || typeof message !== 'object' || message.messageSecret !== this.#messageSecret) {
|
|
157
|
+
return;
|
|
158
|
+
}
|
|
39
159
|
if (this.#syntaxHighlighter.handleMessage(message)) {
|
|
40
160
|
return;
|
|
41
161
|
}
|
|
162
|
+
if (this.#linkPresentationProvider?.handleMessage(message)) {
|
|
163
|
+
return;
|
|
164
|
+
}
|
|
42
165
|
switch (message.type) {
|
|
43
|
-
case 'init': {
|
|
44
|
-
if (!this.#initialized) {
|
|
45
|
-
this.#initialized = true;
|
|
46
|
-
this.#createView(host, !!message.readonly);
|
|
47
|
-
this.model.sourceText.set(new StringValue(message.content), undefined);
|
|
48
|
-
}
|
|
49
|
-
break;
|
|
50
|
-
}
|
|
51
166
|
case 'update': {
|
|
167
|
+
// `replaceSourceText` (not `sourceText.set`) applies authoritative host
|
|
168
|
+
// text: it maps the selection through the change and clears stale
|
|
169
|
+
// pending-paragraph state, so the caret stays valid after an undo shrinks
|
|
170
|
+
// the document. The guard stops this echoing back as a user edit.
|
|
52
171
|
this.isUpdatingFromExtension = true;
|
|
53
|
-
this.model.
|
|
172
|
+
this.model.replaceSourceText(new StringValue(message.content));
|
|
54
173
|
this.isUpdatingFromExtension = false;
|
|
55
174
|
break;
|
|
56
175
|
}
|
|
176
|
+
case 'codeBlockEditorProviders': {
|
|
177
|
+
const providers = readCodeBlockEditorProviderDefinitions(message.codeBlockEditorProviders);
|
|
178
|
+
this.#codeBlockEditorProviders = providers;
|
|
179
|
+
this.#embeddedCodeEditorFactory?.updateProviders(this.#createIframeProviders(providers));
|
|
180
|
+
break;
|
|
181
|
+
}
|
|
182
|
+
case 'resolvedCodeBlockEditor': {
|
|
183
|
+
if (typeof message.requestId !== 'number') {
|
|
184
|
+
break;
|
|
185
|
+
}
|
|
186
|
+
const resolve = this.#codeBlockEditorRequests.get(message.requestId);
|
|
187
|
+
if (resolve) {
|
|
188
|
+
this.#codeBlockEditorRequests.delete(message.requestId);
|
|
189
|
+
resolve(readResolvedCodeBlockEditor(message.descriptor));
|
|
190
|
+
}
|
|
191
|
+
break;
|
|
192
|
+
}
|
|
193
|
+
case 'codeBlockEditorHostTransportMessage': {
|
|
194
|
+
if (typeof message.runtimeId === 'string') {
|
|
195
|
+
this.#codeBlockEditorHostTransports.get(message.runtimeId)?.acceptMessage(message.message);
|
|
196
|
+
}
|
|
197
|
+
break;
|
|
198
|
+
}
|
|
57
199
|
case 'gutterMarkers': {
|
|
58
200
|
const markers: GutterMarker[] = message.markers.map((marker: { start: number; endExclusive: number; type: GutterMarker['type'] }) => ({
|
|
59
201
|
range: OffsetRange.fromTo(marker.start, marker.endExclusive),
|
|
@@ -62,33 +204,92 @@ class Editor extends Disposable {
|
|
|
62
204
|
this.model.gutterMarkers.set(markers, undefined);
|
|
63
205
|
break;
|
|
64
206
|
}
|
|
207
|
+
case 'comments': {
|
|
208
|
+
this.#isUpdatingComments = true;
|
|
209
|
+
this.#comments.set(message.comments.map((comment: { id: string; start: number; endExclusive: number; body: string; author?: string }) => ({
|
|
210
|
+
id: comment.id,
|
|
211
|
+
range: OffsetRange.fromTo(comment.start, comment.endExclusive),
|
|
212
|
+
body: comment.body,
|
|
213
|
+
author: comment.author,
|
|
214
|
+
})));
|
|
215
|
+
this.#isUpdatingComments = false;
|
|
216
|
+
this.#acceptsComments.set(!!message.acceptsComments, undefined);
|
|
217
|
+
break;
|
|
218
|
+
}
|
|
219
|
+
case 'revealComment': {
|
|
220
|
+
this.#commentsView?.revealComment(message.id);
|
|
221
|
+
break;
|
|
222
|
+
}
|
|
223
|
+
case 'command': {
|
|
224
|
+
const command = commands.find(command => command.id === message.command);
|
|
225
|
+
if (command) {
|
|
226
|
+
this.#controller?.executeCommand(command);
|
|
227
|
+
}
|
|
228
|
+
break;
|
|
229
|
+
}
|
|
65
230
|
}
|
|
66
231
|
});
|
|
67
232
|
|
|
68
|
-
this.#
|
|
233
|
+
this.#createView(host, initialState.content);
|
|
234
|
+
this.#vscode.postMessage({ type: 'ready', documentVersion: initialState.documentVersion });
|
|
235
|
+
this._register({
|
|
236
|
+
dispose: () => {
|
|
237
|
+
for (const resolve of this.#codeBlockEditorRequests.values()) {
|
|
238
|
+
resolve(undefined);
|
|
239
|
+
}
|
|
240
|
+
this.#codeBlockEditorRequests.clear();
|
|
241
|
+
for (const transport of Array.from(this.#codeBlockEditorHostTransports.values())) {
|
|
242
|
+
transport.dispose();
|
|
243
|
+
}
|
|
244
|
+
},
|
|
245
|
+
});
|
|
69
246
|
}
|
|
70
247
|
|
|
71
|
-
#createView(host: HTMLElement,
|
|
248
|
+
#createView(host: HTMLElement, content: string): void {
|
|
72
249
|
const model = this.model;
|
|
250
|
+
const scriptNonce = document.querySelector<HTMLMetaElement>('meta[name="vscode-markdown-editor-script-nonce"]')?.content;
|
|
251
|
+
const iframeBootstrapUrl = new URL(location.href);
|
|
252
|
+
// Nested frames must use the empty webview bootstrap, not the restricted index.html entrypoint.
|
|
253
|
+
iframeBootstrapUrl.pathname = '/fake.html';
|
|
254
|
+
const embeddedCodeEditorFactory = this._register(new VirtualizedIframeEmbeddedEditorFactory({
|
|
255
|
+
providers: this.#createIframeProviders(this.#codeBlockEditorProviders),
|
|
256
|
+
scriptNonce,
|
|
257
|
+
themeCss: () => `:root { ${document.documentElement.getAttribute('style') ?? ''} }`,
|
|
258
|
+
iframeBootstrapUrl: iframeBootstrapUrl.href,
|
|
259
|
+
onAmbiguous: (language, providers) => this.#vscode.postMessage({
|
|
260
|
+
type: 'codeBlockEditorDiagnostic',
|
|
261
|
+
message: `Ambiguous providers for ${language}: ${providers.map(provider => provider.id).join(', ')}`,
|
|
262
|
+
}),
|
|
263
|
+
onDidChange: () => this.#view?.refreshEmbeddedCodeEditors(),
|
|
264
|
+
}));
|
|
265
|
+
this.#embeddedCodeEditorFactory = embeddedCodeEditorFactory;
|
|
266
|
+
// The scroll + cursor position last persisted for this document, captured
|
|
267
|
+
// before any listener below can overwrite it, so it survives the editor being
|
|
268
|
+
// re-created (e.g. after a session switch).
|
|
269
|
+
const savedViewState = this.#getViewState();
|
|
73
270
|
|
|
74
271
|
const view = this._register(new EditorView(model, {
|
|
75
|
-
classNames: ['md-theme-vscode'],
|
|
272
|
+
classNames: ['md-theme-vscode-default'],
|
|
76
273
|
syntaxHighlighter: this.#syntaxHighlighter,
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
}
|
|
274
|
+
linkPresentationProvider: this.#linkPresentationProvider,
|
|
275
|
+
embeddedCodeEditorFactory,
|
|
276
|
+
onEmbeddedCodeEditorEdit: (block: CodeBlockAstNode, contentEdit: StringEdit) => {
|
|
81
277
|
const doc = model.document.get();
|
|
82
|
-
const
|
|
83
|
-
if (
|
|
84
|
-
const
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
278
|
+
const blockOffset = findNodeOffsetById(doc, block);
|
|
279
|
+
if (blockOffset === undefined) { return; }
|
|
280
|
+
const contentStart = blockOffset + block.codeOffset;
|
|
281
|
+
model.applyEdit(new StringEdit(
|
|
282
|
+
contentEdit.replacements.map(replacement => StringReplacement.replace(
|
|
283
|
+
replacement.replaceRange.delta(contentStart),
|
|
284
|
+
replacement.newText,
|
|
285
|
+
)),
|
|
286
|
+
));
|
|
287
|
+
},
|
|
288
|
+
onOpenLink: url => {
|
|
289
|
+
this.#vscode.postMessage({ type: 'openLink', href: url });
|
|
290
|
+
},
|
|
291
|
+
onToggleCheckbox: (item, newChecked) => {
|
|
292
|
+
model.setTaskCheckboxChecked(item, newChecked);
|
|
92
293
|
},
|
|
93
294
|
renderCustomCodeBlock: (language, content) => {
|
|
94
295
|
if (language !== 'mermaid') {
|
|
@@ -96,33 +297,373 @@ class Editor extends Disposable {
|
|
|
96
297
|
}
|
|
97
298
|
const div = document.createElement('div');
|
|
98
299
|
div.className = 'md-mermaid';
|
|
300
|
+
div.textContent = content;
|
|
301
|
+
div.setAttribute('aria-busy', 'true');
|
|
99
302
|
const id = `mermaid-${this.#mermaidCounter++}`;
|
|
100
|
-
|
|
101
|
-
.render(id, content)
|
|
303
|
+
loadMermaid()
|
|
304
|
+
.then(mermaid => mermaid.render(id, content))
|
|
102
305
|
.then(({ svg }) => {
|
|
103
306
|
div.innerHTML = svg;
|
|
307
|
+
div.setAttribute('aria-busy', 'false');
|
|
104
308
|
})
|
|
105
|
-
.catch(
|
|
309
|
+
.catch(error => {
|
|
106
310
|
div.textContent = content;
|
|
311
|
+
div.setAttribute('aria-busy', 'false');
|
|
312
|
+
this.#vscode.postMessage({
|
|
313
|
+
type: 'codeBlockEditorDiagnostic',
|
|
314
|
+
message: `Failed to render Mermaid diagram: ${error instanceof Error ? error.message : String(error)}`,
|
|
315
|
+
});
|
|
107
316
|
});
|
|
108
317
|
return div;
|
|
109
318
|
},
|
|
110
319
|
}));
|
|
320
|
+
this.#view = view;
|
|
111
321
|
|
|
112
|
-
|
|
322
|
+
// Wire history chords (undo/redo) to the extension so they run against the
|
|
323
|
+
// backing TextDocument's own undo stack. `record` is deliberately omitted:
|
|
324
|
+
// the TextDocument owns the history, and a second local stack would drift
|
|
325
|
+
// from the Edit menu, dirty state and hot exit.
|
|
326
|
+
this.#controller = this._register(new EditorController(model, view, {
|
|
327
|
+
clipboardStrategy: new AsyncClipboardStrategy(),
|
|
328
|
+
keyboardProfile: vscodeLocalKeyboardProfile,
|
|
329
|
+
forwardedKeyboardProfile: vscodeHostKeyboardProfile,
|
|
330
|
+
historyStrategy: {
|
|
331
|
+
undo: () => this.#vscode.postMessage({ type: 'history', command: 'undo' }),
|
|
332
|
+
redo: () => this.#vscode.postMessage({ type: 'history', command: 'redo' }),
|
|
333
|
+
},
|
|
334
|
+
}));
|
|
335
|
+
let lastEditorFocus: boolean | undefined;
|
|
336
|
+
const postEditorFocus = (): void => {
|
|
337
|
+
const focused = document.hasFocus() && document.activeElement === view.element;
|
|
338
|
+
if (focused === lastEditorFocus) {
|
|
339
|
+
return;
|
|
340
|
+
}
|
|
341
|
+
lastEditorFocus = focused;
|
|
342
|
+
this.#vscode.postMessage({ type: 'editorFocusChanged', focused });
|
|
343
|
+
};
|
|
344
|
+
const onFocusOut = (): void => queueMicrotask(postEditorFocus);
|
|
345
|
+
document.addEventListener('focusin', postEditorFocus);
|
|
346
|
+
document.addEventListener('focusout', onFocusOut);
|
|
347
|
+
window.addEventListener('focus', postEditorFocus);
|
|
348
|
+
window.addEventListener('blur', postEditorFocus);
|
|
349
|
+
this._register({
|
|
350
|
+
dispose: () => {
|
|
351
|
+
document.removeEventListener('focusin', postEditorFocus);
|
|
352
|
+
document.removeEventListener('focusout', onFocusOut);
|
|
353
|
+
window.removeEventListener('focus', postEditorFocus);
|
|
354
|
+
window.removeEventListener('blur', postEditorFocus);
|
|
355
|
+
},
|
|
356
|
+
});
|
|
113
357
|
host.appendChild(view.element);
|
|
358
|
+
postEditorFocus();
|
|
359
|
+
|
|
360
|
+
// Render comments as the VS Code V2 markdown cards. The card colours come
|
|
361
|
+
this.#commentsView = this._register(new CommentsView(this.#comments, view));
|
|
362
|
+
// The comment input (the gdocs-style "add a comment" affordance) is only
|
|
363
|
+
// useful when the workbench feedback store will actually accept the comment;
|
|
364
|
+
// otherwise submitting is a no-op. Mount the controller only while the
|
|
365
|
+
// resource is in scope for a session, and tear it down when it leaves scope.
|
|
366
|
+
let commentController: CommentModeController | undefined;
|
|
367
|
+
this._register(autorun((reader) => {
|
|
368
|
+
const accepts = reader.readObservable(this.#acceptsComments);
|
|
369
|
+
if (accepts && !commentController) {
|
|
370
|
+
commentController = new CommentModeController(model, view, {
|
|
371
|
+
onSubmit: ({ text, range }) => {
|
|
372
|
+
this.#vscode.postMessage({ type: 'addComment', start: range.start, endExclusive: range.endExclusive, text });
|
|
373
|
+
},
|
|
374
|
+
});
|
|
375
|
+
} else if (!accepts && commentController) {
|
|
376
|
+
commentController.dispose();
|
|
377
|
+
commentController = undefined;
|
|
378
|
+
}
|
|
379
|
+
}));
|
|
380
|
+
this._register({ dispose: () => commentController?.dispose() });
|
|
114
381
|
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
382
|
+
// The comment card's delete button mutates the local CommentsModel
|
|
383
|
+
// directly. Mirror those removals back to the extension so the shared
|
|
384
|
+
// store (and the code editor) stay in sync. Removals coming from an
|
|
385
|
+
// extension-driven update set `#isUpdatingComments`, so they are not
|
|
386
|
+
// echoed back.
|
|
387
|
+
let knownCommentIds = new Set(this.#comments.comments.get().map(comment => comment.id));
|
|
388
|
+
this._register(autorun((reader) => {
|
|
389
|
+
const currentIds = new Set(reader.readObservable(this.#comments.comments).map(comment => comment.id));
|
|
390
|
+
if (!this.#isUpdatingComments) {
|
|
391
|
+
for (const id of knownCommentIds) {
|
|
392
|
+
if (!currentIds.has(id)) {
|
|
393
|
+
this.#vscode.postMessage({ type: 'deleteComment', id });
|
|
394
|
+
}
|
|
121
395
|
}
|
|
122
|
-
|
|
123
|
-
|
|
396
|
+
}
|
|
397
|
+
knownCommentIds = currentIds;
|
|
398
|
+
}));
|
|
399
|
+
|
|
400
|
+
if (savedViewState.selection) {
|
|
401
|
+
const max = content.length;
|
|
402
|
+
const anchor = Math.min(savedViewState.selection.anchor, max);
|
|
403
|
+
const active = Math.min(savedViewState.selection.active, max);
|
|
404
|
+
model.selection.set(new Selection(anchor, active), undefined);
|
|
124
405
|
}
|
|
406
|
+
|
|
407
|
+
// Persist scroll as webview state (throttled to a frame). Registered after the
|
|
408
|
+
// restore above so it never clobbers the values we are about to restore.
|
|
409
|
+
let scrollSaveScheduled = false;
|
|
410
|
+
const saveScroll = (): void => {
|
|
411
|
+
scrollSaveScheduled = false;
|
|
412
|
+
this.#patchViewState({ scrollTop: host.scrollTop });
|
|
413
|
+
};
|
|
414
|
+
const onScroll = (): void => {
|
|
415
|
+
if (scrollSaveScheduled) { return; }
|
|
416
|
+
scrollSaveScheduled = true;
|
|
417
|
+
requestAnimationFrame(saveScroll);
|
|
418
|
+
};
|
|
419
|
+
host.addEventListener('scroll', onScroll, { passive: true });
|
|
420
|
+
this._register({ dispose: () => host.removeEventListener('scroll', onScroll) });
|
|
421
|
+
|
|
422
|
+
// Flush the latest scroll synchronously before the webview is hidden or torn
|
|
423
|
+
// down, since the frame-throttled save above may not have run yet.
|
|
424
|
+
const onHide = (): void => {
|
|
425
|
+
if (document.visibilityState === 'hidden') {
|
|
426
|
+
this.#patchViewState({ scrollTop: host.scrollTop });
|
|
427
|
+
}
|
|
428
|
+
};
|
|
429
|
+
document.addEventListener('visibilitychange', onHide);
|
|
430
|
+
window.addEventListener('pagehide', saveScroll);
|
|
431
|
+
this._register({ dispose: () => { document.removeEventListener('visibilitychange', onHide); window.removeEventListener('pagehide', saveScroll); } });
|
|
432
|
+
|
|
433
|
+
// Persist the cursor whenever it moves.
|
|
434
|
+
this._register(autorun((reader) => {
|
|
435
|
+
const sel = reader.readObservable(this.model.selection);
|
|
436
|
+
this.#patchViewState({ selection: sel ? { anchor: sel.anchor, active: sel.active } : undefined });
|
|
437
|
+
}));
|
|
438
|
+
|
|
439
|
+
// Persist the edit/read-only mode as the global default whenever the lock
|
|
440
|
+
// toggle flips it, so the next Markdown editor opens in the same mode. The
|
|
441
|
+
// initial (restored) value is skipped so opening an editor doesn't re-write it.
|
|
442
|
+
let firstReadonly = true;
|
|
443
|
+
this._register(autorun((reader) => {
|
|
444
|
+
const isReadonly = reader.readObservable(this.model.readonlyMode);
|
|
445
|
+
if (!firstReadonly) {
|
|
446
|
+
this.#vscode.postMessage({ type: 'setReadonly', readonly: isReadonly });
|
|
447
|
+
}
|
|
448
|
+
firstReadonly = false;
|
|
449
|
+
}));
|
|
450
|
+
|
|
451
|
+
// Forward user edits to the extension. Edits are ignored by the model while
|
|
452
|
+
// read-only, so this is a no-op in that mode; keeping it always registered
|
|
453
|
+
// means unlocking a read-only editor immediately resumes edit forwarding.
|
|
454
|
+
let previousText = this.model.sourceText.get().value;
|
|
455
|
+
this._register(autorun((reader) => {
|
|
456
|
+
const text = reader.readObservable(this.model.sourceText).value;
|
|
457
|
+
if (!this.isUpdatingFromExtension && text !== previousText) {
|
|
458
|
+
this.#vscode.postMessage({ type: 'edit', ...computeTextEdit(previousText, text) });
|
|
459
|
+
}
|
|
460
|
+
previousText = text;
|
|
461
|
+
}));
|
|
462
|
+
|
|
463
|
+
// Restore scroll last: content height settles over a few frames (async parse,
|
|
464
|
+
// syntax highlighting, mermaid), so re-apply until it sticks.
|
|
465
|
+
// TODO@copilot: Consider using a more robust method for restoring scroll position, e.g. by waiting for the editor to stabilize
|
|
466
|
+
this.#restoreScroll(host, savedViewState.scrollTop);
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
#createIframeProviders(definitions: readonly CodeBlockEditorProviderDefinition[]): readonly IframeEmbeddedEditorProvider[] {
|
|
470
|
+
return definitions.map(definition => ({
|
|
471
|
+
id: definition.id,
|
|
472
|
+
selector: definition.selector,
|
|
473
|
+
createHostTransport: runtimeKey => this.#createCodeBlockEditorHostTransport(definition.id, runtimeKey),
|
|
474
|
+
resolve: definition.source.kind === 'static'
|
|
475
|
+
? async () => definition.source.kind === 'static' ? definition.source.descriptor : undefined
|
|
476
|
+
: language => this.#resolveCodeBlockEditor(definition.id, language),
|
|
477
|
+
}));
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
#createCodeBlockEditorHostTransport(providerId: string, runtimeKey: string): CodeBlockEditorHostTransport {
|
|
481
|
+
const runtimeId = `${providerId}:${this.#nextCodeBlockEditorRuntimeId++}`;
|
|
482
|
+
const transport = new CodeBlockEditorHostTransport(
|
|
483
|
+
runtimeId,
|
|
484
|
+
message => this.#vscode.postMessage({
|
|
485
|
+
type: 'codeBlockEditorHostTransportMessage',
|
|
486
|
+
runtimeId,
|
|
487
|
+
message,
|
|
488
|
+
}),
|
|
489
|
+
() => {
|
|
490
|
+
this.#codeBlockEditorHostTransports.delete(runtimeId);
|
|
491
|
+
this.#vscode.postMessage({
|
|
492
|
+
type: 'disposeCodeBlockEditorHostTransport',
|
|
493
|
+
runtimeId,
|
|
494
|
+
});
|
|
495
|
+
},
|
|
496
|
+
);
|
|
497
|
+
this.#codeBlockEditorHostTransports.set(runtimeId, transport);
|
|
498
|
+
this.#vscode.postMessage({
|
|
499
|
+
type: 'createCodeBlockEditorHostTransport',
|
|
500
|
+
runtimeId,
|
|
501
|
+
providerId,
|
|
502
|
+
runtimeKey,
|
|
503
|
+
});
|
|
504
|
+
return transport;
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
#resolveCodeBlockEditor(providerId: string, language: string): Promise<ResolvedIframeEmbeddedEditor | undefined> {
|
|
508
|
+
const requestId = this.#nextCodeBlockEditorRequestId++;
|
|
509
|
+
return new Promise(resolve => {
|
|
510
|
+
this.#codeBlockEditorRequests.set(requestId, resolve);
|
|
511
|
+
this.#vscode.postMessage({
|
|
512
|
+
type: 'resolveCodeBlockEditor',
|
|
513
|
+
requestId,
|
|
514
|
+
providerId,
|
|
515
|
+
language,
|
|
516
|
+
});
|
|
517
|
+
});
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
#getViewState(): PersistedViewState {
|
|
521
|
+
return (this.#vscode.getState() as PersistedViewState | undefined) ?? {};
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
#patchViewState(patch: PersistedViewState): void {
|
|
525
|
+
this.#vscode.setState({ ...this.#getViewState(), ...patch });
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
#restoreScroll(host: HTMLElement, scrollTop: number | undefined): void {
|
|
529
|
+
if (typeof scrollTop !== 'number' || scrollTop <= 0) {
|
|
530
|
+
return;
|
|
531
|
+
}
|
|
532
|
+
let tries = 0;
|
|
533
|
+
const apply = (): void => {
|
|
534
|
+
host.scrollTop = scrollTop;
|
|
535
|
+
if (++tries < 6 && Math.abs(host.scrollTop - scrollTop) > 1) {
|
|
536
|
+
requestAnimationFrame(apply);
|
|
537
|
+
}
|
|
538
|
+
};
|
|
539
|
+
requestAnimationFrame(apply);
|
|
540
|
+
}
|
|
541
|
+
}
|
|
542
|
+
|
|
543
|
+
let mermaidPromise: Promise<(typeof import('mermaid'))['default']> | undefined;
|
|
544
|
+
|
|
545
|
+
function loadMermaid(): Promise<(typeof import('mermaid'))['default']> {
|
|
546
|
+
if (!mermaidPromise) {
|
|
547
|
+
mermaidPromise = import('mermaid').then(module => {
|
|
548
|
+
module.default.initialize({ startOnLoad: false, theme: 'default' });
|
|
549
|
+
return module.default;
|
|
550
|
+
});
|
|
551
|
+
}
|
|
552
|
+
return mermaidPromise;
|
|
553
|
+
}
|
|
554
|
+
|
|
555
|
+
function readInitialState(): InitialState {
|
|
556
|
+
const element = document.getElementById('vscode-markdown-editor-initial-state');
|
|
557
|
+
if (!(element instanceof HTMLMetaElement)) {
|
|
558
|
+
throw new Error('Markdown editor initial state was not found.');
|
|
559
|
+
}
|
|
560
|
+
element.remove();
|
|
561
|
+
const value: unknown = JSON.parse(decodeURIComponent(element.content));
|
|
562
|
+
if (!isInitialState(value)) {
|
|
563
|
+
throw new Error('Markdown editor initial state is invalid.');
|
|
564
|
+
}
|
|
565
|
+
return value;
|
|
566
|
+
}
|
|
567
|
+
|
|
568
|
+
function isInitialState(value: unknown): value is InitialState {
|
|
569
|
+
if (!value || typeof value !== 'object') {
|
|
570
|
+
return false;
|
|
571
|
+
}
|
|
572
|
+
const candidate = value as Record<string, unknown>;
|
|
573
|
+
return typeof candidate.content === 'string'
|
|
574
|
+
&& typeof candidate.documentVersion === 'number'
|
|
575
|
+
&& typeof candidate.readonly === 'boolean'
|
|
576
|
+
&& typeof candidate.richLinksEnabled === 'boolean'
|
|
577
|
+
&& Array.isArray(candidate.linkPresentationRules);
|
|
578
|
+
}
|
|
579
|
+
|
|
580
|
+
function readCodeBlockEditorProviderDefinitions(value: unknown): readonly CodeBlockEditorProviderDefinition[] {
|
|
581
|
+
if (!Array.isArray(value)) {
|
|
582
|
+
return [];
|
|
583
|
+
}
|
|
584
|
+
const values: unknown[] = value;
|
|
585
|
+
return values.filter(isCodeBlockEditorProviderDefinition);
|
|
586
|
+
}
|
|
587
|
+
|
|
588
|
+
function isCodeBlockEditorProviderDefinition(value: unknown): value is CodeBlockEditorProviderDefinition {
|
|
589
|
+
if (typeof value !== 'object' || value === null) {
|
|
590
|
+
return false;
|
|
591
|
+
}
|
|
592
|
+
const candidate = value as Record<string, unknown>;
|
|
593
|
+
return typeof candidate.id === 'string'
|
|
594
|
+
&& isCodeBlockEditorSelector(candidate.selector)
|
|
595
|
+
&& isCodeBlockEditorSource(candidate.source);
|
|
596
|
+
}
|
|
597
|
+
|
|
598
|
+
function isCodeBlockEditorSelector(value: unknown): value is IframeEmbeddedEditorProviderSelector {
|
|
599
|
+
if (!value || typeof value !== 'object') {
|
|
600
|
+
return false;
|
|
125
601
|
}
|
|
602
|
+
const selector = value as Record<string, unknown>;
|
|
603
|
+
return (typeof selector.language === 'string' && selector.languagePrefix === undefined)
|
|
604
|
+
|| (selector.language === undefined && typeof selector.languagePrefix === 'string');
|
|
605
|
+
}
|
|
606
|
+
|
|
607
|
+
function isCodeBlockEditorSource(value: unknown): value is CodeBlockEditorProviderDefinition['source'] {
|
|
608
|
+
if (!value || typeof value !== 'object') {
|
|
609
|
+
return false;
|
|
610
|
+
}
|
|
611
|
+
const source = value as Record<string, unknown>;
|
|
612
|
+
return source.kind === 'exportApi'
|
|
613
|
+
|| (source.kind === 'static' && readResolvedCodeBlockEditor(source.descriptor) !== undefined);
|
|
614
|
+
}
|
|
615
|
+
|
|
616
|
+
function readResolvedCodeBlockEditor(value: unknown): ResolvedIframeEmbeddedEditor | undefined {
|
|
617
|
+
if (!value || typeof value !== 'object') {
|
|
618
|
+
return undefined;
|
|
619
|
+
}
|
|
620
|
+
const descriptor = value as Record<string, unknown>;
|
|
621
|
+
if (
|
|
622
|
+
typeof descriptor.html !== 'string'
|
|
623
|
+
|| typeof descriptor.runtimeKey !== 'string'
|
|
624
|
+
|| descriptor.runtimeKey.length === 0
|
|
625
|
+
|| (descriptor.resourceBaseUrl !== undefined && typeof descriptor.resourceBaseUrl !== 'string')
|
|
626
|
+
|| (descriptor.hostTransport !== undefined && typeof descriptor.hostTransport !== 'boolean')
|
|
627
|
+
|| (descriptor.contentType !== 'text' && descriptor.contentType !== 'json')
|
|
628
|
+
|| (descriptor.cacheKey !== undefined && typeof descriptor.cacheKey !== 'string')
|
|
629
|
+
|| (descriptor.initialHeight !== undefined && (typeof descriptor.initialHeight !== 'number' || !Number.isFinite(descriptor.initialHeight) || descriptor.initialHeight <= 0))
|
|
630
|
+
|| !isResolvedSandbox(descriptor.sandbox)
|
|
631
|
+
) {
|
|
632
|
+
return undefined;
|
|
633
|
+
}
|
|
634
|
+
return descriptor as unknown as ResolvedIframeEmbeddedEditor;
|
|
635
|
+
}
|
|
636
|
+
|
|
637
|
+
function isResolvedSandbox(value: unknown): boolean {
|
|
638
|
+
if (value === undefined) {
|
|
639
|
+
return true;
|
|
640
|
+
}
|
|
641
|
+
if (!value || typeof value !== 'object') {
|
|
642
|
+
return false;
|
|
643
|
+
}
|
|
644
|
+
const sandbox = value as Record<string, unknown>;
|
|
645
|
+
return ['forms', 'downloads', 'pointerLock', 'clipboardWrite']
|
|
646
|
+
.every(key => sandbox[key] === undefined || typeof sandbox[key] === 'boolean');
|
|
647
|
+
}
|
|
648
|
+
|
|
649
|
+
function computeTextEdit(previousText: string, text: string): { start: number; endExclusive: number; text: string } {
|
|
650
|
+
let start = 0;
|
|
651
|
+
while (start < previousText.length && start < text.length && previousText.charCodeAt(start) === text.charCodeAt(start)) {
|
|
652
|
+
start++;
|
|
653
|
+
}
|
|
654
|
+
|
|
655
|
+
let previousEnd = previousText.length;
|
|
656
|
+
let end = text.length;
|
|
657
|
+
while (previousEnd > start && end > start && previousText.charCodeAt(previousEnd - 1) === text.charCodeAt(end - 1)) {
|
|
658
|
+
previousEnd--;
|
|
659
|
+
end--;
|
|
660
|
+
}
|
|
661
|
+
|
|
662
|
+
return {
|
|
663
|
+
start,
|
|
664
|
+
endExclusive: previousEnd,
|
|
665
|
+
text: text.slice(start, end),
|
|
666
|
+
};
|
|
126
667
|
}
|
|
127
668
|
|
|
128
|
-
new Editor(document.getElementById('editor')
|
|
669
|
+
new Editor(document.getElementById('editor')!, readInitialState());
|