veryfront 0.1.37 → 0.1.39
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/esm/deno.js +3 -2
- package/esm/src/html/dev-scripts.d.ts.map +1 -1
- package/esm/src/html/dev-scripts.js +13 -7
- package/esm/src/server/handlers/preview/markdown-html-generator.d.ts.map +1 -1
- package/esm/src/server/handlers/preview/markdown-html-generator.js +10 -5
- package/esm/src/server/handlers/studio/bridge-modules.handler.d.ts +23 -0
- package/esm/src/server/handlers/studio/bridge-modules.handler.d.ts.map +1 -0
- package/esm/src/server/handlers/studio/bridge-modules.handler.js +100 -0
- package/esm/src/server/runtime-handler/index.js +2 -2
- package/package.json +1 -1
- package/src/deno.js +3 -2
- package/src/src/html/dev-scripts.ts +14 -7
- package/src/src/server/handlers/preview/markdown-html-generator.ts +12 -10
- package/src/src/server/handlers/studio/bridge-modules.handler.ts +126 -0
- package/src/src/server/runtime-handler/index.ts +2 -2
- package/esm/src/server/handlers/studio/endpoints.handler.d.ts +0 -12
- package/esm/src/server/handlers/studio/endpoints.handler.d.ts.map +0 -1
- package/esm/src/server/handlers/studio/endpoints.handler.js +0 -29
- package/esm/src/studio/bridge-template.d.ts +0 -11
- package/esm/src/studio/bridge-template.d.ts.map +0 -1
- package/esm/src/studio/bridge-template.js +0 -4343
- package/src/src/server/handlers/studio/endpoints.handler.ts +0 -49
- package/src/src/studio/bridge-template.ts +0 -4353
|
@@ -1,4353 +0,0 @@
|
|
|
1
|
-
export interface StudioBridgeOptions {
|
|
2
|
-
projectId: string;
|
|
3
|
-
pageId: string;
|
|
4
|
-
pagePath?: string;
|
|
5
|
-
wsUrl?: string;
|
|
6
|
-
yjsGuid?: string;
|
|
7
|
-
debugSkipInit?: boolean;
|
|
8
|
-
debugExposeInternals?: boolean;
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
export function generateStudioBridgeScript(options: StudioBridgeOptions): string {
|
|
12
|
-
return `(function() {
|
|
13
|
-
'use strict';
|
|
14
|
-
|
|
15
|
-
const PROJECT_ID = ${JSON.stringify(options.projectId)};
|
|
16
|
-
const PAGE_ID = ${JSON.stringify(options.pageId)};
|
|
17
|
-
const PAGE_PATH = ${JSON.stringify(options.pagePath ?? options.pageId)};
|
|
18
|
-
const WS_URL = ${JSON.stringify(options.wsUrl ?? "")};
|
|
19
|
-
const YJS_GUID = ${JSON.stringify(options.yjsGuid ?? "")};
|
|
20
|
-
const DEBUG_SKIP_INIT = ${options.debugSkipInit ? "true" : "false"};
|
|
21
|
-
const DEBUG_EXPOSE_INTERNALS = ${options.debugExposeInternals ? "true" : "false"};
|
|
22
|
-
|
|
23
|
-
const DATA_VF_ID = 'data-vf-id';
|
|
24
|
-
const DATA_VF_SELECTOR = 'data-vf-selector';
|
|
25
|
-
const DATA_VF_TEXT = 'data-vf-text';
|
|
26
|
-
const DATA_VF_IGNORE = 'data-vf-ignore';
|
|
27
|
-
|
|
28
|
-
const DATA_NODE_ID = 'data-node-id';
|
|
29
|
-
const DATA_NODE_LINE = 'data-node-line';
|
|
30
|
-
const DATA_NODE_COLUMN = 'data-node-column';
|
|
31
|
-
const DATA_NODE_END_LINE = 'data-node-end-line';
|
|
32
|
-
const DATA_NODE_END_COLUMN = 'data-node-end-column';
|
|
33
|
-
|
|
34
|
-
let inspectMode = false;
|
|
35
|
-
let selectedNodeId = null;
|
|
36
|
-
let hoveredNodeId = null;
|
|
37
|
-
let lastTreeSignature = '';
|
|
38
|
-
|
|
39
|
-
let hoverOverlay = null;
|
|
40
|
-
let selectionOverlay = null;
|
|
41
|
-
let markdownEditorRoot = null;
|
|
42
|
-
let markdownEditorSurface = null;
|
|
43
|
-
let markdownEditorTextarea = null;
|
|
44
|
-
let markdownEditButton = null;
|
|
45
|
-
let markdownFileId = null;
|
|
46
|
-
let markdownSyncTimer = null;
|
|
47
|
-
let markdownSelectionSyncTimer = null;
|
|
48
|
-
let markdownPersistStatus = null;
|
|
49
|
-
let markdownPresenceRoot = null;
|
|
50
|
-
let markdownSelectionsRoot = null;
|
|
51
|
-
let markdownSelectionOverlayRoot = null;
|
|
52
|
-
let markdownOverlaySelections = [];
|
|
53
|
-
let markdownSelectionOverlayRenderFrame = null;
|
|
54
|
-
let markdownSlashMenuRoot = null;
|
|
55
|
-
let markdownSlashMenuTimer = null;
|
|
56
|
-
let markdownSlashMenuContext = null;
|
|
57
|
-
let markdownSlashMenuCommands = [];
|
|
58
|
-
let markdownSlashMenuActiveIndex = 0;
|
|
59
|
-
let markdownInlineToolbarRoot = null;
|
|
60
|
-
let markdownInlineToolbarFrame = null;
|
|
61
|
-
let markdownBlockDragHandle = null;
|
|
62
|
-
let markdownBlockDropIndicator = null;
|
|
63
|
-
let markdownBlockDropLabel = null;
|
|
64
|
-
let markdownBlockDragGhost = null;
|
|
65
|
-
let markdownBlockDragSourceIndex = -1;
|
|
66
|
-
let markdownBlockDropSlotIndex = -1;
|
|
67
|
-
let markdownBlockHandleHoverIndex = -1;
|
|
68
|
-
let markdownBlockDragActive = false;
|
|
69
|
-
let markdownMdxBlocksRoot = null;
|
|
70
|
-
let markdownLexicalApi = null;
|
|
71
|
-
let markdownLexicalSetupPromise = null;
|
|
72
|
-
let markdownCurrentContent = '';
|
|
73
|
-
let markdownCurrentEditorContent = '';
|
|
74
|
-
let markdownLexicalRenderedContent = null;
|
|
75
|
-
let markdownApplyingRemoteUpdate = false;
|
|
76
|
-
let markdownFrontmatter = '';
|
|
77
|
-
let markdownRawBlocks = [];
|
|
78
|
-
let markdownRawBlockTokenPrefix = 'VF_RAW_BLOCK';
|
|
79
|
-
let markdownLatestMdxBlocks = [];
|
|
80
|
-
let markdownLatestMdxImportMap = {};
|
|
81
|
-
let markdownLatestPresenceUsers = [];
|
|
82
|
-
let markdownLatestSelections = [];
|
|
83
|
-
let markdownHasUnsavedChanges = false;
|
|
84
|
-
let markdownSaveInProgress = false;
|
|
85
|
-
let markdownYDoc = null;
|
|
86
|
-
let markdownYProvider = null;
|
|
87
|
-
let markdownYText = null;
|
|
88
|
-
let markdownYjsConnected = false;
|
|
89
|
-
let markdownYjsSetupId = 0;
|
|
90
|
-
let markdownYjsY = null;
|
|
91
|
-
let markdownPendingSelection = null;
|
|
92
|
-
const LEXICAL_YJS_ORIGIN = 'lexical-yjs-binding';
|
|
93
|
-
|
|
94
|
-
const MARKDOWN_SLASH_COMMANDS = [
|
|
95
|
-
{
|
|
96
|
-
id: 'heading-1',
|
|
97
|
-
label: 'Heading 1',
|
|
98
|
-
description: 'Create a top-level heading',
|
|
99
|
-
aliases: ['h1', 'heading', 'title']
|
|
100
|
-
},
|
|
101
|
-
{
|
|
102
|
-
id: 'heading-2',
|
|
103
|
-
label: 'Heading 2',
|
|
104
|
-
description: 'Create a second-level heading',
|
|
105
|
-
aliases: ['h2', 'heading2', 'subheading']
|
|
106
|
-
},
|
|
107
|
-
{
|
|
108
|
-
id: 'heading-3',
|
|
109
|
-
label: 'Heading 3',
|
|
110
|
-
description: 'Create a third-level heading',
|
|
111
|
-
aliases: ['h3', 'heading3']
|
|
112
|
-
},
|
|
113
|
-
{
|
|
114
|
-
id: 'bulleted-list',
|
|
115
|
-
label: 'Bulleted list',
|
|
116
|
-
description: 'Start a bullet list item',
|
|
117
|
-
aliases: ['list', 'bullet', 'ul']
|
|
118
|
-
},
|
|
119
|
-
{
|
|
120
|
-
id: 'numbered-list',
|
|
121
|
-
label: 'Numbered list',
|
|
122
|
-
description: 'Start a numbered list item',
|
|
123
|
-
aliases: ['olist', 'numbered', 'ol']
|
|
124
|
-
},
|
|
125
|
-
{
|
|
126
|
-
id: 'quote-block',
|
|
127
|
-
label: 'Quote',
|
|
128
|
-
description: 'Insert a block quote line',
|
|
129
|
-
aliases: ['quote', 'blockquote']
|
|
130
|
-
},
|
|
131
|
-
{
|
|
132
|
-
id: 'code-block',
|
|
133
|
-
label: 'Code block',
|
|
134
|
-
description: 'Insert a fenced code block',
|
|
135
|
-
aliases: ['code', 'fence', 'snippet']
|
|
136
|
-
},
|
|
137
|
-
{
|
|
138
|
-
id: 'image',
|
|
139
|
-
label: 'Image',
|
|
140
|
-
description: 'Insert markdown image syntax',
|
|
141
|
-
aliases: ['image', 'img', 'photo']
|
|
142
|
-
}
|
|
143
|
-
];
|
|
144
|
-
|
|
145
|
-
function debounce(fn, ms) {
|
|
146
|
-
let timer;
|
|
147
|
-
return function(...args) {
|
|
148
|
-
clearTimeout(timer);
|
|
149
|
-
timer = setTimeout(function() {
|
|
150
|
-
fn.apply(this, args);
|
|
151
|
-
}, ms);
|
|
152
|
-
};
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
function injectOverlayStyles() {
|
|
156
|
-
if (document.getElementById('vf-overlay-styles')) return;
|
|
157
|
-
|
|
158
|
-
const style = document.createElement('style');
|
|
159
|
-
style.id = 'vf-overlay-styles';
|
|
160
|
-
style.textContent = \`
|
|
161
|
-
.vf-overlay {
|
|
162
|
-
position: fixed;
|
|
163
|
-
pointer-events: none;
|
|
164
|
-
z-index: 99999;
|
|
165
|
-
box-sizing: border-box;
|
|
166
|
-
transition: all 0.05s ease-out;
|
|
167
|
-
}
|
|
168
|
-
.vf-overlay-hover {
|
|
169
|
-
border: 2px solid #0081F8;
|
|
170
|
-
background: rgba(0, 129, 248, 0.05);
|
|
171
|
-
}
|
|
172
|
-
.vf-overlay-selection {
|
|
173
|
-
border: 2px solid #0081F8;
|
|
174
|
-
background: rgba(0, 129, 248, 0.1);
|
|
175
|
-
}
|
|
176
|
-
.vf-overlay-label {
|
|
177
|
-
position: absolute;
|
|
178
|
-
top: -22px;
|
|
179
|
-
left: -2px;
|
|
180
|
-
background: #0081F8;
|
|
181
|
-
color: white;
|
|
182
|
-
font-size: 11px;
|
|
183
|
-
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
|
184
|
-
padding: 2px 6px;
|
|
185
|
-
border-radius: 3px 3px 0 0;
|
|
186
|
-
white-space: nowrap;
|
|
187
|
-
pointer-events: none;
|
|
188
|
-
}
|
|
189
|
-
.vf-overlay-label-bottom {
|
|
190
|
-
top: auto;
|
|
191
|
-
bottom: -22px;
|
|
192
|
-
border-radius: 0 0 3px 3px;
|
|
193
|
-
}
|
|
194
|
-
|
|
195
|
-
.vf-markdown-edit-button {
|
|
196
|
-
position: fixed;
|
|
197
|
-
right: 16px;
|
|
198
|
-
bottom: 16px;
|
|
199
|
-
z-index: 100001;
|
|
200
|
-
border: 1px solid rgba(0, 0, 0, 0.2);
|
|
201
|
-
border-radius: 8px;
|
|
202
|
-
background: #111827;
|
|
203
|
-
color: #ffffff;
|
|
204
|
-
font-size: 13px;
|
|
205
|
-
line-height: 1;
|
|
206
|
-
padding: 10px 12px;
|
|
207
|
-
cursor: pointer;
|
|
208
|
-
box-shadow: 0 10px 20px rgba(0, 0, 0, 0.15);
|
|
209
|
-
}
|
|
210
|
-
|
|
211
|
-
.vf-markdown-editor {
|
|
212
|
-
position: fixed;
|
|
213
|
-
inset: 0;
|
|
214
|
-
z-index: 100000;
|
|
215
|
-
background: var(--vf-markdown-editor-bg, #ffffff);
|
|
216
|
-
display: none;
|
|
217
|
-
}
|
|
218
|
-
|
|
219
|
-
.vf-markdown-editor__toolbar {
|
|
220
|
-
display: flex;
|
|
221
|
-
justify-content: space-between;
|
|
222
|
-
align-items: center;
|
|
223
|
-
gap: 8px;
|
|
224
|
-
padding: 12px 16px;
|
|
225
|
-
border-bottom: 1px solid rgba(0, 0, 0, 0.12);
|
|
226
|
-
background: rgba(255, 255, 255, 0.94);
|
|
227
|
-
position: sticky;
|
|
228
|
-
top: 0;
|
|
229
|
-
}
|
|
230
|
-
|
|
231
|
-
.vf-markdown-editor__title {
|
|
232
|
-
font-size: 12px;
|
|
233
|
-
color: #374151;
|
|
234
|
-
font-weight: 600;
|
|
235
|
-
display: inline-flex;
|
|
236
|
-
flex-direction: column;
|
|
237
|
-
gap: 2px;
|
|
238
|
-
}
|
|
239
|
-
|
|
240
|
-
.vf-markdown-editor__title-main {
|
|
241
|
-
font-size: 12px;
|
|
242
|
-
font-weight: 700;
|
|
243
|
-
}
|
|
244
|
-
|
|
245
|
-
.vf-markdown-editor__title-hints {
|
|
246
|
-
font-size: 10px;
|
|
247
|
-
font-weight: 500;
|
|
248
|
-
color: #6b7280;
|
|
249
|
-
}
|
|
250
|
-
|
|
251
|
-
.vf-markdown-editor__actions {
|
|
252
|
-
display: inline-flex;
|
|
253
|
-
align-items: center;
|
|
254
|
-
gap: 8px;
|
|
255
|
-
}
|
|
256
|
-
|
|
257
|
-
.vf-markdown-editor__status {
|
|
258
|
-
font-size: 12px;
|
|
259
|
-
color: #6b7280;
|
|
260
|
-
}
|
|
261
|
-
|
|
262
|
-
.vf-markdown-editor__status[data-state='saving'] {
|
|
263
|
-
color: #b45309;
|
|
264
|
-
}
|
|
265
|
-
|
|
266
|
-
.vf-markdown-editor__status[data-state='saved'] {
|
|
267
|
-
color: #15803d;
|
|
268
|
-
}
|
|
269
|
-
|
|
270
|
-
.vf-markdown-editor__status[data-state='error'] {
|
|
271
|
-
color: #b91c1c;
|
|
272
|
-
}
|
|
273
|
-
|
|
274
|
-
.vf-markdown-editor__presence {
|
|
275
|
-
display: none;
|
|
276
|
-
align-items: center;
|
|
277
|
-
gap: 6px;
|
|
278
|
-
}
|
|
279
|
-
|
|
280
|
-
.vf-markdown-editor__presence-pill {
|
|
281
|
-
border: 1px solid rgba(0, 0, 0, 0.16);
|
|
282
|
-
border-left-width: 4px;
|
|
283
|
-
border-radius: 999px;
|
|
284
|
-
padding: 2px 8px;
|
|
285
|
-
font-size: 11px;
|
|
286
|
-
color: #111827;
|
|
287
|
-
background: #ffffff;
|
|
288
|
-
}
|
|
289
|
-
|
|
290
|
-
.vf-markdown-editor__presence-pill[data-current='true'] {
|
|
291
|
-
font-weight: 600;
|
|
292
|
-
}
|
|
293
|
-
|
|
294
|
-
.vf-markdown-editor__presence-pill[data-agent='true'] {
|
|
295
|
-
font-style: italic;
|
|
296
|
-
}
|
|
297
|
-
|
|
298
|
-
.vf-markdown-editor__selections {
|
|
299
|
-
display: none;
|
|
300
|
-
align-items: center;
|
|
301
|
-
gap: 6px;
|
|
302
|
-
}
|
|
303
|
-
|
|
304
|
-
.vf-markdown-editor__selection-pill {
|
|
305
|
-
border: 1px solid rgba(0, 0, 0, 0.16);
|
|
306
|
-
border-left-width: 4px;
|
|
307
|
-
border-radius: 999px;
|
|
308
|
-
padding: 2px 8px;
|
|
309
|
-
font-size: 11px;
|
|
310
|
-
color: #111827;
|
|
311
|
-
background: #ffffff;
|
|
312
|
-
}
|
|
313
|
-
|
|
314
|
-
.vf-markdown-editor__exit {
|
|
315
|
-
border: 1px solid rgba(0, 0, 0, 0.16);
|
|
316
|
-
border-radius: 6px;
|
|
317
|
-
background: #ffffff;
|
|
318
|
-
color: #111827;
|
|
319
|
-
font-size: 12px;
|
|
320
|
-
padding: 6px 10px;
|
|
321
|
-
cursor: pointer;
|
|
322
|
-
}
|
|
323
|
-
|
|
324
|
-
.vf-markdown-editor__history {
|
|
325
|
-
border: 1px solid rgba(0, 0, 0, 0.16);
|
|
326
|
-
border-radius: 6px;
|
|
327
|
-
background: #ffffff;
|
|
328
|
-
color: #111827;
|
|
329
|
-
font-size: 13px;
|
|
330
|
-
line-height: 1;
|
|
331
|
-
min-width: 28px;
|
|
332
|
-
height: 28px;
|
|
333
|
-
padding: 0 8px;
|
|
334
|
-
cursor: pointer;
|
|
335
|
-
}
|
|
336
|
-
|
|
337
|
-
.vf-markdown-editor__surface-wrap {
|
|
338
|
-
position: relative;
|
|
339
|
-
height: calc(100vh - 52px);
|
|
340
|
-
}
|
|
341
|
-
|
|
342
|
-
.vf-markdown-editor__surface {
|
|
343
|
-
width: 100%;
|
|
344
|
-
max-width: 980px;
|
|
345
|
-
margin: 0 auto;
|
|
346
|
-
height: 100%;
|
|
347
|
-
overflow: auto;
|
|
348
|
-
outline: none;
|
|
349
|
-
position: relative;
|
|
350
|
-
z-index: 1;
|
|
351
|
-
background: transparent;
|
|
352
|
-
padding: 32px 40px;
|
|
353
|
-
box-sizing: border-box;
|
|
354
|
-
}
|
|
355
|
-
|
|
356
|
-
.vf-markdown-editor__selection-overlay {
|
|
357
|
-
position: absolute;
|
|
358
|
-
inset: 0;
|
|
359
|
-
pointer-events: none;
|
|
360
|
-
z-index: 2;
|
|
361
|
-
display: none;
|
|
362
|
-
}
|
|
363
|
-
|
|
364
|
-
.vf-markdown-editor__selection-highlight {
|
|
365
|
-
position: absolute;
|
|
366
|
-
border-radius: 3px;
|
|
367
|
-
opacity: 0.26;
|
|
368
|
-
}
|
|
369
|
-
|
|
370
|
-
.vf-markdown-editor__selection-caret {
|
|
371
|
-
position: absolute;
|
|
372
|
-
width: 2px;
|
|
373
|
-
border-radius: 1px;
|
|
374
|
-
}
|
|
375
|
-
|
|
376
|
-
.vf-markdown-editor__selection-label {
|
|
377
|
-
position: absolute;
|
|
378
|
-
transform: translateY(-100%);
|
|
379
|
-
margin-top: -4px;
|
|
380
|
-
border-radius: 999px;
|
|
381
|
-
padding: 1px 7px;
|
|
382
|
-
font-size: 10px;
|
|
383
|
-
line-height: 1.4;
|
|
384
|
-
white-space: nowrap;
|
|
385
|
-
color: #ffffff;
|
|
386
|
-
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.16);
|
|
387
|
-
}
|
|
388
|
-
|
|
389
|
-
.vf-markdown-editor__slash-menu {
|
|
390
|
-
position: fixed;
|
|
391
|
-
z-index: 100005;
|
|
392
|
-
min-width: 220px;
|
|
393
|
-
max-width: 300px;
|
|
394
|
-
border: 1px solid rgba(17, 24, 39, 0.16);
|
|
395
|
-
border-radius: 10px;
|
|
396
|
-
background: #ffffff;
|
|
397
|
-
box-shadow: 0 12px 28px rgba(0, 0, 0, 0.18);
|
|
398
|
-
padding: 6px;
|
|
399
|
-
display: none;
|
|
400
|
-
}
|
|
401
|
-
|
|
402
|
-
.vf-markdown-editor__slash-item {
|
|
403
|
-
display: block;
|
|
404
|
-
width: 100%;
|
|
405
|
-
border: 0;
|
|
406
|
-
border-radius: 8px;
|
|
407
|
-
background: transparent;
|
|
408
|
-
text-align: left;
|
|
409
|
-
padding: 8px 10px;
|
|
410
|
-
cursor: pointer;
|
|
411
|
-
}
|
|
412
|
-
|
|
413
|
-
.vf-markdown-editor__slash-item:hover,
|
|
414
|
-
.vf-markdown-editor__slash-item[data-active='true'] {
|
|
415
|
-
background: rgba(0, 129, 248, 0.12);
|
|
416
|
-
}
|
|
417
|
-
|
|
418
|
-
.vf-markdown-editor__slash-item-title {
|
|
419
|
-
display: block;
|
|
420
|
-
font-size: 12px;
|
|
421
|
-
font-weight: 600;
|
|
422
|
-
color: #111827;
|
|
423
|
-
line-height: 1.35;
|
|
424
|
-
}
|
|
425
|
-
|
|
426
|
-
.vf-markdown-editor__slash-item-desc {
|
|
427
|
-
display: block;
|
|
428
|
-
margin-top: 2px;
|
|
429
|
-
font-size: 11px;
|
|
430
|
-
color: #6b7280;
|
|
431
|
-
line-height: 1.35;
|
|
432
|
-
}
|
|
433
|
-
|
|
434
|
-
.vf-markdown-editor__inline-toolbar {
|
|
435
|
-
position: fixed;
|
|
436
|
-
z-index: 100006;
|
|
437
|
-
display: none;
|
|
438
|
-
align-items: center;
|
|
439
|
-
gap: 2px;
|
|
440
|
-
border: 1px solid rgba(17, 24, 39, 0.16);
|
|
441
|
-
border-radius: 8px;
|
|
442
|
-
background: #ffffff;
|
|
443
|
-
box-shadow: 0 8px 20px rgba(0, 0, 0, 0.16);
|
|
444
|
-
padding: 4px;
|
|
445
|
-
}
|
|
446
|
-
|
|
447
|
-
.vf-markdown-editor__inline-button {
|
|
448
|
-
border: 0;
|
|
449
|
-
border-radius: 6px;
|
|
450
|
-
background: transparent;
|
|
451
|
-
color: #111827;
|
|
452
|
-
font-size: 12px;
|
|
453
|
-
font-weight: 600;
|
|
454
|
-
line-height: 1;
|
|
455
|
-
min-width: 26px;
|
|
456
|
-
height: 24px;
|
|
457
|
-
padding: 0 7px;
|
|
458
|
-
cursor: pointer;
|
|
459
|
-
}
|
|
460
|
-
|
|
461
|
-
.vf-markdown-editor__inline-button:hover {
|
|
462
|
-
background: rgba(0, 129, 248, 0.12);
|
|
463
|
-
}
|
|
464
|
-
|
|
465
|
-
.vf-markdown-editor__block-handle {
|
|
466
|
-
position: fixed;
|
|
467
|
-
z-index: 100007;
|
|
468
|
-
display: none;
|
|
469
|
-
border: 1px solid rgba(17, 24, 39, 0.18);
|
|
470
|
-
border-radius: 6px;
|
|
471
|
-
background: #ffffff;
|
|
472
|
-
color: #374151;
|
|
473
|
-
font-size: 12px;
|
|
474
|
-
font-weight: 700;
|
|
475
|
-
line-height: 1;
|
|
476
|
-
width: 28px;
|
|
477
|
-
height: 28px;
|
|
478
|
-
padding: 0;
|
|
479
|
-
cursor: grab;
|
|
480
|
-
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.14);
|
|
481
|
-
}
|
|
482
|
-
|
|
483
|
-
.vf-markdown-editor__block-handle:hover {
|
|
484
|
-
background: rgba(0, 129, 248, 0.12);
|
|
485
|
-
}
|
|
486
|
-
|
|
487
|
-
.vf-markdown-editor__block-handle[data-dragging='true'] {
|
|
488
|
-
cursor: grabbing;
|
|
489
|
-
}
|
|
490
|
-
|
|
491
|
-
.vf-markdown-editor__block-drop-indicator {
|
|
492
|
-
position: fixed;
|
|
493
|
-
z-index: 100006;
|
|
494
|
-
display: none;
|
|
495
|
-
height: 2px;
|
|
496
|
-
border-radius: 999px;
|
|
497
|
-
background: #0081f8;
|
|
498
|
-
box-shadow: 0 1px 6px rgba(0, 129, 248, 0.5);
|
|
499
|
-
}
|
|
500
|
-
|
|
501
|
-
.vf-markdown-editor__block-drop-label {
|
|
502
|
-
position: fixed;
|
|
503
|
-
z-index: 100007;
|
|
504
|
-
display: none;
|
|
505
|
-
border-radius: 999px;
|
|
506
|
-
border: 1px solid rgba(0, 129, 248, 0.24);
|
|
507
|
-
background: rgba(255, 255, 255, 0.96);
|
|
508
|
-
color: #0f172a;
|
|
509
|
-
font-size: 11px;
|
|
510
|
-
font-weight: 600;
|
|
511
|
-
line-height: 1.2;
|
|
512
|
-
padding: 3px 8px;
|
|
513
|
-
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.14);
|
|
514
|
-
}
|
|
515
|
-
|
|
516
|
-
.vf-markdown-editor__block-drag-ghost {
|
|
517
|
-
position: fixed;
|
|
518
|
-
top: -9999px;
|
|
519
|
-
left: -9999px;
|
|
520
|
-
width: 260px;
|
|
521
|
-
border: 1px solid rgba(17, 24, 39, 0.22);
|
|
522
|
-
border-radius: 10px;
|
|
523
|
-
background: #ffffff;
|
|
524
|
-
box-shadow: 0 10px 24px rgba(0, 0, 0, 0.2);
|
|
525
|
-
padding: 8px 10px;
|
|
526
|
-
}
|
|
527
|
-
|
|
528
|
-
.vf-markdown-editor__block-drag-ghost-title {
|
|
529
|
-
display: block;
|
|
530
|
-
font-size: 11px;
|
|
531
|
-
font-weight: 700;
|
|
532
|
-
color: #1e293b;
|
|
533
|
-
}
|
|
534
|
-
|
|
535
|
-
.vf-markdown-editor__block-drag-ghost-text {
|
|
536
|
-
display: block;
|
|
537
|
-
margin-top: 4px;
|
|
538
|
-
font-size: 11px;
|
|
539
|
-
color: #475569;
|
|
540
|
-
line-height: 1.35;
|
|
541
|
-
}
|
|
542
|
-
|
|
543
|
-
.vf-markdown-editor__mdx-blocks {
|
|
544
|
-
display: none;
|
|
545
|
-
gap: 8px;
|
|
546
|
-
padding: 8px 16px;
|
|
547
|
-
border-bottom: 1px solid rgba(0, 0, 0, 0.08);
|
|
548
|
-
background: rgba(245, 247, 250, 0.95);
|
|
549
|
-
overflow-x: auto;
|
|
550
|
-
}
|
|
551
|
-
|
|
552
|
-
.vf-markdown-editor__mdx-block {
|
|
553
|
-
display: inline-flex;
|
|
554
|
-
align-items: center;
|
|
555
|
-
gap: 8px;
|
|
556
|
-
border: 1px solid rgba(0, 0, 0, 0.16);
|
|
557
|
-
border-radius: 8px;
|
|
558
|
-
background: #ffffff;
|
|
559
|
-
padding: 6px 8px;
|
|
560
|
-
}
|
|
561
|
-
|
|
562
|
-
.vf-markdown-editor__mdx-block-label {
|
|
563
|
-
font-size: 11px;
|
|
564
|
-
color: #334155;
|
|
565
|
-
white-space: nowrap;
|
|
566
|
-
}
|
|
567
|
-
|
|
568
|
-
.vf-markdown-editor__mdx-note {
|
|
569
|
-
font-size: 10px;
|
|
570
|
-
color: #6b7280;
|
|
571
|
-
white-space: nowrap;
|
|
572
|
-
}
|
|
573
|
-
|
|
574
|
-
.vf-markdown-editor__mdx-open {
|
|
575
|
-
border: 1px solid rgba(0, 0, 0, 0.16);
|
|
576
|
-
border-radius: 6px;
|
|
577
|
-
background: #ffffff;
|
|
578
|
-
color: #0f172a;
|
|
579
|
-
font-size: 11px;
|
|
580
|
-
line-height: 1;
|
|
581
|
-
padding: 5px 7px;
|
|
582
|
-
cursor: pointer;
|
|
583
|
-
white-space: nowrap;
|
|
584
|
-
}
|
|
585
|
-
|
|
586
|
-
.vf-markdown-editor__surface [data-lexical-editor] {
|
|
587
|
-
outline: none;
|
|
588
|
-
}
|
|
589
|
-
|
|
590
|
-
.vf-markdown-editor__surface p:empty::before {
|
|
591
|
-
content: '';
|
|
592
|
-
display: inline-block;
|
|
593
|
-
}
|
|
594
|
-
|
|
595
|
-
.vf-markdown-editor__surface p {
|
|
596
|
-
min-height: 1.5em;
|
|
597
|
-
}
|
|
598
|
-
|
|
599
|
-
.vf-markdown-editor__textarea {
|
|
600
|
-
width: 100%;
|
|
601
|
-
height: calc(100vh - 52px);
|
|
602
|
-
border: 0;
|
|
603
|
-
outline: none;
|
|
604
|
-
resize: none;
|
|
605
|
-
display: none;
|
|
606
|
-
font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, 'Liberation Mono', 'Courier New', monospace;
|
|
607
|
-
font-size: 14px;
|
|
608
|
-
line-height: 1.6;
|
|
609
|
-
color: #111827;
|
|
610
|
-
background: transparent;
|
|
611
|
-
padding: 16px;
|
|
612
|
-
box-sizing: border-box;
|
|
613
|
-
}
|
|
614
|
-
|
|
615
|
-
[data-theme='dark'] .vf-markdown-editor {
|
|
616
|
-
--vf-markdown-editor-bg: #0b1220;
|
|
617
|
-
}
|
|
618
|
-
|
|
619
|
-
[data-theme='dark'] .vf-markdown-editor__toolbar {
|
|
620
|
-
border-bottom-color: rgba(255, 255, 255, 0.18);
|
|
621
|
-
background: rgba(17, 24, 39, 0.92);
|
|
622
|
-
}
|
|
623
|
-
|
|
624
|
-
[data-theme='dark'] .vf-markdown-editor__title {
|
|
625
|
-
color: #d1d5db;
|
|
626
|
-
}
|
|
627
|
-
|
|
628
|
-
[data-theme='dark'] .vf-markdown-editor__title-hints {
|
|
629
|
-
color: #9ca3af;
|
|
630
|
-
}
|
|
631
|
-
|
|
632
|
-
[data-theme='dark'] .vf-markdown-editor__exit {
|
|
633
|
-
background: #111827;
|
|
634
|
-
border-color: rgba(255, 255, 255, 0.2);
|
|
635
|
-
color: #f9fafb;
|
|
636
|
-
}
|
|
637
|
-
|
|
638
|
-
[data-theme='dark'] .vf-markdown-editor__history {
|
|
639
|
-
background: #111827;
|
|
640
|
-
border-color: rgba(255, 255, 255, 0.2);
|
|
641
|
-
color: #f9fafb;
|
|
642
|
-
}
|
|
643
|
-
|
|
644
|
-
[data-theme='dark'] .vf-markdown-editor__status {
|
|
645
|
-
color: #9ca3af;
|
|
646
|
-
}
|
|
647
|
-
|
|
648
|
-
[data-theme='dark'] .vf-markdown-editor__status[data-state='saving'] {
|
|
649
|
-
color: #fbbf24;
|
|
650
|
-
}
|
|
651
|
-
|
|
652
|
-
[data-theme='dark'] .vf-markdown-editor__status[data-state='saved'] {
|
|
653
|
-
color: #4ade80;
|
|
654
|
-
}
|
|
655
|
-
|
|
656
|
-
[data-theme='dark'] .vf-markdown-editor__status[data-state='error'] {
|
|
657
|
-
color: #f87171;
|
|
658
|
-
}
|
|
659
|
-
|
|
660
|
-
[data-theme='dark'] .vf-markdown-editor__presence-pill {
|
|
661
|
-
border-color: rgba(255, 255, 255, 0.2);
|
|
662
|
-
color: #f9fafb;
|
|
663
|
-
background: #111827;
|
|
664
|
-
}
|
|
665
|
-
|
|
666
|
-
[data-theme='dark'] .vf-markdown-editor__selection-pill {
|
|
667
|
-
border-color: rgba(255, 255, 255, 0.2);
|
|
668
|
-
color: #f9fafb;
|
|
669
|
-
background: #111827;
|
|
670
|
-
}
|
|
671
|
-
|
|
672
|
-
[data-theme='dark'] .vf-markdown-editor__textarea {
|
|
673
|
-
color: #f9fafb;
|
|
674
|
-
}
|
|
675
|
-
|
|
676
|
-
[data-theme='dark'] .vf-markdown-editor__slash-menu {
|
|
677
|
-
border-color: rgba(255, 255, 255, 0.2);
|
|
678
|
-
background: #111827;
|
|
679
|
-
}
|
|
680
|
-
|
|
681
|
-
[data-theme='dark'] .vf-markdown-editor__slash-item:hover,
|
|
682
|
-
[data-theme='dark'] .vf-markdown-editor__slash-item[data-active='true'] {
|
|
683
|
-
background: rgba(59, 130, 246, 0.24);
|
|
684
|
-
}
|
|
685
|
-
|
|
686
|
-
[data-theme='dark'] .vf-markdown-editor__slash-item-title {
|
|
687
|
-
color: #f9fafb;
|
|
688
|
-
}
|
|
689
|
-
|
|
690
|
-
[data-theme='dark'] .vf-markdown-editor__slash-item-desc {
|
|
691
|
-
color: #9ca3af;
|
|
692
|
-
}
|
|
693
|
-
|
|
694
|
-
[data-theme='dark'] .vf-markdown-editor__inline-toolbar {
|
|
695
|
-
border-color: rgba(255, 255, 255, 0.2);
|
|
696
|
-
background: #111827;
|
|
697
|
-
}
|
|
698
|
-
|
|
699
|
-
[data-theme='dark'] .vf-markdown-editor__inline-button {
|
|
700
|
-
color: #f9fafb;
|
|
701
|
-
}
|
|
702
|
-
|
|
703
|
-
[data-theme='dark'] .vf-markdown-editor__inline-button:hover {
|
|
704
|
-
background: rgba(59, 130, 246, 0.24);
|
|
705
|
-
}
|
|
706
|
-
|
|
707
|
-
[data-theme='dark'] .vf-markdown-editor__block-handle {
|
|
708
|
-
border-color: rgba(255, 255, 255, 0.22);
|
|
709
|
-
background: #111827;
|
|
710
|
-
color: #d1d5db;
|
|
711
|
-
}
|
|
712
|
-
|
|
713
|
-
[data-theme='dark'] .vf-markdown-editor__block-handle:hover {
|
|
714
|
-
background: rgba(59, 130, 246, 0.24);
|
|
715
|
-
}
|
|
716
|
-
|
|
717
|
-
[data-theme='dark'] .vf-markdown-editor__block-drop-label {
|
|
718
|
-
border-color: rgba(59, 130, 246, 0.35);
|
|
719
|
-
background: rgba(17, 24, 39, 0.94);
|
|
720
|
-
color: #e5e7eb;
|
|
721
|
-
}
|
|
722
|
-
|
|
723
|
-
[data-theme='dark'] .vf-markdown-editor__block-drag-ghost {
|
|
724
|
-
border-color: rgba(255, 255, 255, 0.24);
|
|
725
|
-
background: #111827;
|
|
726
|
-
}
|
|
727
|
-
|
|
728
|
-
[data-theme='dark'] .vf-markdown-editor__block-drag-ghost-title {
|
|
729
|
-
color: #e5e7eb;
|
|
730
|
-
}
|
|
731
|
-
|
|
732
|
-
[data-theme='dark'] .vf-markdown-editor__block-drag-ghost-text {
|
|
733
|
-
color: #94a3b8;
|
|
734
|
-
}
|
|
735
|
-
|
|
736
|
-
[data-theme='dark'] .vf-markdown-editor__mdx-blocks {
|
|
737
|
-
border-bottom-color: rgba(255, 255, 255, 0.12);
|
|
738
|
-
background: rgba(2, 6, 23, 0.7);
|
|
739
|
-
}
|
|
740
|
-
|
|
741
|
-
[data-theme='dark'] .vf-markdown-editor__mdx-block {
|
|
742
|
-
border-color: rgba(255, 255, 255, 0.22);
|
|
743
|
-
background: #111827;
|
|
744
|
-
}
|
|
745
|
-
|
|
746
|
-
[data-theme='dark'] .vf-markdown-editor__mdx-block-label {
|
|
747
|
-
color: #cbd5e1;
|
|
748
|
-
}
|
|
749
|
-
|
|
750
|
-
[data-theme='dark'] .vf-markdown-editor__mdx-note {
|
|
751
|
-
color: #94a3b8;
|
|
752
|
-
}
|
|
753
|
-
|
|
754
|
-
[data-theme='dark'] .vf-markdown-editor__mdx-open {
|
|
755
|
-
border-color: rgba(255, 255, 255, 0.22);
|
|
756
|
-
background: #0b1220;
|
|
757
|
-
color: #e5e7eb;
|
|
758
|
-
}
|
|
759
|
-
\`;
|
|
760
|
-
try {
|
|
761
|
-
document.head.appendChild(style);
|
|
762
|
-
if (!style.sheet) {
|
|
763
|
-
console.warn('[StudioBridge] Inline style injection may be blocked by CSP (style-src).');
|
|
764
|
-
}
|
|
765
|
-
} catch (error) {
|
|
766
|
-
console.warn('[StudioBridge] Failed to inject bridge styles. This may be caused by CSP style-src restrictions.', error);
|
|
767
|
-
}
|
|
768
|
-
}
|
|
769
|
-
|
|
770
|
-
function createOverlay(type) {
|
|
771
|
-
const overlay = document.createElement('div');
|
|
772
|
-
overlay.className = 'vf-overlay vf-overlay-' + type;
|
|
773
|
-
overlay.setAttribute(DATA_VF_IGNORE, 'true');
|
|
774
|
-
|
|
775
|
-
const label = document.createElement('div');
|
|
776
|
-
label.className = 'vf-overlay-label';
|
|
777
|
-
overlay.appendChild(label);
|
|
778
|
-
|
|
779
|
-
overlay.style.display = 'none';
|
|
780
|
-
document.body.appendChild(overlay);
|
|
781
|
-
return overlay;
|
|
782
|
-
}
|
|
783
|
-
|
|
784
|
-
function hideOverlay(overlay) {
|
|
785
|
-
if (overlay) overlay.style.display = 'none';
|
|
786
|
-
}
|
|
787
|
-
|
|
788
|
-
function positionOverlay(overlay, element, nodeName) {
|
|
789
|
-
if (!overlay) return;
|
|
790
|
-
if (!element) {
|
|
791
|
-
hideOverlay(overlay);
|
|
792
|
-
return;
|
|
793
|
-
}
|
|
794
|
-
|
|
795
|
-
const rect = element.getBoundingClientRect();
|
|
796
|
-
|
|
797
|
-
overlay.style.display = 'block';
|
|
798
|
-
overlay.style.top = rect.top + 'px';
|
|
799
|
-
overlay.style.left = rect.left + 'px';
|
|
800
|
-
overlay.style.width = rect.width + 'px';
|
|
801
|
-
overlay.style.height = rect.height + 'px';
|
|
802
|
-
|
|
803
|
-
const label = overlay.querySelector('.vf-overlay-label');
|
|
804
|
-
if (!label) return;
|
|
805
|
-
|
|
806
|
-
label.textContent = nodeName || element.tagName.toLowerCase();
|
|
807
|
-
if (rect.top < 24) {
|
|
808
|
-
label.classList.add('vf-overlay-label-bottom');
|
|
809
|
-
} else {
|
|
810
|
-
label.classList.remove('vf-overlay-label-bottom');
|
|
811
|
-
}
|
|
812
|
-
}
|
|
813
|
-
|
|
814
|
-
function getNodeName(element) {
|
|
815
|
-
const vfId = element.getAttribute(DATA_VF_ID);
|
|
816
|
-
if (vfId) return vfId.split('_')[0];
|
|
817
|
-
return element.tagName.toLowerCase();
|
|
818
|
-
}
|
|
819
|
-
|
|
820
|
-
function findElementById(nodeId) {
|
|
821
|
-
if (!nodeId) return null;
|
|
822
|
-
return (
|
|
823
|
-
document.querySelector('[' + DATA_VF_ID + '="' + nodeId + '"]') ||
|
|
824
|
-
document.querySelector('[' + DATA_VF_SELECTOR + '="' + nodeId + '"]') ||
|
|
825
|
-
document.querySelector('[' + DATA_NODE_ID + '="' + nodeId + '"]')
|
|
826
|
-
);
|
|
827
|
-
}
|
|
828
|
-
|
|
829
|
-
function postToStudio(message) {
|
|
830
|
-
if (!window.parent || window.parent === window) return;
|
|
831
|
-
try {
|
|
832
|
-
window.parent.postMessage(message, '*');
|
|
833
|
-
} catch (e) {
|
|
834
|
-
console.debug('[StudioBridge] postMessage failed:', e);
|
|
835
|
-
}
|
|
836
|
-
}
|
|
837
|
-
|
|
838
|
-
function isFromStudio(event) {
|
|
839
|
-
try {
|
|
840
|
-
const url = new URL(event.origin || '');
|
|
841
|
-
const host = url.hostname;
|
|
842
|
-
return (
|
|
843
|
-
host === 'localhost' ||
|
|
844
|
-
host.endsWith('.veryfront.org') || host === 'veryfront.org' ||
|
|
845
|
-
host.endsWith('.veryfront.com') || host === 'veryfront.com' ||
|
|
846
|
-
host.endsWith('.veryfront.dev') || host === 'veryfront.dev'
|
|
847
|
-
);
|
|
848
|
-
} catch (e) {
|
|
849
|
-
return false;
|
|
850
|
-
}
|
|
851
|
-
}
|
|
852
|
-
|
|
853
|
-
const originalConsole = {};
|
|
854
|
-
const consoleMethods = ['log', 'debug', 'info', 'warn', 'error', 'table', 'clear', 'dir'];
|
|
855
|
-
let logCounter = 0;
|
|
856
|
-
|
|
857
|
-
function setupConsoleCapture() {
|
|
858
|
-
consoleMethods.forEach(method => {
|
|
859
|
-
originalConsole[method] = console[method];
|
|
860
|
-
console[method] = function(...args) {
|
|
861
|
-
originalConsole[method].apply(console, args);
|
|
862
|
-
|
|
863
|
-
const logId = 'vf-' + Date.now() + '-' + ++logCounter;
|
|
864
|
-
|
|
865
|
-
const formattedData = args.map(arg => {
|
|
866
|
-
try {
|
|
867
|
-
if (arg instanceof Error) {
|
|
868
|
-
return { __isError: true, message: arg.message, stack: arg.stack, name: arg.name };
|
|
869
|
-
}
|
|
870
|
-
if (arg === undefined) return { __isUndefined: true };
|
|
871
|
-
if (arg === null) return null;
|
|
872
|
-
if (typeof arg === 'function') return { __isFunction: true, name: arg.name || 'anonymous' };
|
|
873
|
-
if (typeof arg === 'symbol') return { __isSymbol: true, description: arg.description };
|
|
874
|
-
if (typeof arg === 'object') return JSON.parse(JSON.stringify(arg));
|
|
875
|
-
return arg;
|
|
876
|
-
} catch (e) {
|
|
877
|
-
return String(arg);
|
|
878
|
-
}
|
|
879
|
-
});
|
|
880
|
-
|
|
881
|
-
postToStudio({
|
|
882
|
-
action: 'logEvent',
|
|
883
|
-
value: {
|
|
884
|
-
id: logId,
|
|
885
|
-
method: method,
|
|
886
|
-
data: formattedData,
|
|
887
|
-
timestamp: new Date().toISOString()
|
|
888
|
-
}
|
|
889
|
-
});
|
|
890
|
-
};
|
|
891
|
-
});
|
|
892
|
-
}
|
|
893
|
-
|
|
894
|
-
function setupErrorHandling() {
|
|
895
|
-
function hideOverlays() {
|
|
896
|
-
hideOverlay(hoverOverlay);
|
|
897
|
-
hideOverlay(selectionOverlay);
|
|
898
|
-
}
|
|
899
|
-
|
|
900
|
-
window.addEventListener('error', function(event) {
|
|
901
|
-
hideOverlays();
|
|
902
|
-
postToStudio({
|
|
903
|
-
action: 'runtimeError',
|
|
904
|
-
url: window.location.href,
|
|
905
|
-
errors: [
|
|
906
|
-
{
|
|
907
|
-
type: 'error',
|
|
908
|
-
message: event.message,
|
|
909
|
-
file: event.filename,
|
|
910
|
-
line: event.lineno,
|
|
911
|
-
column: event.colno
|
|
912
|
-
}
|
|
913
|
-
]
|
|
914
|
-
});
|
|
915
|
-
});
|
|
916
|
-
|
|
917
|
-
window.addEventListener('unhandledrejection', function(event) {
|
|
918
|
-
hideOverlays();
|
|
919
|
-
const reason = event.reason;
|
|
920
|
-
postToStudio({
|
|
921
|
-
action: 'runtimeError',
|
|
922
|
-
url: window.location.href,
|
|
923
|
-
errors: [
|
|
924
|
-
{
|
|
925
|
-
type: 'error',
|
|
926
|
-
message: reason instanceof Error ? reason.message : String(reason),
|
|
927
|
-
file: reason instanceof Error ? reason.stack : undefined
|
|
928
|
-
}
|
|
929
|
-
]
|
|
930
|
-
});
|
|
931
|
-
});
|
|
932
|
-
}
|
|
933
|
-
|
|
934
|
-
const DOM_IGNORE_TAGS = ['SCRIPT', 'STYLE', 'LINK', 'META', 'NOSCRIPT'];
|
|
935
|
-
|
|
936
|
-
function isValidElement(el) {
|
|
937
|
-
return (
|
|
938
|
-
el &&
|
|
939
|
-
el.nodeType === Node.ELEMENT_NODE &&
|
|
940
|
-
!DOM_IGNORE_TAGS.includes(el.tagName) &&
|
|
941
|
-
!el.hasAttribute(DATA_VF_IGNORE) &&
|
|
942
|
-
el.style.display !== 'none'
|
|
943
|
-
);
|
|
944
|
-
}
|
|
945
|
-
|
|
946
|
-
function getNodeType(el) {
|
|
947
|
-
const tagName = el.tagName.toLowerCase();
|
|
948
|
-
|
|
949
|
-
const vfId = el.getAttribute(DATA_VF_ID) || '';
|
|
950
|
-
if (vfId && /^[A-Z]/.test(vfId)) return 'component';
|
|
951
|
-
if (el.hasAttribute(DATA_VF_TEXT)) return 'text';
|
|
952
|
-
|
|
953
|
-
if (['h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'p', 'blockquote', 'ul', 'ol', 'li', 'pre', 'code'].includes(tagName)) {
|
|
954
|
-
return 'markdown';
|
|
955
|
-
}
|
|
956
|
-
|
|
957
|
-
return 'element';
|
|
958
|
-
}
|
|
959
|
-
|
|
960
|
-
function buildNavigatorTree(root) {
|
|
961
|
-
let nodeIndex = 0;
|
|
962
|
-
|
|
963
|
-
function processElement(el, parentId) {
|
|
964
|
-
if (!isValidElement(el)) {
|
|
965
|
-
const children = [];
|
|
966
|
-
Array.from(el.children || []).forEach(child => {
|
|
967
|
-
children.push(...processElement(child, parentId));
|
|
968
|
-
});
|
|
969
|
-
return children;
|
|
970
|
-
}
|
|
971
|
-
|
|
972
|
-
let id = el.getAttribute(DATA_VF_ID) || el.getAttribute(DATA_NODE_ID) || el.getAttribute(DATA_VF_SELECTOR);
|
|
973
|
-
if (!id) {
|
|
974
|
-
id = 'vf-' + el.tagName.toLowerCase() + '-' + ++nodeIndex;
|
|
975
|
-
el.setAttribute(DATA_VF_SELECTOR, id);
|
|
976
|
-
}
|
|
977
|
-
|
|
978
|
-
const vfId = el.getAttribute(DATA_VF_ID);
|
|
979
|
-
const name = vfId ? vfId.split('_')[0] : el.tagName.toLowerCase();
|
|
980
|
-
|
|
981
|
-
const node = {
|
|
982
|
-
id: id,
|
|
983
|
-
name: name,
|
|
984
|
-
type: getNodeType(el),
|
|
985
|
-
path: PAGE_PATH,
|
|
986
|
-
parentId: parentId,
|
|
987
|
-
start: {
|
|
988
|
-
line: parseInt(el.getAttribute(DATA_NODE_LINE) || '0', 10),
|
|
989
|
-
column: parseInt(el.getAttribute(DATA_NODE_COLUMN) || '0', 10)
|
|
990
|
-
},
|
|
991
|
-
end: {
|
|
992
|
-
line: parseInt(el.getAttribute(DATA_NODE_END_LINE) || '0', 10),
|
|
993
|
-
column: parseInt(el.getAttribute(DATA_NODE_END_COLUMN) || '0', 10)
|
|
994
|
-
},
|
|
995
|
-
children: [],
|
|
996
|
-
text: el.hasAttribute(DATA_VF_TEXT) ? el.textContent?.trim() : undefined,
|
|
997
|
-
isRemote: false
|
|
998
|
-
};
|
|
999
|
-
|
|
1000
|
-
Array.from(el.children || []).forEach(child => {
|
|
1001
|
-
node.children.push(...processElement(child, id));
|
|
1002
|
-
});
|
|
1003
|
-
|
|
1004
|
-
return [node];
|
|
1005
|
-
}
|
|
1006
|
-
|
|
1007
|
-
const rootNode = {
|
|
1008
|
-
id: 'root',
|
|
1009
|
-
name: 'root',
|
|
1010
|
-
type: 'root',
|
|
1011
|
-
path: '',
|
|
1012
|
-
parentId: '',
|
|
1013
|
-
start: { line: 0, column: 0 },
|
|
1014
|
-
end: { line: 0, column: 0 },
|
|
1015
|
-
children: []
|
|
1016
|
-
};
|
|
1017
|
-
|
|
1018
|
-
Array.from(root.children || []).forEach(child => {
|
|
1019
|
-
rootNode.children.push(...processElement(child, 'root'));
|
|
1020
|
-
});
|
|
1021
|
-
|
|
1022
|
-
return rootNode;
|
|
1023
|
-
}
|
|
1024
|
-
|
|
1025
|
-
function createTreeSignature(root) {
|
|
1026
|
-
const validElements = Array.from(root.querySelectorAll('*')).filter(el => isValidElement(el));
|
|
1027
|
-
return validElements.length + '-' + validElements.map(el => el.tagName).join('');
|
|
1028
|
-
}
|
|
1029
|
-
|
|
1030
|
-
let treeUpdateTimer = null;
|
|
1031
|
-
let mutationObserver = null;
|
|
1032
|
-
|
|
1033
|
-
function sendTreeUpdate() {
|
|
1034
|
-
const root = document.getElementById('root') || document.body;
|
|
1035
|
-
if (!root) return;
|
|
1036
|
-
|
|
1037
|
-
const signature = createTreeSignature(root);
|
|
1038
|
-
if (signature === lastTreeSignature) return;
|
|
1039
|
-
lastTreeSignature = signature;
|
|
1040
|
-
|
|
1041
|
-
postToStudio({
|
|
1042
|
-
action: 'treeUpdated',
|
|
1043
|
-
id: PAGE_ID,
|
|
1044
|
-
url: window.location.href,
|
|
1045
|
-
tree: buildNavigatorTree(root),
|
|
1046
|
-
sourceHash: window.__VERYFRONT_SOURCE_HASH__ || null
|
|
1047
|
-
});
|
|
1048
|
-
}
|
|
1049
|
-
|
|
1050
|
-
function debouncedTreeUpdate() {
|
|
1051
|
-
if (treeUpdateTimer) clearTimeout(treeUpdateTimer);
|
|
1052
|
-
treeUpdateTimer = setTimeout(sendTreeUpdate, 150);
|
|
1053
|
-
}
|
|
1054
|
-
|
|
1055
|
-
function setupMutationObserver() {
|
|
1056
|
-
const root = document.getElementById('root') || document.body;
|
|
1057
|
-
if (!root) return;
|
|
1058
|
-
|
|
1059
|
-
mutationObserver = new MutationObserver(function(mutations) {
|
|
1060
|
-
const hasRelevantChanges = mutations.some(m => m.type === 'childList' || m.type === 'characterData');
|
|
1061
|
-
if (hasRelevantChanges) debouncedTreeUpdate();
|
|
1062
|
-
});
|
|
1063
|
-
|
|
1064
|
-
mutationObserver.observe(root, { childList: true, characterData: true, subtree: true });
|
|
1065
|
-
sendTreeUpdate();
|
|
1066
|
-
}
|
|
1067
|
-
|
|
1068
|
-
function showOverlay(overlay, nodeId) {
|
|
1069
|
-
if (!nodeId) {
|
|
1070
|
-
hideOverlay(overlay);
|
|
1071
|
-
return;
|
|
1072
|
-
}
|
|
1073
|
-
|
|
1074
|
-
const el = findElementById(nodeId);
|
|
1075
|
-
if (!el) {
|
|
1076
|
-
hideOverlay(overlay);
|
|
1077
|
-
return;
|
|
1078
|
-
}
|
|
1079
|
-
|
|
1080
|
-
positionOverlay(overlay, el, getNodeName(el));
|
|
1081
|
-
}
|
|
1082
|
-
|
|
1083
|
-
function showHoverOverlay(nodeId) {
|
|
1084
|
-
showOverlay(hoverOverlay, nodeId);
|
|
1085
|
-
}
|
|
1086
|
-
|
|
1087
|
-
function showSelectionOverlay(nodeId) {
|
|
1088
|
-
showOverlay(selectionOverlay, nodeId);
|
|
1089
|
-
}
|
|
1090
|
-
|
|
1091
|
-
function scrollToElement(nodeId) {
|
|
1092
|
-
const el =
|
|
1093
|
-
document.querySelector('[' + DATA_VF_ID + '="' + nodeId + '"]') ||
|
|
1094
|
-
document.querySelector('[' + DATA_NODE_ID + '="' + nodeId + '"]') ||
|
|
1095
|
-
document.querySelector('[' + DATA_VF_SELECTOR + '*="' + nodeId + '"]');
|
|
1096
|
-
|
|
1097
|
-
if (el) el.scrollIntoView({ behavior: 'smooth', block: 'center' });
|
|
1098
|
-
}
|
|
1099
|
-
|
|
1100
|
-
function setupInspectMode() {
|
|
1101
|
-
const INSPECTABLE_SELECTOR = '[' + DATA_VF_ID + '], [' + DATA_VF_SELECTOR + '], [' + DATA_NODE_ID + ']';
|
|
1102
|
-
|
|
1103
|
-
function getElementId(el) {
|
|
1104
|
-
return el.getAttribute(DATA_VF_ID) || el.getAttribute(DATA_NODE_ID) || el.getAttribute(DATA_VF_SELECTOR);
|
|
1105
|
-
}
|
|
1106
|
-
|
|
1107
|
-
document.addEventListener(
|
|
1108
|
-
'click',
|
|
1109
|
-
function(event) {
|
|
1110
|
-
if (!inspectMode) return;
|
|
1111
|
-
|
|
1112
|
-
event.preventDefault();
|
|
1113
|
-
event.stopPropagation();
|
|
1114
|
-
|
|
1115
|
-
const target = event.target.closest(INSPECTABLE_SELECTOR);
|
|
1116
|
-
if (!target) {
|
|
1117
|
-
selectedNodeId = null;
|
|
1118
|
-
hideOverlay(selectionOverlay);
|
|
1119
|
-
postToStudio({ action: 'setSelectedNode', id: null });
|
|
1120
|
-
return;
|
|
1121
|
-
}
|
|
1122
|
-
|
|
1123
|
-
const id = getElementId(target);
|
|
1124
|
-
selectedNodeId = id;
|
|
1125
|
-
showSelectionOverlay(id);
|
|
1126
|
-
postToStudio({ action: 'setSelectedNode', id: id });
|
|
1127
|
-
},
|
|
1128
|
-
true
|
|
1129
|
-
);
|
|
1130
|
-
|
|
1131
|
-
document.addEventListener('pointerover', function(event) {
|
|
1132
|
-
if (!inspectMode || event.pointerType === 'touch') return;
|
|
1133
|
-
|
|
1134
|
-
const target = event.target.closest(INSPECTABLE_SELECTOR);
|
|
1135
|
-
if (!target) return;
|
|
1136
|
-
|
|
1137
|
-
const id = getElementId(target);
|
|
1138
|
-
if (id === hoveredNodeId) return;
|
|
1139
|
-
|
|
1140
|
-
hoveredNodeId = id;
|
|
1141
|
-
showHoverOverlay(id);
|
|
1142
|
-
});
|
|
1143
|
-
|
|
1144
|
-
document.addEventListener('pointerout', function(event) {
|
|
1145
|
-
if (!inspectMode || event.pointerType === 'touch') return;
|
|
1146
|
-
|
|
1147
|
-
const target = event.target.closest(INSPECTABLE_SELECTOR);
|
|
1148
|
-
if (!target) return;
|
|
1149
|
-
|
|
1150
|
-
const relatedTarget = event.relatedTarget;
|
|
1151
|
-
if (relatedTarget && target.contains(relatedTarget)) return;
|
|
1152
|
-
|
|
1153
|
-
hoveredNodeId = null;
|
|
1154
|
-
hideOverlay(hoverOverlay);
|
|
1155
|
-
});
|
|
1156
|
-
|
|
1157
|
-
const updateOverlays = debounce(function() {
|
|
1158
|
-
if (inspectMode && hoveredNodeId) showHoverOverlay(hoveredNodeId);
|
|
1159
|
-
if (selectedNodeId) showSelectionOverlay(selectedNodeId);
|
|
1160
|
-
}, 16);
|
|
1161
|
-
|
|
1162
|
-
window.addEventListener('scroll', updateOverlays, true);
|
|
1163
|
-
window.addEventListener('resize', updateOverlays);
|
|
1164
|
-
}
|
|
1165
|
-
|
|
1166
|
-
function setColorMode(mode) {
|
|
1167
|
-
document.documentElement.setAttribute('data-theme', mode);
|
|
1168
|
-
document.documentElement.classList.remove('light', 'dark');
|
|
1169
|
-
document.documentElement.classList.add(mode);
|
|
1170
|
-
}
|
|
1171
|
-
|
|
1172
|
-
function isMarkdownPage() {
|
|
1173
|
-
if (typeof PAGE_PATH !== 'string') {
|
|
1174
|
-
return false;
|
|
1175
|
-
}
|
|
1176
|
-
const lowerPath = PAGE_PATH.toLowerCase();
|
|
1177
|
-
return lowerPath.endsWith('.md') || lowerPath.endsWith('.mdx');
|
|
1178
|
-
}
|
|
1179
|
-
|
|
1180
|
-
function isMdxPage() {
|
|
1181
|
-
return typeof PAGE_PATH === 'string' && PAGE_PATH.toLowerCase().endsWith('.mdx');
|
|
1182
|
-
}
|
|
1183
|
-
|
|
1184
|
-
function openFilePathInStudio(filePath, lineNumber, symbolName) {
|
|
1185
|
-
if (typeof filePath !== 'string' || !filePath) {
|
|
1186
|
-
return;
|
|
1187
|
-
}
|
|
1188
|
-
const safeLine = Number.isFinite(lineNumber) ? Math.max(1, Math.trunc(lineNumber)) : 1;
|
|
1189
|
-
const payload = {
|
|
1190
|
-
action: 'openFile',
|
|
1191
|
-
filePath: filePath,
|
|
1192
|
-
lineNumber: safeLine,
|
|
1193
|
-
columnNumber: 1
|
|
1194
|
-
};
|
|
1195
|
-
if (typeof symbolName === 'string' && symbolName.trim()) {
|
|
1196
|
-
payload.symbolName = symbolName.trim();
|
|
1197
|
-
}
|
|
1198
|
-
postToStudio(payload);
|
|
1199
|
-
}
|
|
1200
|
-
|
|
1201
|
-
function openMarkdownSourceInStudio(lineNumber) {
|
|
1202
|
-
openFilePathInStudio(PAGE_PATH, lineNumber);
|
|
1203
|
-
}
|
|
1204
|
-
|
|
1205
|
-
function normalizePathSegments(segments) {
|
|
1206
|
-
const stack = [];
|
|
1207
|
-
for (const segment of segments) {
|
|
1208
|
-
if (!segment || segment === '.') {
|
|
1209
|
-
continue;
|
|
1210
|
-
}
|
|
1211
|
-
if (segment === '..') {
|
|
1212
|
-
if (stack.length > 0) {
|
|
1213
|
-
stack.pop();
|
|
1214
|
-
}
|
|
1215
|
-
continue;
|
|
1216
|
-
}
|
|
1217
|
-
stack.push(segment);
|
|
1218
|
-
}
|
|
1219
|
-
return stack;
|
|
1220
|
-
}
|
|
1221
|
-
|
|
1222
|
-
function resolveImportPathForPage(importPath) {
|
|
1223
|
-
const sourcePath = typeof importPath === 'string' ? importPath.trim() : '';
|
|
1224
|
-
if (!sourcePath) {
|
|
1225
|
-
return '';
|
|
1226
|
-
}
|
|
1227
|
-
|
|
1228
|
-
if (sourcePath.startsWith('@/') || sourcePath.startsWith('~/')) {
|
|
1229
|
-
return sourcePath.slice(2);
|
|
1230
|
-
}
|
|
1231
|
-
|
|
1232
|
-
if (sourcePath.startsWith('/')) {
|
|
1233
|
-
let normalizedPath = sourcePath;
|
|
1234
|
-
while (normalizedPath.startsWith('/')) {
|
|
1235
|
-
normalizedPath = normalizedPath.slice(1);
|
|
1236
|
-
}
|
|
1237
|
-
return normalizedPath;
|
|
1238
|
-
}
|
|
1239
|
-
|
|
1240
|
-
if (!PAGE_PATH || !sourcePath.startsWith('.')) {
|
|
1241
|
-
return sourcePath;
|
|
1242
|
-
}
|
|
1243
|
-
|
|
1244
|
-
const baseParts = String(PAGE_PATH).split('/');
|
|
1245
|
-
baseParts.pop();
|
|
1246
|
-
const resolved = normalizePathSegments(baseParts.concat(sourcePath.split('/')));
|
|
1247
|
-
return resolved.join('/');
|
|
1248
|
-
}
|
|
1249
|
-
|
|
1250
|
-
function isLikelyProjectImportPath(importPath) {
|
|
1251
|
-
if (typeof importPath !== 'string') {
|
|
1252
|
-
return false;
|
|
1253
|
-
}
|
|
1254
|
-
const value = importPath.trim();
|
|
1255
|
-
if (!value) {
|
|
1256
|
-
return false;
|
|
1257
|
-
}
|
|
1258
|
-
return (
|
|
1259
|
-
value.startsWith('.') ||
|
|
1260
|
-
value.startsWith('/') ||
|
|
1261
|
-
value.startsWith('@/') ||
|
|
1262
|
-
value.startsWith('~/')
|
|
1263
|
-
);
|
|
1264
|
-
}
|
|
1265
|
-
|
|
1266
|
-
function guessStudioFilePath(filePath) {
|
|
1267
|
-
const sourcePath = typeof filePath === 'string' ? filePath.trim() : '';
|
|
1268
|
-
if (!sourcePath) {
|
|
1269
|
-
return '';
|
|
1270
|
-
}
|
|
1271
|
-
|
|
1272
|
-
const hasKnownExtension = sourcePath.match(/\\.(tsx?|jsx?|mdx?|json|css|scss|sass|less)$/i);
|
|
1273
|
-
if (hasKnownExtension) {
|
|
1274
|
-
return sourcePath;
|
|
1275
|
-
}
|
|
1276
|
-
|
|
1277
|
-
if (sourcePath.endsWith('/')) {
|
|
1278
|
-
return sourcePath + 'index.tsx';
|
|
1279
|
-
}
|
|
1280
|
-
|
|
1281
|
-
return sourcePath + '.tsx';
|
|
1282
|
-
}
|
|
1283
|
-
|
|
1284
|
-
function parseMdxImportMap(content) {
|
|
1285
|
-
const source = typeof content === 'string' ? content : '';
|
|
1286
|
-
const importMap = {};
|
|
1287
|
-
if (!source) {
|
|
1288
|
-
return importMap;
|
|
1289
|
-
}
|
|
1290
|
-
|
|
1291
|
-
const stripImportComments = function(specifierText) {
|
|
1292
|
-
return String(specifierText || '')
|
|
1293
|
-
.replace(/\\/\\*[\\s\\S]*?\\*\\//g, ' ')
|
|
1294
|
-
.replace(/\\/\\/[^\\n\\r]*/g, ' ');
|
|
1295
|
-
};
|
|
1296
|
-
|
|
1297
|
-
const normalizeImportSpecifier = function(specifierText) {
|
|
1298
|
-
return stripImportComments(specifierText)
|
|
1299
|
-
.replace(/\\s+/g, ' ')
|
|
1300
|
-
.trim();
|
|
1301
|
-
};
|
|
1302
|
-
|
|
1303
|
-
const setImportEntry = function(localName, resolvedPath, symbolName, importKind) {
|
|
1304
|
-
const key = typeof localName === 'string' ? localName.trim() : '';
|
|
1305
|
-
const filePath = typeof resolvedPath === 'string' ? resolvedPath.trim() : '';
|
|
1306
|
-
if (!key || !filePath) {
|
|
1307
|
-
return;
|
|
1308
|
-
}
|
|
1309
|
-
importMap[key] = {
|
|
1310
|
-
filePath: filePath,
|
|
1311
|
-
symbolName: typeof symbolName === 'string' ? symbolName.trim() : '',
|
|
1312
|
-
importKind: typeof importKind === 'string' ? importKind : 'unknown'
|
|
1313
|
-
};
|
|
1314
|
-
};
|
|
1315
|
-
|
|
1316
|
-
const mapNamedImports = function(namedSpecifier, resolvedPath) {
|
|
1317
|
-
const text = String(namedSpecifier || '').trim();
|
|
1318
|
-
if (!text.startsWith('{') || !text.endsWith('}')) {
|
|
1319
|
-
return;
|
|
1320
|
-
}
|
|
1321
|
-
const named = text.slice(1, -1).split(',');
|
|
1322
|
-
for (const entry of named) {
|
|
1323
|
-
const part = entry.trim();
|
|
1324
|
-
if (!part) {
|
|
1325
|
-
continue;
|
|
1326
|
-
}
|
|
1327
|
-
const normalizedPart = normalizeImportSpecifier(part).trim();
|
|
1328
|
-
if (!normalizedPart || /^type\\s+/.test(normalizedPart)) {
|
|
1329
|
-
continue;
|
|
1330
|
-
}
|
|
1331
|
-
const aliasMatch = normalizedPart.match(/^([A-Za-z_$][\\w$]*)\\s+as\\s+([A-Za-z_$][\\w$]*)$/);
|
|
1332
|
-
const sourceName = aliasMatch ? aliasMatch[1] : normalizedPart;
|
|
1333
|
-
const localName = aliasMatch ? aliasMatch[2] : normalizedPart;
|
|
1334
|
-
if (localName) {
|
|
1335
|
-
const isDefaultAlias = sourceName === 'default';
|
|
1336
|
-
setImportEntry(localName, resolvedPath, isDefaultAlias ? '' : sourceName, isDefaultAlias ? 'default' : 'named');
|
|
1337
|
-
}
|
|
1338
|
-
}
|
|
1339
|
-
};
|
|
1340
|
-
|
|
1341
|
-
const importPattern = /^import\\s+([\\s\\S]*?)\\s+from\\s+['\"]([^'\"]+)['\"]\\s*;?/gm;
|
|
1342
|
-
let match = importPattern.exec(source);
|
|
1343
|
-
while (match) {
|
|
1344
|
-
const specifier = normalizeImportSpecifier(match[1] || '');
|
|
1345
|
-
if (!specifier) {
|
|
1346
|
-
match = importPattern.exec(source);
|
|
1347
|
-
continue;
|
|
1348
|
-
}
|
|
1349
|
-
const typeOnlySpecifier = specifier.startsWith('type ');
|
|
1350
|
-
const normalizedSpecifier = typeOnlySpecifier ? specifier.slice(5).trim() : specifier;
|
|
1351
|
-
if (typeOnlySpecifier) {
|
|
1352
|
-
match = importPattern.exec(source);
|
|
1353
|
-
continue;
|
|
1354
|
-
}
|
|
1355
|
-
const rawImportPath = String(match[2] || '').trim();
|
|
1356
|
-
if (!isLikelyProjectImportPath(rawImportPath)) {
|
|
1357
|
-
match = importPattern.exec(source);
|
|
1358
|
-
continue;
|
|
1359
|
-
}
|
|
1360
|
-
const resolvedPath = guessStudioFilePath(resolveImportPathForPage(rawImportPath));
|
|
1361
|
-
if (!resolvedPath) {
|
|
1362
|
-
match = importPattern.exec(source);
|
|
1363
|
-
continue;
|
|
1364
|
-
}
|
|
1365
|
-
|
|
1366
|
-
if (normalizedSpecifier.startsWith('{') && normalizedSpecifier.endsWith('}')) {
|
|
1367
|
-
mapNamedImports(normalizedSpecifier, resolvedPath);
|
|
1368
|
-
} else if (normalizedSpecifier.startsWith('* as ')) {
|
|
1369
|
-
const namespaceName = normalizedSpecifier.slice(5).trim();
|
|
1370
|
-
if (namespaceName) {
|
|
1371
|
-
setImportEntry(namespaceName, resolvedPath, '', 'namespace');
|
|
1372
|
-
}
|
|
1373
|
-
} else {
|
|
1374
|
-
const commaIndex = normalizedSpecifier.indexOf(',');
|
|
1375
|
-
if (commaIndex >= 0) {
|
|
1376
|
-
const defaultPart = normalizedSpecifier.slice(0, commaIndex).trim();
|
|
1377
|
-
const restPart = normalizedSpecifier.slice(commaIndex + 1).trim();
|
|
1378
|
-
const normalizedDefaultPart = defaultPart.trim();
|
|
1379
|
-
if (normalizedDefaultPart && !/^type\\s+/.test(normalizedDefaultPart)) {
|
|
1380
|
-
setImportEntry(normalizedDefaultPart, resolvedPath, '', 'default');
|
|
1381
|
-
}
|
|
1382
|
-
if (restPart.startsWith('{')) {
|
|
1383
|
-
mapNamedImports(restPart, resolvedPath);
|
|
1384
|
-
} else if (restPart.startsWith('* as ')) {
|
|
1385
|
-
const namespaceName = restPart.slice(5).trim();
|
|
1386
|
-
if (namespaceName) {
|
|
1387
|
-
setImportEntry(namespaceName, resolvedPath, '', 'namespace');
|
|
1388
|
-
}
|
|
1389
|
-
}
|
|
1390
|
-
} else {
|
|
1391
|
-
const defaultPart = normalizedSpecifier.trim();
|
|
1392
|
-
const normalizedDefaultPart = defaultPart.trim();
|
|
1393
|
-
if (normalizedDefaultPart && !/^type\\s+/.test(normalizedDefaultPart)) {
|
|
1394
|
-
setImportEntry(normalizedDefaultPart, resolvedPath, '', 'default');
|
|
1395
|
-
}
|
|
1396
|
-
}
|
|
1397
|
-
}
|
|
1398
|
-
|
|
1399
|
-
match = importPattern.exec(source);
|
|
1400
|
-
}
|
|
1401
|
-
|
|
1402
|
-
return importMap;
|
|
1403
|
-
}
|
|
1404
|
-
|
|
1405
|
-
function postMarkdownEditorReady() {
|
|
1406
|
-
if (!markdownFileId) {
|
|
1407
|
-
return;
|
|
1408
|
-
}
|
|
1409
|
-
postToStudio({
|
|
1410
|
-
action: 'markdownEditorReady',
|
|
1411
|
-
fileId: markdownFileId,
|
|
1412
|
-
filePath: PAGE_PATH
|
|
1413
|
-
});
|
|
1414
|
-
}
|
|
1415
|
-
|
|
1416
|
-
function scheduleMarkdownSync(content) {
|
|
1417
|
-
if (!markdownFileId) {
|
|
1418
|
-
return;
|
|
1419
|
-
}
|
|
1420
|
-
if (markdownSyncTimer) {
|
|
1421
|
-
clearTimeout(markdownSyncTimer);
|
|
1422
|
-
}
|
|
1423
|
-
markdownSyncTimer = setTimeout(function() {
|
|
1424
|
-
postToStudio({
|
|
1425
|
-
action: 'markdownContentChange',
|
|
1426
|
-
fileId: markdownFileId,
|
|
1427
|
-
filePath: PAGE_PATH,
|
|
1428
|
-
content: content
|
|
1429
|
-
});
|
|
1430
|
-
}, 120);
|
|
1431
|
-
}
|
|
1432
|
-
|
|
1433
|
-
function computeTextDiff(oldText, newText) {
|
|
1434
|
-
var prefixLen = 0;
|
|
1435
|
-
var minLen = Math.min(oldText.length, newText.length);
|
|
1436
|
-
while (prefixLen < minLen && oldText.charCodeAt(prefixLen) === newText.charCodeAt(prefixLen)) {
|
|
1437
|
-
prefixLen++;
|
|
1438
|
-
}
|
|
1439
|
-
var suffixLen = 0;
|
|
1440
|
-
var maxSuffix = minLen - prefixLen;
|
|
1441
|
-
while (suffixLen < maxSuffix &&
|
|
1442
|
-
oldText.charCodeAt(oldText.length - 1 - suffixLen) === newText.charCodeAt(newText.length - 1 - suffixLen)) {
|
|
1443
|
-
suffixLen++;
|
|
1444
|
-
}
|
|
1445
|
-
return {
|
|
1446
|
-
index: prefixLen,
|
|
1447
|
-
deleteCount: oldText.length - prefixLen - suffixLen,
|
|
1448
|
-
insertText: newText.slice(prefixLen, suffixLen > 0 ? newText.length - suffixLen : undefined)
|
|
1449
|
-
};
|
|
1450
|
-
}
|
|
1451
|
-
|
|
1452
|
-
function syncLocalChangeToYText(fullContent) {
|
|
1453
|
-
if (!markdownYText || !markdownYDoc) {
|
|
1454
|
-
return;
|
|
1455
|
-
}
|
|
1456
|
-
var currentYContent = markdownYText.toString();
|
|
1457
|
-
if (currentYContent === fullContent) {
|
|
1458
|
-
return;
|
|
1459
|
-
}
|
|
1460
|
-
var diff = computeTextDiff(currentYContent, fullContent);
|
|
1461
|
-
if (diff.deleteCount === 0 && diff.insertText === '') {
|
|
1462
|
-
return;
|
|
1463
|
-
}
|
|
1464
|
-
markdownYDoc.transact(function() {
|
|
1465
|
-
if (diff.deleteCount > 0) {
|
|
1466
|
-
markdownYText.delete(diff.index, diff.deleteCount);
|
|
1467
|
-
}
|
|
1468
|
-
if (diff.insertText) {
|
|
1469
|
-
markdownYText.insert(diff.index, diff.insertText);
|
|
1470
|
-
}
|
|
1471
|
-
}, LEXICAL_YJS_ORIGIN);
|
|
1472
|
-
}
|
|
1473
|
-
|
|
1474
|
-
function setupMarkdownYjsConnection(config) {
|
|
1475
|
-
if (markdownYDoc) {
|
|
1476
|
-
return;
|
|
1477
|
-
}
|
|
1478
|
-
|
|
1479
|
-
var setupId = ++markdownYjsSetupId;
|
|
1480
|
-
|
|
1481
|
-
Promise.all([
|
|
1482
|
-
import('https://esm.sh/yjs@13.6.28?target=es2022'),
|
|
1483
|
-
import('https://esm.sh/y-websocket@2.1.0?deps=yjs@13.6.28&target=es2022')
|
|
1484
|
-
]).then(function(modules) {
|
|
1485
|
-
// Abort if edit mode was closed while imports were loading
|
|
1486
|
-
if (setupId !== markdownYjsSetupId) {
|
|
1487
|
-
return;
|
|
1488
|
-
}
|
|
1489
|
-
|
|
1490
|
-
var Y = modules[0];
|
|
1491
|
-
var WebsocketProvider = modules[1].WebsocketProvider;
|
|
1492
|
-
markdownYjsY = Y;
|
|
1493
|
-
|
|
1494
|
-
var doc = new Y.Doc({ guid: config.guid });
|
|
1495
|
-
// Cookie auth: authToken cookie on .veryfront.com is sent automatically
|
|
1496
|
-
// with the WebSocket upgrade request. No explicit token param needed.
|
|
1497
|
-
var provider = new WebsocketProvider(config.wsUrl, config.guid, doc, {
|
|
1498
|
-
resyncInterval: -1
|
|
1499
|
-
});
|
|
1500
|
-
|
|
1501
|
-
var ytext = doc.getText(config.fileId);
|
|
1502
|
-
|
|
1503
|
-
markdownYDoc = doc;
|
|
1504
|
-
markdownYProvider = provider;
|
|
1505
|
-
markdownYText = ytext;
|
|
1506
|
-
|
|
1507
|
-
// Filter non-binary messages to prevent y-websocket parse errors
|
|
1508
|
-
provider.on('status', function(event) {
|
|
1509
|
-
console.debug('[StudioBridge] Yjs status:', event.status);
|
|
1510
|
-
if (event.status === 'connected' && provider.ws) {
|
|
1511
|
-
var origOnMessage = provider.ws.onmessage;
|
|
1512
|
-
provider.ws.onmessage = function(wsEvent) {
|
|
1513
|
-
if (typeof wsEvent.data === 'string') {
|
|
1514
|
-
return;
|
|
1515
|
-
}
|
|
1516
|
-
if (origOnMessage) {
|
|
1517
|
-
origOnMessage.call(provider.ws, wsEvent);
|
|
1518
|
-
}
|
|
1519
|
-
};
|
|
1520
|
-
}
|
|
1521
|
-
});
|
|
1522
|
-
|
|
1523
|
-
// Extract user identity from authToken JWT cookie for presence
|
|
1524
|
-
var presenceUser = { id: 'preview-' + Math.random().toString(36).slice(2), name: 'Preview' };
|
|
1525
|
-
try {
|
|
1526
|
-
var cookieMatch = document.cookie.match(/authToken=([^;]+)/);
|
|
1527
|
-
if (cookieMatch) {
|
|
1528
|
-
var parts = cookieMatch[1].split('.');
|
|
1529
|
-
if (parts.length === 3) {
|
|
1530
|
-
var payload = JSON.parse(atob(parts[1].replace(/-/g, '+').replace(/_/g, '/')));
|
|
1531
|
-
if (payload.userId) {
|
|
1532
|
-
presenceUser.id = payload.userId;
|
|
1533
|
-
}
|
|
1534
|
-
if (payload.email) {
|
|
1535
|
-
var local = payload.email.split('@')[0] || '';
|
|
1536
|
-
if (local.includes('.') || local.includes('_')) {
|
|
1537
|
-
presenceUser.name = local.split(/[._]/).map(function(p) { return p.charAt(0).toUpperCase() + p.slice(1); }).join(' ');
|
|
1538
|
-
} else {
|
|
1539
|
-
presenceUser.name = local.charAt(0).toUpperCase() + local.slice(1);
|
|
1540
|
-
}
|
|
1541
|
-
}
|
|
1542
|
-
}
|
|
1543
|
-
}
|
|
1544
|
-
} catch (e) {
|
|
1545
|
-
// Fall back to defaults on any parse error
|
|
1546
|
-
}
|
|
1547
|
-
|
|
1548
|
-
// Set local user on awareness for presence
|
|
1549
|
-
provider.awareness.setLocalStateField('user', {
|
|
1550
|
-
id: presenceUser.id,
|
|
1551
|
-
name: presenceUser.name,
|
|
1552
|
-
color: '#10b981'
|
|
1553
|
-
});
|
|
1554
|
-
|
|
1555
|
-
// Observe awareness for remote presence and selection changes
|
|
1556
|
-
function syncAwareness() {
|
|
1557
|
-
var states = Array.from(provider.awareness.getStates().entries());
|
|
1558
|
-
|
|
1559
|
-
// Sync presence users
|
|
1560
|
-
var users = [];
|
|
1561
|
-
for (var i = 0; i < states.length; i++) {
|
|
1562
|
-
var clientId = states[i][0];
|
|
1563
|
-
var state = states[i][1];
|
|
1564
|
-
var user = state.user;
|
|
1565
|
-
if (!user || typeof user.name !== 'string') {
|
|
1566
|
-
continue;
|
|
1567
|
-
}
|
|
1568
|
-
users.push({
|
|
1569
|
-
id: user.id || String(clientId),
|
|
1570
|
-
name: user.name,
|
|
1571
|
-
color: user.color || '#6b7280',
|
|
1572
|
-
isCurrentUser: clientId === provider.awareness.clientID,
|
|
1573
|
-
isAgent: user.isAgent || false
|
|
1574
|
-
});
|
|
1575
|
-
}
|
|
1576
|
-
setMarkdownPresence(users);
|
|
1577
|
-
|
|
1578
|
-
// Sync remote selections
|
|
1579
|
-
var selections = [];
|
|
1580
|
-
for (var j = 0; j < states.length; j++) {
|
|
1581
|
-
var cId = states[j][0];
|
|
1582
|
-
var st = states[j][1];
|
|
1583
|
-
var u = st.user;
|
|
1584
|
-
var ranges = st.selection;
|
|
1585
|
-
if (!u || !Array.isArray(ranges) || ranges.length === 0) {
|
|
1586
|
-
continue;
|
|
1587
|
-
}
|
|
1588
|
-
for (var k = 0; k < ranges.length; k++) {
|
|
1589
|
-
var range = ranges[k];
|
|
1590
|
-
var anchorPos = Y.createAbsolutePositionFromRelativePosition(range.anchor, doc);
|
|
1591
|
-
var markerPos = Y.createAbsolutePositionFromRelativePosition(range.marker, doc);
|
|
1592
|
-
if (!anchorPos || !markerPos || anchorPos.type !== ytext || markerPos.type !== ytext) {
|
|
1593
|
-
continue;
|
|
1594
|
-
}
|
|
1595
|
-
selections.push({
|
|
1596
|
-
id: u.id || String(cId),
|
|
1597
|
-
name: u.name || 'Anonymous',
|
|
1598
|
-
color: u.color || '#6b7280',
|
|
1599
|
-
isCurrentUser: cId === provider.awareness.clientID,
|
|
1600
|
-
start: Math.min(anchorPos.index, markerPos.index),
|
|
1601
|
-
end: Math.max(anchorPos.index, markerPos.index)
|
|
1602
|
-
});
|
|
1603
|
-
}
|
|
1604
|
-
}
|
|
1605
|
-
setMarkdownSelections(selections);
|
|
1606
|
-
}
|
|
1607
|
-
|
|
1608
|
-
provider.awareness.on('change', syncAwareness);
|
|
1609
|
-
|
|
1610
|
-
provider.on('sync', function(synced) {
|
|
1611
|
-
if (synced && !markdownYjsConnected) {
|
|
1612
|
-
markdownYjsConnected = true;
|
|
1613
|
-
|
|
1614
|
-
var ytextContent = ytext.toString();
|
|
1615
|
-
if (markdownCurrentContent && markdownCurrentContent !== ytextContent) {
|
|
1616
|
-
// User typed before sync completed — push local edits to Y.Text
|
|
1617
|
-
syncLocalChangeToYText(markdownCurrentContent);
|
|
1618
|
-
} else if (ytextContent) {
|
|
1619
|
-
// No local edits — seed editor from Y.Text
|
|
1620
|
-
applyMarkdownContent(ytextContent);
|
|
1621
|
-
}
|
|
1622
|
-
|
|
1623
|
-
// Replay any selection queued before Yjs was ready
|
|
1624
|
-
if (markdownPendingSelection) {
|
|
1625
|
-
var ps = markdownPendingSelection;
|
|
1626
|
-
markdownPendingSelection = null;
|
|
1627
|
-
var cs = Math.max(0, Math.min(ytext.length, ps.start));
|
|
1628
|
-
var ce = Math.max(0, Math.min(ytext.length, ps.end));
|
|
1629
|
-
provider.awareness.setLocalStateField('selection', [{
|
|
1630
|
-
anchor: Y.createRelativePositionFromTypeIndex(ytext, cs),
|
|
1631
|
-
marker: Y.createRelativePositionFromTypeIndex(ytext, ce)
|
|
1632
|
-
}]);
|
|
1633
|
-
}
|
|
1634
|
-
|
|
1635
|
-
// Observe Y.Text for remote changes (from other users / Monaco)
|
|
1636
|
-
ytext.observe(function(event) {
|
|
1637
|
-
if (event.transaction.origin === LEXICAL_YJS_ORIGIN) {
|
|
1638
|
-
return;
|
|
1639
|
-
}
|
|
1640
|
-
var fullContent = ytext.toString();
|
|
1641
|
-
if (fullContent === markdownCurrentContent) {
|
|
1642
|
-
return;
|
|
1643
|
-
}
|
|
1644
|
-
applyMarkdownContent(fullContent);
|
|
1645
|
-
});
|
|
1646
|
-
|
|
1647
|
-
// Initial awareness sync after Yjs is connected
|
|
1648
|
-
syncAwareness();
|
|
1649
|
-
|
|
1650
|
-
console.debug('[StudioBridge] Yjs synced, bound to Y.Text for fileId:', config.fileId);
|
|
1651
|
-
}
|
|
1652
|
-
});
|
|
1653
|
-
}).catch(function(error) {
|
|
1654
|
-
console.error('[StudioBridge] Failed to setup Yjs connection:', error);
|
|
1655
|
-
});
|
|
1656
|
-
}
|
|
1657
|
-
|
|
1658
|
-
function disposeMarkdownYjs() {
|
|
1659
|
-
markdownYjsSetupId++;
|
|
1660
|
-
if (markdownYProvider) {
|
|
1661
|
-
markdownYProvider.disconnect();
|
|
1662
|
-
markdownYProvider.destroy();
|
|
1663
|
-
markdownYProvider = null;
|
|
1664
|
-
}
|
|
1665
|
-
if (markdownYDoc) {
|
|
1666
|
-
markdownYDoc.destroy();
|
|
1667
|
-
markdownYDoc = null;
|
|
1668
|
-
}
|
|
1669
|
-
markdownYText = null;
|
|
1670
|
-
markdownYjsConnected = false;
|
|
1671
|
-
markdownYjsY = null;
|
|
1672
|
-
}
|
|
1673
|
-
|
|
1674
|
-
function getTextOffsetWithinRoot(root, targetNode, targetOffset) {
|
|
1675
|
-
if (!root || !targetNode) {
|
|
1676
|
-
return 0;
|
|
1677
|
-
}
|
|
1678
|
-
|
|
1679
|
-
if (!root.contains(targetNode)) {
|
|
1680
|
-
return 0;
|
|
1681
|
-
}
|
|
1682
|
-
|
|
1683
|
-
try {
|
|
1684
|
-
const range = document.createRange();
|
|
1685
|
-
range.selectNodeContents(root);
|
|
1686
|
-
range.setEnd(targetNode, targetOffset);
|
|
1687
|
-
return range.toString().length;
|
|
1688
|
-
} catch {
|
|
1689
|
-
return 0;
|
|
1690
|
-
}
|
|
1691
|
-
}
|
|
1692
|
-
|
|
1693
|
-
function getMarkdownEditorSelection() {
|
|
1694
|
-
if (markdownLexicalApi && markdownEditorSurface) {
|
|
1695
|
-
const selection = window.getSelection();
|
|
1696
|
-
if (!selection || selection.rangeCount === 0) {
|
|
1697
|
-
return null;
|
|
1698
|
-
}
|
|
1699
|
-
|
|
1700
|
-
const range = selection.getRangeAt(0);
|
|
1701
|
-
if (
|
|
1702
|
-
!markdownEditorSurface.contains(range.startContainer) ||
|
|
1703
|
-
!markdownEditorSurface.contains(range.endContainer)
|
|
1704
|
-
) {
|
|
1705
|
-
return null;
|
|
1706
|
-
}
|
|
1707
|
-
|
|
1708
|
-
const start = getTextOffsetWithinRoot(
|
|
1709
|
-
markdownEditorSurface,
|
|
1710
|
-
range.startContainer,
|
|
1711
|
-
range.startOffset
|
|
1712
|
-
);
|
|
1713
|
-
const end = getTextOffsetWithinRoot(
|
|
1714
|
-
markdownEditorSurface,
|
|
1715
|
-
range.endContainer,
|
|
1716
|
-
range.endOffset
|
|
1717
|
-
);
|
|
1718
|
-
return {
|
|
1719
|
-
start: Math.max(0, Math.min(start, end)),
|
|
1720
|
-
end: Math.max(0, Math.max(start, end))
|
|
1721
|
-
};
|
|
1722
|
-
}
|
|
1723
|
-
|
|
1724
|
-
if (markdownEditorTextarea) {
|
|
1725
|
-
const start = typeof markdownEditorTextarea.selectionStart === 'number'
|
|
1726
|
-
? markdownEditorTextarea.selectionStart
|
|
1727
|
-
: 0;
|
|
1728
|
-
const end = typeof markdownEditorTextarea.selectionEnd === 'number'
|
|
1729
|
-
? markdownEditorTextarea.selectionEnd
|
|
1730
|
-
: start;
|
|
1731
|
-
|
|
1732
|
-
return {
|
|
1733
|
-
start: Math.max(0, Math.min(start, end)),
|
|
1734
|
-
end: Math.max(0, Math.max(start, end))
|
|
1735
|
-
};
|
|
1736
|
-
}
|
|
1737
|
-
|
|
1738
|
-
return null;
|
|
1739
|
-
}
|
|
1740
|
-
|
|
1741
|
-
function getMarkdownRawBlockLength(index) {
|
|
1742
|
-
const rawBlock = markdownRawBlocks[index];
|
|
1743
|
-
if (typeof rawBlock !== 'string') {
|
|
1744
|
-
return 0;
|
|
1745
|
-
}
|
|
1746
|
-
return rawBlock.length;
|
|
1747
|
-
}
|
|
1748
|
-
|
|
1749
|
-
function escapeRegexText(value) {
|
|
1750
|
-
const text = String(value || '');
|
|
1751
|
-
let escaped = '';
|
|
1752
|
-
for (let i = 0; i < text.length; i += 1) {
|
|
1753
|
-
const char = text[i];
|
|
1754
|
-
if ('\\\\^$.*+?()[]{}|'.indexOf(char) >= 0) {
|
|
1755
|
-
escaped += '\\\\' + char;
|
|
1756
|
-
} else {
|
|
1757
|
-
escaped += char;
|
|
1758
|
-
}
|
|
1759
|
-
}
|
|
1760
|
-
return escaped;
|
|
1761
|
-
}
|
|
1762
|
-
|
|
1763
|
-
function getMarkdownRawBlockTokenPattern() {
|
|
1764
|
-
const prefix = typeof markdownRawBlockTokenPrefix === 'string' && markdownRawBlockTokenPrefix
|
|
1765
|
-
? markdownRawBlockTokenPrefix
|
|
1766
|
-
: 'VF_RAW_BLOCK';
|
|
1767
|
-
const escapedPrefix = escapeRegexText(prefix);
|
|
1768
|
-
return new RegExp('\\\\[\\\\[' + escapedPrefix + '_(\\\\d+)\\\\]\\\\]', 'g');
|
|
1769
|
-
}
|
|
1770
|
-
|
|
1771
|
-
function editorOffsetToBodyOffset(editorOffset, bias) {
|
|
1772
|
-
const editorContent = typeof markdownCurrentEditorContent === 'string'
|
|
1773
|
-
? markdownCurrentEditorContent
|
|
1774
|
-
: '';
|
|
1775
|
-
const maxOffset = editorContent.length;
|
|
1776
|
-
const safeOffset = Math.max(0, Math.min(maxOffset, Math.trunc(editorOffset || 0)));
|
|
1777
|
-
const tokenPattern = getMarkdownRawBlockTokenPattern();
|
|
1778
|
-
let diffBefore = 0;
|
|
1779
|
-
let match = tokenPattern.exec(editorContent);
|
|
1780
|
-
|
|
1781
|
-
while (match) {
|
|
1782
|
-
const token = match[0];
|
|
1783
|
-
const tokenStartEditor = match.index;
|
|
1784
|
-
const tokenEndEditor = tokenStartEditor + token.length;
|
|
1785
|
-
const rawLength = getMarkdownRawBlockLength(Number(match[1]));
|
|
1786
|
-
const tokenDelta = rawLength - token.length;
|
|
1787
|
-
const tokenStartBody = tokenStartEditor + diffBefore;
|
|
1788
|
-
|
|
1789
|
-
if (safeOffset >= tokenEndEditor) {
|
|
1790
|
-
diffBefore += tokenDelta;
|
|
1791
|
-
match = tokenPattern.exec(editorContent);
|
|
1792
|
-
continue;
|
|
1793
|
-
}
|
|
1794
|
-
|
|
1795
|
-
if (safeOffset > tokenStartEditor) {
|
|
1796
|
-
if (bias === 'end') {
|
|
1797
|
-
return tokenStartBody + rawLength;
|
|
1798
|
-
}
|
|
1799
|
-
return tokenStartBody;
|
|
1800
|
-
}
|
|
1801
|
-
break;
|
|
1802
|
-
}
|
|
1803
|
-
|
|
1804
|
-
return safeOffset + diffBefore;
|
|
1805
|
-
}
|
|
1806
|
-
|
|
1807
|
-
function bodyOffsetToEditorOffset(bodyOffset, bias) {
|
|
1808
|
-
const editorContent = typeof markdownCurrentEditorContent === 'string'
|
|
1809
|
-
? markdownCurrentEditorContent
|
|
1810
|
-
: '';
|
|
1811
|
-
const safeBodyOffset = Math.max(0, Math.trunc(bodyOffset || 0));
|
|
1812
|
-
const tokenPattern = getMarkdownRawBlockTokenPattern();
|
|
1813
|
-
let diffBefore = 0;
|
|
1814
|
-
let match = tokenPattern.exec(editorContent);
|
|
1815
|
-
|
|
1816
|
-
while (match) {
|
|
1817
|
-
const token = match[0];
|
|
1818
|
-
const tokenStartEditor = match.index;
|
|
1819
|
-
const tokenEndEditor = tokenStartEditor + token.length;
|
|
1820
|
-
const rawLength = getMarkdownRawBlockLength(Number(match[1]));
|
|
1821
|
-
const tokenStartBody = tokenStartEditor + diffBefore;
|
|
1822
|
-
const tokenEndBody = tokenStartBody + rawLength;
|
|
1823
|
-
|
|
1824
|
-
if (safeBodyOffset > tokenEndBody) {
|
|
1825
|
-
diffBefore += rawLength - token.length;
|
|
1826
|
-
match = tokenPattern.exec(editorContent);
|
|
1827
|
-
continue;
|
|
1828
|
-
}
|
|
1829
|
-
|
|
1830
|
-
if (safeBodyOffset >= tokenStartBody && safeBodyOffset <= tokenEndBody) {
|
|
1831
|
-
if (bias === 'end') {
|
|
1832
|
-
return tokenEndEditor;
|
|
1833
|
-
}
|
|
1834
|
-
return tokenStartEditor;
|
|
1835
|
-
}
|
|
1836
|
-
|
|
1837
|
-
const mappedOffset = safeBodyOffset - diffBefore;
|
|
1838
|
-
return Math.max(0, Math.min(editorContent.length, mappedOffset));
|
|
1839
|
-
}
|
|
1840
|
-
|
|
1841
|
-
const mappedOffset = safeBodyOffset - diffBefore;
|
|
1842
|
-
return Math.max(0, Math.min(editorContent.length, mappedOffset));
|
|
1843
|
-
}
|
|
1844
|
-
|
|
1845
|
-
function editorOffsetToSourceOffset(editorOffset, bias) {
|
|
1846
|
-
const frontmatterLength = typeof markdownFrontmatter === 'string' ? markdownFrontmatter.length : 0;
|
|
1847
|
-
const bodyOffset = editorOffsetToBodyOffset(editorOffset, bias);
|
|
1848
|
-
return Math.max(0, frontmatterLength + bodyOffset);
|
|
1849
|
-
}
|
|
1850
|
-
|
|
1851
|
-
function sourceSelectionToEditorRange(start, end) {
|
|
1852
|
-
const frontmatterLength = typeof markdownFrontmatter === 'string' ? markdownFrontmatter.length : 0;
|
|
1853
|
-
const safeStart = Math.max(0, Math.trunc(start || 0));
|
|
1854
|
-
const safeEnd = Math.max(0, Math.trunc(end || 0));
|
|
1855
|
-
const sourceStart = Math.min(safeStart, safeEnd);
|
|
1856
|
-
const sourceEnd = Math.max(safeStart, safeEnd);
|
|
1857
|
-
|
|
1858
|
-
if (sourceEnd <= frontmatterLength) {
|
|
1859
|
-
return null;
|
|
1860
|
-
}
|
|
1861
|
-
|
|
1862
|
-
const bodyStart = Math.max(0, sourceStart - frontmatterLength);
|
|
1863
|
-
const bodyEnd = Math.max(0, sourceEnd - frontmatterLength);
|
|
1864
|
-
const editorStart = bodyOffsetToEditorOffset(bodyStart, 'start');
|
|
1865
|
-
const editorEnd = bodyOffsetToEditorOffset(bodyEnd, 'end');
|
|
1866
|
-
|
|
1867
|
-
return {
|
|
1868
|
-
start: Math.max(0, Math.min(editorStart, editorEnd)),
|
|
1869
|
-
end: Math.max(0, Math.max(editorStart, editorEnd))
|
|
1870
|
-
};
|
|
1871
|
-
}
|
|
1872
|
-
|
|
1873
|
-
function setMarkdownEditorSelection(start, end) {
|
|
1874
|
-
const safeStart = Math.max(0, Math.trunc(start || 0));
|
|
1875
|
-
const endValue = typeof end === 'number' ? end : safeStart;
|
|
1876
|
-
const safeEnd = Math.max(0, Math.trunc(endValue));
|
|
1877
|
-
|
|
1878
|
-
if (markdownLexicalApi && markdownEditorSurface) {
|
|
1879
|
-
const selection = window.getSelection();
|
|
1880
|
-
if (!selection) {
|
|
1881
|
-
return;
|
|
1882
|
-
}
|
|
1883
|
-
|
|
1884
|
-
const anchor = resolveMarkdownTextPoint(markdownEditorSurface, safeStart);
|
|
1885
|
-
const focus = resolveMarkdownTextPoint(markdownEditorSurface, safeEnd);
|
|
1886
|
-
try {
|
|
1887
|
-
const range = document.createRange();
|
|
1888
|
-
range.setStart(anchor.node, anchor.offset);
|
|
1889
|
-
range.setEnd(focus.node, focus.offset);
|
|
1890
|
-
selection.removeAllRanges();
|
|
1891
|
-
selection.addRange(range);
|
|
1892
|
-
} catch {
|
|
1893
|
-
// Ignore when point resolution races with Lexical DOM updates.
|
|
1894
|
-
}
|
|
1895
|
-
return;
|
|
1896
|
-
}
|
|
1897
|
-
|
|
1898
|
-
if (markdownEditorTextarea) {
|
|
1899
|
-
const max = markdownEditorTextarea.value.length;
|
|
1900
|
-
markdownEditorTextarea.setSelectionRange(Math.min(safeStart, max), Math.min(safeEnd, max));
|
|
1901
|
-
}
|
|
1902
|
-
}
|
|
1903
|
-
|
|
1904
|
-
function hideMarkdownSlashMenu() {
|
|
1905
|
-
markdownSlashMenuContext = null;
|
|
1906
|
-
markdownSlashMenuCommands = [];
|
|
1907
|
-
markdownSlashMenuActiveIndex = 0;
|
|
1908
|
-
if (!markdownSlashMenuRoot) {
|
|
1909
|
-
return;
|
|
1910
|
-
}
|
|
1911
|
-
markdownSlashMenuRoot.style.display = 'none';
|
|
1912
|
-
markdownSlashMenuRoot.textContent = '';
|
|
1913
|
-
}
|
|
1914
|
-
|
|
1915
|
-
function getMarkdownSlashCommandInsert(id, indent) {
|
|
1916
|
-
const prefix = typeof indent === 'string' ? indent : '';
|
|
1917
|
-
if (id === 'heading-1') {
|
|
1918
|
-
const text = prefix + '# ';
|
|
1919
|
-
return { text: text, caretOffset: text.length };
|
|
1920
|
-
}
|
|
1921
|
-
if (id === 'heading-2') {
|
|
1922
|
-
const text = prefix + '## ';
|
|
1923
|
-
return { text: text, caretOffset: text.length };
|
|
1924
|
-
}
|
|
1925
|
-
if (id === 'heading-3') {
|
|
1926
|
-
const text = prefix + '### ';
|
|
1927
|
-
return { text: text, caretOffset: text.length };
|
|
1928
|
-
}
|
|
1929
|
-
if (id === 'bulleted-list') {
|
|
1930
|
-
const text = prefix + '- ';
|
|
1931
|
-
return { text: text, caretOffset: text.length };
|
|
1932
|
-
}
|
|
1933
|
-
if (id === 'numbered-list') {
|
|
1934
|
-
const text = prefix + '1. ';
|
|
1935
|
-
return { text: text, caretOffset: text.length };
|
|
1936
|
-
}
|
|
1937
|
-
if (id === 'quote-block') {
|
|
1938
|
-
const text = prefix + '> ';
|
|
1939
|
-
return { text: text, caretOffset: text.length };
|
|
1940
|
-
}
|
|
1941
|
-
if (id === 'code-block') {
|
|
1942
|
-
const fence = String.fromCharCode(96, 96, 96);
|
|
1943
|
-
const text = prefix + fence + '\\n' + prefix + '\\n' + prefix + fence;
|
|
1944
|
-
return {
|
|
1945
|
-
text: text,
|
|
1946
|
-
caretOffset: (prefix + fence + '\\n' + prefix).length
|
|
1947
|
-
};
|
|
1948
|
-
}
|
|
1949
|
-
if (id === 'image') {
|
|
1950
|
-
const text = prefix + '';
|
|
1951
|
-
return {
|
|
1952
|
-
text: text,
|
|
1953
|
-
caretOffset: (prefix + '.length
|
|
1954
|
-
};
|
|
1955
|
-
}
|
|
1956
|
-
return null;
|
|
1957
|
-
}
|
|
1958
|
-
|
|
1959
|
-
function applyMarkdownSlashCommand(index) {
|
|
1960
|
-
if (!markdownSlashMenuContext || markdownSlashMenuCommands.length === 0) {
|
|
1961
|
-
return false;
|
|
1962
|
-
}
|
|
1963
|
-
|
|
1964
|
-
const command = markdownSlashMenuCommands[index];
|
|
1965
|
-
if (!command) {
|
|
1966
|
-
return false;
|
|
1967
|
-
}
|
|
1968
|
-
|
|
1969
|
-
const insert = getMarkdownSlashCommandInsert(command.id, markdownSlashMenuContext.indent);
|
|
1970
|
-
if (!insert) {
|
|
1971
|
-
return false;
|
|
1972
|
-
}
|
|
1973
|
-
|
|
1974
|
-
const editorContent = typeof markdownCurrentEditorContent === 'string'
|
|
1975
|
-
? markdownCurrentEditorContent
|
|
1976
|
-
: '';
|
|
1977
|
-
const before = editorContent.slice(0, markdownSlashMenuContext.lineStart);
|
|
1978
|
-
const after = editorContent.slice(markdownSlashMenuContext.caret);
|
|
1979
|
-
const nextEditorContent = before + insert.text + after;
|
|
1980
|
-
const nextCaret = before.length + insert.caretOffset;
|
|
1981
|
-
const nextFullContent = composeMarkdownContent(restoreRawBlocksFromEditor(nextEditorContent));
|
|
1982
|
-
const hasChanged = nextFullContent !== markdownCurrentContent;
|
|
1983
|
-
|
|
1984
|
-
applyMarkdownContent(nextFullContent);
|
|
1985
|
-
if (hasChanged) {
|
|
1986
|
-
markdownHasUnsavedChanges = true;
|
|
1987
|
-
scheduleMarkdownSync(nextFullContent);
|
|
1988
|
-
}
|
|
1989
|
-
|
|
1990
|
-
setTimeout(function() {
|
|
1991
|
-
focusMarkdownEditor();
|
|
1992
|
-
setMarkdownEditorSelection(nextCaret, nextCaret);
|
|
1993
|
-
scheduleMarkdownSelectionSync();
|
|
1994
|
-
scheduleMarkdownSelectionOverlayRender();
|
|
1995
|
-
scheduleMarkdownSlashMenuUpdate();
|
|
1996
|
-
}, 0);
|
|
1997
|
-
|
|
1998
|
-
hideMarkdownSlashMenu();
|
|
1999
|
-
return true;
|
|
2000
|
-
}
|
|
2001
|
-
|
|
2002
|
-
function renderMarkdownSlashMenu() {
|
|
2003
|
-
if (!markdownSlashMenuRoot || !markdownSlashMenuContext || markdownSlashMenuCommands.length === 0) {
|
|
2004
|
-
hideMarkdownSlashMenu();
|
|
2005
|
-
return;
|
|
2006
|
-
}
|
|
2007
|
-
|
|
2008
|
-
markdownSlashMenuRoot.textContent = '';
|
|
2009
|
-
|
|
2010
|
-
const maxLeft = Math.max(8, window.innerWidth - 312);
|
|
2011
|
-
const maxTop = Math.max(8, window.innerHeight - 220);
|
|
2012
|
-
const left = Math.max(8, Math.min(maxLeft, markdownSlashMenuContext.anchorLeft));
|
|
2013
|
-
const top = Math.max(8, Math.min(maxTop, markdownSlashMenuContext.anchorTop));
|
|
2014
|
-
markdownSlashMenuRoot.style.left = left + 'px';
|
|
2015
|
-
markdownSlashMenuRoot.style.top = top + 'px';
|
|
2016
|
-
|
|
2017
|
-
markdownSlashMenuCommands.forEach(function(command, index) {
|
|
2018
|
-
const item = document.createElement('button');
|
|
2019
|
-
item.type = 'button';
|
|
2020
|
-
item.className = 'vf-markdown-editor__slash-item';
|
|
2021
|
-
item.setAttribute('data-active', index === markdownSlashMenuActiveIndex ? 'true' : 'false');
|
|
2022
|
-
item.setAttribute(DATA_VF_IGNORE, 'true');
|
|
2023
|
-
item.addEventListener('mousedown', function(event) {
|
|
2024
|
-
event.preventDefault();
|
|
2025
|
-
});
|
|
2026
|
-
item.addEventListener('click', function(event) {
|
|
2027
|
-
event.preventDefault();
|
|
2028
|
-
markdownSlashMenuActiveIndex = index;
|
|
2029
|
-
applyMarkdownSlashCommand(markdownSlashMenuActiveIndex);
|
|
2030
|
-
});
|
|
2031
|
-
|
|
2032
|
-
const title = document.createElement('span');
|
|
2033
|
-
title.className = 'vf-markdown-editor__slash-item-title';
|
|
2034
|
-
title.textContent = command.label;
|
|
2035
|
-
|
|
2036
|
-
const description = document.createElement('span');
|
|
2037
|
-
description.className = 'vf-markdown-editor__slash-item-desc';
|
|
2038
|
-
description.textContent = command.description;
|
|
2039
|
-
|
|
2040
|
-
item.appendChild(title);
|
|
2041
|
-
item.appendChild(description);
|
|
2042
|
-
markdownSlashMenuRoot.appendChild(item);
|
|
2043
|
-
});
|
|
2044
|
-
|
|
2045
|
-
markdownSlashMenuRoot.style.display = 'block';
|
|
2046
|
-
}
|
|
2047
|
-
|
|
2048
|
-
function updateMarkdownSlashMenu() {
|
|
2049
|
-
if (
|
|
2050
|
-
!markdownEditorRoot ||
|
|
2051
|
-
markdownEditorRoot.style.display !== 'block' ||
|
|
2052
|
-
!markdownLexicalApi ||
|
|
2053
|
-
!markdownEditorSurface ||
|
|
2054
|
-
markdownEditorSurface.style.display === 'none'
|
|
2055
|
-
) {
|
|
2056
|
-
hideMarkdownSlashMenu();
|
|
2057
|
-
return;
|
|
2058
|
-
}
|
|
2059
|
-
|
|
2060
|
-
const selection = getMarkdownEditorSelection();
|
|
2061
|
-
if (!selection || selection.start !== selection.end) {
|
|
2062
|
-
hideMarkdownSlashMenu();
|
|
2063
|
-
return;
|
|
2064
|
-
}
|
|
2065
|
-
|
|
2066
|
-
const caret = selection.start;
|
|
2067
|
-
const editorContent = typeof markdownCurrentEditorContent === 'string'
|
|
2068
|
-
? markdownCurrentEditorContent
|
|
2069
|
-
: '';
|
|
2070
|
-
const lineStart = editorContent.lastIndexOf('\\n', Math.max(0, caret - 1)) + 1;
|
|
2071
|
-
const line = editorContent.slice(lineStart, caret);
|
|
2072
|
-
const match = line.match(/^(\\s*)\\/([a-z0-9-]*)$/i);
|
|
2073
|
-
if (!match) {
|
|
2074
|
-
hideMarkdownSlashMenu();
|
|
2075
|
-
return;
|
|
2076
|
-
}
|
|
2077
|
-
|
|
2078
|
-
const query = (match[2] || '').toLowerCase();
|
|
2079
|
-
const commands = MARKDOWN_SLASH_COMMANDS.filter(function(command) {
|
|
2080
|
-
if (!query) {
|
|
2081
|
-
return true;
|
|
2082
|
-
}
|
|
2083
|
-
if (command.label.toLowerCase().includes(query)) {
|
|
2084
|
-
return true;
|
|
2085
|
-
}
|
|
2086
|
-
return command.aliases.some(function(alias) {
|
|
2087
|
-
return alias.startsWith(query);
|
|
2088
|
-
});
|
|
2089
|
-
});
|
|
2090
|
-
|
|
2091
|
-
if (commands.length === 0) {
|
|
2092
|
-
hideMarkdownSlashMenu();
|
|
2093
|
-
return;
|
|
2094
|
-
}
|
|
2095
|
-
|
|
2096
|
-
const domSelection = window.getSelection();
|
|
2097
|
-
if (!domSelection || domSelection.rangeCount === 0) {
|
|
2098
|
-
hideMarkdownSlashMenu();
|
|
2099
|
-
return;
|
|
2100
|
-
}
|
|
2101
|
-
const caretRect = domSelection.getRangeAt(0).getBoundingClientRect();
|
|
2102
|
-
const anchorLeft = caretRect.left;
|
|
2103
|
-
const anchorTop = caretRect.bottom + 8;
|
|
2104
|
-
|
|
2105
|
-
markdownSlashMenuCommands = commands.slice(0, 8);
|
|
2106
|
-
markdownSlashMenuActiveIndex = Math.max(0, Math.min(markdownSlashMenuActiveIndex, markdownSlashMenuCommands.length - 1));
|
|
2107
|
-
markdownSlashMenuContext = {
|
|
2108
|
-
lineStart: lineStart,
|
|
2109
|
-
caret: caret,
|
|
2110
|
-
indent: match[1] || '',
|
|
2111
|
-
query: query,
|
|
2112
|
-
anchorLeft: anchorLeft,
|
|
2113
|
-
anchorTop: anchorTop
|
|
2114
|
-
};
|
|
2115
|
-
renderMarkdownSlashMenu();
|
|
2116
|
-
}
|
|
2117
|
-
|
|
2118
|
-
function scheduleMarkdownSlashMenuUpdate() {
|
|
2119
|
-
if (markdownSlashMenuTimer) {
|
|
2120
|
-
clearTimeout(markdownSlashMenuTimer);
|
|
2121
|
-
}
|
|
2122
|
-
markdownSlashMenuTimer = setTimeout(function() {
|
|
2123
|
-
markdownSlashMenuTimer = null;
|
|
2124
|
-
updateMarkdownSlashMenu();
|
|
2125
|
-
}, 0);
|
|
2126
|
-
}
|
|
2127
|
-
|
|
2128
|
-
function handleMarkdownSlashMenuKeydown(event) {
|
|
2129
|
-
if (!markdownSlashMenuRoot || markdownSlashMenuRoot.style.display !== 'block' || markdownSlashMenuCommands.length === 0) {
|
|
2130
|
-
return false;
|
|
2131
|
-
}
|
|
2132
|
-
|
|
2133
|
-
if (event.key === 'ArrowDown') {
|
|
2134
|
-
event.preventDefault();
|
|
2135
|
-
markdownSlashMenuActiveIndex = (markdownSlashMenuActiveIndex + 1) % markdownSlashMenuCommands.length;
|
|
2136
|
-
renderMarkdownSlashMenu();
|
|
2137
|
-
return true;
|
|
2138
|
-
}
|
|
2139
|
-
|
|
2140
|
-
if (event.key === 'ArrowUp') {
|
|
2141
|
-
event.preventDefault();
|
|
2142
|
-
markdownSlashMenuActiveIndex = (markdownSlashMenuActiveIndex - 1 + markdownSlashMenuCommands.length) % markdownSlashMenuCommands.length;
|
|
2143
|
-
renderMarkdownSlashMenu();
|
|
2144
|
-
return true;
|
|
2145
|
-
}
|
|
2146
|
-
|
|
2147
|
-
if (event.key === 'Enter' || event.key === 'Tab') {
|
|
2148
|
-
event.preventDefault();
|
|
2149
|
-
return applyMarkdownSlashCommand(markdownSlashMenuActiveIndex);
|
|
2150
|
-
}
|
|
2151
|
-
|
|
2152
|
-
if (event.key === 'Escape') {
|
|
2153
|
-
event.preventDefault();
|
|
2154
|
-
hideMarkdownSlashMenu();
|
|
2155
|
-
return true;
|
|
2156
|
-
}
|
|
2157
|
-
|
|
2158
|
-
return false;
|
|
2159
|
-
}
|
|
2160
|
-
|
|
2161
|
-
function hideMarkdownInlineToolbar() {
|
|
2162
|
-
if (!markdownInlineToolbarRoot) {
|
|
2163
|
-
return;
|
|
2164
|
-
}
|
|
2165
|
-
markdownInlineToolbarRoot.style.display = 'none';
|
|
2166
|
-
}
|
|
2167
|
-
|
|
2168
|
-
function toggleMarkdownInlineFormat(format) {
|
|
2169
|
-
if (!markdownLexicalApi || !markdownLexicalApi.editor || !markdownLexicalApi.lexicalModule) {
|
|
2170
|
-
return;
|
|
2171
|
-
}
|
|
2172
|
-
if (typeof format !== 'string' || !format) {
|
|
2173
|
-
return;
|
|
2174
|
-
}
|
|
2175
|
-
|
|
2176
|
-
markdownLexicalApi.editor.focus();
|
|
2177
|
-
markdownLexicalApi.editor.dispatchCommand(markdownLexicalApi.lexicalModule.FORMAT_TEXT_COMMAND, format);
|
|
2178
|
-
scheduleMarkdownInlineToolbarUpdate();
|
|
2179
|
-
}
|
|
2180
|
-
|
|
2181
|
-
function updateMarkdownInlineToolbar() {
|
|
2182
|
-
if (
|
|
2183
|
-
!markdownInlineToolbarRoot ||
|
|
2184
|
-
!markdownEditorRoot ||
|
|
2185
|
-
markdownEditorRoot.style.display !== 'block' ||
|
|
2186
|
-
!markdownLexicalApi ||
|
|
2187
|
-
!markdownEditorSurface ||
|
|
2188
|
-
markdownEditorSurface.style.display === 'none'
|
|
2189
|
-
) {
|
|
2190
|
-
hideMarkdownInlineToolbar();
|
|
2191
|
-
return;
|
|
2192
|
-
}
|
|
2193
|
-
|
|
2194
|
-
const selection = window.getSelection();
|
|
2195
|
-
if (!selection || selection.rangeCount === 0 || selection.isCollapsed) {
|
|
2196
|
-
hideMarkdownInlineToolbar();
|
|
2197
|
-
return;
|
|
2198
|
-
}
|
|
2199
|
-
|
|
2200
|
-
const range = selection.getRangeAt(0);
|
|
2201
|
-
if (
|
|
2202
|
-
!markdownEditorSurface.contains(range.startContainer) ||
|
|
2203
|
-
!markdownEditorSurface.contains(range.endContainer)
|
|
2204
|
-
) {
|
|
2205
|
-
hideMarkdownInlineToolbar();
|
|
2206
|
-
return;
|
|
2207
|
-
}
|
|
2208
|
-
|
|
2209
|
-
const rect = range.getBoundingClientRect();
|
|
2210
|
-
if (!rect || rect.width <= 0 || rect.height <= 0) {
|
|
2211
|
-
hideMarkdownInlineToolbar();
|
|
2212
|
-
return;
|
|
2213
|
-
}
|
|
2214
|
-
|
|
2215
|
-
const left = Math.max(8, Math.min(window.innerWidth - 180, rect.left + rect.width / 2 - 66));
|
|
2216
|
-
const top = Math.max(8, Math.min(window.innerHeight - 56, rect.top - 44));
|
|
2217
|
-
markdownInlineToolbarRoot.style.left = left + 'px';
|
|
2218
|
-
markdownInlineToolbarRoot.style.top = top + 'px';
|
|
2219
|
-
markdownInlineToolbarRoot.style.display = 'inline-flex';
|
|
2220
|
-
}
|
|
2221
|
-
|
|
2222
|
-
function scheduleMarkdownInlineToolbarUpdate() {
|
|
2223
|
-
if (markdownInlineToolbarFrame) {
|
|
2224
|
-
cancelAnimationFrame(markdownInlineToolbarFrame);
|
|
2225
|
-
}
|
|
2226
|
-
|
|
2227
|
-
markdownInlineToolbarFrame = requestAnimationFrame(function() {
|
|
2228
|
-
markdownInlineToolbarFrame = null;
|
|
2229
|
-
updateMarkdownInlineToolbar();
|
|
2230
|
-
});
|
|
2231
|
-
}
|
|
2232
|
-
|
|
2233
|
-
function getMarkdownTopLevelBlocks() {
|
|
2234
|
-
if (!markdownEditorSurface) {
|
|
2235
|
-
return [];
|
|
2236
|
-
}
|
|
2237
|
-
|
|
2238
|
-
return Array.from(markdownEditorSurface.children).filter(function(node) {
|
|
2239
|
-
return node && node.nodeType === Node.ELEMENT_NODE;
|
|
2240
|
-
});
|
|
2241
|
-
}
|
|
2242
|
-
|
|
2243
|
-
function hideMarkdownBlockDragHandle() {
|
|
2244
|
-
markdownBlockHandleHoverIndex = -1;
|
|
2245
|
-
if (!markdownBlockDragHandle) {
|
|
2246
|
-
return;
|
|
2247
|
-
}
|
|
2248
|
-
markdownBlockDragHandle.style.display = 'none';
|
|
2249
|
-
markdownBlockDragHandle.removeAttribute('data-block-index');
|
|
2250
|
-
}
|
|
2251
|
-
|
|
2252
|
-
function hideMarkdownBlockDropIndicator() {
|
|
2253
|
-
markdownBlockDropSlotIndex = -1;
|
|
2254
|
-
if (markdownBlockDropIndicator) {
|
|
2255
|
-
markdownBlockDropIndicator.style.display = 'none';
|
|
2256
|
-
}
|
|
2257
|
-
if (markdownBlockDropLabel) {
|
|
2258
|
-
markdownBlockDropLabel.style.display = 'none';
|
|
2259
|
-
markdownBlockDropLabel.textContent = '';
|
|
2260
|
-
}
|
|
2261
|
-
}
|
|
2262
|
-
|
|
2263
|
-
function hideMarkdownBlockDragUi() {
|
|
2264
|
-
markdownBlockDragActive = false;
|
|
2265
|
-
markdownBlockDragSourceIndex = -1;
|
|
2266
|
-
if (markdownBlockDragHandle) {
|
|
2267
|
-
markdownBlockDragHandle.setAttribute('data-dragging', 'false');
|
|
2268
|
-
}
|
|
2269
|
-
removeMarkdownDragGhost();
|
|
2270
|
-
hideMarkdownBlockDropIndicator();
|
|
2271
|
-
hideMarkdownBlockDragHandle();
|
|
2272
|
-
}
|
|
2273
|
-
|
|
2274
|
-
function getMarkdownBlockElementFromNode(node) {
|
|
2275
|
-
if (!markdownEditorSurface || !node) {
|
|
2276
|
-
return null;
|
|
2277
|
-
}
|
|
2278
|
-
|
|
2279
|
-
let current = node.nodeType === Node.ELEMENT_NODE ? node : node.parentElement;
|
|
2280
|
-
while (current && current.parentElement !== markdownEditorSurface) {
|
|
2281
|
-
current = current.parentElement;
|
|
2282
|
-
}
|
|
2283
|
-
|
|
2284
|
-
if (!current || current.parentElement !== markdownEditorSurface) {
|
|
2285
|
-
return null;
|
|
2286
|
-
}
|
|
2287
|
-
return current;
|
|
2288
|
-
}
|
|
2289
|
-
|
|
2290
|
-
function getMarkdownBlockTypeInfo(block) {
|
|
2291
|
-
if (!block || !block.tagName) {
|
|
2292
|
-
return { label: 'block', color: '#0081f8' };
|
|
2293
|
-
}
|
|
2294
|
-
|
|
2295
|
-
const tag = block.tagName.toLowerCase();
|
|
2296
|
-
if (tag === 'h1') {
|
|
2297
|
-
return { label: 'heading 1', color: '#7c3aed' };
|
|
2298
|
-
}
|
|
2299
|
-
if (tag === 'h2') {
|
|
2300
|
-
return { label: 'heading 2', color: '#7c3aed' };
|
|
2301
|
-
}
|
|
2302
|
-
if (tag === 'h3') {
|
|
2303
|
-
return { label: 'heading 3', color: '#7c3aed' };
|
|
2304
|
-
}
|
|
2305
|
-
if (tag === 'ul' || tag === 'ol') {
|
|
2306
|
-
return { label: 'list', color: '#0d9488' };
|
|
2307
|
-
}
|
|
2308
|
-
if (tag === 'blockquote') {
|
|
2309
|
-
return { label: 'quote', color: '#2563eb' };
|
|
2310
|
-
}
|
|
2311
|
-
if (tag === 'pre') {
|
|
2312
|
-
return { label: 'code block', color: '#ea580c' };
|
|
2313
|
-
}
|
|
2314
|
-
if (tag === 'img' || tag === 'figure') {
|
|
2315
|
-
return { label: 'image', color: '#db2777' };
|
|
2316
|
-
}
|
|
2317
|
-
if (tag === 'p') {
|
|
2318
|
-
return { label: 'paragraph', color: '#16a34a' };
|
|
2319
|
-
}
|
|
2320
|
-
return { label: tag, color: '#0081f8' };
|
|
2321
|
-
}
|
|
2322
|
-
|
|
2323
|
-
function getMarkdownBlockPreviewText(block) {
|
|
2324
|
-
if (!block) {
|
|
2325
|
-
return '';
|
|
2326
|
-
}
|
|
2327
|
-
const text = String(block.textContent || '').replace(new RegExp('\\s+', 'g'), ' ').trim();
|
|
2328
|
-
if (!text) {
|
|
2329
|
-
return 'Empty block';
|
|
2330
|
-
}
|
|
2331
|
-
if (text.length > 84) {
|
|
2332
|
-
return text.slice(0, 84) + '...';
|
|
2333
|
-
}
|
|
2334
|
-
return text;
|
|
2335
|
-
}
|
|
2336
|
-
|
|
2337
|
-
function removeMarkdownDragGhost() {
|
|
2338
|
-
if (!markdownBlockDragGhost) {
|
|
2339
|
-
return;
|
|
2340
|
-
}
|
|
2341
|
-
markdownBlockDragGhost.remove();
|
|
2342
|
-
markdownBlockDragGhost = null;
|
|
2343
|
-
}
|
|
2344
|
-
|
|
2345
|
-
function createMarkdownDragGhost(block) {
|
|
2346
|
-
const typeInfo = getMarkdownBlockTypeInfo(block);
|
|
2347
|
-
const ghost = document.createElement('div');
|
|
2348
|
-
ghost.className = 'vf-markdown-editor__block-drag-ghost';
|
|
2349
|
-
ghost.setAttribute(DATA_VF_IGNORE, 'true');
|
|
2350
|
-
|
|
2351
|
-
const title = document.createElement('span');
|
|
2352
|
-
title.className = 'vf-markdown-editor__block-drag-ghost-title';
|
|
2353
|
-
title.setAttribute(DATA_VF_IGNORE, 'true');
|
|
2354
|
-
title.textContent = 'Moving ' + typeInfo.label;
|
|
2355
|
-
|
|
2356
|
-
const text = document.createElement('span');
|
|
2357
|
-
text.className = 'vf-markdown-editor__block-drag-ghost-text';
|
|
2358
|
-
text.setAttribute(DATA_VF_IGNORE, 'true');
|
|
2359
|
-
text.textContent = getMarkdownBlockPreviewText(block);
|
|
2360
|
-
|
|
2361
|
-
ghost.appendChild(title);
|
|
2362
|
-
ghost.appendChild(text);
|
|
2363
|
-
return ghost;
|
|
2364
|
-
}
|
|
2365
|
-
|
|
2366
|
-
function getLineNumberForOffset(text, offset) {
|
|
2367
|
-
const source = typeof text === 'string' ? text : '';
|
|
2368
|
-
const maxOffset = Math.max(0, Math.min(source.length, Math.trunc(offset || 0)));
|
|
2369
|
-
let line = 1;
|
|
2370
|
-
for (let i = 0; i < maxOffset; i += 1) {
|
|
2371
|
-
if (source.charCodeAt(i) === 10) {
|
|
2372
|
-
line += 1;
|
|
2373
|
-
}
|
|
2374
|
-
}
|
|
2375
|
-
return line;
|
|
2376
|
-
}
|
|
2377
|
-
|
|
2378
|
-
function getMdxComponentName(blockText) {
|
|
2379
|
-
const source = typeof blockText === 'string' ? blockText : '';
|
|
2380
|
-
const fence = String.fromCharCode(96, 96, 96);
|
|
2381
|
-
const componentMatch = source.match(/<\\s*([A-Z][\\w.]*)/);
|
|
2382
|
-
if (componentMatch && componentMatch[1]) {
|
|
2383
|
-
return componentMatch[1];
|
|
2384
|
-
}
|
|
2385
|
-
if (source.trim().startsWith(fence + 'tsx')) {
|
|
2386
|
-
return 'tsx block';
|
|
2387
|
-
}
|
|
2388
|
-
if (source.trim().startsWith(fence + 'jsx')) {
|
|
2389
|
-
return 'jsx block';
|
|
2390
|
-
}
|
|
2391
|
-
return 'component block';
|
|
2392
|
-
}
|
|
2393
|
-
|
|
2394
|
-
function getMdxBlockOpenUiState(block) {
|
|
2395
|
-
const hasResolvedTarget = !!(block && typeof block.filePath === 'string' && block.filePath.trim());
|
|
2396
|
-
return {
|
|
2397
|
-
hasResolvedTarget: hasResolvedTarget,
|
|
2398
|
-
buttonLabel: hasResolvedTarget ? 'Edit in Studio' : 'Open MDX source',
|
|
2399
|
-
showUnresolvedNote: !hasResolvedTarget
|
|
2400
|
-
};
|
|
2401
|
-
}
|
|
2402
|
-
|
|
2403
|
-
function setMarkdownMdxBlocks(blocks) {
|
|
2404
|
-
markdownLatestMdxBlocks = Array.isArray(blocks) ? blocks : [];
|
|
2405
|
-
if (!markdownMdxBlocksRoot) {
|
|
2406
|
-
return;
|
|
2407
|
-
}
|
|
2408
|
-
|
|
2409
|
-
markdownMdxBlocksRoot.textContent = '';
|
|
2410
|
-
if (!isMdxPage() || markdownLatestMdxBlocks.length === 0) {
|
|
2411
|
-
markdownMdxBlocksRoot.style.display = 'none';
|
|
2412
|
-
return;
|
|
2413
|
-
}
|
|
2414
|
-
|
|
2415
|
-
markdownMdxBlocksRoot.style.display = 'flex';
|
|
2416
|
-
for (const block of markdownLatestMdxBlocks.slice(0, 8)) {
|
|
2417
|
-
if (!block || typeof block.label !== 'string') {
|
|
2418
|
-
continue;
|
|
2419
|
-
}
|
|
2420
|
-
|
|
2421
|
-
const item = document.createElement('div');
|
|
2422
|
-
item.className = 'vf-markdown-editor__mdx-block';
|
|
2423
|
-
item.setAttribute(DATA_VF_IGNORE, 'true');
|
|
2424
|
-
|
|
2425
|
-
const label = document.createElement('div');
|
|
2426
|
-
label.className = 'vf-markdown-editor__mdx-block-label';
|
|
2427
|
-
label.setAttribute(DATA_VF_IGNORE, 'true');
|
|
2428
|
-
const safeLine = Number.isFinite(block.lineNumber) ? Math.max(1, Math.trunc(block.lineNumber)) : 1;
|
|
2429
|
-
label.textContent = block.label + ' (line ' + String(safeLine) + ')';
|
|
2430
|
-
const openUiState = getMdxBlockOpenUiState(block);
|
|
2431
|
-
|
|
2432
|
-
const openButton = document.createElement('button');
|
|
2433
|
-
openButton.type = 'button';
|
|
2434
|
-
openButton.className = 'vf-markdown-editor__mdx-open';
|
|
2435
|
-
openButton.setAttribute(DATA_VF_IGNORE, 'true');
|
|
2436
|
-
openButton.textContent = openUiState.buttonLabel;
|
|
2437
|
-
if (openUiState.showUnresolvedNote) {
|
|
2438
|
-
openButton.title = 'Component import could not be resolved. Opening current MDX source.';
|
|
2439
|
-
}
|
|
2440
|
-
openButton.addEventListener('click', function() {
|
|
2441
|
-
const targetFile = typeof block.filePath === 'string' && block.filePath ? block.filePath : PAGE_PATH;
|
|
2442
|
-
const targetLine = targetFile === PAGE_PATH ? safeLine : 1;
|
|
2443
|
-
const targetSymbol = targetFile === PAGE_PATH
|
|
2444
|
-
? ''
|
|
2445
|
-
: (typeof block.symbolName === 'string' ? block.symbolName : '');
|
|
2446
|
-
openFilePathInStudio(targetFile, targetLine, targetSymbol);
|
|
2447
|
-
});
|
|
2448
|
-
|
|
2449
|
-
item.appendChild(label);
|
|
2450
|
-
if (openUiState.showUnresolvedNote) {
|
|
2451
|
-
const fallbackNote = document.createElement('span');
|
|
2452
|
-
fallbackNote.className = 'vf-markdown-editor__mdx-note';
|
|
2453
|
-
fallbackNote.setAttribute(DATA_VF_IGNORE, 'true');
|
|
2454
|
-
fallbackNote.textContent = 'Unresolved import';
|
|
2455
|
-
item.appendChild(fallbackNote);
|
|
2456
|
-
}
|
|
2457
|
-
item.appendChild(openButton);
|
|
2458
|
-
markdownMdxBlocksRoot.appendChild(item);
|
|
2459
|
-
}
|
|
2460
|
-
|
|
2461
|
-
if (markdownMdxBlocksRoot.childNodes.length === 0) {
|
|
2462
|
-
markdownMdxBlocksRoot.style.display = 'none';
|
|
2463
|
-
}
|
|
2464
|
-
}
|
|
2465
|
-
|
|
2466
|
-
function getMarkdownBlockHoverIndexFromPointer(targetNode, clientX, clientY) {
|
|
2467
|
-
const blocks = getMarkdownTopLevelBlocks();
|
|
2468
|
-
if (blocks.length === 0) {
|
|
2469
|
-
return -1;
|
|
2470
|
-
}
|
|
2471
|
-
|
|
2472
|
-
const directBlock = getMarkdownBlockElementFromNode(targetNode);
|
|
2473
|
-
const directIndex = blocks.indexOf(directBlock);
|
|
2474
|
-
if (directIndex >= 0) {
|
|
2475
|
-
return directIndex;
|
|
2476
|
-
}
|
|
2477
|
-
|
|
2478
|
-
if (!markdownEditorSurface) {
|
|
2479
|
-
return -1;
|
|
2480
|
-
}
|
|
2481
|
-
const surfaceRect = markdownEditorSurface.getBoundingClientRect();
|
|
2482
|
-
const leftBoundary = surfaceRect.left - 44;
|
|
2483
|
-
const rightBoundary = surfaceRect.left + Math.min(96, surfaceRect.width * 0.35);
|
|
2484
|
-
if (clientX < leftBoundary || clientX > rightBoundary) {
|
|
2485
|
-
return -1;
|
|
2486
|
-
}
|
|
2487
|
-
|
|
2488
|
-
for (let i = 0; i < blocks.length; i += 1) {
|
|
2489
|
-
const rect = blocks[i].getBoundingClientRect();
|
|
2490
|
-
if (clientY >= rect.top - 4 && clientY <= rect.bottom + 4) {
|
|
2491
|
-
return i;
|
|
2492
|
-
}
|
|
2493
|
-
}
|
|
2494
|
-
|
|
2495
|
-
const firstRect = blocks[0].getBoundingClientRect();
|
|
2496
|
-
const lastRect = blocks[blocks.length - 1].getBoundingClientRect();
|
|
2497
|
-
if (clientY < firstRect.top) {
|
|
2498
|
-
return 0;
|
|
2499
|
-
}
|
|
2500
|
-
if (clientY > lastRect.bottom) {
|
|
2501
|
-
return blocks.length - 1;
|
|
2502
|
-
}
|
|
2503
|
-
return -1;
|
|
2504
|
-
}
|
|
2505
|
-
|
|
2506
|
-
function positionMarkdownBlockDragHandle(block, index) {
|
|
2507
|
-
if (!markdownBlockDragHandle || !block || !markdownEditorRoot || markdownEditorRoot.style.display !== 'block') {
|
|
2508
|
-
hideMarkdownBlockDragHandle();
|
|
2509
|
-
return;
|
|
2510
|
-
}
|
|
2511
|
-
|
|
2512
|
-
const rect = block.getBoundingClientRect();
|
|
2513
|
-
const surfaceRect = markdownEditorSurface ? markdownEditorSurface.getBoundingClientRect() : null;
|
|
2514
|
-
if (!surfaceRect || rect.width <= 0 || rect.height <= 0) {
|
|
2515
|
-
hideMarkdownBlockDragHandle();
|
|
2516
|
-
return;
|
|
2517
|
-
}
|
|
2518
|
-
|
|
2519
|
-
// Ignore blocks outside the visible scroll viewport.
|
|
2520
|
-
if (rect.bottom < surfaceRect.top || rect.top > surfaceRect.bottom) {
|
|
2521
|
-
hideMarkdownBlockDragHandle();
|
|
2522
|
-
return;
|
|
2523
|
-
}
|
|
2524
|
-
|
|
2525
|
-
const left = Math.max(6, rect.left - 36);
|
|
2526
|
-
const top = Math.max(6, rect.top + 1);
|
|
2527
|
-
markdownBlockDragHandle.style.left = left + 'px';
|
|
2528
|
-
markdownBlockDragHandle.style.top = top + 'px';
|
|
2529
|
-
markdownBlockDragHandle.style.display = 'block';
|
|
2530
|
-
markdownBlockDragHandle.setAttribute('data-block-index', String(index));
|
|
2531
|
-
markdownBlockHandleHoverIndex = index;
|
|
2532
|
-
}
|
|
2533
|
-
|
|
2534
|
-
function refreshMarkdownBlockDragHandlePosition() {
|
|
2535
|
-
if (markdownBlockDragActive || markdownBlockHandleHoverIndex < 0) {
|
|
2536
|
-
return;
|
|
2537
|
-
}
|
|
2538
|
-
const blocks = getMarkdownTopLevelBlocks();
|
|
2539
|
-
const block = blocks[markdownBlockHandleHoverIndex];
|
|
2540
|
-
if (!block) {
|
|
2541
|
-
hideMarkdownBlockDragHandle();
|
|
2542
|
-
return;
|
|
2543
|
-
}
|
|
2544
|
-
positionMarkdownBlockDragHandle(block, markdownBlockHandleHoverIndex);
|
|
2545
|
-
}
|
|
2546
|
-
|
|
2547
|
-
function getMarkdownDropSlotIndexFromPointer(clientY) {
|
|
2548
|
-
const blocks = getMarkdownTopLevelBlocks();
|
|
2549
|
-
if (blocks.length === 0) {
|
|
2550
|
-
return -1;
|
|
2551
|
-
}
|
|
2552
|
-
|
|
2553
|
-
for (let i = 0; i < blocks.length; i += 1) {
|
|
2554
|
-
const rect = blocks[i].getBoundingClientRect();
|
|
2555
|
-
const midpoint = rect.top + rect.height / 2;
|
|
2556
|
-
if (clientY < midpoint) {
|
|
2557
|
-
return i;
|
|
2558
|
-
}
|
|
2559
|
-
}
|
|
2560
|
-
return blocks.length;
|
|
2561
|
-
}
|
|
2562
|
-
|
|
2563
|
-
function autoScrollMarkdownSurfaceDuringDrag(clientY) {
|
|
2564
|
-
if (!markdownEditorSurface) {
|
|
2565
|
-
return;
|
|
2566
|
-
}
|
|
2567
|
-
|
|
2568
|
-
const rect = markdownEditorSurface.getBoundingClientRect();
|
|
2569
|
-
const threshold = 42;
|
|
2570
|
-
const maxStep = 20;
|
|
2571
|
-
let delta = 0;
|
|
2572
|
-
|
|
2573
|
-
if (clientY < rect.top + threshold) {
|
|
2574
|
-
const distance = rect.top + threshold - clientY;
|
|
2575
|
-
delta = -Math.min(maxStep, Math.max(2, Math.floor(distance / 3)));
|
|
2576
|
-
} else if (clientY > rect.bottom - threshold) {
|
|
2577
|
-
const distance = clientY - (rect.bottom - threshold);
|
|
2578
|
-
delta = Math.min(maxStep, Math.max(2, Math.floor(distance / 3)));
|
|
2579
|
-
}
|
|
2580
|
-
|
|
2581
|
-
if (delta !== 0) {
|
|
2582
|
-
markdownEditorSurface.scrollTop += delta;
|
|
2583
|
-
refreshMarkdownBlockDragHandlePosition();
|
|
2584
|
-
}
|
|
2585
|
-
}
|
|
2586
|
-
|
|
2587
|
-
function showMarkdownBlockDropIndicator(slotIndex) {
|
|
2588
|
-
if (!markdownBlockDropIndicator || !markdownEditorSurface) {
|
|
2589
|
-
return;
|
|
2590
|
-
}
|
|
2591
|
-
|
|
2592
|
-
const blocks = getMarkdownTopLevelBlocks();
|
|
2593
|
-
if (blocks.length === 0) {
|
|
2594
|
-
hideMarkdownBlockDropIndicator();
|
|
2595
|
-
return;
|
|
2596
|
-
}
|
|
2597
|
-
|
|
2598
|
-
const safeSlot = Math.max(0, Math.min(blocks.length, Math.trunc(slotIndex || 0)));
|
|
2599
|
-
const surfaceRect = markdownEditorSurface.getBoundingClientRect();
|
|
2600
|
-
let top = surfaceRect.top + 6;
|
|
2601
|
-
|
|
2602
|
-
if (safeSlot >= blocks.length) {
|
|
2603
|
-
const lastRect = blocks[blocks.length - 1].getBoundingClientRect();
|
|
2604
|
-
top = lastRect.bottom + 1;
|
|
2605
|
-
} else {
|
|
2606
|
-
const rect = blocks[safeSlot].getBoundingClientRect();
|
|
2607
|
-
top = rect.top - 1;
|
|
2608
|
-
}
|
|
2609
|
-
|
|
2610
|
-
markdownBlockDropIndicator.style.left = Math.max(8, surfaceRect.left + 8) + 'px';
|
|
2611
|
-
markdownBlockDropIndicator.style.top = Math.max(8, top) + 'px';
|
|
2612
|
-
markdownBlockDropIndicator.style.width = Math.max(40, surfaceRect.width - 16) + 'px';
|
|
2613
|
-
markdownBlockDropIndicator.style.display = 'block';
|
|
2614
|
-
markdownBlockDropSlotIndex = safeSlot;
|
|
2615
|
-
|
|
2616
|
-
const dropType = safeSlot >= blocks.length
|
|
2617
|
-
? { label: 'end of document', color: '#0284c7' }
|
|
2618
|
-
: getMarkdownBlockTypeInfo(blocks[safeSlot]);
|
|
2619
|
-
markdownBlockDropIndicator.style.background = dropType.color;
|
|
2620
|
-
markdownBlockDropIndicator.style.boxShadow = '0 1px 6px ' + dropType.color;
|
|
2621
|
-
|
|
2622
|
-
if (markdownBlockDropLabel) {
|
|
2623
|
-
markdownBlockDropLabel.textContent = safeSlot >= blocks.length
|
|
2624
|
-
? 'Drop at end'
|
|
2625
|
-
: 'Drop before ' + dropType.label;
|
|
2626
|
-
markdownBlockDropLabel.style.left = Math.max(8, surfaceRect.left + 8) + 'px';
|
|
2627
|
-
markdownBlockDropLabel.style.top = Math.max(8, top - 26) + 'px';
|
|
2628
|
-
markdownBlockDropLabel.style.borderColor = dropType.color;
|
|
2629
|
-
markdownBlockDropLabel.style.display = 'block';
|
|
2630
|
-
}
|
|
2631
|
-
}
|
|
2632
|
-
|
|
2633
|
-
function moveMarkdownLexicalBlock(sourceIndex, targetSlotIndex) {
|
|
2634
|
-
if (!markdownLexicalApi || !markdownLexicalApi.editor || !markdownLexicalApi.lexicalModule) {
|
|
2635
|
-
return false;
|
|
2636
|
-
}
|
|
2637
|
-
|
|
2638
|
-
const source = Math.trunc(sourceIndex);
|
|
2639
|
-
const targetSlot = Math.trunc(targetSlotIndex);
|
|
2640
|
-
if (!Number.isInteger(source) || !Number.isInteger(targetSlot)) {
|
|
2641
|
-
return false;
|
|
2642
|
-
}
|
|
2643
|
-
|
|
2644
|
-
let didMove = false;
|
|
2645
|
-
markdownLexicalApi.editor.update(function() {
|
|
2646
|
-
const root = markdownLexicalApi.lexicalModule.$getRoot();
|
|
2647
|
-
const children = root.getChildren();
|
|
2648
|
-
const maxSlot = children.length;
|
|
2649
|
-
if (source < 0 || source >= maxSlot || targetSlot < 0 || targetSlot > maxSlot) {
|
|
2650
|
-
return;
|
|
2651
|
-
}
|
|
2652
|
-
|
|
2653
|
-
let adjustedSlot = targetSlot;
|
|
2654
|
-
if (source < adjustedSlot) {
|
|
2655
|
-
adjustedSlot -= 1;
|
|
2656
|
-
}
|
|
2657
|
-
if (adjustedSlot === source) {
|
|
2658
|
-
return;
|
|
2659
|
-
}
|
|
2660
|
-
|
|
2661
|
-
const node = children[source];
|
|
2662
|
-
node.remove();
|
|
2663
|
-
const afterRemoval = root.getChildren();
|
|
2664
|
-
if (adjustedSlot >= afterRemoval.length) {
|
|
2665
|
-
root.append(node);
|
|
2666
|
-
} else {
|
|
2667
|
-
afterRemoval[adjustedSlot].insertBefore(node);
|
|
2668
|
-
}
|
|
2669
|
-
didMove = true;
|
|
2670
|
-
});
|
|
2671
|
-
|
|
2672
|
-
if (didMove) {
|
|
2673
|
-
scheduleMarkdownSelectionSync();
|
|
2674
|
-
scheduleMarkdownSelectionOverlayRender();
|
|
2675
|
-
scheduleMarkdownSlashMenuUpdate();
|
|
2676
|
-
scheduleMarkdownInlineToolbarUpdate();
|
|
2677
|
-
}
|
|
2678
|
-
return didMove;
|
|
2679
|
-
}
|
|
2680
|
-
|
|
2681
|
-
function getMarkdownCurrentBlockIndexFromSelection() {
|
|
2682
|
-
if (!markdownEditorSurface) {
|
|
2683
|
-
return -1;
|
|
2684
|
-
}
|
|
2685
|
-
const selection = window.getSelection();
|
|
2686
|
-
if (!selection || selection.rangeCount === 0) {
|
|
2687
|
-
return -1;
|
|
2688
|
-
}
|
|
2689
|
-
|
|
2690
|
-
const range = selection.getRangeAt(0);
|
|
2691
|
-
if (!markdownEditorSurface.contains(range.startContainer)) {
|
|
2692
|
-
return -1;
|
|
2693
|
-
}
|
|
2694
|
-
|
|
2695
|
-
const block = getMarkdownBlockElementFromNode(range.startContainer);
|
|
2696
|
-
if (!block) {
|
|
2697
|
-
return -1;
|
|
2698
|
-
}
|
|
2699
|
-
|
|
2700
|
-
const blocks = getMarkdownTopLevelBlocks();
|
|
2701
|
-
return blocks.indexOf(block);
|
|
2702
|
-
}
|
|
2703
|
-
|
|
2704
|
-
function moveMarkdownCurrentBlockByDelta(delta) {
|
|
2705
|
-
const blocks = getMarkdownTopLevelBlocks();
|
|
2706
|
-
if (blocks.length <= 1) {
|
|
2707
|
-
return false;
|
|
2708
|
-
}
|
|
2709
|
-
|
|
2710
|
-
const index = getMarkdownCurrentBlockIndexFromSelection();
|
|
2711
|
-
if (index < 0) {
|
|
2712
|
-
return false;
|
|
2713
|
-
}
|
|
2714
|
-
|
|
2715
|
-
const step = Math.sign(delta);
|
|
2716
|
-
if (step === 0) {
|
|
2717
|
-
return false;
|
|
2718
|
-
}
|
|
2719
|
-
|
|
2720
|
-
let targetSlot = index;
|
|
2721
|
-
if (step < 0) {
|
|
2722
|
-
targetSlot = Math.max(0, index - 1);
|
|
2723
|
-
} else {
|
|
2724
|
-
targetSlot = Math.min(blocks.length, index + 2);
|
|
2725
|
-
}
|
|
2726
|
-
|
|
2727
|
-
const moved = moveMarkdownLexicalBlock(index, targetSlot);
|
|
2728
|
-
if (moved) {
|
|
2729
|
-
setTimeout(function() {
|
|
2730
|
-
const nextBlocks = getMarkdownTopLevelBlocks();
|
|
2731
|
-
const nextIndex = Math.max(0, Math.min(nextBlocks.length - 1, index + step));
|
|
2732
|
-
const nextBlock = nextBlocks[nextIndex];
|
|
2733
|
-
if (nextBlock) {
|
|
2734
|
-
positionMarkdownBlockDragHandle(nextBlock, nextIndex);
|
|
2735
|
-
}
|
|
2736
|
-
}, 0);
|
|
2737
|
-
}
|
|
2738
|
-
return moved;
|
|
2739
|
-
}
|
|
2740
|
-
|
|
2741
|
-
function scheduleMarkdownSelectionSync() {
|
|
2742
|
-
if (!markdownFileId) {
|
|
2743
|
-
return;
|
|
2744
|
-
}
|
|
2745
|
-
|
|
2746
|
-
if (markdownSelectionSyncTimer) {
|
|
2747
|
-
clearTimeout(markdownSelectionSyncTimer);
|
|
2748
|
-
}
|
|
2749
|
-
|
|
2750
|
-
markdownSelectionSyncTimer = setTimeout(function() {
|
|
2751
|
-
const selection = getMarkdownEditorSelection();
|
|
2752
|
-
if (!selection) {
|
|
2753
|
-
return;
|
|
2754
|
-
}
|
|
2755
|
-
|
|
2756
|
-
const start = editorOffsetToSourceOffset(selection.start, 'start');
|
|
2757
|
-
const end = editorOffsetToSourceOffset(selection.end, 'end');
|
|
2758
|
-
|
|
2759
|
-
// Set local selection on Yjs awareness directly
|
|
2760
|
-
if (markdownYjsConnected && markdownYText && markdownYjsY && markdownYProvider) {
|
|
2761
|
-
var clampedStart = Math.max(0, Math.min(markdownYText.length, start));
|
|
2762
|
-
var clampedEnd = Math.max(0, Math.min(markdownYText.length, end));
|
|
2763
|
-
markdownYProvider.awareness.setLocalStateField('selection', [{
|
|
2764
|
-
anchor: markdownYjsY.createRelativePositionFromTypeIndex(markdownYText, clampedStart),
|
|
2765
|
-
marker: markdownYjsY.createRelativePositionFromTypeIndex(markdownYText, clampedEnd)
|
|
2766
|
-
}]);
|
|
2767
|
-
markdownPendingSelection = null;
|
|
2768
|
-
} else {
|
|
2769
|
-
// Queue selection for replay after Yjs connects
|
|
2770
|
-
markdownPendingSelection = { start: start, end: end };
|
|
2771
|
-
}
|
|
2772
|
-
}, 80);
|
|
2773
|
-
}
|
|
2774
|
-
|
|
2775
|
-
function clearMarkdownSelectionSync() {
|
|
2776
|
-
if (markdownYProvider) {
|
|
2777
|
-
markdownYProvider.awareness.setLocalStateField('selection', null);
|
|
2778
|
-
}
|
|
2779
|
-
}
|
|
2780
|
-
|
|
2781
|
-
function clearMarkdownSelectionOverlay() {
|
|
2782
|
-
if (!markdownSelectionOverlayRoot) {
|
|
2783
|
-
return;
|
|
2784
|
-
}
|
|
2785
|
-
markdownSelectionOverlayRoot.textContent = '';
|
|
2786
|
-
markdownSelectionOverlayRoot.style.display = 'none';
|
|
2787
|
-
}
|
|
2788
|
-
|
|
2789
|
-
function resolveMarkdownTextPoint(root, rawOffset) {
|
|
2790
|
-
const offset = Math.max(0, Math.trunc(rawOffset || 0));
|
|
2791
|
-
const walker = document.createTreeWalker(root, NodeFilter.SHOW_TEXT);
|
|
2792
|
-
let remaining = offset;
|
|
2793
|
-
let lastTextNode = null;
|
|
2794
|
-
let node = walker.nextNode();
|
|
2795
|
-
|
|
2796
|
-
while (node) {
|
|
2797
|
-
lastTextNode = node;
|
|
2798
|
-
const textLength = node.textContent ? node.textContent.length : 0;
|
|
2799
|
-
if (remaining <= textLength) {
|
|
2800
|
-
return { node: node, offset: remaining };
|
|
2801
|
-
}
|
|
2802
|
-
remaining -= textLength;
|
|
2803
|
-
node = walker.nextNode();
|
|
2804
|
-
}
|
|
2805
|
-
|
|
2806
|
-
if (lastTextNode) {
|
|
2807
|
-
const textLength = lastTextNode.textContent ? lastTextNode.textContent.length : 0;
|
|
2808
|
-
return { node: lastTextNode, offset: textLength };
|
|
2809
|
-
}
|
|
2810
|
-
|
|
2811
|
-
return {
|
|
2812
|
-
node: root,
|
|
2813
|
-
offset: offset > 0 ? root.childNodes.length : 0
|
|
2814
|
-
};
|
|
2815
|
-
}
|
|
2816
|
-
|
|
2817
|
-
function createMarkdownEditorRange(start, end) {
|
|
2818
|
-
if (!markdownEditorSurface) {
|
|
2819
|
-
return null;
|
|
2820
|
-
}
|
|
2821
|
-
|
|
2822
|
-
const safeStart = Math.max(0, Math.min(start, end));
|
|
2823
|
-
const safeEnd = Math.max(0, Math.max(start, end));
|
|
2824
|
-
const startPoint = resolveMarkdownTextPoint(markdownEditorSurface, safeStart);
|
|
2825
|
-
const endPoint = resolveMarkdownTextPoint(markdownEditorSurface, safeEnd);
|
|
2826
|
-
|
|
2827
|
-
try {
|
|
2828
|
-
const range = document.createRange();
|
|
2829
|
-
range.setStart(startPoint.node, startPoint.offset);
|
|
2830
|
-
range.setEnd(endPoint.node, endPoint.offset);
|
|
2831
|
-
return range;
|
|
2832
|
-
} catch {
|
|
2833
|
-
return null;
|
|
2834
|
-
}
|
|
2835
|
-
}
|
|
2836
|
-
|
|
2837
|
-
function toMarkdownOverlayRect(rect, surfaceRect) {
|
|
2838
|
-
const rawLeft = rect.left - surfaceRect.left;
|
|
2839
|
-
const rawTop = rect.top - surfaceRect.top;
|
|
2840
|
-
const rawRight = rawLeft + rect.width;
|
|
2841
|
-
const rawBottom = rawTop + rect.height;
|
|
2842
|
-
|
|
2843
|
-
const left = Math.max(0, rawLeft);
|
|
2844
|
-
const top = Math.max(0, rawTop);
|
|
2845
|
-
const right = Math.min(surfaceRect.width, rawRight);
|
|
2846
|
-
const bottom = Math.min(surfaceRect.height, rawBottom);
|
|
2847
|
-
const width = right - left;
|
|
2848
|
-
const height = bottom - top;
|
|
2849
|
-
|
|
2850
|
-
if (width <= 0 || height <= 0) {
|
|
2851
|
-
return null;
|
|
2852
|
-
}
|
|
2853
|
-
|
|
2854
|
-
return {
|
|
2855
|
-
left: left,
|
|
2856
|
-
top: top,
|
|
2857
|
-
width: width,
|
|
2858
|
-
height: height
|
|
2859
|
-
};
|
|
2860
|
-
}
|
|
2861
|
-
|
|
2862
|
-
function renderMarkdownSelectionOverlay() {
|
|
2863
|
-
if (!markdownSelectionOverlayRoot) {
|
|
2864
|
-
return;
|
|
2865
|
-
}
|
|
2866
|
-
|
|
2867
|
-
if (
|
|
2868
|
-
!markdownEditorRoot ||
|
|
2869
|
-
markdownEditorRoot.style.display !== 'block' ||
|
|
2870
|
-
!markdownEditorSurface ||
|
|
2871
|
-
!markdownLexicalApi ||
|
|
2872
|
-
!Array.isArray(markdownOverlaySelections) ||
|
|
2873
|
-
markdownOverlaySelections.length === 0
|
|
2874
|
-
) {
|
|
2875
|
-
clearMarkdownSelectionOverlay();
|
|
2876
|
-
return;
|
|
2877
|
-
}
|
|
2878
|
-
|
|
2879
|
-
const surfaceRect = markdownEditorSurface.getBoundingClientRect();
|
|
2880
|
-
if (surfaceRect.width <= 0 || surfaceRect.height <= 0) {
|
|
2881
|
-
clearMarkdownSelectionOverlay();
|
|
2882
|
-
return;
|
|
2883
|
-
}
|
|
2884
|
-
|
|
2885
|
-
const computedStyle = window.getComputedStyle(markdownEditorSurface);
|
|
2886
|
-
const lineHeight = Math.max(14, Number.parseFloat(computedStyle.lineHeight || '0') || 22);
|
|
2887
|
-
markdownSelectionOverlayRoot.textContent = '';
|
|
2888
|
-
markdownSelectionOverlayRoot.style.display = 'block';
|
|
2889
|
-
|
|
2890
|
-
for (const selection of markdownOverlaySelections) {
|
|
2891
|
-
if (!selection || typeof selection.start !== 'number' || typeof selection.end !== 'number') {
|
|
2892
|
-
continue;
|
|
2893
|
-
}
|
|
2894
|
-
|
|
2895
|
-
const range = createMarkdownEditorRange(selection.start, selection.end);
|
|
2896
|
-
if (!range) {
|
|
2897
|
-
continue;
|
|
2898
|
-
}
|
|
2899
|
-
|
|
2900
|
-
const color = typeof selection.color === 'string' && selection.color ? selection.color : '#6b7280';
|
|
2901
|
-
const name = typeof selection.name === 'string' && selection.name ? selection.name : 'Anonymous';
|
|
2902
|
-
let labelAnchor = null;
|
|
2903
|
-
|
|
2904
|
-
if (selection.start === selection.end) {
|
|
2905
|
-
const caretRect = range.getBoundingClientRect();
|
|
2906
|
-
const clippedCaret = toMarkdownOverlayRect(
|
|
2907
|
-
{
|
|
2908
|
-
left: caretRect.left,
|
|
2909
|
-
top: caretRect.top,
|
|
2910
|
-
width: 2,
|
|
2911
|
-
height: Math.max(caretRect.height, lineHeight)
|
|
2912
|
-
},
|
|
2913
|
-
surfaceRect
|
|
2914
|
-
);
|
|
2915
|
-
|
|
2916
|
-
if (!clippedCaret) {
|
|
2917
|
-
continue;
|
|
2918
|
-
}
|
|
2919
|
-
|
|
2920
|
-
const caret = document.createElement('div');
|
|
2921
|
-
caret.className = 'vf-markdown-editor__selection-caret';
|
|
2922
|
-
caret.setAttribute(DATA_VF_IGNORE, 'true');
|
|
2923
|
-
caret.style.left = clippedCaret.left + 'px';
|
|
2924
|
-
caret.style.top = clippedCaret.top + 'px';
|
|
2925
|
-
caret.style.height = clippedCaret.height + 'px';
|
|
2926
|
-
caret.style.background = color;
|
|
2927
|
-
markdownSelectionOverlayRoot.appendChild(caret);
|
|
2928
|
-
|
|
2929
|
-
labelAnchor = { left: clippedCaret.left, top: clippedCaret.top };
|
|
2930
|
-
} else {
|
|
2931
|
-
const rectList = Array.from(range.getClientRects());
|
|
2932
|
-
for (const rect of rectList) {
|
|
2933
|
-
const clippedRect = toMarkdownOverlayRect(rect, surfaceRect);
|
|
2934
|
-
if (!clippedRect) {
|
|
2935
|
-
continue;
|
|
2936
|
-
}
|
|
2937
|
-
|
|
2938
|
-
const highlight = document.createElement('div');
|
|
2939
|
-
highlight.className = 'vf-markdown-editor__selection-highlight';
|
|
2940
|
-
highlight.setAttribute(DATA_VF_IGNORE, 'true');
|
|
2941
|
-
highlight.style.left = clippedRect.left + 'px';
|
|
2942
|
-
highlight.style.top = clippedRect.top + 'px';
|
|
2943
|
-
highlight.style.width = clippedRect.width + 'px';
|
|
2944
|
-
highlight.style.height = clippedRect.height + 'px';
|
|
2945
|
-
highlight.style.background = color;
|
|
2946
|
-
markdownSelectionOverlayRoot.appendChild(highlight);
|
|
2947
|
-
|
|
2948
|
-
if (!labelAnchor) {
|
|
2949
|
-
labelAnchor = { left: clippedRect.left, top: clippedRect.top };
|
|
2950
|
-
}
|
|
2951
|
-
}
|
|
2952
|
-
}
|
|
2953
|
-
|
|
2954
|
-
if (!labelAnchor) {
|
|
2955
|
-
continue;
|
|
2956
|
-
}
|
|
2957
|
-
|
|
2958
|
-
const label = document.createElement('div');
|
|
2959
|
-
label.className = 'vf-markdown-editor__selection-label';
|
|
2960
|
-
label.setAttribute(DATA_VF_IGNORE, 'true');
|
|
2961
|
-
label.textContent = name;
|
|
2962
|
-
label.style.left = labelAnchor.left + 'px';
|
|
2963
|
-
label.style.top = labelAnchor.top + 'px';
|
|
2964
|
-
label.style.background = color;
|
|
2965
|
-
markdownSelectionOverlayRoot.appendChild(label);
|
|
2966
|
-
}
|
|
2967
|
-
|
|
2968
|
-
if (markdownSelectionOverlayRoot.childNodes.length === 0) {
|
|
2969
|
-
clearMarkdownSelectionOverlay();
|
|
2970
|
-
}
|
|
2971
|
-
}
|
|
2972
|
-
|
|
2973
|
-
function scheduleMarkdownSelectionOverlayRender() {
|
|
2974
|
-
if (markdownSelectionOverlayRenderFrame) {
|
|
2975
|
-
cancelAnimationFrame(markdownSelectionOverlayRenderFrame);
|
|
2976
|
-
}
|
|
2977
|
-
|
|
2978
|
-
markdownSelectionOverlayRenderFrame = requestAnimationFrame(function() {
|
|
2979
|
-
markdownSelectionOverlayRenderFrame = null;
|
|
2980
|
-
renderMarkdownSelectionOverlay();
|
|
2981
|
-
});
|
|
2982
|
-
}
|
|
2983
|
-
|
|
2984
|
-
function extractMarkdownParts(content) {
|
|
2985
|
-
if (typeof content !== 'string') {
|
|
2986
|
-
return {
|
|
2987
|
-
frontmatter: '',
|
|
2988
|
-
body: ''
|
|
2989
|
-
};
|
|
2990
|
-
}
|
|
2991
|
-
|
|
2992
|
-
const frontmatterPattern = new RegExp(
|
|
2993
|
-
'^---[ \\\\t]*\\\\r?\\\\n[\\\\s\\\\S]*?\\\\r?\\\\n---[ \\\\t]*(?:\\\\r?\\\\n)?'
|
|
2994
|
-
);
|
|
2995
|
-
const match = content.match(frontmatterPattern);
|
|
2996
|
-
if (!match) {
|
|
2997
|
-
return {
|
|
2998
|
-
frontmatter: '',
|
|
2999
|
-
body: content
|
|
3000
|
-
};
|
|
3001
|
-
}
|
|
3002
|
-
|
|
3003
|
-
return {
|
|
3004
|
-
frontmatter: match[0],
|
|
3005
|
-
body: content.slice(match[0].length)
|
|
3006
|
-
};
|
|
3007
|
-
}
|
|
3008
|
-
|
|
3009
|
-
function composeMarkdownContent(body) {
|
|
3010
|
-
const safeBody = typeof body === 'string' ? body : '';
|
|
3011
|
-
if (!markdownFrontmatter) {
|
|
3012
|
-
return safeBody;
|
|
3013
|
-
}
|
|
3014
|
-
if (!safeBody) {
|
|
3015
|
-
return markdownFrontmatter;
|
|
3016
|
-
}
|
|
3017
|
-
if (markdownFrontmatter.endsWith('\\n')) {
|
|
3018
|
-
return markdownFrontmatter + safeBody;
|
|
3019
|
-
}
|
|
3020
|
-
return markdownFrontmatter + '\\n' + safeBody;
|
|
3021
|
-
}
|
|
3022
|
-
|
|
3023
|
-
function extractRawBlocksForEditor(body, mdxImportMap) {
|
|
3024
|
-
const source = typeof body === 'string' ? body : '';
|
|
3025
|
-
const rawBlocks = [];
|
|
3026
|
-
const mdxBlocks = [];
|
|
3027
|
-
const tokenPrefix = 'VF_RAW_BLOCK_' + Date.now().toString(36) + '_' + Math.random().toString(36).slice(2, 8);
|
|
3028
|
-
const trackMdxBlocks = isMdxPage();
|
|
3029
|
-
const importMap = mdxImportMap && typeof mdxImportMap === 'object' ? mdxImportMap : {};
|
|
3030
|
-
const createToken = function(index) {
|
|
3031
|
-
return '[[' + tokenPrefix + '_' + index + ']]';
|
|
3032
|
-
};
|
|
3033
|
-
const registerMdxBlock = function(rawBlock, tokenIndex, offset, inputText) {
|
|
3034
|
-
if (!trackMdxBlocks) {
|
|
3035
|
-
return;
|
|
3036
|
-
}
|
|
3037
|
-
|
|
3038
|
-
const trimmed = String(rawBlock || '').trimStart();
|
|
3039
|
-
const fence = String.fromCharCode(96, 96, 96);
|
|
3040
|
-
const startsWithTsxFence = trimmed.startsWith(fence + 'tsx') || trimmed.startsWith(fence + 'jsx');
|
|
3041
|
-
const startsWithUpperTag = /^<\\s*[A-Z]/.test(trimmed);
|
|
3042
|
-
const hasTsxProps = trimmed.indexOf('{') >= 0 && trimmed.indexOf('}') >= 0;
|
|
3043
|
-
|
|
3044
|
-
if (!startsWithTsxFence && !startsWithUpperTag && !hasTsxProps) {
|
|
3045
|
-
return;
|
|
3046
|
-
}
|
|
3047
|
-
|
|
3048
|
-
const label = startsWithTsxFence
|
|
3049
|
-
? 'TSX block'
|
|
3050
|
-
: 'JSX ' + getMdxComponentName(trimmed);
|
|
3051
|
-
const componentName = getMdxComponentName(trimmed);
|
|
3052
|
-
const componentNamePattern = /^[A-Z][\\w$]*(?:\\.[A-Z][\\w$]*)*$/;
|
|
3053
|
-
const normalizedComponentName = componentNamePattern.test(componentName) ? componentName : '';
|
|
3054
|
-
const componentParts = normalizedComponentName ? normalizedComponentName.split('.') : [];
|
|
3055
|
-
const namespaceName = componentParts.length > 0 ? componentParts[0] : '';
|
|
3056
|
-
const fallbackSymbol = componentParts.length > 0 ? componentParts[componentParts.length - 1] : '';
|
|
3057
|
-
const directEntry = normalizedComponentName ? importMap[normalizedComponentName] : null;
|
|
3058
|
-
const namespaceEntry = !directEntry && namespaceName ? importMap[namespaceName] : null;
|
|
3059
|
-
const importEntry = directEntry || namespaceEntry || null;
|
|
3060
|
-
const entryPath = importEntry && typeof importEntry.filePath === 'string'
|
|
3061
|
-
? importEntry.filePath
|
|
3062
|
-
: (
|
|
3063
|
-
typeof importEntry === 'string'
|
|
3064
|
-
? importEntry
|
|
3065
|
-
: ''
|
|
3066
|
-
);
|
|
3067
|
-
const entrySymbol = importEntry && typeof importEntry.symbolName === 'string'
|
|
3068
|
-
? importEntry.symbolName
|
|
3069
|
-
: '';
|
|
3070
|
-
const entryKind = importEntry && typeof importEntry.importKind === 'string'
|
|
3071
|
-
? importEntry.importKind
|
|
3072
|
-
: '';
|
|
3073
|
-
const componentPath = entryPath || '';
|
|
3074
|
-
let componentSymbol = fallbackSymbol;
|
|
3075
|
-
if (entrySymbol) {
|
|
3076
|
-
componentSymbol = entrySymbol;
|
|
3077
|
-
} else if (entryKind === 'namespace' && componentParts.length > 1) {
|
|
3078
|
-
componentSymbol = componentParts[componentParts.length - 1];
|
|
3079
|
-
}
|
|
3080
|
-
mdxBlocks.push({
|
|
3081
|
-
tokenIndex: tokenIndex,
|
|
3082
|
-
label: label,
|
|
3083
|
-
lineNumber: getLineNumberForOffset(inputText, offset),
|
|
3084
|
-
filePath: componentPath,
|
|
3085
|
-
symbolName: componentSymbol
|
|
3086
|
-
});
|
|
3087
|
-
};
|
|
3088
|
-
const replaceWithToken = function(match, leadingNewline, offset, inputText) {
|
|
3089
|
-
const safeLeading = typeof leadingNewline === 'string' ? leadingNewline : '';
|
|
3090
|
-
const tokenIndex = rawBlocks.length;
|
|
3091
|
-
const rawBlock = typeof match === 'string' ? match.trimStart() : '';
|
|
3092
|
-
rawBlocks.push(rawBlock);
|
|
3093
|
-
registerMdxBlock(rawBlock, tokenIndex, Math.max(0, (offset || 0) + safeLeading.length), inputText || source);
|
|
3094
|
-
return safeLeading + createToken(tokenIndex);
|
|
3095
|
-
};
|
|
3096
|
-
|
|
3097
|
-
const mermaidFencePattern = new RegExp(
|
|
3098
|
-
'(^|\\\\n)\\\\x60\\\\x60\\\\x60mermaid[^\\\\n]*\\\\n[\\\\s\\\\S]*?\\\\n\\\\x60\\\\x60\\\\x60(?=\\\\n|$)',
|
|
3099
|
-
'g'
|
|
3100
|
-
);
|
|
3101
|
-
const tsxFencePattern = new RegExp(
|
|
3102
|
-
'(^|\\\\n)\\\\x60\\\\x60\\\\x60(?:tsx|jsx)[^\\\\n]*\\\\n[\\\\s\\\\S]*?\\\\n\\\\x60\\\\x60\\\\x60(?=\\\\n|$)',
|
|
3103
|
-
'g'
|
|
3104
|
-
);
|
|
3105
|
-
const htmlBlockPattern = new RegExp(
|
|
3106
|
-
'(^|\\\\n)<[A-Za-z][\\\\w:-]*(?:\\\\s[^>\\\\n]*)?>[\\\\s\\\\S]*?<\\\\/[A-Za-z][\\\\w:-]*>(?=\\\\n|$)',
|
|
3107
|
-
'g'
|
|
3108
|
-
);
|
|
3109
|
-
const htmlSelfClosingPattern = new RegExp(
|
|
3110
|
-
'(^|\\\\n)<[A-Za-z][\\\\w:-]*(?:\\\\s[^>\\\\n]*)?\\\\/>(?=\\\\n|$)',
|
|
3111
|
-
'g'
|
|
3112
|
-
);
|
|
3113
|
-
|
|
3114
|
-
let editorBody = source.replace(
|
|
3115
|
-
mermaidFencePattern,
|
|
3116
|
-
replaceWithToken
|
|
3117
|
-
);
|
|
3118
|
-
|
|
3119
|
-
editorBody = editorBody.replace(
|
|
3120
|
-
tsxFencePattern,
|
|
3121
|
-
replaceWithToken
|
|
3122
|
-
);
|
|
3123
|
-
|
|
3124
|
-
editorBody = editorBody.replace(
|
|
3125
|
-
htmlBlockPattern,
|
|
3126
|
-
replaceWithToken
|
|
3127
|
-
);
|
|
3128
|
-
|
|
3129
|
-
editorBody = editorBody.replace(
|
|
3130
|
-
htmlSelfClosingPattern,
|
|
3131
|
-
replaceWithToken
|
|
3132
|
-
);
|
|
3133
|
-
|
|
3134
|
-
return {
|
|
3135
|
-
editorBody: editorBody,
|
|
3136
|
-
rawBlocks: rawBlocks,
|
|
3137
|
-
mdxBlocks: mdxBlocks,
|
|
3138
|
-
tokenPrefix: tokenPrefix
|
|
3139
|
-
};
|
|
3140
|
-
}
|
|
3141
|
-
|
|
3142
|
-
function restoreRawBlocksFromEditor(editorBody) {
|
|
3143
|
-
const source = typeof editorBody === 'string' ? editorBody : '';
|
|
3144
|
-
if (!source || markdownRawBlocks.length === 0) {
|
|
3145
|
-
return source;
|
|
3146
|
-
}
|
|
3147
|
-
const rawBlockTokenPattern = getMarkdownRawBlockTokenPattern();
|
|
3148
|
-
|
|
3149
|
-
return source.replace(rawBlockTokenPattern, function(match, indexText) {
|
|
3150
|
-
const index = Number(indexText);
|
|
3151
|
-
if (!Number.isInteger(index) || index < 0 || index >= markdownRawBlocks.length) {
|
|
3152
|
-
return match;
|
|
3153
|
-
}
|
|
3154
|
-
const rawBlock = markdownRawBlocks[index];
|
|
3155
|
-
return typeof rawBlock === 'string' ? rawBlock : match;
|
|
3156
|
-
});
|
|
3157
|
-
}
|
|
3158
|
-
|
|
3159
|
-
function handleMarkdownLocalChange(content) {
|
|
3160
|
-
if (typeof content !== 'string') {
|
|
3161
|
-
return;
|
|
3162
|
-
}
|
|
3163
|
-
|
|
3164
|
-
markdownCurrentEditorContent = content;
|
|
3165
|
-
const restoredBody = restoreRawBlocksFromEditor(content);
|
|
3166
|
-
const fullContent = composeMarkdownContent(restoredBody);
|
|
3167
|
-
if (fullContent === markdownCurrentContent) {
|
|
3168
|
-
return;
|
|
3169
|
-
}
|
|
3170
|
-
markdownCurrentContent = fullContent;
|
|
3171
|
-
markdownHasUnsavedChanges = true;
|
|
3172
|
-
if (markdownYjsConnected) {
|
|
3173
|
-
syncLocalChangeToYText(fullContent);
|
|
3174
|
-
}
|
|
3175
|
-
scheduleMarkdownSync(fullContent);
|
|
3176
|
-
scheduleMarkdownSelectionOverlayRender();
|
|
3177
|
-
}
|
|
3178
|
-
|
|
3179
|
-
function saveMarkdownContent() {
|
|
3180
|
-
if (!markdownHasUnsavedChanges) {
|
|
3181
|
-
return;
|
|
3182
|
-
}
|
|
3183
|
-
markdownSaveInProgress = true;
|
|
3184
|
-
setMarkdownPersistStatus('saving');
|
|
3185
|
-
if (markdownSyncTimer) {
|
|
3186
|
-
clearTimeout(markdownSyncTimer);
|
|
3187
|
-
markdownSyncTimer = null;
|
|
3188
|
-
}
|
|
3189
|
-
postToStudio({
|
|
3190
|
-
action: 'markdownContentChange',
|
|
3191
|
-
fileId: markdownFileId,
|
|
3192
|
-
filePath: PAGE_PATH,
|
|
3193
|
-
content: markdownCurrentContent,
|
|
3194
|
-
save: true
|
|
3195
|
-
});
|
|
3196
|
-
markdownHasUnsavedChanges = false;
|
|
3197
|
-
}
|
|
3198
|
-
|
|
3199
|
-
function setupMarkdownLexicalEditor() {
|
|
3200
|
-
if (!markdownEditorSurface || markdownLexicalApi || markdownLexicalSetupPromise) {
|
|
3201
|
-
return;
|
|
3202
|
-
}
|
|
3203
|
-
|
|
3204
|
-
markdownLexicalSetupPromise = Promise.all([
|
|
3205
|
-
import('https://esm.sh/lexical@0.21.0?target=es2022'),
|
|
3206
|
-
import('https://esm.sh/@lexical/rich-text@0.21.0?target=es2022'),
|
|
3207
|
-
import('https://esm.sh/@lexical/list@0.21.0?target=es2022'),
|
|
3208
|
-
import('https://esm.sh/@lexical/markdown@0.21.0?target=es2022'),
|
|
3209
|
-
import('https://esm.sh/@lexical/history@0.21.0?target=es2022')
|
|
3210
|
-
]).then(function(modules) {
|
|
3211
|
-
if (!markdownEditorSurface) {
|
|
3212
|
-
return;
|
|
3213
|
-
}
|
|
3214
|
-
|
|
3215
|
-
const lexicalModule = modules[0];
|
|
3216
|
-
const richTextModule = modules[1];
|
|
3217
|
-
const listModule = modules[2];
|
|
3218
|
-
const markdownModule = modules[3];
|
|
3219
|
-
const historyModule = modules[4];
|
|
3220
|
-
|
|
3221
|
-
const editor = lexicalModule.createEditor({
|
|
3222
|
-
namespace: 'veryfront-markdown-preview',
|
|
3223
|
-
nodes: [
|
|
3224
|
-
richTextModule.HeadingNode,
|
|
3225
|
-
richTextModule.QuoteNode,
|
|
3226
|
-
listModule.ListNode,
|
|
3227
|
-
listModule.ListItemNode
|
|
3228
|
-
],
|
|
3229
|
-
onError: function(error) {
|
|
3230
|
-
console.error('[StudioBridge] Markdown Lexical error', error);
|
|
3231
|
-
}
|
|
3232
|
-
});
|
|
3233
|
-
|
|
3234
|
-
const unregisterRichText = richTextModule.registerRichText(editor);
|
|
3235
|
-
const unregisterList = listModule.registerList(editor);
|
|
3236
|
-
const unregisterHistory = historyModule.registerHistory(
|
|
3237
|
-
editor,
|
|
3238
|
-
historyModule.createEmptyHistoryState(),
|
|
3239
|
-
1000
|
|
3240
|
-
);
|
|
3241
|
-
const unregisterUpdate = editor.registerUpdateListener(function(update) {
|
|
3242
|
-
if (markdownApplyingRemoteUpdate) {
|
|
3243
|
-
return;
|
|
3244
|
-
}
|
|
3245
|
-
|
|
3246
|
-
let nextContent = '';
|
|
3247
|
-
update.editorState.read(function() {
|
|
3248
|
-
nextContent = markdownModule.$convertToMarkdownString(markdownModule.TRANSFORMERS, undefined, true);
|
|
3249
|
-
});
|
|
3250
|
-
const restoredBody = restoreRawBlocksFromEditor(nextContent);
|
|
3251
|
-
const fullContent = composeMarkdownContent(restoredBody);
|
|
3252
|
-
|
|
3253
|
-
if (fullContent === markdownLexicalRenderedContent) {
|
|
3254
|
-
return;
|
|
3255
|
-
}
|
|
3256
|
-
markdownLexicalRenderedContent = fullContent;
|
|
3257
|
-
|
|
3258
|
-
handleMarkdownLocalChange(nextContent);
|
|
3259
|
-
scheduleMarkdownSlashMenuUpdate();
|
|
3260
|
-
scheduleMarkdownInlineToolbarUpdate();
|
|
3261
|
-
});
|
|
3262
|
-
|
|
3263
|
-
editor.setRootElement(markdownEditorSurface);
|
|
3264
|
-
editor.update(function() {
|
|
3265
|
-
const root = lexicalModule.$getRoot();
|
|
3266
|
-
if (root.getChildrenSize() === 0) {
|
|
3267
|
-
root.append(lexicalModule.$createParagraphNode());
|
|
3268
|
-
}
|
|
3269
|
-
});
|
|
3270
|
-
markdownLexicalApi = {
|
|
3271
|
-
editor: editor,
|
|
3272
|
-
lexicalModule: lexicalModule,
|
|
3273
|
-
markdownModule: markdownModule,
|
|
3274
|
-
unregisterRichText: unregisterRichText,
|
|
3275
|
-
unregisterList: unregisterList,
|
|
3276
|
-
unregisterHistory: unregisterHistory,
|
|
3277
|
-
unregisterUpdate: unregisterUpdate
|
|
3278
|
-
};
|
|
3279
|
-
|
|
3280
|
-
markdownEditorSurface.style.display = 'block';
|
|
3281
|
-
if (markdownEditorTextarea) {
|
|
3282
|
-
markdownEditorTextarea.style.display = 'none';
|
|
3283
|
-
}
|
|
3284
|
-
|
|
3285
|
-
applyMarkdownContent(markdownCurrentContent);
|
|
3286
|
-
hideMarkdownBlockDropIndicator();
|
|
3287
|
-
}).catch(function(error) {
|
|
3288
|
-
console.warn(
|
|
3289
|
-
'[StudioBridge] Failed to load Lexical markdown editor; falling back to textarea',
|
|
3290
|
-
error
|
|
3291
|
-
);
|
|
3292
|
-
if (markdownEditorSurface) {
|
|
3293
|
-
markdownEditorSurface.style.display = 'none';
|
|
3294
|
-
}
|
|
3295
|
-
if (markdownEditorTextarea) {
|
|
3296
|
-
markdownEditorTextarea.style.display = 'block';
|
|
3297
|
-
}
|
|
3298
|
-
hideMarkdownSlashMenu();
|
|
3299
|
-
hideMarkdownInlineToolbar();
|
|
3300
|
-
hideMarkdownBlockDragUi();
|
|
3301
|
-
clearMarkdownSelectionOverlay();
|
|
3302
|
-
});
|
|
3303
|
-
}
|
|
3304
|
-
|
|
3305
|
-
function focusMarkdownEditor() {
|
|
3306
|
-
if (markdownLexicalApi && markdownEditorSurface) {
|
|
3307
|
-
markdownEditorSurface.focus();
|
|
3308
|
-
return;
|
|
3309
|
-
}
|
|
3310
|
-
if (markdownEditorTextarea) {
|
|
3311
|
-
markdownEditorTextarea.focus();
|
|
3312
|
-
}
|
|
3313
|
-
}
|
|
3314
|
-
|
|
3315
|
-
function applyMarkdownHistoryCommand(command) {
|
|
3316
|
-
if (!markdownLexicalApi || !markdownLexicalApi.editor || !markdownLexicalApi.lexicalModule) {
|
|
3317
|
-
return;
|
|
3318
|
-
}
|
|
3319
|
-
if (!command) {
|
|
3320
|
-
return;
|
|
3321
|
-
}
|
|
3322
|
-
|
|
3323
|
-
markdownLexicalApi.editor.focus();
|
|
3324
|
-
markdownLexicalApi.editor.dispatchCommand(command, undefined);
|
|
3325
|
-
scheduleMarkdownSelectionSync();
|
|
3326
|
-
scheduleMarkdownSelectionOverlayRender();
|
|
3327
|
-
scheduleMarkdownSlashMenuUpdate();
|
|
3328
|
-
scheduleMarkdownInlineToolbarUpdate();
|
|
3329
|
-
}
|
|
3330
|
-
|
|
3331
|
-
function applyMarkdownContent(content) {
|
|
3332
|
-
if (typeof content !== 'string') {
|
|
3333
|
-
return;
|
|
3334
|
-
}
|
|
3335
|
-
|
|
3336
|
-
if (markdownLexicalApi && markdownLexicalRenderedContent === content) {
|
|
3337
|
-
console.debug('[StudioBridge] applyMarkdownContent: skipped (content unchanged)');
|
|
3338
|
-
markdownCurrentContent = content;
|
|
3339
|
-
scheduleMarkdownSelectionOverlayRender();
|
|
3340
|
-
scheduleMarkdownSlashMenuUpdate();
|
|
3341
|
-
scheduleMarkdownInlineToolbarUpdate();
|
|
3342
|
-
hideMarkdownBlockDropIndicator();
|
|
3343
|
-
return;
|
|
3344
|
-
}
|
|
3345
|
-
|
|
3346
|
-
console.debug('[StudioBridge] applyMarkdownContent: rebuilding Lexical DOM, content length:', content.length, 'rendered match:', markdownLexicalRenderedContent === content, 'current match:', markdownCurrentContent === content);
|
|
3347
|
-
|
|
3348
|
-
const mdxImportMap = parseMdxImportMap(content);
|
|
3349
|
-
const parts = extractMarkdownParts(content);
|
|
3350
|
-
const extracted = extractRawBlocksForEditor(parts.body, mdxImportMap);
|
|
3351
|
-
const editorContent = extracted.editorBody;
|
|
3352
|
-
const mdxBlocks = Array.isArray(extracted.mdxBlocks) ? extracted.mdxBlocks : [];
|
|
3353
|
-
markdownFrontmatter = parts.frontmatter;
|
|
3354
|
-
markdownRawBlocks = extracted.rawBlocks;
|
|
3355
|
-
markdownRawBlockTokenPrefix = extracted.tokenPrefix;
|
|
3356
|
-
markdownLatestMdxImportMap = mdxImportMap;
|
|
3357
|
-
setMarkdownMdxBlocks(mdxBlocks);
|
|
3358
|
-
|
|
3359
|
-
markdownCurrentContent = content;
|
|
3360
|
-
markdownCurrentEditorContent = editorContent;
|
|
3361
|
-
|
|
3362
|
-
if (markdownLexicalApi) {
|
|
3363
|
-
markdownApplyingRemoteUpdate = true;
|
|
3364
|
-
try {
|
|
3365
|
-
markdownLexicalRenderedContent = content;
|
|
3366
|
-
markdownLexicalApi.editor.update(function() {
|
|
3367
|
-
const lexicalModule = markdownLexicalApi.lexicalModule;
|
|
3368
|
-
const markdownModule = markdownLexicalApi.markdownModule;
|
|
3369
|
-
const root = lexicalModule.$getRoot();
|
|
3370
|
-
root.clear();
|
|
3371
|
-
markdownModule.$convertFromMarkdownString(editorContent, markdownModule.TRANSFORMERS, undefined, true);
|
|
3372
|
-
if (root.getChildrenSize() === 0) {
|
|
3373
|
-
root.append(lexicalModule.$createParagraphNode());
|
|
3374
|
-
}
|
|
3375
|
-
}, { discrete: true });
|
|
3376
|
-
} finally {
|
|
3377
|
-
markdownApplyingRemoteUpdate = false;
|
|
3378
|
-
}
|
|
3379
|
-
scheduleMarkdownSelectionOverlayRender();
|
|
3380
|
-
scheduleMarkdownSlashMenuUpdate();
|
|
3381
|
-
scheduleMarkdownInlineToolbarUpdate();
|
|
3382
|
-
hideMarkdownBlockDropIndicator();
|
|
3383
|
-
return;
|
|
3384
|
-
}
|
|
3385
|
-
|
|
3386
|
-
if (!markdownEditorTextarea || markdownEditorTextarea.value === editorContent) {
|
|
3387
|
-
clearMarkdownSelectionOverlay();
|
|
3388
|
-
hideMarkdownSlashMenu();
|
|
3389
|
-
hideMarkdownInlineToolbar();
|
|
3390
|
-
hideMarkdownBlockDragUi();
|
|
3391
|
-
return;
|
|
3392
|
-
}
|
|
3393
|
-
|
|
3394
|
-
const selectionStart = markdownEditorTextarea.selectionStart;
|
|
3395
|
-
const selectionEnd = markdownEditorTextarea.selectionEnd;
|
|
3396
|
-
markdownEditorTextarea.value = editorContent;
|
|
3397
|
-
|
|
3398
|
-
if (typeof selectionStart === 'number' && typeof selectionEnd === 'number') {
|
|
3399
|
-
const max = markdownEditorTextarea.value.length;
|
|
3400
|
-
markdownEditorTextarea.setSelectionRange(Math.min(selectionStart, max), Math.min(selectionEnd, max));
|
|
3401
|
-
}
|
|
3402
|
-
clearMarkdownSelectionOverlay();
|
|
3403
|
-
hideMarkdownSlashMenu();
|
|
3404
|
-
hideMarkdownInlineToolbar();
|
|
3405
|
-
hideMarkdownBlockDragUi();
|
|
3406
|
-
}
|
|
3407
|
-
|
|
3408
|
-
function setMarkdownPersistStatus(status) {
|
|
3409
|
-
if (!markdownPersistStatus) {
|
|
3410
|
-
return;
|
|
3411
|
-
}
|
|
3412
|
-
|
|
3413
|
-
const nextStatus = status === 'saving' || status === 'saved' || status === 'error'
|
|
3414
|
-
? status
|
|
3415
|
-
: 'saved';
|
|
3416
|
-
|
|
3417
|
-
markdownPersistStatus.setAttribute('data-state', nextStatus);
|
|
3418
|
-
if (nextStatus === 'saving') {
|
|
3419
|
-
markdownPersistStatus.textContent = 'Saving...';
|
|
3420
|
-
return;
|
|
3421
|
-
}
|
|
3422
|
-
if (nextStatus === 'error') {
|
|
3423
|
-
markdownPersistStatus.textContent = 'Save failed';
|
|
3424
|
-
return;
|
|
3425
|
-
}
|
|
3426
|
-
markdownPersistStatus.textContent = 'Saved';
|
|
3427
|
-
}
|
|
3428
|
-
|
|
3429
|
-
function setMarkdownPresence(users) {
|
|
3430
|
-
markdownLatestPresenceUsers = Array.isArray(users) ? users : [];
|
|
3431
|
-
if (!markdownPresenceRoot) {
|
|
3432
|
-
return;
|
|
3433
|
-
}
|
|
3434
|
-
|
|
3435
|
-
markdownPresenceRoot.textContent = '';
|
|
3436
|
-
if (!Array.isArray(users) || users.length === 0) {
|
|
3437
|
-
markdownPresenceRoot.style.display = 'none';
|
|
3438
|
-
return;
|
|
3439
|
-
}
|
|
3440
|
-
|
|
3441
|
-
const visibleUsers = users.filter(function(user) {
|
|
3442
|
-
return user && typeof user.name === 'string';
|
|
3443
|
-
}).slice(0, 4);
|
|
3444
|
-
|
|
3445
|
-
if (visibleUsers.length === 0) {
|
|
3446
|
-
markdownPresenceRoot.style.display = 'none';
|
|
3447
|
-
return;
|
|
3448
|
-
}
|
|
3449
|
-
|
|
3450
|
-
markdownPresenceRoot.style.display = 'inline-flex';
|
|
3451
|
-
|
|
3452
|
-
for (const user of visibleUsers) {
|
|
3453
|
-
const pill = document.createElement('div');
|
|
3454
|
-
pill.className = 'vf-markdown-editor__presence-pill';
|
|
3455
|
-
pill.setAttribute(DATA_VF_IGNORE, 'true');
|
|
3456
|
-
pill.setAttribute('data-current', user.isCurrentUser ? 'true' : 'false');
|
|
3457
|
-
pill.setAttribute('data-agent', user.isAgent ? 'true' : 'false');
|
|
3458
|
-
pill.textContent = user.isCurrentUser ? 'You' : user.name;
|
|
3459
|
-
|
|
3460
|
-
const color = typeof user.color === 'string' && user.color ? user.color : '#6b7280';
|
|
3461
|
-
pill.style.borderLeftColor = color;
|
|
3462
|
-
|
|
3463
|
-
markdownPresenceRoot.appendChild(pill);
|
|
3464
|
-
}
|
|
3465
|
-
|
|
3466
|
-
if (users.length > visibleUsers.length) {
|
|
3467
|
-
const extra = document.createElement('div');
|
|
3468
|
-
extra.className = 'vf-markdown-editor__presence-pill';
|
|
3469
|
-
extra.setAttribute(DATA_VF_IGNORE, 'true');
|
|
3470
|
-
extra.textContent = '+' + String(users.length - visibleUsers.length);
|
|
3471
|
-
markdownPresenceRoot.appendChild(extra);
|
|
3472
|
-
}
|
|
3473
|
-
}
|
|
3474
|
-
|
|
3475
|
-
function setMarkdownSelections(selections) {
|
|
3476
|
-
markdownLatestSelections = Array.isArray(selections) ? selections : [];
|
|
3477
|
-
if (!markdownSelectionsRoot) {
|
|
3478
|
-
return;
|
|
3479
|
-
}
|
|
3480
|
-
|
|
3481
|
-
markdownSelectionsRoot.textContent = '';
|
|
3482
|
-
markdownOverlaySelections = [];
|
|
3483
|
-
if (!Array.isArray(selections) || selections.length === 0) {
|
|
3484
|
-
markdownSelectionsRoot.style.display = 'none';
|
|
3485
|
-
clearMarkdownSelectionOverlay();
|
|
3486
|
-
return;
|
|
3487
|
-
}
|
|
3488
|
-
|
|
3489
|
-
const visibleSelections = selections.filter(function(selection) {
|
|
3490
|
-
return (
|
|
3491
|
-
selection &&
|
|
3492
|
-
typeof selection.name === 'string' &&
|
|
3493
|
-
typeof selection.start === 'number' &&
|
|
3494
|
-
typeof selection.end === 'number'
|
|
3495
|
-
);
|
|
3496
|
-
}).slice(0, 4);
|
|
3497
|
-
|
|
3498
|
-
if (visibleSelections.length === 0) {
|
|
3499
|
-
markdownSelectionsRoot.style.display = 'none';
|
|
3500
|
-
clearMarkdownSelectionOverlay();
|
|
3501
|
-
return;
|
|
3502
|
-
}
|
|
3503
|
-
|
|
3504
|
-
markdownSelectionsRoot.style.display = 'inline-flex';
|
|
3505
|
-
|
|
3506
|
-
for (const selection of visibleSelections) {
|
|
3507
|
-
const pill = document.createElement('div');
|
|
3508
|
-
pill.className = 'vf-markdown-editor__selection-pill';
|
|
3509
|
-
pill.setAttribute(DATA_VF_IGNORE, 'true');
|
|
3510
|
-
const color = typeof selection.color === 'string' && selection.color ? selection.color : '#6b7280';
|
|
3511
|
-
const displayName = selection.isCurrentUser ? 'You' : selection.name;
|
|
3512
|
-
pill.style.borderLeftColor = color;
|
|
3513
|
-
|
|
3514
|
-
const start = Math.max(0, Math.trunc(selection.start));
|
|
3515
|
-
const end = Math.max(0, Math.trunc(selection.end));
|
|
3516
|
-
const rangeLabel = start === end ? '@' + String(start) : String(start) + '-' + String(end);
|
|
3517
|
-
pill.textContent = displayName + ' ' + rangeLabel;
|
|
3518
|
-
markdownSelectionsRoot.appendChild(pill);
|
|
3519
|
-
|
|
3520
|
-
const editorRange = sourceSelectionToEditorRange(start, end);
|
|
3521
|
-
if (!editorRange) {
|
|
3522
|
-
continue;
|
|
3523
|
-
}
|
|
3524
|
-
|
|
3525
|
-
markdownOverlaySelections.push({
|
|
3526
|
-
name: displayName,
|
|
3527
|
-
color: color,
|
|
3528
|
-
start: editorRange.start,
|
|
3529
|
-
end: editorRange.end
|
|
3530
|
-
});
|
|
3531
|
-
}
|
|
3532
|
-
|
|
3533
|
-
if (selections.length > visibleSelections.length) {
|
|
3534
|
-
const extra = document.createElement('div');
|
|
3535
|
-
extra.className = 'vf-markdown-editor__selection-pill';
|
|
3536
|
-
extra.setAttribute(DATA_VF_IGNORE, 'true');
|
|
3537
|
-
extra.textContent = '+' + String(selections.length - visibleSelections.length);
|
|
3538
|
-
markdownSelectionsRoot.appendChild(extra);
|
|
3539
|
-
}
|
|
3540
|
-
|
|
3541
|
-
scheduleMarkdownSelectionOverlayRender();
|
|
3542
|
-
}
|
|
3543
|
-
|
|
3544
|
-
function ensureMarkdownEditor() {
|
|
3545
|
-
if (markdownEditorRoot) {
|
|
3546
|
-
return markdownEditorRoot;
|
|
3547
|
-
}
|
|
3548
|
-
|
|
3549
|
-
const editorRoot = document.createElement('div');
|
|
3550
|
-
editorRoot.className = 'vf-markdown-editor';
|
|
3551
|
-
editorRoot.setAttribute(DATA_VF_IGNORE, 'true');
|
|
3552
|
-
|
|
3553
|
-
const toolbar = document.createElement('div');
|
|
3554
|
-
toolbar.className = 'vf-markdown-editor__toolbar';
|
|
3555
|
-
toolbar.setAttribute(DATA_VF_IGNORE, 'true');
|
|
3556
|
-
|
|
3557
|
-
const title = document.createElement('div');
|
|
3558
|
-
title.className = 'vf-markdown-editor__title';
|
|
3559
|
-
title.setAttribute(DATA_VF_IGNORE, 'true');
|
|
3560
|
-
|
|
3561
|
-
const titleMain = document.createElement('div');
|
|
3562
|
-
titleMain.className = 'vf-markdown-editor__title-main';
|
|
3563
|
-
titleMain.setAttribute(DATA_VF_IGNORE, 'true');
|
|
3564
|
-
titleMain.textContent = 'Markdown editor';
|
|
3565
|
-
|
|
3566
|
-
const titleHints = document.createElement('div');
|
|
3567
|
-
titleHints.className = 'vf-markdown-editor__title-hints';
|
|
3568
|
-
titleHints.setAttribute(DATA_VF_IGNORE, 'true');
|
|
3569
|
-
titleHints.textContent = '/ commands | Shift+Alt+Up/Down move block | Undo/Redo';
|
|
3570
|
-
|
|
3571
|
-
title.appendChild(titleMain);
|
|
3572
|
-
title.appendChild(titleHints);
|
|
3573
|
-
|
|
3574
|
-
const actions = document.createElement('div');
|
|
3575
|
-
actions.className = 'vf-markdown-editor__actions';
|
|
3576
|
-
actions.setAttribute(DATA_VF_IGNORE, 'true');
|
|
3577
|
-
|
|
3578
|
-
const status = document.createElement('div');
|
|
3579
|
-
status.className = 'vf-markdown-editor__status';
|
|
3580
|
-
status.setAttribute(DATA_VF_IGNORE, 'true');
|
|
3581
|
-
status.textContent = '';
|
|
3582
|
-
status.setAttribute('data-state', '');
|
|
3583
|
-
|
|
3584
|
-
const presence = document.createElement('div');
|
|
3585
|
-
presence.className = 'vf-markdown-editor__presence';
|
|
3586
|
-
presence.setAttribute(DATA_VF_IGNORE, 'true');
|
|
3587
|
-
|
|
3588
|
-
const selections = document.createElement('div');
|
|
3589
|
-
selections.className = 'vf-markdown-editor__selections';
|
|
3590
|
-
selections.setAttribute(DATA_VF_IGNORE, 'true');
|
|
3591
|
-
|
|
3592
|
-
const undoButton = document.createElement('button');
|
|
3593
|
-
undoButton.type = 'button';
|
|
3594
|
-
undoButton.className = 'vf-markdown-editor__history';
|
|
3595
|
-
undoButton.setAttribute(DATA_VF_IGNORE, 'true');
|
|
3596
|
-
undoButton.setAttribute('title', 'Undo');
|
|
3597
|
-
undoButton.textContent = 'Undo';
|
|
3598
|
-
undoButton.addEventListener('click', function() {
|
|
3599
|
-
if (!markdownLexicalApi || !markdownLexicalApi.lexicalModule) {
|
|
3600
|
-
return;
|
|
3601
|
-
}
|
|
3602
|
-
applyMarkdownHistoryCommand(markdownLexicalApi.lexicalModule.UNDO_COMMAND);
|
|
3603
|
-
});
|
|
3604
|
-
|
|
3605
|
-
const redoButton = document.createElement('button');
|
|
3606
|
-
redoButton.type = 'button';
|
|
3607
|
-
redoButton.className = 'vf-markdown-editor__history';
|
|
3608
|
-
redoButton.setAttribute(DATA_VF_IGNORE, 'true');
|
|
3609
|
-
redoButton.setAttribute('title', 'Redo');
|
|
3610
|
-
redoButton.textContent = 'Redo';
|
|
3611
|
-
redoButton.addEventListener('click', function() {
|
|
3612
|
-
if (!markdownLexicalApi || !markdownLexicalApi.lexicalModule) {
|
|
3613
|
-
return;
|
|
3614
|
-
}
|
|
3615
|
-
applyMarkdownHistoryCommand(markdownLexicalApi.lexicalModule.REDO_COMMAND);
|
|
3616
|
-
});
|
|
3617
|
-
|
|
3618
|
-
const openStudioButton = document.createElement('button');
|
|
3619
|
-
openStudioButton.type = 'button';
|
|
3620
|
-
openStudioButton.className = 'vf-markdown-editor__history';
|
|
3621
|
-
openStudioButton.setAttribute(DATA_VF_IGNORE, 'true');
|
|
3622
|
-
openStudioButton.setAttribute('title', 'Open file in Studio');
|
|
3623
|
-
openStudioButton.textContent = 'Open';
|
|
3624
|
-
openStudioButton.addEventListener('click', function() {
|
|
3625
|
-
openMarkdownSourceInStudio(1);
|
|
3626
|
-
});
|
|
3627
|
-
|
|
3628
|
-
const exitButton = document.createElement('button');
|
|
3629
|
-
exitButton.type = 'button';
|
|
3630
|
-
exitButton.className = 'vf-markdown-editor__exit';
|
|
3631
|
-
exitButton.setAttribute(DATA_VF_IGNORE, 'true');
|
|
3632
|
-
exitButton.textContent = 'Done';
|
|
3633
|
-
exitButton.addEventListener('click', function() {
|
|
3634
|
-
setMarkdownEditMode(false);
|
|
3635
|
-
});
|
|
3636
|
-
|
|
3637
|
-
actions.appendChild(status);
|
|
3638
|
-
actions.appendChild(presence);
|
|
3639
|
-
actions.appendChild(selections);
|
|
3640
|
-
actions.appendChild(undoButton);
|
|
3641
|
-
actions.appendChild(redoButton);
|
|
3642
|
-
actions.appendChild(openStudioButton);
|
|
3643
|
-
actions.appendChild(exitButton);
|
|
3644
|
-
|
|
3645
|
-
toolbar.appendChild(title);
|
|
3646
|
-
toolbar.appendChild(actions);
|
|
3647
|
-
|
|
3648
|
-
const mdxBlocks = document.createElement('div');
|
|
3649
|
-
mdxBlocks.className = 'vf-markdown-editor__mdx-blocks';
|
|
3650
|
-
mdxBlocks.setAttribute(DATA_VF_IGNORE, 'true');
|
|
3651
|
-
|
|
3652
|
-
const surface = document.createElement('div');
|
|
3653
|
-
surface.className = 'vf-markdown-editor__surface markdown-body';
|
|
3654
|
-
surface.setAttribute(DATA_VF_IGNORE, 'true');
|
|
3655
|
-
surface.setAttribute('contenteditable', 'true');
|
|
3656
|
-
surface.setAttribute('aria-label', 'Markdown editor');
|
|
3657
|
-
surface.addEventListener('keyup', scheduleMarkdownSelectionSync);
|
|
3658
|
-
surface.addEventListener('mouseup', scheduleMarkdownSelectionSync);
|
|
3659
|
-
surface.addEventListener('input', function() {
|
|
3660
|
-
scheduleMarkdownSelectionSync();
|
|
3661
|
-
scheduleMarkdownSlashMenuUpdate();
|
|
3662
|
-
});
|
|
3663
|
-
surface.addEventListener('keyup', scheduleMarkdownSlashMenuUpdate);
|
|
3664
|
-
surface.addEventListener('mouseup', scheduleMarkdownSlashMenuUpdate);
|
|
3665
|
-
surface.addEventListener('keydown', handleMarkdownSlashMenuKeydown);
|
|
3666
|
-
surface.addEventListener('keydown', function(event) {
|
|
3667
|
-
if (!event.shiftKey || !event.altKey) {
|
|
3668
|
-
return;
|
|
3669
|
-
}
|
|
3670
|
-
if (event.key === 'ArrowUp') {
|
|
3671
|
-
const moved = moveMarkdownCurrentBlockByDelta(-1);
|
|
3672
|
-
if (moved) {
|
|
3673
|
-
event.preventDefault();
|
|
3674
|
-
}
|
|
3675
|
-
return;
|
|
3676
|
-
}
|
|
3677
|
-
if (event.key === 'ArrowDown') {
|
|
3678
|
-
const moved = moveMarkdownCurrentBlockByDelta(1);
|
|
3679
|
-
if (moved) {
|
|
3680
|
-
event.preventDefault();
|
|
3681
|
-
}
|
|
3682
|
-
}
|
|
3683
|
-
});
|
|
3684
|
-
surface.addEventListener('keydown', function(event) {
|
|
3685
|
-
if ((event.metaKey || event.ctrlKey) && event.key === 's') {
|
|
3686
|
-
event.preventDefault();
|
|
3687
|
-
saveMarkdownContent();
|
|
3688
|
-
}
|
|
3689
|
-
});
|
|
3690
|
-
surface.addEventListener('scroll', scheduleMarkdownSelectionOverlayRender);
|
|
3691
|
-
surface.addEventListener('scroll', scheduleMarkdownSlashMenuUpdate);
|
|
3692
|
-
surface.addEventListener('keyup', scheduleMarkdownInlineToolbarUpdate);
|
|
3693
|
-
surface.addEventListener('mouseup', scheduleMarkdownInlineToolbarUpdate);
|
|
3694
|
-
surface.addEventListener('input', scheduleMarkdownInlineToolbarUpdate);
|
|
3695
|
-
surface.addEventListener('scroll', scheduleMarkdownInlineToolbarUpdate);
|
|
3696
|
-
surface.addEventListener('scroll', refreshMarkdownBlockDragHandlePosition);
|
|
3697
|
-
|
|
3698
|
-
const surfaceWrap = document.createElement('div');
|
|
3699
|
-
surfaceWrap.className = 'vf-markdown-editor__surface-wrap';
|
|
3700
|
-
surfaceWrap.setAttribute(DATA_VF_IGNORE, 'true');
|
|
3701
|
-
|
|
3702
|
-
const selectionOverlay = document.createElement('div');
|
|
3703
|
-
selectionOverlay.className = 'vf-markdown-editor__selection-overlay';
|
|
3704
|
-
selectionOverlay.setAttribute(DATA_VF_IGNORE, 'true');
|
|
3705
|
-
|
|
3706
|
-
const slashMenu = document.createElement('div');
|
|
3707
|
-
slashMenu.className = 'vf-markdown-editor__slash-menu';
|
|
3708
|
-
slashMenu.setAttribute(DATA_VF_IGNORE, 'true');
|
|
3709
|
-
|
|
3710
|
-
const inlineToolbar = document.createElement('div');
|
|
3711
|
-
inlineToolbar.className = 'vf-markdown-editor__inline-toolbar';
|
|
3712
|
-
inlineToolbar.setAttribute(DATA_VF_IGNORE, 'true');
|
|
3713
|
-
|
|
3714
|
-
const boldButton = document.createElement('button');
|
|
3715
|
-
boldButton.type = 'button';
|
|
3716
|
-
boldButton.className = 'vf-markdown-editor__inline-button';
|
|
3717
|
-
boldButton.setAttribute(DATA_VF_IGNORE, 'true');
|
|
3718
|
-
boldButton.textContent = 'B';
|
|
3719
|
-
boldButton.addEventListener('mousedown', function(event) {
|
|
3720
|
-
event.preventDefault();
|
|
3721
|
-
});
|
|
3722
|
-
boldButton.addEventListener('click', function(event) {
|
|
3723
|
-
event.preventDefault();
|
|
3724
|
-
toggleMarkdownInlineFormat('bold');
|
|
3725
|
-
});
|
|
3726
|
-
|
|
3727
|
-
const italicButton = document.createElement('button');
|
|
3728
|
-
italicButton.type = 'button';
|
|
3729
|
-
italicButton.className = 'vf-markdown-editor__inline-button';
|
|
3730
|
-
italicButton.setAttribute(DATA_VF_IGNORE, 'true');
|
|
3731
|
-
italicButton.textContent = 'I';
|
|
3732
|
-
italicButton.addEventListener('mousedown', function(event) {
|
|
3733
|
-
event.preventDefault();
|
|
3734
|
-
});
|
|
3735
|
-
italicButton.addEventListener('click', function(event) {
|
|
3736
|
-
event.preventDefault();
|
|
3737
|
-
toggleMarkdownInlineFormat('italic');
|
|
3738
|
-
});
|
|
3739
|
-
|
|
3740
|
-
const codeButton = document.createElement('button');
|
|
3741
|
-
codeButton.type = 'button';
|
|
3742
|
-
codeButton.className = 'vf-markdown-editor__inline-button';
|
|
3743
|
-
codeButton.setAttribute(DATA_VF_IGNORE, 'true');
|
|
3744
|
-
codeButton.textContent = '</>';
|
|
3745
|
-
codeButton.addEventListener('mousedown', function(event) {
|
|
3746
|
-
event.preventDefault();
|
|
3747
|
-
});
|
|
3748
|
-
codeButton.addEventListener('click', function(event) {
|
|
3749
|
-
event.preventDefault();
|
|
3750
|
-
toggleMarkdownInlineFormat('code');
|
|
3751
|
-
});
|
|
3752
|
-
|
|
3753
|
-
inlineToolbar.appendChild(boldButton);
|
|
3754
|
-
inlineToolbar.appendChild(italicButton);
|
|
3755
|
-
inlineToolbar.appendChild(codeButton);
|
|
3756
|
-
|
|
3757
|
-
const blockDragHandle = document.createElement('button');
|
|
3758
|
-
blockDragHandle.type = 'button';
|
|
3759
|
-
blockDragHandle.className = 'vf-markdown-editor__block-handle';
|
|
3760
|
-
blockDragHandle.setAttribute(DATA_VF_IGNORE, 'true');
|
|
3761
|
-
blockDragHandle.textContent = '::';
|
|
3762
|
-
blockDragHandle.draggable = true;
|
|
3763
|
-
blockDragHandle.setAttribute('data-dragging', 'false');
|
|
3764
|
-
blockDragHandle.addEventListener('dragstart', function(event) {
|
|
3765
|
-
const indexText = blockDragHandle.getAttribute('data-block-index');
|
|
3766
|
-
const index = Number(indexText);
|
|
3767
|
-
if (!Number.isInteger(index)) {
|
|
3768
|
-
event.preventDefault();
|
|
3769
|
-
return;
|
|
3770
|
-
}
|
|
3771
|
-
markdownBlockDragSourceIndex = index;
|
|
3772
|
-
markdownBlockDragActive = true;
|
|
3773
|
-
blockDragHandle.setAttribute('data-dragging', 'true');
|
|
3774
|
-
if (event.dataTransfer) {
|
|
3775
|
-
event.dataTransfer.effectAllowed = 'move';
|
|
3776
|
-
event.dataTransfer.setData('text/plain', String(index));
|
|
3777
|
-
|
|
3778
|
-
const blocks = getMarkdownTopLevelBlocks();
|
|
3779
|
-
const block = blocks[index];
|
|
3780
|
-
removeMarkdownDragGhost();
|
|
3781
|
-
if (block) {
|
|
3782
|
-
const ghost = createMarkdownDragGhost(block);
|
|
3783
|
-
document.body.appendChild(ghost);
|
|
3784
|
-
markdownBlockDragGhost = ghost;
|
|
3785
|
-
event.dataTransfer.setDragImage(ghost, 14, 14);
|
|
3786
|
-
}
|
|
3787
|
-
}
|
|
3788
|
-
showMarkdownBlockDropIndicator(index);
|
|
3789
|
-
});
|
|
3790
|
-
blockDragHandle.addEventListener('mouseenter', function() {
|
|
3791
|
-
if (markdownBlockHandleHoverIndex >= 0) {
|
|
3792
|
-
blockDragHandle.style.display = 'block';
|
|
3793
|
-
}
|
|
3794
|
-
});
|
|
3795
|
-
blockDragHandle.addEventListener('mouseleave', function(event) {
|
|
3796
|
-
if (markdownBlockDragActive) {
|
|
3797
|
-
return;
|
|
3798
|
-
}
|
|
3799
|
-
const next = event.relatedTarget;
|
|
3800
|
-
if (next && markdownEditorSurface && markdownEditorSurface.contains(next)) {
|
|
3801
|
-
return;
|
|
3802
|
-
}
|
|
3803
|
-
hideMarkdownBlockDragHandle();
|
|
3804
|
-
});
|
|
3805
|
-
blockDragHandle.addEventListener('dragend', function() {
|
|
3806
|
-
hideMarkdownBlockDragUi();
|
|
3807
|
-
});
|
|
3808
|
-
|
|
3809
|
-
const blockDropIndicator = document.createElement('div');
|
|
3810
|
-
blockDropIndicator.className = 'vf-markdown-editor__block-drop-indicator';
|
|
3811
|
-
blockDropIndicator.setAttribute(DATA_VF_IGNORE, 'true');
|
|
3812
|
-
|
|
3813
|
-
const blockDropLabel = document.createElement('div');
|
|
3814
|
-
blockDropLabel.className = 'vf-markdown-editor__block-drop-label';
|
|
3815
|
-
blockDropLabel.setAttribute(DATA_VF_IGNORE, 'true');
|
|
3816
|
-
|
|
3817
|
-
surfaceWrap.appendChild(surface);
|
|
3818
|
-
surfaceWrap.appendChild(selectionOverlay);
|
|
3819
|
-
|
|
3820
|
-
const textarea = document.createElement('textarea');
|
|
3821
|
-
textarea.className = 'vf-markdown-editor__textarea';
|
|
3822
|
-
textarea.setAttribute(DATA_VF_IGNORE, 'true');
|
|
3823
|
-
textarea.setAttribute('aria-label', 'Markdown editor');
|
|
3824
|
-
textarea.spellcheck = false;
|
|
3825
|
-
textarea.addEventListener('input', function() {
|
|
3826
|
-
handleMarkdownLocalChange(textarea.value);
|
|
3827
|
-
scheduleMarkdownSelectionSync();
|
|
3828
|
-
hideMarkdownSlashMenu();
|
|
3829
|
-
});
|
|
3830
|
-
textarea.addEventListener('select', scheduleMarkdownSelectionSync);
|
|
3831
|
-
textarea.addEventListener('keyup', scheduleMarkdownSelectionSync);
|
|
3832
|
-
textarea.addEventListener('click', scheduleMarkdownSelectionSync);
|
|
3833
|
-
textarea.addEventListener('input', clearMarkdownSelectionOverlay);
|
|
3834
|
-
textarea.addEventListener('keydown', function() {
|
|
3835
|
-
hideMarkdownSlashMenu();
|
|
3836
|
-
hideMarkdownInlineToolbar();
|
|
3837
|
-
hideMarkdownBlockDragUi();
|
|
3838
|
-
});
|
|
3839
|
-
|
|
3840
|
-
surface.addEventListener('mousemove', function(event) {
|
|
3841
|
-
if (markdownBlockDragActive) {
|
|
3842
|
-
return;
|
|
3843
|
-
}
|
|
3844
|
-
|
|
3845
|
-
const index = getMarkdownBlockHoverIndexFromPointer(event.target, event.clientX, event.clientY);
|
|
3846
|
-
if (index < 0) {
|
|
3847
|
-
hideMarkdownBlockDragHandle();
|
|
3848
|
-
return;
|
|
3849
|
-
}
|
|
3850
|
-
const blocks = getMarkdownTopLevelBlocks();
|
|
3851
|
-
const block = blocks[index];
|
|
3852
|
-
if (!block) {
|
|
3853
|
-
hideMarkdownBlockDragHandle();
|
|
3854
|
-
return;
|
|
3855
|
-
}
|
|
3856
|
-
positionMarkdownBlockDragHandle(block, index);
|
|
3857
|
-
});
|
|
3858
|
-
|
|
3859
|
-
surface.addEventListener('mouseleave', function(event) {
|
|
3860
|
-
if (!markdownBlockDragActive) {
|
|
3861
|
-
const next = event.relatedTarget;
|
|
3862
|
-
if (next && markdownBlockDragHandle && (next === markdownBlockDragHandle || markdownBlockDragHandle.contains(next))) {
|
|
3863
|
-
return;
|
|
3864
|
-
}
|
|
3865
|
-
hideMarkdownBlockDragHandle();
|
|
3866
|
-
}
|
|
3867
|
-
});
|
|
3868
|
-
|
|
3869
|
-
surface.addEventListener('dragover', function(event) {
|
|
3870
|
-
if (!markdownBlockDragActive) {
|
|
3871
|
-
return;
|
|
3872
|
-
}
|
|
3873
|
-
event.preventDefault();
|
|
3874
|
-
if (event.dataTransfer) {
|
|
3875
|
-
event.dataTransfer.dropEffect = 'move';
|
|
3876
|
-
}
|
|
3877
|
-
autoScrollMarkdownSurfaceDuringDrag(event.clientY);
|
|
3878
|
-
const slotIndex = getMarkdownDropSlotIndexFromPointer(event.clientY);
|
|
3879
|
-
if (slotIndex >= 0) {
|
|
3880
|
-
showMarkdownBlockDropIndicator(slotIndex);
|
|
3881
|
-
}
|
|
3882
|
-
});
|
|
3883
|
-
|
|
3884
|
-
surface.addEventListener('drop', function(event) {
|
|
3885
|
-
if (!markdownBlockDragActive) {
|
|
3886
|
-
return;
|
|
3887
|
-
}
|
|
3888
|
-
event.preventDefault();
|
|
3889
|
-
|
|
3890
|
-
const fallbackSlot = getMarkdownDropSlotIndexFromPointer(event.clientY);
|
|
3891
|
-
const slotIndex = markdownBlockDropSlotIndex >= 0 ? markdownBlockDropSlotIndex : fallbackSlot;
|
|
3892
|
-
const sourceIndex = markdownBlockDragSourceIndex;
|
|
3893
|
-
hideMarkdownBlockDragUi();
|
|
3894
|
-
if (sourceIndex < 0 || slotIndex < 0) {
|
|
3895
|
-
return;
|
|
3896
|
-
}
|
|
3897
|
-
moveMarkdownLexicalBlock(sourceIndex, slotIndex);
|
|
3898
|
-
});
|
|
3899
|
-
|
|
3900
|
-
document.addEventListener('selectionchange', function() {
|
|
3901
|
-
if (!markdownEditorRoot || markdownEditorRoot.style.display !== 'block') {
|
|
3902
|
-
return;
|
|
3903
|
-
}
|
|
3904
|
-
scheduleMarkdownSelectionSync();
|
|
3905
|
-
scheduleMarkdownSelectionOverlayRender();
|
|
3906
|
-
scheduleMarkdownSlashMenuUpdate();
|
|
3907
|
-
scheduleMarkdownInlineToolbarUpdate();
|
|
3908
|
-
});
|
|
3909
|
-
|
|
3910
|
-
window.addEventListener('resize', scheduleMarkdownSelectionOverlayRender);
|
|
3911
|
-
window.addEventListener('resize', scheduleMarkdownSlashMenuUpdate);
|
|
3912
|
-
window.addEventListener('resize', scheduleMarkdownInlineToolbarUpdate);
|
|
3913
|
-
window.addEventListener('resize', hideMarkdownBlockDragUi);
|
|
3914
|
-
|
|
3915
|
-
editorRoot.appendChild(toolbar);
|
|
3916
|
-
editorRoot.appendChild(mdxBlocks);
|
|
3917
|
-
editorRoot.appendChild(surfaceWrap);
|
|
3918
|
-
editorRoot.appendChild(textarea);
|
|
3919
|
-
editorRoot.appendChild(slashMenu);
|
|
3920
|
-
editorRoot.appendChild(inlineToolbar);
|
|
3921
|
-
editorRoot.appendChild(blockDragHandle);
|
|
3922
|
-
editorRoot.appendChild(blockDropIndicator);
|
|
3923
|
-
editorRoot.appendChild(blockDropLabel);
|
|
3924
|
-
document.body.appendChild(editorRoot);
|
|
3925
|
-
|
|
3926
|
-
markdownEditorRoot = editorRoot;
|
|
3927
|
-
markdownEditorSurface = surface;
|
|
3928
|
-
markdownEditorTextarea = textarea;
|
|
3929
|
-
markdownPersistStatus = status;
|
|
3930
|
-
markdownPresenceRoot = presence;
|
|
3931
|
-
markdownSelectionsRoot = selections;
|
|
3932
|
-
markdownMdxBlocksRoot = mdxBlocks;
|
|
3933
|
-
markdownSelectionOverlayRoot = selectionOverlay;
|
|
3934
|
-
markdownSlashMenuRoot = slashMenu;
|
|
3935
|
-
markdownInlineToolbarRoot = inlineToolbar;
|
|
3936
|
-
markdownBlockDragHandle = blockDragHandle;
|
|
3937
|
-
markdownBlockDropIndicator = blockDropIndicator;
|
|
3938
|
-
markdownBlockDropLabel = blockDropLabel;
|
|
3939
|
-
setMarkdownMdxBlocks(markdownLatestMdxBlocks);
|
|
3940
|
-
setMarkdownPresence(markdownLatestPresenceUsers);
|
|
3941
|
-
setMarkdownSelections(markdownLatestSelections);
|
|
3942
|
-
setupMarkdownLexicalEditor();
|
|
3943
|
-
applyMarkdownContent(markdownCurrentContent);
|
|
3944
|
-
|
|
3945
|
-
return editorRoot;
|
|
3946
|
-
}
|
|
3947
|
-
|
|
3948
|
-
function setMarkdownEditMode(enabled) {
|
|
3949
|
-
const markdownBody = document.getElementById('markdown-body');
|
|
3950
|
-
if (!markdownBody || !isMarkdownPage()) {
|
|
3951
|
-
return;
|
|
3952
|
-
}
|
|
3953
|
-
|
|
3954
|
-
if (enabled) {
|
|
3955
|
-
ensureMarkdownEditor();
|
|
3956
|
-
setupMarkdownLexicalEditor();
|
|
3957
|
-
markdownBody.style.display = 'none';
|
|
3958
|
-
markdownEditorRoot.style.display = 'block';
|
|
3959
|
-
markdownHasUnsavedChanges = false;
|
|
3960
|
-
focusMarkdownEditor();
|
|
3961
|
-
scheduleMarkdownSelectionSync();
|
|
3962
|
-
scheduleMarkdownSelectionOverlayRender();
|
|
3963
|
-
scheduleMarkdownSlashMenuUpdate();
|
|
3964
|
-
scheduleMarkdownInlineToolbarUpdate();
|
|
3965
|
-
postMarkdownEditorReady();
|
|
3966
|
-
|
|
3967
|
-
// Self-connect to Yjs when server-injected config is available
|
|
3968
|
-
if (WS_URL && YJS_GUID && !markdownYDoc) {
|
|
3969
|
-
setupMarkdownYjsConnection({
|
|
3970
|
-
wsUrl: WS_URL,
|
|
3971
|
-
guid: YJS_GUID,
|
|
3972
|
-
fileId: markdownFileId,
|
|
3973
|
-
});
|
|
3974
|
-
}
|
|
3975
|
-
} else {
|
|
3976
|
-
markdownBody.style.display = '';
|
|
3977
|
-
if (markdownEditorRoot) {
|
|
3978
|
-
markdownEditorRoot.style.display = 'none';
|
|
3979
|
-
}
|
|
3980
|
-
hideMarkdownSlashMenu();
|
|
3981
|
-
hideMarkdownInlineToolbar();
|
|
3982
|
-
hideMarkdownBlockDragUi();
|
|
3983
|
-
markdownOverlaySelections = [];
|
|
3984
|
-
clearMarkdownSelectionOverlay();
|
|
3985
|
-
clearMarkdownSelectionSync();
|
|
3986
|
-
disposeMarkdownYjs();
|
|
3987
|
-
}
|
|
3988
|
-
|
|
3989
|
-
const nextUrl = new URL(window.location.href);
|
|
3990
|
-
if (enabled) {
|
|
3991
|
-
nextUrl.searchParams.set('edit', 'true');
|
|
3992
|
-
} else {
|
|
3993
|
-
nextUrl.searchParams.delete('edit');
|
|
3994
|
-
}
|
|
3995
|
-
window.history.replaceState(window.history.state, '', nextUrl.toString());
|
|
3996
|
-
}
|
|
3997
|
-
|
|
3998
|
-
function ensureMarkdownEditButton() {
|
|
3999
|
-
if (markdownEditButton || !isMarkdownPage()) {
|
|
4000
|
-
return;
|
|
4001
|
-
}
|
|
4002
|
-
|
|
4003
|
-
const button = document.createElement('button');
|
|
4004
|
-
button.type = 'button';
|
|
4005
|
-
button.className = 'vf-markdown-edit-button';
|
|
4006
|
-
button.textContent = 'Edit';
|
|
4007
|
-
button.setAttribute(DATA_VF_IGNORE, 'true');
|
|
4008
|
-
button.addEventListener('click', function() {
|
|
4009
|
-
setMarkdownEditMode(true);
|
|
4010
|
-
});
|
|
4011
|
-
|
|
4012
|
-
document.body.appendChild(button);
|
|
4013
|
-
markdownEditButton = button;
|
|
4014
|
-
}
|
|
4015
|
-
|
|
4016
|
-
function setupMarkdownEditor(params) {
|
|
4017
|
-
if (!isMarkdownPage()) {
|
|
4018
|
-
return;
|
|
4019
|
-
}
|
|
4020
|
-
|
|
4021
|
-
markdownFileId = params.get('vf_file_id') || PAGE_ID || null;
|
|
4022
|
-
ensureMarkdownEditButton();
|
|
4023
|
-
|
|
4024
|
-
if (params.get('edit') === 'true') {
|
|
4025
|
-
setMarkdownEditMode(true);
|
|
4026
|
-
}
|
|
4027
|
-
}
|
|
4028
|
-
|
|
4029
|
-
let html2canvasLoaded = false;
|
|
4030
|
-
let html2canvasPromise = null;
|
|
4031
|
-
|
|
4032
|
-
function loadHtml2Canvas() {
|
|
4033
|
-
if (html2canvasLoaded) return Promise.resolve();
|
|
4034
|
-
if (html2canvasPromise) return html2canvasPromise;
|
|
4035
|
-
|
|
4036
|
-
html2canvasPromise = new Promise((resolve, reject) => {
|
|
4037
|
-
const script = document.createElement('script');
|
|
4038
|
-
script.src = 'https://cdn.jsdelivr.net/npm/html2canvas-pro@2.0.0/dist/html2canvas-pro.min.js';
|
|
4039
|
-
script.onload = () => {
|
|
4040
|
-
html2canvasLoaded = true;
|
|
4041
|
-
resolve();
|
|
4042
|
-
};
|
|
4043
|
-
script.onerror = (event) => {
|
|
4044
|
-
console.warn(
|
|
4045
|
-
'[StudioBridge] Failed to load html2canvas script. This may be caused by CSP script-src restrictions.',
|
|
4046
|
-
event
|
|
4047
|
-
);
|
|
4048
|
-
reject(new Error('Failed to load html2canvas script'));
|
|
4049
|
-
};
|
|
4050
|
-
try {
|
|
4051
|
-
document.head.appendChild(script);
|
|
4052
|
-
} catch (error) {
|
|
4053
|
-
console.warn(
|
|
4054
|
-
'[StudioBridge] Failed to append html2canvas script element. This may be caused by CSP script-src restrictions.',
|
|
4055
|
-
error
|
|
4056
|
-
);
|
|
4057
|
-
reject(error instanceof Error ? error : new Error('Failed to append html2canvas script element'));
|
|
4058
|
-
}
|
|
4059
|
-
});
|
|
4060
|
-
|
|
4061
|
-
return html2canvasPromise;
|
|
4062
|
-
}
|
|
4063
|
-
|
|
4064
|
-
async function captureScreenshot(options) {
|
|
4065
|
-
const { scrollTo, fullPage, quality = 0.8 } = options || {};
|
|
4066
|
-
const originalScrollY = window.scrollY;
|
|
4067
|
-
|
|
4068
|
-
try {
|
|
4069
|
-
await loadHtml2Canvas();
|
|
4070
|
-
|
|
4071
|
-
if (typeof scrollTo === 'number') {
|
|
4072
|
-
window.scrollTo(0, scrollTo);
|
|
4073
|
-
await new Promise(r => setTimeout(r, 150));
|
|
4074
|
-
}
|
|
4075
|
-
|
|
4076
|
-
const canvasOptions = {
|
|
4077
|
-
useCORS: true,
|
|
4078
|
-
logging: false,
|
|
4079
|
-
scale: window.devicePixelRatio || 1
|
|
4080
|
-
};
|
|
4081
|
-
|
|
4082
|
-
if (fullPage) {
|
|
4083
|
-
canvasOptions.height = document.documentElement.scrollHeight;
|
|
4084
|
-
canvasOptions.windowHeight = document.documentElement.scrollHeight;
|
|
4085
|
-
canvasOptions.y = 0;
|
|
4086
|
-
window.scrollTo(0, 0);
|
|
4087
|
-
await new Promise(r => setTimeout(r, 100));
|
|
4088
|
-
}
|
|
4089
|
-
|
|
4090
|
-
const html2canvasFn = window.html2canvas.default || window.html2canvas;
|
|
4091
|
-
const canvas = await html2canvasFn(document.body, canvasOptions);
|
|
4092
|
-
|
|
4093
|
-
if (!canvas || canvas.width === 0 || canvas.height === 0) {
|
|
4094
|
-
console.error('[bridge] html2canvas produced empty canvas:', canvas?.width, 'x', canvas?.height);
|
|
4095
|
-
window.scrollTo(0, originalScrollY);
|
|
4096
|
-
return {
|
|
4097
|
-
success: false,
|
|
4098
|
-
error: 'html2canvas produced empty canvas (0x0 dimensions)'
|
|
4099
|
-
};
|
|
4100
|
-
}
|
|
4101
|
-
|
|
4102
|
-
const dataUrl = canvas.toDataURL('image/png', quality);
|
|
4103
|
-
|
|
4104
|
-
if (!dataUrl || !dataUrl.startsWith('data:image/') || dataUrl.length < 100) {
|
|
4105
|
-
console.error('[bridge] html2canvas produced invalid data URL:', dataUrl?.substring(0, 50));
|
|
4106
|
-
window.scrollTo(0, originalScrollY);
|
|
4107
|
-
return {
|
|
4108
|
-
success: false,
|
|
4109
|
-
error: 'html2canvas produced invalid image data'
|
|
4110
|
-
};
|
|
4111
|
-
}
|
|
4112
|
-
|
|
4113
|
-
window.scrollTo(0, originalScrollY);
|
|
4114
|
-
|
|
4115
|
-
return {
|
|
4116
|
-
success: true,
|
|
4117
|
-
data: dataUrl,
|
|
4118
|
-
width: canvas.width,
|
|
4119
|
-
height: canvas.height,
|
|
4120
|
-
scrollY: window.scrollY,
|
|
4121
|
-
totalHeight: document.documentElement.scrollHeight,
|
|
4122
|
-
viewportHeight: window.innerHeight,
|
|
4123
|
-
url: window.location.href
|
|
4124
|
-
};
|
|
4125
|
-
} catch (error) {
|
|
4126
|
-
console.error('[bridge] html2canvas error:', error);
|
|
4127
|
-
window.scrollTo(0, originalScrollY);
|
|
4128
|
-
return {
|
|
4129
|
-
success: false,
|
|
4130
|
-
error: error.message || String(error)
|
|
4131
|
-
};
|
|
4132
|
-
}
|
|
4133
|
-
}
|
|
4134
|
-
|
|
4135
|
-
async function captureMultipleSections(sectionCount) {
|
|
4136
|
-
const originalScrollY = window.scrollY;
|
|
4137
|
-
const results = [];
|
|
4138
|
-
const totalHeight = document.documentElement.scrollHeight;
|
|
4139
|
-
const viewportHeight = window.innerHeight;
|
|
4140
|
-
const sections = sectionCount || Math.ceil(totalHeight / viewportHeight);
|
|
4141
|
-
|
|
4142
|
-
try {
|
|
4143
|
-
for (let i = 0; i < sections; i++) {
|
|
4144
|
-
const scrollY = Math.min(i * viewportHeight, totalHeight - viewportHeight);
|
|
4145
|
-
const result = await captureScreenshot({ scrollTo: scrollY });
|
|
4146
|
-
if (result.success) {
|
|
4147
|
-
results.push({ ...result, section: i + 1, totalSections: sections });
|
|
4148
|
-
}
|
|
4149
|
-
}
|
|
4150
|
-
} finally {
|
|
4151
|
-
window.scrollTo(0, originalScrollY);
|
|
4152
|
-
}
|
|
4153
|
-
|
|
4154
|
-
return results;
|
|
4155
|
-
}
|
|
4156
|
-
|
|
4157
|
-
function handleStudioMessage(event) {
|
|
4158
|
-
if (!isFromStudio(event)) return;
|
|
4159
|
-
|
|
4160
|
-
const message = event.data;
|
|
4161
|
-
if (!message?.action) return;
|
|
4162
|
-
|
|
4163
|
-
switch (message.action) {
|
|
4164
|
-
case 'routeChange':
|
|
4165
|
-
if (message.url) {
|
|
4166
|
-
postToStudio({ action: 'onPageTransitionStart', url: message.url, projectId: PROJECT_ID });
|
|
4167
|
-
window.location.href = message.url;
|
|
4168
|
-
}
|
|
4169
|
-
return;
|
|
4170
|
-
|
|
4171
|
-
case 'reload':
|
|
4172
|
-
window.location.reload();
|
|
4173
|
-
return;
|
|
4174
|
-
|
|
4175
|
-
case 'goBack':
|
|
4176
|
-
window.history.back();
|
|
4177
|
-
return;
|
|
4178
|
-
|
|
4179
|
-
case 'goForward':
|
|
4180
|
-
window.history.forward();
|
|
4181
|
-
return;
|
|
4182
|
-
|
|
4183
|
-
case 'colorMode':
|
|
4184
|
-
setColorMode(message.value);
|
|
4185
|
-
return;
|
|
4186
|
-
|
|
4187
|
-
case 'toggleInspectMode':
|
|
4188
|
-
inspectMode = message.value;
|
|
4189
|
-
if (inspectMode) return;
|
|
4190
|
-
|
|
4191
|
-
hideOverlay(hoverOverlay);
|
|
4192
|
-
hoveredNodeId = null;
|
|
4193
|
-
|
|
4194
|
-
if (!message.deselectElements) return;
|
|
4195
|
-
|
|
4196
|
-
hideOverlay(selectionOverlay);
|
|
4197
|
-
selectedNodeId = null;
|
|
4198
|
-
return;
|
|
4199
|
-
|
|
4200
|
-
case 'setSelectedNode':
|
|
4201
|
-
selectedNodeId = message.id;
|
|
4202
|
-
showSelectionOverlay(message.id);
|
|
4203
|
-
if (message.scroll) scrollToElement(message.id);
|
|
4204
|
-
return;
|
|
4205
|
-
|
|
4206
|
-
case 'setHoveredNode':
|
|
4207
|
-
if (!inspectMode) showHoverOverlay(message.id);
|
|
4208
|
-
return;
|
|
4209
|
-
|
|
4210
|
-
case 'setMarkdownPersistState':
|
|
4211
|
-
if (!isMarkdownPage()) {
|
|
4212
|
-
return;
|
|
4213
|
-
}
|
|
4214
|
-
if (message.fileId && markdownFileId && message.fileId !== markdownFileId) {
|
|
4215
|
-
return;
|
|
4216
|
-
}
|
|
4217
|
-
if (markdownSaveInProgress) {
|
|
4218
|
-
setMarkdownPersistStatus(message.status || 'saved');
|
|
4219
|
-
if (message.status === 'saved' || message.status === 'error') {
|
|
4220
|
-
markdownSaveInProgress = false;
|
|
4221
|
-
markdownHasUnsavedChanges = false;
|
|
4222
|
-
}
|
|
4223
|
-
}
|
|
4224
|
-
return;
|
|
4225
|
-
|
|
4226
|
-
case 'screenshot':
|
|
4227
|
-
(async function() {
|
|
4228
|
-
if (message.multipleSections) {
|
|
4229
|
-
const results = await captureMultipleSections(message.sectionCount);
|
|
4230
|
-
postToStudio({
|
|
4231
|
-
action: 'screenshotResult',
|
|
4232
|
-
requestId: message.requestId,
|
|
4233
|
-
multiple: true,
|
|
4234
|
-
results: results
|
|
4235
|
-
});
|
|
4236
|
-
return;
|
|
4237
|
-
}
|
|
4238
|
-
|
|
4239
|
-
const result = await captureScreenshot(message.options);
|
|
4240
|
-
postToStudio({
|
|
4241
|
-
action: 'screenshotResult',
|
|
4242
|
-
requestId: message.requestId,
|
|
4243
|
-
multiple: false,
|
|
4244
|
-
...result
|
|
4245
|
-
});
|
|
4246
|
-
})();
|
|
4247
|
-
return;
|
|
4248
|
-
|
|
4249
|
-
default:
|
|
4250
|
-
console.debug('[StudioBridge] Unknown action:', message.action);
|
|
4251
|
-
return;
|
|
4252
|
-
}
|
|
4253
|
-
}
|
|
4254
|
-
|
|
4255
|
-
function notifyAppLoaded() {
|
|
4256
|
-
postToStudio({ action: 'appLoaded', url: window.location.href });
|
|
4257
|
-
|
|
4258
|
-
postToStudio({
|
|
4259
|
-
action: 'appUpdated',
|
|
4260
|
-
url: window.location.href,
|
|
4261
|
-
id: PAGE_ID,
|
|
4262
|
-
isInitialLoad: true,
|
|
4263
|
-
errors: [],
|
|
4264
|
-
warnings: []
|
|
4265
|
-
});
|
|
4266
|
-
|
|
4267
|
-
postToStudio({
|
|
4268
|
-
action: 'onPageTransitionEnd',
|
|
4269
|
-
url: window.location.href,
|
|
4270
|
-
projectId: PROJECT_ID,
|
|
4271
|
-
id: PAGE_ID,
|
|
4272
|
-
params: {}
|
|
4273
|
-
});
|
|
4274
|
-
}
|
|
4275
|
-
|
|
4276
|
-
function notifyAppUnloaded() {
|
|
4277
|
-
postToStudio({ action: 'appUnloaded', url: window.location.href });
|
|
4278
|
-
}
|
|
4279
|
-
|
|
4280
|
-
function init() {
|
|
4281
|
-
const params = new URLSearchParams(window.location.search);
|
|
4282
|
-
const studioEmbed = params.get('studio_embed') === 'true';
|
|
4283
|
-
const isStandalone = window.parent === window && !studioEmbed;
|
|
4284
|
-
|
|
4285
|
-
if (isStandalone) {
|
|
4286
|
-
// Allow standalone markdown editing when WS_URL is available (server-injected Yjs config)
|
|
4287
|
-
if (!WS_URL) {
|
|
4288
|
-
console.debug('[StudioBridge] Not in iframe and not studio_embed mode, skipping initialization');
|
|
4289
|
-
return;
|
|
4290
|
-
}
|
|
4291
|
-
}
|
|
4292
|
-
|
|
4293
|
-
console.debug('[StudioBridge] Initializing...');
|
|
4294
|
-
|
|
4295
|
-
// Only set up Studio interaction features when embedded in Studio
|
|
4296
|
-
if (!isStandalone) {
|
|
4297
|
-
injectOverlayStyles();
|
|
4298
|
-
hoverOverlay = createOverlay('hover');
|
|
4299
|
-
selectionOverlay = createOverlay('selection');
|
|
4300
|
-
|
|
4301
|
-
setupConsoleCapture();
|
|
4302
|
-
setupErrorHandling();
|
|
4303
|
-
setupInspectMode();
|
|
4304
|
-
}
|
|
4305
|
-
|
|
4306
|
-
setupMarkdownEditor(params);
|
|
4307
|
-
|
|
4308
|
-
window.addEventListener('message', handleStudioMessage);
|
|
4309
|
-
|
|
4310
|
-
if (!isStandalone) {
|
|
4311
|
-
// IMPORTANT: notifyAppLoaded() must be called BEFORE setupMutationObserver()
|
|
4312
|
-
// because notifyAppLoaded sends onPageTransitionEnd which sets previewId,
|
|
4313
|
-
// and treeUpdated (from setupMutationObserver) requires previewId to be set
|
|
4314
|
-
if (document.readyState === 'loading') {
|
|
4315
|
-
document.addEventListener('DOMContentLoaded', function() {
|
|
4316
|
-
notifyAppLoaded();
|
|
4317
|
-
setupMutationObserver();
|
|
4318
|
-
});
|
|
4319
|
-
} else {
|
|
4320
|
-
notifyAppLoaded();
|
|
4321
|
-
setupMutationObserver();
|
|
4322
|
-
}
|
|
4323
|
-
|
|
4324
|
-
window.addEventListener('beforeunload', notifyAppUnloaded);
|
|
4325
|
-
}
|
|
4326
|
-
|
|
4327
|
-
const colorMode = params.get('color_mode');
|
|
4328
|
-
if (colorMode) setColorMode(colorMode);
|
|
4329
|
-
|
|
4330
|
-
if (!isStandalone) {
|
|
4331
|
-
const inspectModeParam = params.get('inspect_mode');
|
|
4332
|
-
if (inspectModeParam === 'true') {
|
|
4333
|
-
inspectMode = true;
|
|
4334
|
-
console.debug('[StudioBridge] Inspect mode enabled from query param');
|
|
4335
|
-
}
|
|
4336
|
-
}
|
|
4337
|
-
|
|
4338
|
-
console.debug('[StudioBridge] Initialized successfully');
|
|
4339
|
-
}
|
|
4340
|
-
|
|
4341
|
-
if (DEBUG_EXPOSE_INTERNALS && typeof window !== 'undefined') {
|
|
4342
|
-
window.__VF_STUDIO_BRIDGE_DEBUG = {
|
|
4343
|
-
parseMdxImportMap: parseMdxImportMap,
|
|
4344
|
-
extractRawBlocksForEditor: extractRawBlocksForEditor,
|
|
4345
|
-
getMdxBlockOpenUiState: getMdxBlockOpenUiState
|
|
4346
|
-
};
|
|
4347
|
-
}
|
|
4348
|
-
|
|
4349
|
-
if (!DEBUG_SKIP_INIT) {
|
|
4350
|
-
init();
|
|
4351
|
-
}
|
|
4352
|
-
})();`;
|
|
4353
|
-
}
|