veryfront 0.1.26 → 0.1.28
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +3 -11
- package/esm/cli/app/shell.d.ts.map +1 -1
- package/esm/cli/app/shell.js +9 -5
- package/esm/cli/commands/demo/demo.js +1 -1
- package/esm/cli/commands/init/catalog.d.ts.map +1 -1
- package/esm/cli/commands/init/catalog.js +13 -5
- package/esm/cli/commands/init/command-help.js +4 -4
- package/esm/cli/commands/init/types.d.ts +1 -1
- package/esm/cli/commands/init/types.d.ts.map +1 -1
- package/esm/cli/commands/serve/command.d.ts.map +1 -1
- package/esm/cli/commands/serve/command.js +0 -4
- package/esm/cli/commands/start/command.d.ts.map +1 -1
- package/esm/cli/commands/start/command.js +16 -9
- package/esm/cli/help/tips.js +6 -6
- package/esm/cli/mcp/remote-file-tools.js +1 -1
- package/esm/cli/mcp/tools/catalog-tools.d.ts +3 -3
- package/esm/cli/mcp/tools/catalog-tools.d.ts.map +1 -1
- package/esm/cli/mcp/tools/catalog-tools.js +21 -13
- package/esm/cli/mcp/tools/project-tools.js +1 -1
- package/esm/cli/templates/index.js +11 -11
- package/esm/cli/templates/manifest.d.ts +22 -15
- package/esm/cli/templates/manifest.js +24 -17
- package/esm/cli/templates/types.d.ts +1 -1
- package/esm/cli/templates/types.d.ts.map +1 -1
- package/esm/cli/utils/index.d.ts.map +1 -1
- package/esm/cli/utils/index.js +13 -1
- package/esm/deno.js +1 -1
- package/esm/src/html/html-shell-generator.d.ts.map +1 -1
- package/esm/src/html/html-shell-generator.js +2 -0
- package/esm/src/html/styles-builder/project-css-cache.d.ts +8 -1
- package/esm/src/html/styles-builder/project-css-cache.d.ts.map +1 -1
- package/esm/src/html/styles-builder/project-css-cache.js +13 -2
- package/esm/src/html/styles-builder/tailwind-compiler.d.ts +2 -0
- package/esm/src/html/styles-builder/tailwind-compiler.d.ts.map +1 -1
- package/esm/src/html/styles-builder/tailwind-compiler.js +52 -19
- package/esm/src/modules/react-loader/css-import-collector.d.ts +29 -0
- package/esm/src/modules/react-loader/css-import-collector.d.ts.map +1 -0
- package/esm/src/modules/react-loader/css-import-collector.js +41 -0
- package/esm/src/modules/react-loader/ssr-module-loader/loader.d.ts.map +1 -1
- package/esm/src/modules/react-loader/ssr-module-loader/loader.js +6 -0
- package/esm/src/modules/react-loader/ssr-module-loader/ssr-dependency-validator.d.ts.map +1 -1
- package/esm/src/modules/react-loader/ssr-module-loader/ssr-dependency-validator.js +5 -0
- package/esm/src/platform/adapters/fs/factory.d.ts.map +1 -1
- package/esm/src/platform/adapters/fs/factory.js +5 -1
- package/esm/src/platform/adapters/fs/veryfront/websocket-manager.d.ts +1 -0
- package/esm/src/platform/adapters/fs/veryfront/websocket-manager.d.ts.map +1 -1
- package/esm/src/platform/adapters/fs/veryfront/websocket-manager.js +19 -5
- package/esm/src/platform/compat/process.d.ts.map +1 -1
- package/esm/src/platform/compat/process.js +20 -3
- package/esm/src/proxy/main.js +31 -12
- package/esm/src/proxy/token-manager.d.ts +2 -0
- package/esm/src/proxy/token-manager.d.ts.map +1 -1
- package/esm/src/proxy/token-manager.js +47 -8
- package/esm/src/rendering/orchestrator/css-candidate-manifest.d.ts +23 -0
- package/esm/src/rendering/orchestrator/css-candidate-manifest.d.ts.map +1 -0
- package/esm/src/rendering/orchestrator/css-candidate-manifest.js +132 -0
- package/esm/src/rendering/orchestrator/html.d.ts +11 -1
- package/esm/src/rendering/orchestrator/html.d.ts.map +1 -1
- package/esm/src/rendering/orchestrator/html.js +103 -18
- package/esm/src/rendering/orchestrator/pipeline.d.ts.map +1 -1
- package/esm/src/rendering/orchestrator/pipeline.js +14 -2
- package/esm/src/server/bootstrap.d.ts +2 -0
- package/esm/src/server/bootstrap.d.ts.map +1 -1
- package/esm/src/server/bootstrap.js +10 -0
- package/esm/src/server/handlers/preview/markdown-html-generator.d.ts.map +1 -1
- package/esm/src/server/handlers/preview/markdown-html-generator.js +11 -5
- package/esm/src/server/production-server.js +10 -2
- package/esm/src/studio/bridge-template.d.ts +2 -0
- package/esm/src/studio/bridge-template.d.ts.map +1 -1
- package/esm/src/studio/bridge-template.js +3390 -52
- package/esm/src/transforms/css-modules/naming.d.ts +33 -0
- package/esm/src/transforms/css-modules/naming.d.ts.map +1 -0
- package/esm/src/transforms/css-modules/naming.js +128 -0
- package/esm/src/transforms/esm/import-parser.d.ts +1 -0
- package/esm/src/transforms/esm/import-parser.d.ts.map +1 -1
- package/esm/src/transforms/esm/import-parser.js +16 -5
- package/esm/src/transforms/pipeline/index.d.ts.map +1 -1
- package/esm/src/transforms/pipeline/index.js +3 -1
- package/esm/src/transforms/pipeline/stages/index.d.ts +1 -0
- package/esm/src/transforms/pipeline/stages/index.d.ts.map +1 -1
- package/esm/src/transforms/pipeline/stages/index.js +1 -0
- package/esm/src/transforms/pipeline/stages/ssr-css-strip.d.ts +18 -0
- package/esm/src/transforms/pipeline/stages/ssr-css-strip.d.ts.map +1 -0
- package/esm/src/transforms/pipeline/stages/ssr-css-strip.js +168 -0
- package/package.json +1 -1
- package/src/cli/app/shell.ts +9 -5
- package/src/cli/commands/demo/demo.ts +1 -1
- package/src/cli/commands/init/catalog.ts +13 -5
- package/src/cli/commands/init/command-help.ts +4 -4
- package/src/cli/commands/init/types.ts +5 -5
- package/src/cli/commands/serve/command.ts +0 -5
- package/src/cli/commands/start/command.ts +15 -10
- package/src/cli/help/tips.ts +6 -6
- package/src/cli/mcp/remote-file-tools.ts +1 -1
- package/src/cli/mcp/tools/catalog-tools.ts +21 -13
- package/src/cli/mcp/tools/project-tools.ts +1 -1
- package/src/cli/templates/index.ts +11 -11
- package/src/cli/templates/manifest.js +24 -17
- package/src/cli/templates/types.ts +5 -5
- package/src/cli/utils/index.ts +12 -1
- package/src/deno.js +1 -1
- package/src/src/html/html-shell-generator.ts +2 -0
- package/src/src/html/styles-builder/project-css-cache.ts +24 -1
- package/src/src/html/styles-builder/tailwind-compiler.ts +67 -26
- package/src/src/modules/react-loader/css-import-collector.ts +50 -0
- package/src/src/modules/react-loader/ssr-module-loader/loader.ts +7 -0
- package/src/src/modules/react-loader/ssr-module-loader/ssr-dependency-validator.ts +6 -0
- package/src/src/platform/adapters/fs/factory.ts +5 -1
- package/src/src/platform/adapters/fs/veryfront/websocket-manager.ts +21 -5
- package/src/src/platform/compat/process.ts +28 -4
- package/src/src/proxy/main.ts +32 -12
- package/src/src/proxy/token-manager.ts +54 -8
- package/src/src/rendering/orchestrator/css-candidate-manifest.ts +176 -0
- package/src/src/rendering/orchestrator/html.ts +128 -16
- package/src/src/rendering/orchestrator/pipeline.ts +183 -165
- package/src/src/server/bootstrap.ts +16 -0
- package/src/src/server/handlers/preview/markdown-html-generator.ts +12 -5
- package/src/src/server/production-server.ts +12 -2
- package/src/src/studio/bridge-template.ts +3392 -52
- package/src/src/transforms/css-modules/naming.ts +152 -0
- package/src/src/transforms/esm/import-parser.ts +15 -5
- package/src/src/transforms/pipeline/index.ts +3 -0
- package/src/src/transforms/pipeline/stages/index.ts +1 -0
- package/src/src/transforms/pipeline/stages/ssr-css-strip.ts +201 -0
|
@@ -5,6 +5,8 @@ export function generateStudioBridgeScript(options) {
|
|
|
5
5
|
const PROJECT_ID = ${JSON.stringify(options.projectId)};
|
|
6
6
|
const PAGE_ID = ${JSON.stringify(options.pageId)};
|
|
7
7
|
const PAGE_PATH = ${JSON.stringify(options.pagePath ?? options.pageId)};
|
|
8
|
+
const DEBUG_SKIP_INIT = ${options.debugSkipInit ? "true" : "false"};
|
|
9
|
+
const DEBUG_EXPOSE_INTERNALS = ${options.debugExposeInternals ? "true" : "false"};
|
|
8
10
|
|
|
9
11
|
const DATA_VF_ID = 'data-vf-id';
|
|
10
12
|
const DATA_VF_SELECTOR = 'data-vf-selector';
|
|
@@ -24,6 +26,100 @@ export function generateStudioBridgeScript(options) {
|
|
|
24
26
|
|
|
25
27
|
let hoverOverlay = null;
|
|
26
28
|
let selectionOverlay = null;
|
|
29
|
+
let markdownEditorRoot = null;
|
|
30
|
+
let markdownEditorSurface = null;
|
|
31
|
+
let markdownEditorTextarea = null;
|
|
32
|
+
let markdownEditButton = null;
|
|
33
|
+
let markdownFileId = null;
|
|
34
|
+
let markdownSyncTimer = null;
|
|
35
|
+
let markdownSelectionSyncTimer = null;
|
|
36
|
+
let markdownPersistStatus = null;
|
|
37
|
+
let markdownPresenceRoot = null;
|
|
38
|
+
let markdownSelectionsRoot = null;
|
|
39
|
+
let markdownSelectionOverlayRoot = null;
|
|
40
|
+
let markdownOverlaySelections = [];
|
|
41
|
+
let markdownSelectionOverlayRenderFrame = null;
|
|
42
|
+
let markdownSlashMenuRoot = null;
|
|
43
|
+
let markdownSlashMenuTimer = null;
|
|
44
|
+
let markdownSlashMenuContext = null;
|
|
45
|
+
let markdownSlashMenuCommands = [];
|
|
46
|
+
let markdownSlashMenuActiveIndex = 0;
|
|
47
|
+
let markdownInlineToolbarRoot = null;
|
|
48
|
+
let markdownInlineToolbarFrame = null;
|
|
49
|
+
let markdownBlockDragHandle = null;
|
|
50
|
+
let markdownBlockDropIndicator = null;
|
|
51
|
+
let markdownBlockDropLabel = null;
|
|
52
|
+
let markdownBlockDragGhost = null;
|
|
53
|
+
let markdownBlockDragSourceIndex = -1;
|
|
54
|
+
let markdownBlockDropSlotIndex = -1;
|
|
55
|
+
let markdownBlockHandleHoverIndex = -1;
|
|
56
|
+
let markdownBlockDragActive = false;
|
|
57
|
+
let markdownMdxBlocksRoot = null;
|
|
58
|
+
let markdownLexicalApi = null;
|
|
59
|
+
let markdownLexicalSetupPromise = null;
|
|
60
|
+
let markdownCurrentContent = '';
|
|
61
|
+
let markdownCurrentEditorContent = '';
|
|
62
|
+
let markdownLexicalRenderedContent = null;
|
|
63
|
+
let markdownApplyingRemoteUpdate = false;
|
|
64
|
+
let markdownFrontmatter = '';
|
|
65
|
+
let markdownRawBlocks = [];
|
|
66
|
+
let markdownRawBlockTokenPrefix = 'VF_RAW_BLOCK';
|
|
67
|
+
let markdownLatestMdxBlocks = [];
|
|
68
|
+
let markdownLatestMdxImportMap = {};
|
|
69
|
+
let markdownLatestPresenceUsers = [];
|
|
70
|
+
let markdownLatestSelections = [];
|
|
71
|
+
let markdownHasUnsavedChanges = false;
|
|
72
|
+
|
|
73
|
+
const MARKDOWN_SLASH_COMMANDS = [
|
|
74
|
+
{
|
|
75
|
+
id: 'heading-1',
|
|
76
|
+
label: 'Heading 1',
|
|
77
|
+
description: 'Create a top-level heading',
|
|
78
|
+
aliases: ['h1', 'heading', 'title']
|
|
79
|
+
},
|
|
80
|
+
{
|
|
81
|
+
id: 'heading-2',
|
|
82
|
+
label: 'Heading 2',
|
|
83
|
+
description: 'Create a second-level heading',
|
|
84
|
+
aliases: ['h2', 'heading2', 'subheading']
|
|
85
|
+
},
|
|
86
|
+
{
|
|
87
|
+
id: 'heading-3',
|
|
88
|
+
label: 'Heading 3',
|
|
89
|
+
description: 'Create a third-level heading',
|
|
90
|
+
aliases: ['h3', 'heading3']
|
|
91
|
+
},
|
|
92
|
+
{
|
|
93
|
+
id: 'bulleted-list',
|
|
94
|
+
label: 'Bulleted list',
|
|
95
|
+
description: 'Start a bullet list item',
|
|
96
|
+
aliases: ['list', 'bullet', 'ul']
|
|
97
|
+
},
|
|
98
|
+
{
|
|
99
|
+
id: 'numbered-list',
|
|
100
|
+
label: 'Numbered list',
|
|
101
|
+
description: 'Start a numbered list item',
|
|
102
|
+
aliases: ['olist', 'numbered', 'ol']
|
|
103
|
+
},
|
|
104
|
+
{
|
|
105
|
+
id: 'quote-block',
|
|
106
|
+
label: 'Quote',
|
|
107
|
+
description: 'Insert a block quote line',
|
|
108
|
+
aliases: ['quote', 'blockquote']
|
|
109
|
+
},
|
|
110
|
+
{
|
|
111
|
+
id: 'code-block',
|
|
112
|
+
label: 'Code block',
|
|
113
|
+
description: 'Insert a fenced code block',
|
|
114
|
+
aliases: ['code', 'fence', 'snippet']
|
|
115
|
+
},
|
|
116
|
+
{
|
|
117
|
+
id: 'image',
|
|
118
|
+
label: 'Image',
|
|
119
|
+
description: 'Insert markdown image syntax',
|
|
120
|
+
aliases: ['image', 'img', 'photo']
|
|
121
|
+
}
|
|
122
|
+
];
|
|
27
123
|
|
|
28
124
|
function debounce(fn, ms) {
|
|
29
125
|
let timer;
|
|
@@ -74,8 +170,580 @@ export function generateStudioBridgeScript(options) {
|
|
|
74
170
|
bottom: -22px;
|
|
75
171
|
border-radius: 0 0 3px 3px;
|
|
76
172
|
}
|
|
173
|
+
|
|
174
|
+
.vf-markdown-edit-button {
|
|
175
|
+
position: fixed;
|
|
176
|
+
right: 16px;
|
|
177
|
+
bottom: 16px;
|
|
178
|
+
z-index: 100001;
|
|
179
|
+
border: 1px solid rgba(0, 0, 0, 0.2);
|
|
180
|
+
border-radius: 8px;
|
|
181
|
+
background: #111827;
|
|
182
|
+
color: #ffffff;
|
|
183
|
+
font-size: 13px;
|
|
184
|
+
line-height: 1;
|
|
185
|
+
padding: 10px 12px;
|
|
186
|
+
cursor: pointer;
|
|
187
|
+
box-shadow: 0 10px 20px rgba(0, 0, 0, 0.15);
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
.vf-markdown-editor {
|
|
191
|
+
position: fixed;
|
|
192
|
+
inset: 0;
|
|
193
|
+
z-index: 100000;
|
|
194
|
+
background: var(--vf-markdown-editor-bg, #ffffff);
|
|
195
|
+
display: none;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
.vf-markdown-editor__toolbar {
|
|
199
|
+
display: flex;
|
|
200
|
+
justify-content: space-between;
|
|
201
|
+
align-items: center;
|
|
202
|
+
gap: 8px;
|
|
203
|
+
padding: 12px 16px;
|
|
204
|
+
border-bottom: 1px solid rgba(0, 0, 0, 0.12);
|
|
205
|
+
background: rgba(255, 255, 255, 0.94);
|
|
206
|
+
position: sticky;
|
|
207
|
+
top: 0;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
.vf-markdown-editor__title {
|
|
211
|
+
font-size: 12px;
|
|
212
|
+
color: #374151;
|
|
213
|
+
font-weight: 600;
|
|
214
|
+
display: inline-flex;
|
|
215
|
+
flex-direction: column;
|
|
216
|
+
gap: 2px;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
.vf-markdown-editor__title-main {
|
|
220
|
+
font-size: 12px;
|
|
221
|
+
font-weight: 700;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
.vf-markdown-editor__title-hints {
|
|
225
|
+
font-size: 10px;
|
|
226
|
+
font-weight: 500;
|
|
227
|
+
color: #6b7280;
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
.vf-markdown-editor__actions {
|
|
231
|
+
display: inline-flex;
|
|
232
|
+
align-items: center;
|
|
233
|
+
gap: 8px;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
.vf-markdown-editor__status {
|
|
237
|
+
font-size: 12px;
|
|
238
|
+
color: #6b7280;
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
.vf-markdown-editor__status[data-state='saving'] {
|
|
242
|
+
color: #b45309;
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
.vf-markdown-editor__status[data-state='saved'] {
|
|
246
|
+
color: #15803d;
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
.vf-markdown-editor__status[data-state='error'] {
|
|
250
|
+
color: #b91c1c;
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
.vf-markdown-editor__presence {
|
|
254
|
+
display: none;
|
|
255
|
+
align-items: center;
|
|
256
|
+
gap: 6px;
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
.vf-markdown-editor__presence-pill {
|
|
260
|
+
border: 1px solid rgba(0, 0, 0, 0.16);
|
|
261
|
+
border-left-width: 4px;
|
|
262
|
+
border-radius: 999px;
|
|
263
|
+
padding: 2px 8px;
|
|
264
|
+
font-size: 11px;
|
|
265
|
+
color: #111827;
|
|
266
|
+
background: #ffffff;
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
.vf-markdown-editor__presence-pill[data-current='true'] {
|
|
270
|
+
font-weight: 600;
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
.vf-markdown-editor__presence-pill[data-agent='true'] {
|
|
274
|
+
font-style: italic;
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
.vf-markdown-editor__selections {
|
|
278
|
+
display: none;
|
|
279
|
+
align-items: center;
|
|
280
|
+
gap: 6px;
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
.vf-markdown-editor__selection-pill {
|
|
284
|
+
border: 1px solid rgba(0, 0, 0, 0.16);
|
|
285
|
+
border-left-width: 4px;
|
|
286
|
+
border-radius: 999px;
|
|
287
|
+
padding: 2px 8px;
|
|
288
|
+
font-size: 11px;
|
|
289
|
+
color: #111827;
|
|
290
|
+
background: #ffffff;
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
.vf-markdown-editor__exit {
|
|
294
|
+
border: 1px solid rgba(0, 0, 0, 0.16);
|
|
295
|
+
border-radius: 6px;
|
|
296
|
+
background: #ffffff;
|
|
297
|
+
color: #111827;
|
|
298
|
+
font-size: 12px;
|
|
299
|
+
padding: 6px 10px;
|
|
300
|
+
cursor: pointer;
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
.vf-markdown-editor__history {
|
|
304
|
+
border: 1px solid rgba(0, 0, 0, 0.16);
|
|
305
|
+
border-radius: 6px;
|
|
306
|
+
background: #ffffff;
|
|
307
|
+
color: #111827;
|
|
308
|
+
font-size: 13px;
|
|
309
|
+
line-height: 1;
|
|
310
|
+
min-width: 28px;
|
|
311
|
+
height: 28px;
|
|
312
|
+
padding: 0 8px;
|
|
313
|
+
cursor: pointer;
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
.vf-markdown-editor__surface-wrap {
|
|
317
|
+
position: relative;
|
|
318
|
+
height: calc(100vh - 52px);
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
.vf-markdown-editor__surface {
|
|
322
|
+
width: 100%;
|
|
323
|
+
max-width: 980px;
|
|
324
|
+
margin: 0 auto;
|
|
325
|
+
height: 100%;
|
|
326
|
+
overflow: auto;
|
|
327
|
+
outline: none;
|
|
328
|
+
position: relative;
|
|
329
|
+
z-index: 1;
|
|
330
|
+
background: transparent;
|
|
331
|
+
padding: 32px 40px;
|
|
332
|
+
box-sizing: border-box;
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
.vf-markdown-editor__selection-overlay {
|
|
336
|
+
position: absolute;
|
|
337
|
+
inset: 0;
|
|
338
|
+
pointer-events: none;
|
|
339
|
+
z-index: 2;
|
|
340
|
+
display: none;
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
.vf-markdown-editor__selection-highlight {
|
|
344
|
+
position: absolute;
|
|
345
|
+
border-radius: 3px;
|
|
346
|
+
opacity: 0.26;
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
.vf-markdown-editor__selection-caret {
|
|
350
|
+
position: absolute;
|
|
351
|
+
width: 2px;
|
|
352
|
+
border-radius: 1px;
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
.vf-markdown-editor__selection-label {
|
|
356
|
+
position: absolute;
|
|
357
|
+
transform: translateY(-100%);
|
|
358
|
+
margin-top: -4px;
|
|
359
|
+
border-radius: 999px;
|
|
360
|
+
padding: 1px 7px;
|
|
361
|
+
font-size: 10px;
|
|
362
|
+
line-height: 1.4;
|
|
363
|
+
white-space: nowrap;
|
|
364
|
+
color: #ffffff;
|
|
365
|
+
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.16);
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
.vf-markdown-editor__slash-menu {
|
|
369
|
+
position: fixed;
|
|
370
|
+
z-index: 100005;
|
|
371
|
+
min-width: 220px;
|
|
372
|
+
max-width: 300px;
|
|
373
|
+
border: 1px solid rgba(17, 24, 39, 0.16);
|
|
374
|
+
border-radius: 10px;
|
|
375
|
+
background: #ffffff;
|
|
376
|
+
box-shadow: 0 12px 28px rgba(0, 0, 0, 0.18);
|
|
377
|
+
padding: 6px;
|
|
378
|
+
display: none;
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
.vf-markdown-editor__slash-item {
|
|
382
|
+
display: block;
|
|
383
|
+
width: 100%;
|
|
384
|
+
border: 0;
|
|
385
|
+
border-radius: 8px;
|
|
386
|
+
background: transparent;
|
|
387
|
+
text-align: left;
|
|
388
|
+
padding: 8px 10px;
|
|
389
|
+
cursor: pointer;
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
.vf-markdown-editor__slash-item:hover,
|
|
393
|
+
.vf-markdown-editor__slash-item[data-active='true'] {
|
|
394
|
+
background: rgba(0, 129, 248, 0.12);
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
.vf-markdown-editor__slash-item-title {
|
|
398
|
+
display: block;
|
|
399
|
+
font-size: 12px;
|
|
400
|
+
font-weight: 600;
|
|
401
|
+
color: #111827;
|
|
402
|
+
line-height: 1.35;
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
.vf-markdown-editor__slash-item-desc {
|
|
406
|
+
display: block;
|
|
407
|
+
margin-top: 2px;
|
|
408
|
+
font-size: 11px;
|
|
409
|
+
color: #6b7280;
|
|
410
|
+
line-height: 1.35;
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
.vf-markdown-editor__inline-toolbar {
|
|
414
|
+
position: fixed;
|
|
415
|
+
z-index: 100006;
|
|
416
|
+
display: none;
|
|
417
|
+
align-items: center;
|
|
418
|
+
gap: 2px;
|
|
419
|
+
border: 1px solid rgba(17, 24, 39, 0.16);
|
|
420
|
+
border-radius: 8px;
|
|
421
|
+
background: #ffffff;
|
|
422
|
+
box-shadow: 0 8px 20px rgba(0, 0, 0, 0.16);
|
|
423
|
+
padding: 4px;
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
.vf-markdown-editor__inline-button {
|
|
427
|
+
border: 0;
|
|
428
|
+
border-radius: 6px;
|
|
429
|
+
background: transparent;
|
|
430
|
+
color: #111827;
|
|
431
|
+
font-size: 12px;
|
|
432
|
+
font-weight: 600;
|
|
433
|
+
line-height: 1;
|
|
434
|
+
min-width: 26px;
|
|
435
|
+
height: 24px;
|
|
436
|
+
padding: 0 7px;
|
|
437
|
+
cursor: pointer;
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
.vf-markdown-editor__inline-button:hover {
|
|
441
|
+
background: rgba(0, 129, 248, 0.12);
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
.vf-markdown-editor__block-handle {
|
|
445
|
+
position: fixed;
|
|
446
|
+
z-index: 100007;
|
|
447
|
+
display: none;
|
|
448
|
+
border: 1px solid rgba(17, 24, 39, 0.18);
|
|
449
|
+
border-radius: 6px;
|
|
450
|
+
background: #ffffff;
|
|
451
|
+
color: #374151;
|
|
452
|
+
font-size: 12px;
|
|
453
|
+
font-weight: 700;
|
|
454
|
+
line-height: 1;
|
|
455
|
+
width: 28px;
|
|
456
|
+
height: 28px;
|
|
457
|
+
padding: 0;
|
|
458
|
+
cursor: grab;
|
|
459
|
+
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.14);
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
.vf-markdown-editor__block-handle:hover {
|
|
463
|
+
background: rgba(0, 129, 248, 0.12);
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
.vf-markdown-editor__block-handle[data-dragging='true'] {
|
|
467
|
+
cursor: grabbing;
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
.vf-markdown-editor__block-drop-indicator {
|
|
471
|
+
position: fixed;
|
|
472
|
+
z-index: 100006;
|
|
473
|
+
display: none;
|
|
474
|
+
height: 2px;
|
|
475
|
+
border-radius: 999px;
|
|
476
|
+
background: #0081f8;
|
|
477
|
+
box-shadow: 0 1px 6px rgba(0, 129, 248, 0.5);
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
.vf-markdown-editor__block-drop-label {
|
|
481
|
+
position: fixed;
|
|
482
|
+
z-index: 100007;
|
|
483
|
+
display: none;
|
|
484
|
+
border-radius: 999px;
|
|
485
|
+
border: 1px solid rgba(0, 129, 248, 0.24);
|
|
486
|
+
background: rgba(255, 255, 255, 0.96);
|
|
487
|
+
color: #0f172a;
|
|
488
|
+
font-size: 11px;
|
|
489
|
+
font-weight: 600;
|
|
490
|
+
line-height: 1.2;
|
|
491
|
+
padding: 3px 8px;
|
|
492
|
+
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.14);
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
.vf-markdown-editor__block-drag-ghost {
|
|
496
|
+
position: fixed;
|
|
497
|
+
top: -9999px;
|
|
498
|
+
left: -9999px;
|
|
499
|
+
width: 260px;
|
|
500
|
+
border: 1px solid rgba(17, 24, 39, 0.22);
|
|
501
|
+
border-radius: 10px;
|
|
502
|
+
background: #ffffff;
|
|
503
|
+
box-shadow: 0 10px 24px rgba(0, 0, 0, 0.2);
|
|
504
|
+
padding: 8px 10px;
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
.vf-markdown-editor__block-drag-ghost-title {
|
|
508
|
+
display: block;
|
|
509
|
+
font-size: 11px;
|
|
510
|
+
font-weight: 700;
|
|
511
|
+
color: #1e293b;
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
.vf-markdown-editor__block-drag-ghost-text {
|
|
515
|
+
display: block;
|
|
516
|
+
margin-top: 4px;
|
|
517
|
+
font-size: 11px;
|
|
518
|
+
color: #475569;
|
|
519
|
+
line-height: 1.35;
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
.vf-markdown-editor__mdx-blocks {
|
|
523
|
+
display: none;
|
|
524
|
+
gap: 8px;
|
|
525
|
+
padding: 8px 16px;
|
|
526
|
+
border-bottom: 1px solid rgba(0, 0, 0, 0.08);
|
|
527
|
+
background: rgba(245, 247, 250, 0.95);
|
|
528
|
+
overflow-x: auto;
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
.vf-markdown-editor__mdx-block {
|
|
532
|
+
display: inline-flex;
|
|
533
|
+
align-items: center;
|
|
534
|
+
gap: 8px;
|
|
535
|
+
border: 1px solid rgba(0, 0, 0, 0.16);
|
|
536
|
+
border-radius: 8px;
|
|
537
|
+
background: #ffffff;
|
|
538
|
+
padding: 6px 8px;
|
|
539
|
+
}
|
|
540
|
+
|
|
541
|
+
.vf-markdown-editor__mdx-block-label {
|
|
542
|
+
font-size: 11px;
|
|
543
|
+
color: #334155;
|
|
544
|
+
white-space: nowrap;
|
|
545
|
+
}
|
|
546
|
+
|
|
547
|
+
.vf-markdown-editor__mdx-note {
|
|
548
|
+
font-size: 10px;
|
|
549
|
+
color: #6b7280;
|
|
550
|
+
white-space: nowrap;
|
|
551
|
+
}
|
|
552
|
+
|
|
553
|
+
.vf-markdown-editor__mdx-open {
|
|
554
|
+
border: 1px solid rgba(0, 0, 0, 0.16);
|
|
555
|
+
border-radius: 6px;
|
|
556
|
+
background: #ffffff;
|
|
557
|
+
color: #0f172a;
|
|
558
|
+
font-size: 11px;
|
|
559
|
+
line-height: 1;
|
|
560
|
+
padding: 5px 7px;
|
|
561
|
+
cursor: pointer;
|
|
562
|
+
white-space: nowrap;
|
|
563
|
+
}
|
|
564
|
+
|
|
565
|
+
.vf-markdown-editor__surface [data-lexical-editor] {
|
|
566
|
+
outline: none;
|
|
567
|
+
}
|
|
568
|
+
|
|
569
|
+
.vf-markdown-editor__surface p:empty::before {
|
|
570
|
+
content: '';
|
|
571
|
+
display: inline-block;
|
|
572
|
+
}
|
|
573
|
+
|
|
574
|
+
.vf-markdown-editor__surface p {
|
|
575
|
+
min-height: 1.5em;
|
|
576
|
+
}
|
|
577
|
+
|
|
578
|
+
.vf-markdown-editor__textarea {
|
|
579
|
+
width: 100%;
|
|
580
|
+
height: calc(100vh - 52px);
|
|
581
|
+
border: 0;
|
|
582
|
+
outline: none;
|
|
583
|
+
resize: none;
|
|
584
|
+
display: none;
|
|
585
|
+
font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, 'Liberation Mono', 'Courier New', monospace;
|
|
586
|
+
font-size: 14px;
|
|
587
|
+
line-height: 1.6;
|
|
588
|
+
color: #111827;
|
|
589
|
+
background: transparent;
|
|
590
|
+
padding: 16px;
|
|
591
|
+
box-sizing: border-box;
|
|
592
|
+
}
|
|
593
|
+
|
|
594
|
+
[data-theme='dark'] .vf-markdown-editor {
|
|
595
|
+
--vf-markdown-editor-bg: #0b1220;
|
|
596
|
+
}
|
|
597
|
+
|
|
598
|
+
[data-theme='dark'] .vf-markdown-editor__toolbar {
|
|
599
|
+
border-bottom-color: rgba(255, 255, 255, 0.18);
|
|
600
|
+
background: rgba(17, 24, 39, 0.92);
|
|
601
|
+
}
|
|
602
|
+
|
|
603
|
+
[data-theme='dark'] .vf-markdown-editor__title {
|
|
604
|
+
color: #d1d5db;
|
|
605
|
+
}
|
|
606
|
+
|
|
607
|
+
[data-theme='dark'] .vf-markdown-editor__title-hints {
|
|
608
|
+
color: #9ca3af;
|
|
609
|
+
}
|
|
610
|
+
|
|
611
|
+
[data-theme='dark'] .vf-markdown-editor__exit {
|
|
612
|
+
background: #111827;
|
|
613
|
+
border-color: rgba(255, 255, 255, 0.2);
|
|
614
|
+
color: #f9fafb;
|
|
615
|
+
}
|
|
616
|
+
|
|
617
|
+
[data-theme='dark'] .vf-markdown-editor__history {
|
|
618
|
+
background: #111827;
|
|
619
|
+
border-color: rgba(255, 255, 255, 0.2);
|
|
620
|
+
color: #f9fafb;
|
|
621
|
+
}
|
|
622
|
+
|
|
623
|
+
[data-theme='dark'] .vf-markdown-editor__status {
|
|
624
|
+
color: #9ca3af;
|
|
625
|
+
}
|
|
626
|
+
|
|
627
|
+
[data-theme='dark'] .vf-markdown-editor__status[data-state='saving'] {
|
|
628
|
+
color: #fbbf24;
|
|
629
|
+
}
|
|
630
|
+
|
|
631
|
+
[data-theme='dark'] .vf-markdown-editor__status[data-state='saved'] {
|
|
632
|
+
color: #4ade80;
|
|
633
|
+
}
|
|
634
|
+
|
|
635
|
+
[data-theme='dark'] .vf-markdown-editor__status[data-state='error'] {
|
|
636
|
+
color: #f87171;
|
|
637
|
+
}
|
|
638
|
+
|
|
639
|
+
[data-theme='dark'] .vf-markdown-editor__presence-pill {
|
|
640
|
+
border-color: rgba(255, 255, 255, 0.2);
|
|
641
|
+
color: #f9fafb;
|
|
642
|
+
background: #111827;
|
|
643
|
+
}
|
|
644
|
+
|
|
645
|
+
[data-theme='dark'] .vf-markdown-editor__selection-pill {
|
|
646
|
+
border-color: rgba(255, 255, 255, 0.2);
|
|
647
|
+
color: #f9fafb;
|
|
648
|
+
background: #111827;
|
|
649
|
+
}
|
|
650
|
+
|
|
651
|
+
[data-theme='dark'] .vf-markdown-editor__textarea {
|
|
652
|
+
color: #f9fafb;
|
|
653
|
+
}
|
|
654
|
+
|
|
655
|
+
[data-theme='dark'] .vf-markdown-editor__slash-menu {
|
|
656
|
+
border-color: rgba(255, 255, 255, 0.2);
|
|
657
|
+
background: #111827;
|
|
658
|
+
}
|
|
659
|
+
|
|
660
|
+
[data-theme='dark'] .vf-markdown-editor__slash-item:hover,
|
|
661
|
+
[data-theme='dark'] .vf-markdown-editor__slash-item[data-active='true'] {
|
|
662
|
+
background: rgba(59, 130, 246, 0.24);
|
|
663
|
+
}
|
|
664
|
+
|
|
665
|
+
[data-theme='dark'] .vf-markdown-editor__slash-item-title {
|
|
666
|
+
color: #f9fafb;
|
|
667
|
+
}
|
|
668
|
+
|
|
669
|
+
[data-theme='dark'] .vf-markdown-editor__slash-item-desc {
|
|
670
|
+
color: #9ca3af;
|
|
671
|
+
}
|
|
672
|
+
|
|
673
|
+
[data-theme='dark'] .vf-markdown-editor__inline-toolbar {
|
|
674
|
+
border-color: rgba(255, 255, 255, 0.2);
|
|
675
|
+
background: #111827;
|
|
676
|
+
}
|
|
677
|
+
|
|
678
|
+
[data-theme='dark'] .vf-markdown-editor__inline-button {
|
|
679
|
+
color: #f9fafb;
|
|
680
|
+
}
|
|
681
|
+
|
|
682
|
+
[data-theme='dark'] .vf-markdown-editor__inline-button:hover {
|
|
683
|
+
background: rgba(59, 130, 246, 0.24);
|
|
684
|
+
}
|
|
685
|
+
|
|
686
|
+
[data-theme='dark'] .vf-markdown-editor__block-handle {
|
|
687
|
+
border-color: rgba(255, 255, 255, 0.22);
|
|
688
|
+
background: #111827;
|
|
689
|
+
color: #d1d5db;
|
|
690
|
+
}
|
|
691
|
+
|
|
692
|
+
[data-theme='dark'] .vf-markdown-editor__block-handle:hover {
|
|
693
|
+
background: rgba(59, 130, 246, 0.24);
|
|
694
|
+
}
|
|
695
|
+
|
|
696
|
+
[data-theme='dark'] .vf-markdown-editor__block-drop-label {
|
|
697
|
+
border-color: rgba(59, 130, 246, 0.35);
|
|
698
|
+
background: rgba(17, 24, 39, 0.94);
|
|
699
|
+
color: #e5e7eb;
|
|
700
|
+
}
|
|
701
|
+
|
|
702
|
+
[data-theme='dark'] .vf-markdown-editor__block-drag-ghost {
|
|
703
|
+
border-color: rgba(255, 255, 255, 0.24);
|
|
704
|
+
background: #111827;
|
|
705
|
+
}
|
|
706
|
+
|
|
707
|
+
[data-theme='dark'] .vf-markdown-editor__block-drag-ghost-title {
|
|
708
|
+
color: #e5e7eb;
|
|
709
|
+
}
|
|
710
|
+
|
|
711
|
+
[data-theme='dark'] .vf-markdown-editor__block-drag-ghost-text {
|
|
712
|
+
color: #94a3b8;
|
|
713
|
+
}
|
|
714
|
+
|
|
715
|
+
[data-theme='dark'] .vf-markdown-editor__mdx-blocks {
|
|
716
|
+
border-bottom-color: rgba(255, 255, 255, 0.12);
|
|
717
|
+
background: rgba(2, 6, 23, 0.7);
|
|
718
|
+
}
|
|
719
|
+
|
|
720
|
+
[data-theme='dark'] .vf-markdown-editor__mdx-block {
|
|
721
|
+
border-color: rgba(255, 255, 255, 0.22);
|
|
722
|
+
background: #111827;
|
|
723
|
+
}
|
|
724
|
+
|
|
725
|
+
[data-theme='dark'] .vf-markdown-editor__mdx-block-label {
|
|
726
|
+
color: #cbd5e1;
|
|
727
|
+
}
|
|
728
|
+
|
|
729
|
+
[data-theme='dark'] .vf-markdown-editor__mdx-note {
|
|
730
|
+
color: #94a3b8;
|
|
731
|
+
}
|
|
732
|
+
|
|
733
|
+
[data-theme='dark'] .vf-markdown-editor__mdx-open {
|
|
734
|
+
border-color: rgba(255, 255, 255, 0.22);
|
|
735
|
+
background: #0b1220;
|
|
736
|
+
color: #e5e7eb;
|
|
737
|
+
}
|
|
77
738
|
\`;
|
|
78
|
-
|
|
739
|
+
try {
|
|
740
|
+
document.head.appendChild(style);
|
|
741
|
+
if (!style.sheet) {
|
|
742
|
+
console.warn('[StudioBridge] Inline style injection may be blocked by CSP (style-src).');
|
|
743
|
+
}
|
|
744
|
+
} catch (error) {
|
|
745
|
+
console.warn('[StudioBridge] Failed to inject bridge styles. This may be caused by CSP style-src restrictions.', error);
|
|
746
|
+
}
|
|
79
747
|
}
|
|
80
748
|
|
|
81
749
|
function createOverlay(type) {
|
|
@@ -147,13 +815,18 @@ export function generateStudioBridgeScript(options) {
|
|
|
147
815
|
}
|
|
148
816
|
|
|
149
817
|
function isFromStudio(event) {
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
818
|
+
try {
|
|
819
|
+
const url = new URL(event.origin || '');
|
|
820
|
+
const host = url.hostname;
|
|
821
|
+
return (
|
|
822
|
+
host === 'localhost' ||
|
|
823
|
+
host.endsWith('.veryfront.org') || host === 'veryfront.org' ||
|
|
824
|
+
host.endsWith('.veryfront.com') || host === 'veryfront.com' ||
|
|
825
|
+
host.endsWith('.veryfront.dev') || host === 'veryfront.dev'
|
|
826
|
+
);
|
|
827
|
+
} catch (e) {
|
|
828
|
+
return false;
|
|
829
|
+
}
|
|
157
830
|
}
|
|
158
831
|
|
|
159
832
|
const originalConsole = {};
|
|
@@ -475,59 +1148,2673 @@ export function generateStudioBridgeScript(options) {
|
|
|
475
1148
|
document.documentElement.classList.add(mode);
|
|
476
1149
|
}
|
|
477
1150
|
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
html2canvasPromise = new Promise((resolve, reject) => {
|
|
486
|
-
const script = document.createElement('script');
|
|
487
|
-
script.src = 'https://cdn.jsdelivr.net/npm/html2canvas-pro@2.0.0/dist/html2canvas-pro.min.js';
|
|
488
|
-
script.onload = () => {
|
|
489
|
-
html2canvasLoaded = true;
|
|
490
|
-
resolve();
|
|
491
|
-
};
|
|
492
|
-
script.onerror = reject;
|
|
493
|
-
document.head.appendChild(script);
|
|
494
|
-
});
|
|
1151
|
+
function isMarkdownPage() {
|
|
1152
|
+
if (typeof PAGE_PATH !== 'string') {
|
|
1153
|
+
return false;
|
|
1154
|
+
}
|
|
1155
|
+
const lowerPath = PAGE_PATH.toLowerCase();
|
|
1156
|
+
return lowerPath.endsWith('.md') || lowerPath.endsWith('.mdx');
|
|
1157
|
+
}
|
|
495
1158
|
|
|
496
|
-
|
|
1159
|
+
function isMdxPage() {
|
|
1160
|
+
return typeof PAGE_PATH === 'string' && PAGE_PATH.toLowerCase().endsWith('.mdx');
|
|
497
1161
|
}
|
|
498
1162
|
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
1163
|
+
function openFilePathInStudio(filePath, lineNumber, symbolName) {
|
|
1164
|
+
if (typeof filePath !== 'string' || !filePath) {
|
|
1165
|
+
return;
|
|
1166
|
+
}
|
|
1167
|
+
const safeLine = Number.isFinite(lineNumber) ? Math.max(1, Math.trunc(lineNumber)) : 1;
|
|
1168
|
+
const payload = {
|
|
1169
|
+
action: 'openFile',
|
|
1170
|
+
filePath: filePath,
|
|
1171
|
+
lineNumber: safeLine,
|
|
1172
|
+
columnNumber: 1
|
|
1173
|
+
};
|
|
1174
|
+
if (typeof symbolName === 'string' && symbolName.trim()) {
|
|
1175
|
+
payload.symbolName = symbolName.trim();
|
|
1176
|
+
}
|
|
1177
|
+
postToStudio(payload);
|
|
1178
|
+
}
|
|
502
1179
|
|
|
503
|
-
|
|
504
|
-
|
|
1180
|
+
function openMarkdownSourceInStudio(lineNumber) {
|
|
1181
|
+
openFilePathInStudio(PAGE_PATH, lineNumber);
|
|
1182
|
+
}
|
|
505
1183
|
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
1184
|
+
function normalizePathSegments(segments) {
|
|
1185
|
+
const stack = [];
|
|
1186
|
+
for (const segment of segments) {
|
|
1187
|
+
if (!segment || segment === '.') {
|
|
1188
|
+
continue;
|
|
1189
|
+
}
|
|
1190
|
+
if (segment === '..') {
|
|
1191
|
+
if (stack.length > 0) {
|
|
1192
|
+
stack.pop();
|
|
1193
|
+
}
|
|
1194
|
+
continue;
|
|
509
1195
|
}
|
|
1196
|
+
stack.push(segment);
|
|
1197
|
+
}
|
|
1198
|
+
return stack;
|
|
1199
|
+
}
|
|
510
1200
|
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
1201
|
+
function resolveImportPathForPage(importPath) {
|
|
1202
|
+
const sourcePath = typeof importPath === 'string' ? importPath.trim() : '';
|
|
1203
|
+
if (!sourcePath) {
|
|
1204
|
+
return '';
|
|
1205
|
+
}
|
|
516
1206
|
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
1207
|
+
if (sourcePath.startsWith('@/') || sourcePath.startsWith('~/')) {
|
|
1208
|
+
return sourcePath.slice(2);
|
|
1209
|
+
}
|
|
1210
|
+
|
|
1211
|
+
if (sourcePath.startsWith('/')) {
|
|
1212
|
+
let normalizedPath = sourcePath;
|
|
1213
|
+
while (normalizedPath.startsWith('/')) {
|
|
1214
|
+
normalizedPath = normalizedPath.slice(1);
|
|
523
1215
|
}
|
|
1216
|
+
return normalizedPath;
|
|
1217
|
+
}
|
|
524
1218
|
|
|
525
|
-
|
|
526
|
-
|
|
1219
|
+
if (!PAGE_PATH || !sourcePath.startsWith('.')) {
|
|
1220
|
+
return sourcePath;
|
|
1221
|
+
}
|
|
527
1222
|
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
1223
|
+
const baseParts = String(PAGE_PATH).split('/');
|
|
1224
|
+
baseParts.pop();
|
|
1225
|
+
const resolved = normalizePathSegments(baseParts.concat(sourcePath.split('/')));
|
|
1226
|
+
return resolved.join('/');
|
|
1227
|
+
}
|
|
1228
|
+
|
|
1229
|
+
function isLikelyProjectImportPath(importPath) {
|
|
1230
|
+
if (typeof importPath !== 'string') {
|
|
1231
|
+
return false;
|
|
1232
|
+
}
|
|
1233
|
+
const value = importPath.trim();
|
|
1234
|
+
if (!value) {
|
|
1235
|
+
return false;
|
|
1236
|
+
}
|
|
1237
|
+
return (
|
|
1238
|
+
value.startsWith('.') ||
|
|
1239
|
+
value.startsWith('/') ||
|
|
1240
|
+
value.startsWith('@/') ||
|
|
1241
|
+
value.startsWith('~/')
|
|
1242
|
+
);
|
|
1243
|
+
}
|
|
1244
|
+
|
|
1245
|
+
function guessStudioFilePath(filePath) {
|
|
1246
|
+
const sourcePath = typeof filePath === 'string' ? filePath.trim() : '';
|
|
1247
|
+
if (!sourcePath) {
|
|
1248
|
+
return '';
|
|
1249
|
+
}
|
|
1250
|
+
|
|
1251
|
+
const hasKnownExtension = sourcePath.match(/\\.(tsx?|jsx?|mdx?|json|css|scss|sass|less)$/i);
|
|
1252
|
+
if (hasKnownExtension) {
|
|
1253
|
+
return sourcePath;
|
|
1254
|
+
}
|
|
1255
|
+
|
|
1256
|
+
if (sourcePath.endsWith('/')) {
|
|
1257
|
+
return sourcePath + 'index.tsx';
|
|
1258
|
+
}
|
|
1259
|
+
|
|
1260
|
+
return sourcePath + '.tsx';
|
|
1261
|
+
}
|
|
1262
|
+
|
|
1263
|
+
function parseMdxImportMap(content) {
|
|
1264
|
+
const source = typeof content === 'string' ? content : '';
|
|
1265
|
+
const importMap = {};
|
|
1266
|
+
if (!source) {
|
|
1267
|
+
return importMap;
|
|
1268
|
+
}
|
|
1269
|
+
|
|
1270
|
+
const stripImportComments = function(specifierText) {
|
|
1271
|
+
return String(specifierText || '')
|
|
1272
|
+
.replace(/\\/\\*[\\s\\S]*?\\*\\//g, ' ')
|
|
1273
|
+
.replace(/\\/\\/[^\\n\\r]*/g, ' ');
|
|
1274
|
+
};
|
|
1275
|
+
|
|
1276
|
+
const normalizeImportSpecifier = function(specifierText) {
|
|
1277
|
+
return stripImportComments(specifierText)
|
|
1278
|
+
.replace(/\\s+/g, ' ')
|
|
1279
|
+
.trim();
|
|
1280
|
+
};
|
|
1281
|
+
|
|
1282
|
+
const setImportEntry = function(localName, resolvedPath, symbolName, importKind) {
|
|
1283
|
+
const key = typeof localName === 'string' ? localName.trim() : '';
|
|
1284
|
+
const filePath = typeof resolvedPath === 'string' ? resolvedPath.trim() : '';
|
|
1285
|
+
if (!key || !filePath) {
|
|
1286
|
+
return;
|
|
1287
|
+
}
|
|
1288
|
+
importMap[key] = {
|
|
1289
|
+
filePath: filePath,
|
|
1290
|
+
symbolName: typeof symbolName === 'string' ? symbolName.trim() : '',
|
|
1291
|
+
importKind: typeof importKind === 'string' ? importKind : 'unknown'
|
|
1292
|
+
};
|
|
1293
|
+
};
|
|
1294
|
+
|
|
1295
|
+
const mapNamedImports = function(namedSpecifier, resolvedPath) {
|
|
1296
|
+
const text = String(namedSpecifier || '').trim();
|
|
1297
|
+
if (!text.startsWith('{') || !text.endsWith('}')) {
|
|
1298
|
+
return;
|
|
1299
|
+
}
|
|
1300
|
+
const named = text.slice(1, -1).split(',');
|
|
1301
|
+
for (const entry of named) {
|
|
1302
|
+
const part = entry.trim();
|
|
1303
|
+
if (!part) {
|
|
1304
|
+
continue;
|
|
1305
|
+
}
|
|
1306
|
+
const normalizedPart = normalizeImportSpecifier(part).trim();
|
|
1307
|
+
if (!normalizedPart || /^type\\s+/.test(normalizedPart)) {
|
|
1308
|
+
continue;
|
|
1309
|
+
}
|
|
1310
|
+
const aliasMatch = normalizedPart.match(/^([A-Za-z_$][\\w$]*)\\s+as\\s+([A-Za-z_$][\\w$]*)$/);
|
|
1311
|
+
const sourceName = aliasMatch ? aliasMatch[1] : normalizedPart;
|
|
1312
|
+
const localName = aliasMatch ? aliasMatch[2] : normalizedPart;
|
|
1313
|
+
if (localName) {
|
|
1314
|
+
const isDefaultAlias = sourceName === 'default';
|
|
1315
|
+
setImportEntry(localName, resolvedPath, isDefaultAlias ? '' : sourceName, isDefaultAlias ? 'default' : 'named');
|
|
1316
|
+
}
|
|
1317
|
+
}
|
|
1318
|
+
};
|
|
1319
|
+
|
|
1320
|
+
const importPattern = /^import\\s+([\\s\\S]*?)\\s+from\\s+['\"]([^'\"]+)['\"]\\s*;?/gm;
|
|
1321
|
+
let match = importPattern.exec(source);
|
|
1322
|
+
while (match) {
|
|
1323
|
+
const specifier = normalizeImportSpecifier(match[1] || '');
|
|
1324
|
+
if (!specifier) {
|
|
1325
|
+
match = importPattern.exec(source);
|
|
1326
|
+
continue;
|
|
1327
|
+
}
|
|
1328
|
+
const typeOnlySpecifier = specifier.startsWith('type ');
|
|
1329
|
+
const normalizedSpecifier = typeOnlySpecifier ? specifier.slice(5).trim() : specifier;
|
|
1330
|
+
if (typeOnlySpecifier) {
|
|
1331
|
+
match = importPattern.exec(source);
|
|
1332
|
+
continue;
|
|
1333
|
+
}
|
|
1334
|
+
const rawImportPath = String(match[2] || '').trim();
|
|
1335
|
+
if (!isLikelyProjectImportPath(rawImportPath)) {
|
|
1336
|
+
match = importPattern.exec(source);
|
|
1337
|
+
continue;
|
|
1338
|
+
}
|
|
1339
|
+
const resolvedPath = guessStudioFilePath(resolveImportPathForPage(rawImportPath));
|
|
1340
|
+
if (!resolvedPath) {
|
|
1341
|
+
match = importPattern.exec(source);
|
|
1342
|
+
continue;
|
|
1343
|
+
}
|
|
1344
|
+
|
|
1345
|
+
if (normalizedSpecifier.startsWith('{') && normalizedSpecifier.endsWith('}')) {
|
|
1346
|
+
mapNamedImports(normalizedSpecifier, resolvedPath);
|
|
1347
|
+
} else if (normalizedSpecifier.startsWith('* as ')) {
|
|
1348
|
+
const namespaceName = normalizedSpecifier.slice(5).trim();
|
|
1349
|
+
if (namespaceName) {
|
|
1350
|
+
setImportEntry(namespaceName, resolvedPath, '', 'namespace');
|
|
1351
|
+
}
|
|
1352
|
+
} else {
|
|
1353
|
+
const commaIndex = normalizedSpecifier.indexOf(',');
|
|
1354
|
+
if (commaIndex >= 0) {
|
|
1355
|
+
const defaultPart = normalizedSpecifier.slice(0, commaIndex).trim();
|
|
1356
|
+
const restPart = normalizedSpecifier.slice(commaIndex + 1).trim();
|
|
1357
|
+
const normalizedDefaultPart = defaultPart.trim();
|
|
1358
|
+
if (normalizedDefaultPart && !/^type\\s+/.test(normalizedDefaultPart)) {
|
|
1359
|
+
setImportEntry(normalizedDefaultPart, resolvedPath, '', 'default');
|
|
1360
|
+
}
|
|
1361
|
+
if (restPart.startsWith('{')) {
|
|
1362
|
+
mapNamedImports(restPart, resolvedPath);
|
|
1363
|
+
} else if (restPart.startsWith('* as ')) {
|
|
1364
|
+
const namespaceName = restPart.slice(5).trim();
|
|
1365
|
+
if (namespaceName) {
|
|
1366
|
+
setImportEntry(namespaceName, resolvedPath, '', 'namespace');
|
|
1367
|
+
}
|
|
1368
|
+
}
|
|
1369
|
+
} else {
|
|
1370
|
+
const defaultPart = normalizedSpecifier.trim();
|
|
1371
|
+
const normalizedDefaultPart = defaultPart.trim();
|
|
1372
|
+
if (normalizedDefaultPart && !/^type\\s+/.test(normalizedDefaultPart)) {
|
|
1373
|
+
setImportEntry(normalizedDefaultPart, resolvedPath, '', 'default');
|
|
1374
|
+
}
|
|
1375
|
+
}
|
|
1376
|
+
}
|
|
1377
|
+
|
|
1378
|
+
match = importPattern.exec(source);
|
|
1379
|
+
}
|
|
1380
|
+
|
|
1381
|
+
return importMap;
|
|
1382
|
+
}
|
|
1383
|
+
|
|
1384
|
+
function postMarkdownEditorReady() {
|
|
1385
|
+
if (!markdownFileId) {
|
|
1386
|
+
return;
|
|
1387
|
+
}
|
|
1388
|
+
postToStudio({
|
|
1389
|
+
action: 'markdownEditorReady',
|
|
1390
|
+
fileId: markdownFileId,
|
|
1391
|
+
filePath: PAGE_PATH
|
|
1392
|
+
});
|
|
1393
|
+
}
|
|
1394
|
+
|
|
1395
|
+
function scheduleMarkdownSync(content) {
|
|
1396
|
+
if (!markdownFileId) {
|
|
1397
|
+
return;
|
|
1398
|
+
}
|
|
1399
|
+
if (markdownSyncTimer) {
|
|
1400
|
+
clearTimeout(markdownSyncTimer);
|
|
1401
|
+
}
|
|
1402
|
+
markdownSyncTimer = setTimeout(function() {
|
|
1403
|
+
postToStudio({
|
|
1404
|
+
action: 'markdownContentChange',
|
|
1405
|
+
fileId: markdownFileId,
|
|
1406
|
+
filePath: PAGE_PATH,
|
|
1407
|
+
content: content
|
|
1408
|
+
});
|
|
1409
|
+
}, 120);
|
|
1410
|
+
}
|
|
1411
|
+
|
|
1412
|
+
function getTextOffsetWithinRoot(root, targetNode, targetOffset) {
|
|
1413
|
+
if (!root || !targetNode) {
|
|
1414
|
+
return 0;
|
|
1415
|
+
}
|
|
1416
|
+
|
|
1417
|
+
if (!root.contains(targetNode)) {
|
|
1418
|
+
return 0;
|
|
1419
|
+
}
|
|
1420
|
+
|
|
1421
|
+
try {
|
|
1422
|
+
const range = document.createRange();
|
|
1423
|
+
range.selectNodeContents(root);
|
|
1424
|
+
range.setEnd(targetNode, targetOffset);
|
|
1425
|
+
return range.toString().length;
|
|
1426
|
+
} catch {
|
|
1427
|
+
return 0;
|
|
1428
|
+
}
|
|
1429
|
+
}
|
|
1430
|
+
|
|
1431
|
+
function getMarkdownEditorSelection() {
|
|
1432
|
+
if (markdownLexicalApi && markdownEditorSurface) {
|
|
1433
|
+
const selection = window.getSelection();
|
|
1434
|
+
if (!selection || selection.rangeCount === 0) {
|
|
1435
|
+
return null;
|
|
1436
|
+
}
|
|
1437
|
+
|
|
1438
|
+
const range = selection.getRangeAt(0);
|
|
1439
|
+
if (
|
|
1440
|
+
!markdownEditorSurface.contains(range.startContainer) ||
|
|
1441
|
+
!markdownEditorSurface.contains(range.endContainer)
|
|
1442
|
+
) {
|
|
1443
|
+
return null;
|
|
1444
|
+
}
|
|
1445
|
+
|
|
1446
|
+
const start = getTextOffsetWithinRoot(
|
|
1447
|
+
markdownEditorSurface,
|
|
1448
|
+
range.startContainer,
|
|
1449
|
+
range.startOffset
|
|
1450
|
+
);
|
|
1451
|
+
const end = getTextOffsetWithinRoot(
|
|
1452
|
+
markdownEditorSurface,
|
|
1453
|
+
range.endContainer,
|
|
1454
|
+
range.endOffset
|
|
1455
|
+
);
|
|
1456
|
+
return {
|
|
1457
|
+
start: Math.max(0, Math.min(start, end)),
|
|
1458
|
+
end: Math.max(0, Math.max(start, end))
|
|
1459
|
+
};
|
|
1460
|
+
}
|
|
1461
|
+
|
|
1462
|
+
if (markdownEditorTextarea) {
|
|
1463
|
+
const start = typeof markdownEditorTextarea.selectionStart === 'number'
|
|
1464
|
+
? markdownEditorTextarea.selectionStart
|
|
1465
|
+
: 0;
|
|
1466
|
+
const end = typeof markdownEditorTextarea.selectionEnd === 'number'
|
|
1467
|
+
? markdownEditorTextarea.selectionEnd
|
|
1468
|
+
: start;
|
|
1469
|
+
|
|
1470
|
+
return {
|
|
1471
|
+
start: Math.max(0, Math.min(start, end)),
|
|
1472
|
+
end: Math.max(0, Math.max(start, end))
|
|
1473
|
+
};
|
|
1474
|
+
}
|
|
1475
|
+
|
|
1476
|
+
return null;
|
|
1477
|
+
}
|
|
1478
|
+
|
|
1479
|
+
function getMarkdownRawBlockLength(index) {
|
|
1480
|
+
const rawBlock = markdownRawBlocks[index];
|
|
1481
|
+
if (typeof rawBlock !== 'string') {
|
|
1482
|
+
return 0;
|
|
1483
|
+
}
|
|
1484
|
+
return rawBlock.length;
|
|
1485
|
+
}
|
|
1486
|
+
|
|
1487
|
+
function escapeRegexText(value) {
|
|
1488
|
+
const text = String(value || '');
|
|
1489
|
+
let escaped = '';
|
|
1490
|
+
for (let i = 0; i < text.length; i += 1) {
|
|
1491
|
+
const char = text[i];
|
|
1492
|
+
if ('\\\\^$.*+?()[]{}|'.indexOf(char) >= 0) {
|
|
1493
|
+
escaped += '\\\\' + char;
|
|
1494
|
+
} else {
|
|
1495
|
+
escaped += char;
|
|
1496
|
+
}
|
|
1497
|
+
}
|
|
1498
|
+
return escaped;
|
|
1499
|
+
}
|
|
1500
|
+
|
|
1501
|
+
function getMarkdownRawBlockTokenPattern() {
|
|
1502
|
+
const prefix = typeof markdownRawBlockTokenPrefix === 'string' && markdownRawBlockTokenPrefix
|
|
1503
|
+
? markdownRawBlockTokenPrefix
|
|
1504
|
+
: 'VF_RAW_BLOCK';
|
|
1505
|
+
const escapedPrefix = escapeRegexText(prefix);
|
|
1506
|
+
return new RegExp('\\\\[\\\\[' + escapedPrefix + '_(\\\\d+)\\\\]\\\\]', 'g');
|
|
1507
|
+
}
|
|
1508
|
+
|
|
1509
|
+
function editorOffsetToBodyOffset(editorOffset, bias) {
|
|
1510
|
+
const editorContent = typeof markdownCurrentEditorContent === 'string'
|
|
1511
|
+
? markdownCurrentEditorContent
|
|
1512
|
+
: '';
|
|
1513
|
+
const maxOffset = editorContent.length;
|
|
1514
|
+
const safeOffset = Math.max(0, Math.min(maxOffset, Math.trunc(editorOffset || 0)));
|
|
1515
|
+
const tokenPattern = getMarkdownRawBlockTokenPattern();
|
|
1516
|
+
let diffBefore = 0;
|
|
1517
|
+
let match = tokenPattern.exec(editorContent);
|
|
1518
|
+
|
|
1519
|
+
while (match) {
|
|
1520
|
+
const token = match[0];
|
|
1521
|
+
const tokenStartEditor = match.index;
|
|
1522
|
+
const tokenEndEditor = tokenStartEditor + token.length;
|
|
1523
|
+
const rawLength = getMarkdownRawBlockLength(Number(match[1]));
|
|
1524
|
+
const tokenDelta = rawLength - token.length;
|
|
1525
|
+
const tokenStartBody = tokenStartEditor + diffBefore;
|
|
1526
|
+
|
|
1527
|
+
if (safeOffset >= tokenEndEditor) {
|
|
1528
|
+
diffBefore += tokenDelta;
|
|
1529
|
+
match = tokenPattern.exec(editorContent);
|
|
1530
|
+
continue;
|
|
1531
|
+
}
|
|
1532
|
+
|
|
1533
|
+
if (safeOffset > tokenStartEditor) {
|
|
1534
|
+
if (bias === 'end') {
|
|
1535
|
+
return tokenStartBody + rawLength;
|
|
1536
|
+
}
|
|
1537
|
+
return tokenStartBody;
|
|
1538
|
+
}
|
|
1539
|
+
break;
|
|
1540
|
+
}
|
|
1541
|
+
|
|
1542
|
+
return safeOffset + diffBefore;
|
|
1543
|
+
}
|
|
1544
|
+
|
|
1545
|
+
function bodyOffsetToEditorOffset(bodyOffset, bias) {
|
|
1546
|
+
const editorContent = typeof markdownCurrentEditorContent === 'string'
|
|
1547
|
+
? markdownCurrentEditorContent
|
|
1548
|
+
: '';
|
|
1549
|
+
const safeBodyOffset = Math.max(0, Math.trunc(bodyOffset || 0));
|
|
1550
|
+
const tokenPattern = getMarkdownRawBlockTokenPattern();
|
|
1551
|
+
let diffBefore = 0;
|
|
1552
|
+
let match = tokenPattern.exec(editorContent);
|
|
1553
|
+
|
|
1554
|
+
while (match) {
|
|
1555
|
+
const token = match[0];
|
|
1556
|
+
const tokenStartEditor = match.index;
|
|
1557
|
+
const tokenEndEditor = tokenStartEditor + token.length;
|
|
1558
|
+
const rawLength = getMarkdownRawBlockLength(Number(match[1]));
|
|
1559
|
+
const tokenStartBody = tokenStartEditor + diffBefore;
|
|
1560
|
+
const tokenEndBody = tokenStartBody + rawLength;
|
|
1561
|
+
|
|
1562
|
+
if (safeBodyOffset > tokenEndBody) {
|
|
1563
|
+
diffBefore += rawLength - token.length;
|
|
1564
|
+
match = tokenPattern.exec(editorContent);
|
|
1565
|
+
continue;
|
|
1566
|
+
}
|
|
1567
|
+
|
|
1568
|
+
if (safeBodyOffset >= tokenStartBody && safeBodyOffset <= tokenEndBody) {
|
|
1569
|
+
if (bias === 'end') {
|
|
1570
|
+
return tokenEndEditor;
|
|
1571
|
+
}
|
|
1572
|
+
return tokenStartEditor;
|
|
1573
|
+
}
|
|
1574
|
+
|
|
1575
|
+
const mappedOffset = safeBodyOffset - diffBefore;
|
|
1576
|
+
return Math.max(0, Math.min(editorContent.length, mappedOffset));
|
|
1577
|
+
}
|
|
1578
|
+
|
|
1579
|
+
const mappedOffset = safeBodyOffset - diffBefore;
|
|
1580
|
+
return Math.max(0, Math.min(editorContent.length, mappedOffset));
|
|
1581
|
+
}
|
|
1582
|
+
|
|
1583
|
+
function editorOffsetToSourceOffset(editorOffset, bias) {
|
|
1584
|
+
const frontmatterLength = typeof markdownFrontmatter === 'string' ? markdownFrontmatter.length : 0;
|
|
1585
|
+
const bodyOffset = editorOffsetToBodyOffset(editorOffset, bias);
|
|
1586
|
+
return Math.max(0, frontmatterLength + bodyOffset);
|
|
1587
|
+
}
|
|
1588
|
+
|
|
1589
|
+
function sourceSelectionToEditorRange(start, end) {
|
|
1590
|
+
const frontmatterLength = typeof markdownFrontmatter === 'string' ? markdownFrontmatter.length : 0;
|
|
1591
|
+
const safeStart = Math.max(0, Math.trunc(start || 0));
|
|
1592
|
+
const safeEnd = Math.max(0, Math.trunc(end || 0));
|
|
1593
|
+
const sourceStart = Math.min(safeStart, safeEnd);
|
|
1594
|
+
const sourceEnd = Math.max(safeStart, safeEnd);
|
|
1595
|
+
|
|
1596
|
+
if (sourceEnd <= frontmatterLength) {
|
|
1597
|
+
return null;
|
|
1598
|
+
}
|
|
1599
|
+
|
|
1600
|
+
const bodyStart = Math.max(0, sourceStart - frontmatterLength);
|
|
1601
|
+
const bodyEnd = Math.max(0, sourceEnd - frontmatterLength);
|
|
1602
|
+
const editorStart = bodyOffsetToEditorOffset(bodyStart, 'start');
|
|
1603
|
+
const editorEnd = bodyOffsetToEditorOffset(bodyEnd, 'end');
|
|
1604
|
+
|
|
1605
|
+
return {
|
|
1606
|
+
start: Math.max(0, Math.min(editorStart, editorEnd)),
|
|
1607
|
+
end: Math.max(0, Math.max(editorStart, editorEnd))
|
|
1608
|
+
};
|
|
1609
|
+
}
|
|
1610
|
+
|
|
1611
|
+
function setMarkdownEditorSelection(start, end) {
|
|
1612
|
+
const safeStart = Math.max(0, Math.trunc(start || 0));
|
|
1613
|
+
const endValue = typeof end === 'number' ? end : safeStart;
|
|
1614
|
+
const safeEnd = Math.max(0, Math.trunc(endValue));
|
|
1615
|
+
|
|
1616
|
+
if (markdownLexicalApi && markdownEditorSurface) {
|
|
1617
|
+
const selection = window.getSelection();
|
|
1618
|
+
if (!selection) {
|
|
1619
|
+
return;
|
|
1620
|
+
}
|
|
1621
|
+
|
|
1622
|
+
const anchor = resolveMarkdownTextPoint(markdownEditorSurface, safeStart);
|
|
1623
|
+
const focus = resolveMarkdownTextPoint(markdownEditorSurface, safeEnd);
|
|
1624
|
+
try {
|
|
1625
|
+
const range = document.createRange();
|
|
1626
|
+
range.setStart(anchor.node, anchor.offset);
|
|
1627
|
+
range.setEnd(focus.node, focus.offset);
|
|
1628
|
+
selection.removeAllRanges();
|
|
1629
|
+
selection.addRange(range);
|
|
1630
|
+
} catch {
|
|
1631
|
+
// Ignore when point resolution races with Lexical DOM updates.
|
|
1632
|
+
}
|
|
1633
|
+
return;
|
|
1634
|
+
}
|
|
1635
|
+
|
|
1636
|
+
if (markdownEditorTextarea) {
|
|
1637
|
+
const max = markdownEditorTextarea.value.length;
|
|
1638
|
+
markdownEditorTextarea.setSelectionRange(Math.min(safeStart, max), Math.min(safeEnd, max));
|
|
1639
|
+
}
|
|
1640
|
+
}
|
|
1641
|
+
|
|
1642
|
+
function hideMarkdownSlashMenu() {
|
|
1643
|
+
markdownSlashMenuContext = null;
|
|
1644
|
+
markdownSlashMenuCommands = [];
|
|
1645
|
+
markdownSlashMenuActiveIndex = 0;
|
|
1646
|
+
if (!markdownSlashMenuRoot) {
|
|
1647
|
+
return;
|
|
1648
|
+
}
|
|
1649
|
+
markdownSlashMenuRoot.style.display = 'none';
|
|
1650
|
+
markdownSlashMenuRoot.textContent = '';
|
|
1651
|
+
}
|
|
1652
|
+
|
|
1653
|
+
function getMarkdownSlashCommandInsert(id, indent) {
|
|
1654
|
+
const prefix = typeof indent === 'string' ? indent : '';
|
|
1655
|
+
if (id === 'heading-1') {
|
|
1656
|
+
const text = prefix + '# ';
|
|
1657
|
+
return { text: text, caretOffset: text.length };
|
|
1658
|
+
}
|
|
1659
|
+
if (id === 'heading-2') {
|
|
1660
|
+
const text = prefix + '## ';
|
|
1661
|
+
return { text: text, caretOffset: text.length };
|
|
1662
|
+
}
|
|
1663
|
+
if (id === 'heading-3') {
|
|
1664
|
+
const text = prefix + '### ';
|
|
1665
|
+
return { text: text, caretOffset: text.length };
|
|
1666
|
+
}
|
|
1667
|
+
if (id === 'bulleted-list') {
|
|
1668
|
+
const text = prefix + '- ';
|
|
1669
|
+
return { text: text, caretOffset: text.length };
|
|
1670
|
+
}
|
|
1671
|
+
if (id === 'numbered-list') {
|
|
1672
|
+
const text = prefix + '1. ';
|
|
1673
|
+
return { text: text, caretOffset: text.length };
|
|
1674
|
+
}
|
|
1675
|
+
if (id === 'quote-block') {
|
|
1676
|
+
const text = prefix + '> ';
|
|
1677
|
+
return { text: text, caretOffset: text.length };
|
|
1678
|
+
}
|
|
1679
|
+
if (id === 'code-block') {
|
|
1680
|
+
const fence = String.fromCharCode(96, 96, 96);
|
|
1681
|
+
const text = prefix + fence + '\\n' + prefix + '\\n' + prefix + fence;
|
|
1682
|
+
return {
|
|
1683
|
+
text: text,
|
|
1684
|
+
caretOffset: (prefix + fence + '\\n' + prefix).length
|
|
1685
|
+
};
|
|
1686
|
+
}
|
|
1687
|
+
if (id === 'image') {
|
|
1688
|
+
const text = prefix + '';
|
|
1689
|
+
return {
|
|
1690
|
+
text: text,
|
|
1691
|
+
caretOffset: (prefix + '.length
|
|
1692
|
+
};
|
|
1693
|
+
}
|
|
1694
|
+
return null;
|
|
1695
|
+
}
|
|
1696
|
+
|
|
1697
|
+
function applyMarkdownSlashCommand(index) {
|
|
1698
|
+
if (!markdownSlashMenuContext || markdownSlashMenuCommands.length === 0) {
|
|
1699
|
+
return false;
|
|
1700
|
+
}
|
|
1701
|
+
|
|
1702
|
+
const command = markdownSlashMenuCommands[index];
|
|
1703
|
+
if (!command) {
|
|
1704
|
+
return false;
|
|
1705
|
+
}
|
|
1706
|
+
|
|
1707
|
+
const insert = getMarkdownSlashCommandInsert(command.id, markdownSlashMenuContext.indent);
|
|
1708
|
+
if (!insert) {
|
|
1709
|
+
return false;
|
|
1710
|
+
}
|
|
1711
|
+
|
|
1712
|
+
const editorContent = typeof markdownCurrentEditorContent === 'string'
|
|
1713
|
+
? markdownCurrentEditorContent
|
|
1714
|
+
: '';
|
|
1715
|
+
const before = editorContent.slice(0, markdownSlashMenuContext.lineStart);
|
|
1716
|
+
const after = editorContent.slice(markdownSlashMenuContext.caret);
|
|
1717
|
+
const nextEditorContent = before + insert.text + after;
|
|
1718
|
+
const nextCaret = before.length + insert.caretOffset;
|
|
1719
|
+
const nextFullContent = composeMarkdownContent(restoreRawBlocksFromEditor(nextEditorContent));
|
|
1720
|
+
const hasChanged = nextFullContent !== markdownCurrentContent;
|
|
1721
|
+
|
|
1722
|
+
applyMarkdownContent(nextFullContent);
|
|
1723
|
+
if (hasChanged) {
|
|
1724
|
+
markdownHasUnsavedChanges = true;
|
|
1725
|
+
scheduleMarkdownSync(nextFullContent);
|
|
1726
|
+
}
|
|
1727
|
+
|
|
1728
|
+
setTimeout(function() {
|
|
1729
|
+
focusMarkdownEditor();
|
|
1730
|
+
setMarkdownEditorSelection(nextCaret, nextCaret);
|
|
1731
|
+
scheduleMarkdownSelectionSync();
|
|
1732
|
+
scheduleMarkdownSelectionOverlayRender();
|
|
1733
|
+
scheduleMarkdownSlashMenuUpdate();
|
|
1734
|
+
}, 0);
|
|
1735
|
+
|
|
1736
|
+
hideMarkdownSlashMenu();
|
|
1737
|
+
return true;
|
|
1738
|
+
}
|
|
1739
|
+
|
|
1740
|
+
function renderMarkdownSlashMenu() {
|
|
1741
|
+
if (!markdownSlashMenuRoot || !markdownSlashMenuContext || markdownSlashMenuCommands.length === 0) {
|
|
1742
|
+
hideMarkdownSlashMenu();
|
|
1743
|
+
return;
|
|
1744
|
+
}
|
|
1745
|
+
|
|
1746
|
+
markdownSlashMenuRoot.textContent = '';
|
|
1747
|
+
|
|
1748
|
+
const maxLeft = Math.max(8, window.innerWidth - 312);
|
|
1749
|
+
const maxTop = Math.max(8, window.innerHeight - 220);
|
|
1750
|
+
const left = Math.max(8, Math.min(maxLeft, markdownSlashMenuContext.anchorLeft));
|
|
1751
|
+
const top = Math.max(8, Math.min(maxTop, markdownSlashMenuContext.anchorTop));
|
|
1752
|
+
markdownSlashMenuRoot.style.left = left + 'px';
|
|
1753
|
+
markdownSlashMenuRoot.style.top = top + 'px';
|
|
1754
|
+
|
|
1755
|
+
markdownSlashMenuCommands.forEach(function(command, index) {
|
|
1756
|
+
const item = document.createElement('button');
|
|
1757
|
+
item.type = 'button';
|
|
1758
|
+
item.className = 'vf-markdown-editor__slash-item';
|
|
1759
|
+
item.setAttribute('data-active', index === markdownSlashMenuActiveIndex ? 'true' : 'false');
|
|
1760
|
+
item.setAttribute(DATA_VF_IGNORE, 'true');
|
|
1761
|
+
item.addEventListener('mousedown', function(event) {
|
|
1762
|
+
event.preventDefault();
|
|
1763
|
+
});
|
|
1764
|
+
item.addEventListener('click', function(event) {
|
|
1765
|
+
event.preventDefault();
|
|
1766
|
+
markdownSlashMenuActiveIndex = index;
|
|
1767
|
+
applyMarkdownSlashCommand(markdownSlashMenuActiveIndex);
|
|
1768
|
+
});
|
|
1769
|
+
|
|
1770
|
+
const title = document.createElement('span');
|
|
1771
|
+
title.className = 'vf-markdown-editor__slash-item-title';
|
|
1772
|
+
title.textContent = command.label;
|
|
1773
|
+
|
|
1774
|
+
const description = document.createElement('span');
|
|
1775
|
+
description.className = 'vf-markdown-editor__slash-item-desc';
|
|
1776
|
+
description.textContent = command.description;
|
|
1777
|
+
|
|
1778
|
+
item.appendChild(title);
|
|
1779
|
+
item.appendChild(description);
|
|
1780
|
+
markdownSlashMenuRoot.appendChild(item);
|
|
1781
|
+
});
|
|
1782
|
+
|
|
1783
|
+
markdownSlashMenuRoot.style.display = 'block';
|
|
1784
|
+
}
|
|
1785
|
+
|
|
1786
|
+
function updateMarkdownSlashMenu() {
|
|
1787
|
+
if (
|
|
1788
|
+
!markdownEditorRoot ||
|
|
1789
|
+
markdownEditorRoot.style.display !== 'block' ||
|
|
1790
|
+
!markdownLexicalApi ||
|
|
1791
|
+
!markdownEditorSurface ||
|
|
1792
|
+
markdownEditorSurface.style.display === 'none'
|
|
1793
|
+
) {
|
|
1794
|
+
hideMarkdownSlashMenu();
|
|
1795
|
+
return;
|
|
1796
|
+
}
|
|
1797
|
+
|
|
1798
|
+
const selection = getMarkdownEditorSelection();
|
|
1799
|
+
if (!selection || selection.start !== selection.end) {
|
|
1800
|
+
hideMarkdownSlashMenu();
|
|
1801
|
+
return;
|
|
1802
|
+
}
|
|
1803
|
+
|
|
1804
|
+
const caret = selection.start;
|
|
1805
|
+
const editorContent = typeof markdownCurrentEditorContent === 'string'
|
|
1806
|
+
? markdownCurrentEditorContent
|
|
1807
|
+
: '';
|
|
1808
|
+
const lineStart = editorContent.lastIndexOf('\\n', Math.max(0, caret - 1)) + 1;
|
|
1809
|
+
const line = editorContent.slice(lineStart, caret);
|
|
1810
|
+
const match = line.match(/^(\\s*)\\/([a-z0-9-]*)$/i);
|
|
1811
|
+
if (!match) {
|
|
1812
|
+
hideMarkdownSlashMenu();
|
|
1813
|
+
return;
|
|
1814
|
+
}
|
|
1815
|
+
|
|
1816
|
+
const query = (match[2] || '').toLowerCase();
|
|
1817
|
+
const commands = MARKDOWN_SLASH_COMMANDS.filter(function(command) {
|
|
1818
|
+
if (!query) {
|
|
1819
|
+
return true;
|
|
1820
|
+
}
|
|
1821
|
+
if (command.label.toLowerCase().includes(query)) {
|
|
1822
|
+
return true;
|
|
1823
|
+
}
|
|
1824
|
+
return command.aliases.some(function(alias) {
|
|
1825
|
+
return alias.startsWith(query);
|
|
1826
|
+
});
|
|
1827
|
+
});
|
|
1828
|
+
|
|
1829
|
+
if (commands.length === 0) {
|
|
1830
|
+
hideMarkdownSlashMenu();
|
|
1831
|
+
return;
|
|
1832
|
+
}
|
|
1833
|
+
|
|
1834
|
+
const domSelection = window.getSelection();
|
|
1835
|
+
if (!domSelection || domSelection.rangeCount === 0) {
|
|
1836
|
+
hideMarkdownSlashMenu();
|
|
1837
|
+
return;
|
|
1838
|
+
}
|
|
1839
|
+
const caretRect = domSelection.getRangeAt(0).getBoundingClientRect();
|
|
1840
|
+
const anchorLeft = caretRect.left;
|
|
1841
|
+
const anchorTop = caretRect.bottom + 8;
|
|
1842
|
+
|
|
1843
|
+
markdownSlashMenuCommands = commands.slice(0, 8);
|
|
1844
|
+
markdownSlashMenuActiveIndex = Math.max(0, Math.min(markdownSlashMenuActiveIndex, markdownSlashMenuCommands.length - 1));
|
|
1845
|
+
markdownSlashMenuContext = {
|
|
1846
|
+
lineStart: lineStart,
|
|
1847
|
+
caret: caret,
|
|
1848
|
+
indent: match[1] || '',
|
|
1849
|
+
query: query,
|
|
1850
|
+
anchorLeft: anchorLeft,
|
|
1851
|
+
anchorTop: anchorTop
|
|
1852
|
+
};
|
|
1853
|
+
renderMarkdownSlashMenu();
|
|
1854
|
+
}
|
|
1855
|
+
|
|
1856
|
+
function scheduleMarkdownSlashMenuUpdate() {
|
|
1857
|
+
if (markdownSlashMenuTimer) {
|
|
1858
|
+
clearTimeout(markdownSlashMenuTimer);
|
|
1859
|
+
}
|
|
1860
|
+
markdownSlashMenuTimer = setTimeout(function() {
|
|
1861
|
+
markdownSlashMenuTimer = null;
|
|
1862
|
+
updateMarkdownSlashMenu();
|
|
1863
|
+
}, 0);
|
|
1864
|
+
}
|
|
1865
|
+
|
|
1866
|
+
function handleMarkdownSlashMenuKeydown(event) {
|
|
1867
|
+
if (!markdownSlashMenuRoot || markdownSlashMenuRoot.style.display !== 'block' || markdownSlashMenuCommands.length === 0) {
|
|
1868
|
+
return false;
|
|
1869
|
+
}
|
|
1870
|
+
|
|
1871
|
+
if (event.key === 'ArrowDown') {
|
|
1872
|
+
event.preventDefault();
|
|
1873
|
+
markdownSlashMenuActiveIndex = (markdownSlashMenuActiveIndex + 1) % markdownSlashMenuCommands.length;
|
|
1874
|
+
renderMarkdownSlashMenu();
|
|
1875
|
+
return true;
|
|
1876
|
+
}
|
|
1877
|
+
|
|
1878
|
+
if (event.key === 'ArrowUp') {
|
|
1879
|
+
event.preventDefault();
|
|
1880
|
+
markdownSlashMenuActiveIndex = (markdownSlashMenuActiveIndex - 1 + markdownSlashMenuCommands.length) % markdownSlashMenuCommands.length;
|
|
1881
|
+
renderMarkdownSlashMenu();
|
|
1882
|
+
return true;
|
|
1883
|
+
}
|
|
1884
|
+
|
|
1885
|
+
if (event.key === 'Enter' || event.key === 'Tab') {
|
|
1886
|
+
event.preventDefault();
|
|
1887
|
+
return applyMarkdownSlashCommand(markdownSlashMenuActiveIndex);
|
|
1888
|
+
}
|
|
1889
|
+
|
|
1890
|
+
if (event.key === 'Escape') {
|
|
1891
|
+
event.preventDefault();
|
|
1892
|
+
hideMarkdownSlashMenu();
|
|
1893
|
+
return true;
|
|
1894
|
+
}
|
|
1895
|
+
|
|
1896
|
+
return false;
|
|
1897
|
+
}
|
|
1898
|
+
|
|
1899
|
+
function hideMarkdownInlineToolbar() {
|
|
1900
|
+
if (!markdownInlineToolbarRoot) {
|
|
1901
|
+
return;
|
|
1902
|
+
}
|
|
1903
|
+
markdownInlineToolbarRoot.style.display = 'none';
|
|
1904
|
+
}
|
|
1905
|
+
|
|
1906
|
+
function toggleMarkdownInlineFormat(format) {
|
|
1907
|
+
if (!markdownLexicalApi || !markdownLexicalApi.editor || !markdownLexicalApi.lexicalModule) {
|
|
1908
|
+
return;
|
|
1909
|
+
}
|
|
1910
|
+
if (typeof format !== 'string' || !format) {
|
|
1911
|
+
return;
|
|
1912
|
+
}
|
|
1913
|
+
|
|
1914
|
+
markdownLexicalApi.editor.focus();
|
|
1915
|
+
markdownLexicalApi.editor.dispatchCommand(markdownLexicalApi.lexicalModule.FORMAT_TEXT_COMMAND, format);
|
|
1916
|
+
scheduleMarkdownInlineToolbarUpdate();
|
|
1917
|
+
}
|
|
1918
|
+
|
|
1919
|
+
function updateMarkdownInlineToolbar() {
|
|
1920
|
+
if (
|
|
1921
|
+
!markdownInlineToolbarRoot ||
|
|
1922
|
+
!markdownEditorRoot ||
|
|
1923
|
+
markdownEditorRoot.style.display !== 'block' ||
|
|
1924
|
+
!markdownLexicalApi ||
|
|
1925
|
+
!markdownEditorSurface ||
|
|
1926
|
+
markdownEditorSurface.style.display === 'none'
|
|
1927
|
+
) {
|
|
1928
|
+
hideMarkdownInlineToolbar();
|
|
1929
|
+
return;
|
|
1930
|
+
}
|
|
1931
|
+
|
|
1932
|
+
const selection = window.getSelection();
|
|
1933
|
+
if (!selection || selection.rangeCount === 0 || selection.isCollapsed) {
|
|
1934
|
+
hideMarkdownInlineToolbar();
|
|
1935
|
+
return;
|
|
1936
|
+
}
|
|
1937
|
+
|
|
1938
|
+
const range = selection.getRangeAt(0);
|
|
1939
|
+
if (
|
|
1940
|
+
!markdownEditorSurface.contains(range.startContainer) ||
|
|
1941
|
+
!markdownEditorSurface.contains(range.endContainer)
|
|
1942
|
+
) {
|
|
1943
|
+
hideMarkdownInlineToolbar();
|
|
1944
|
+
return;
|
|
1945
|
+
}
|
|
1946
|
+
|
|
1947
|
+
const rect = range.getBoundingClientRect();
|
|
1948
|
+
if (!rect || rect.width <= 0 || rect.height <= 0) {
|
|
1949
|
+
hideMarkdownInlineToolbar();
|
|
1950
|
+
return;
|
|
1951
|
+
}
|
|
1952
|
+
|
|
1953
|
+
const left = Math.max(8, Math.min(window.innerWidth - 180, rect.left + rect.width / 2 - 66));
|
|
1954
|
+
const top = Math.max(8, Math.min(window.innerHeight - 56, rect.top - 44));
|
|
1955
|
+
markdownInlineToolbarRoot.style.left = left + 'px';
|
|
1956
|
+
markdownInlineToolbarRoot.style.top = top + 'px';
|
|
1957
|
+
markdownInlineToolbarRoot.style.display = 'inline-flex';
|
|
1958
|
+
}
|
|
1959
|
+
|
|
1960
|
+
function scheduleMarkdownInlineToolbarUpdate() {
|
|
1961
|
+
if (markdownInlineToolbarFrame) {
|
|
1962
|
+
cancelAnimationFrame(markdownInlineToolbarFrame);
|
|
1963
|
+
}
|
|
1964
|
+
|
|
1965
|
+
markdownInlineToolbarFrame = requestAnimationFrame(function() {
|
|
1966
|
+
markdownInlineToolbarFrame = null;
|
|
1967
|
+
updateMarkdownInlineToolbar();
|
|
1968
|
+
});
|
|
1969
|
+
}
|
|
1970
|
+
|
|
1971
|
+
function getMarkdownTopLevelBlocks() {
|
|
1972
|
+
if (!markdownEditorSurface) {
|
|
1973
|
+
return [];
|
|
1974
|
+
}
|
|
1975
|
+
|
|
1976
|
+
return Array.from(markdownEditorSurface.children).filter(function(node) {
|
|
1977
|
+
return node && node.nodeType === Node.ELEMENT_NODE;
|
|
1978
|
+
});
|
|
1979
|
+
}
|
|
1980
|
+
|
|
1981
|
+
function hideMarkdownBlockDragHandle() {
|
|
1982
|
+
markdownBlockHandleHoverIndex = -1;
|
|
1983
|
+
if (!markdownBlockDragHandle) {
|
|
1984
|
+
return;
|
|
1985
|
+
}
|
|
1986
|
+
markdownBlockDragHandle.style.display = 'none';
|
|
1987
|
+
markdownBlockDragHandle.removeAttribute('data-block-index');
|
|
1988
|
+
}
|
|
1989
|
+
|
|
1990
|
+
function hideMarkdownBlockDropIndicator() {
|
|
1991
|
+
markdownBlockDropSlotIndex = -1;
|
|
1992
|
+
if (markdownBlockDropIndicator) {
|
|
1993
|
+
markdownBlockDropIndicator.style.display = 'none';
|
|
1994
|
+
}
|
|
1995
|
+
if (markdownBlockDropLabel) {
|
|
1996
|
+
markdownBlockDropLabel.style.display = 'none';
|
|
1997
|
+
markdownBlockDropLabel.textContent = '';
|
|
1998
|
+
}
|
|
1999
|
+
}
|
|
2000
|
+
|
|
2001
|
+
function hideMarkdownBlockDragUi() {
|
|
2002
|
+
markdownBlockDragActive = false;
|
|
2003
|
+
markdownBlockDragSourceIndex = -1;
|
|
2004
|
+
if (markdownBlockDragHandle) {
|
|
2005
|
+
markdownBlockDragHandle.setAttribute('data-dragging', 'false');
|
|
2006
|
+
}
|
|
2007
|
+
removeMarkdownDragGhost();
|
|
2008
|
+
hideMarkdownBlockDropIndicator();
|
|
2009
|
+
hideMarkdownBlockDragHandle();
|
|
2010
|
+
}
|
|
2011
|
+
|
|
2012
|
+
function getMarkdownBlockElementFromNode(node) {
|
|
2013
|
+
if (!markdownEditorSurface || !node) {
|
|
2014
|
+
return null;
|
|
2015
|
+
}
|
|
2016
|
+
|
|
2017
|
+
let current = node.nodeType === Node.ELEMENT_NODE ? node : node.parentElement;
|
|
2018
|
+
while (current && current.parentElement !== markdownEditorSurface) {
|
|
2019
|
+
current = current.parentElement;
|
|
2020
|
+
}
|
|
2021
|
+
|
|
2022
|
+
if (!current || current.parentElement !== markdownEditorSurface) {
|
|
2023
|
+
return null;
|
|
2024
|
+
}
|
|
2025
|
+
return current;
|
|
2026
|
+
}
|
|
2027
|
+
|
|
2028
|
+
function getMarkdownBlockTypeInfo(block) {
|
|
2029
|
+
if (!block || !block.tagName) {
|
|
2030
|
+
return { label: 'block', color: '#0081f8' };
|
|
2031
|
+
}
|
|
2032
|
+
|
|
2033
|
+
const tag = block.tagName.toLowerCase();
|
|
2034
|
+
if (tag === 'h1') {
|
|
2035
|
+
return { label: 'heading 1', color: '#7c3aed' };
|
|
2036
|
+
}
|
|
2037
|
+
if (tag === 'h2') {
|
|
2038
|
+
return { label: 'heading 2', color: '#7c3aed' };
|
|
2039
|
+
}
|
|
2040
|
+
if (tag === 'h3') {
|
|
2041
|
+
return { label: 'heading 3', color: '#7c3aed' };
|
|
2042
|
+
}
|
|
2043
|
+
if (tag === 'ul' || tag === 'ol') {
|
|
2044
|
+
return { label: 'list', color: '#0d9488' };
|
|
2045
|
+
}
|
|
2046
|
+
if (tag === 'blockquote') {
|
|
2047
|
+
return { label: 'quote', color: '#2563eb' };
|
|
2048
|
+
}
|
|
2049
|
+
if (tag === 'pre') {
|
|
2050
|
+
return { label: 'code block', color: '#ea580c' };
|
|
2051
|
+
}
|
|
2052
|
+
if (tag === 'img' || tag === 'figure') {
|
|
2053
|
+
return { label: 'image', color: '#db2777' };
|
|
2054
|
+
}
|
|
2055
|
+
if (tag === 'p') {
|
|
2056
|
+
return { label: 'paragraph', color: '#16a34a' };
|
|
2057
|
+
}
|
|
2058
|
+
return { label: tag, color: '#0081f8' };
|
|
2059
|
+
}
|
|
2060
|
+
|
|
2061
|
+
function getMarkdownBlockPreviewText(block) {
|
|
2062
|
+
if (!block) {
|
|
2063
|
+
return '';
|
|
2064
|
+
}
|
|
2065
|
+
const text = String(block.textContent || '').replace(new RegExp('\\s+', 'g'), ' ').trim();
|
|
2066
|
+
if (!text) {
|
|
2067
|
+
return 'Empty block';
|
|
2068
|
+
}
|
|
2069
|
+
if (text.length > 84) {
|
|
2070
|
+
return text.slice(0, 84) + '...';
|
|
2071
|
+
}
|
|
2072
|
+
return text;
|
|
2073
|
+
}
|
|
2074
|
+
|
|
2075
|
+
function removeMarkdownDragGhost() {
|
|
2076
|
+
if (!markdownBlockDragGhost) {
|
|
2077
|
+
return;
|
|
2078
|
+
}
|
|
2079
|
+
markdownBlockDragGhost.remove();
|
|
2080
|
+
markdownBlockDragGhost = null;
|
|
2081
|
+
}
|
|
2082
|
+
|
|
2083
|
+
function createMarkdownDragGhost(block) {
|
|
2084
|
+
const typeInfo = getMarkdownBlockTypeInfo(block);
|
|
2085
|
+
const ghost = document.createElement('div');
|
|
2086
|
+
ghost.className = 'vf-markdown-editor__block-drag-ghost';
|
|
2087
|
+
ghost.setAttribute(DATA_VF_IGNORE, 'true');
|
|
2088
|
+
|
|
2089
|
+
const title = document.createElement('span');
|
|
2090
|
+
title.className = 'vf-markdown-editor__block-drag-ghost-title';
|
|
2091
|
+
title.setAttribute(DATA_VF_IGNORE, 'true');
|
|
2092
|
+
title.textContent = 'Moving ' + typeInfo.label;
|
|
2093
|
+
|
|
2094
|
+
const text = document.createElement('span');
|
|
2095
|
+
text.className = 'vf-markdown-editor__block-drag-ghost-text';
|
|
2096
|
+
text.setAttribute(DATA_VF_IGNORE, 'true');
|
|
2097
|
+
text.textContent = getMarkdownBlockPreviewText(block);
|
|
2098
|
+
|
|
2099
|
+
ghost.appendChild(title);
|
|
2100
|
+
ghost.appendChild(text);
|
|
2101
|
+
return ghost;
|
|
2102
|
+
}
|
|
2103
|
+
|
|
2104
|
+
function getLineNumberForOffset(text, offset) {
|
|
2105
|
+
const source = typeof text === 'string' ? text : '';
|
|
2106
|
+
const maxOffset = Math.max(0, Math.min(source.length, Math.trunc(offset || 0)));
|
|
2107
|
+
let line = 1;
|
|
2108
|
+
for (let i = 0; i < maxOffset; i += 1) {
|
|
2109
|
+
if (source.charCodeAt(i) === 10) {
|
|
2110
|
+
line += 1;
|
|
2111
|
+
}
|
|
2112
|
+
}
|
|
2113
|
+
return line;
|
|
2114
|
+
}
|
|
2115
|
+
|
|
2116
|
+
function getMdxComponentName(blockText) {
|
|
2117
|
+
const source = typeof blockText === 'string' ? blockText : '';
|
|
2118
|
+
const fence = String.fromCharCode(96, 96, 96);
|
|
2119
|
+
const componentMatch = source.match(/<\\s*([A-Z][\\w.]*)/);
|
|
2120
|
+
if (componentMatch && componentMatch[1]) {
|
|
2121
|
+
return componentMatch[1];
|
|
2122
|
+
}
|
|
2123
|
+
if (source.trim().startsWith(fence + 'tsx')) {
|
|
2124
|
+
return 'tsx block';
|
|
2125
|
+
}
|
|
2126
|
+
if (source.trim().startsWith(fence + 'jsx')) {
|
|
2127
|
+
return 'jsx block';
|
|
2128
|
+
}
|
|
2129
|
+
return 'component block';
|
|
2130
|
+
}
|
|
2131
|
+
|
|
2132
|
+
function getMdxBlockOpenUiState(block) {
|
|
2133
|
+
const hasResolvedTarget = !!(block && typeof block.filePath === 'string' && block.filePath.trim());
|
|
2134
|
+
return {
|
|
2135
|
+
hasResolvedTarget: hasResolvedTarget,
|
|
2136
|
+
buttonLabel: hasResolvedTarget ? 'Edit in Studio' : 'Open MDX source',
|
|
2137
|
+
showUnresolvedNote: !hasResolvedTarget
|
|
2138
|
+
};
|
|
2139
|
+
}
|
|
2140
|
+
|
|
2141
|
+
function setMarkdownMdxBlocks(blocks) {
|
|
2142
|
+
markdownLatestMdxBlocks = Array.isArray(blocks) ? blocks : [];
|
|
2143
|
+
if (!markdownMdxBlocksRoot) {
|
|
2144
|
+
return;
|
|
2145
|
+
}
|
|
2146
|
+
|
|
2147
|
+
markdownMdxBlocksRoot.textContent = '';
|
|
2148
|
+
if (!isMdxPage() || markdownLatestMdxBlocks.length === 0) {
|
|
2149
|
+
markdownMdxBlocksRoot.style.display = 'none';
|
|
2150
|
+
return;
|
|
2151
|
+
}
|
|
2152
|
+
|
|
2153
|
+
markdownMdxBlocksRoot.style.display = 'flex';
|
|
2154
|
+
for (const block of markdownLatestMdxBlocks.slice(0, 8)) {
|
|
2155
|
+
if (!block || typeof block.label !== 'string') {
|
|
2156
|
+
continue;
|
|
2157
|
+
}
|
|
2158
|
+
|
|
2159
|
+
const item = document.createElement('div');
|
|
2160
|
+
item.className = 'vf-markdown-editor__mdx-block';
|
|
2161
|
+
item.setAttribute(DATA_VF_IGNORE, 'true');
|
|
2162
|
+
|
|
2163
|
+
const label = document.createElement('div');
|
|
2164
|
+
label.className = 'vf-markdown-editor__mdx-block-label';
|
|
2165
|
+
label.setAttribute(DATA_VF_IGNORE, 'true');
|
|
2166
|
+
const safeLine = Number.isFinite(block.lineNumber) ? Math.max(1, Math.trunc(block.lineNumber)) : 1;
|
|
2167
|
+
label.textContent = block.label + ' (line ' + String(safeLine) + ')';
|
|
2168
|
+
const openUiState = getMdxBlockOpenUiState(block);
|
|
2169
|
+
|
|
2170
|
+
const openButton = document.createElement('button');
|
|
2171
|
+
openButton.type = 'button';
|
|
2172
|
+
openButton.className = 'vf-markdown-editor__mdx-open';
|
|
2173
|
+
openButton.setAttribute(DATA_VF_IGNORE, 'true');
|
|
2174
|
+
openButton.textContent = openUiState.buttonLabel;
|
|
2175
|
+
if (openUiState.showUnresolvedNote) {
|
|
2176
|
+
openButton.title = 'Component import could not be resolved. Opening current MDX source.';
|
|
2177
|
+
}
|
|
2178
|
+
openButton.addEventListener('click', function() {
|
|
2179
|
+
const targetFile = typeof block.filePath === 'string' && block.filePath ? block.filePath : PAGE_PATH;
|
|
2180
|
+
const targetLine = targetFile === PAGE_PATH ? safeLine : 1;
|
|
2181
|
+
const targetSymbol = targetFile === PAGE_PATH
|
|
2182
|
+
? ''
|
|
2183
|
+
: (typeof block.symbolName === 'string' ? block.symbolName : '');
|
|
2184
|
+
openFilePathInStudio(targetFile, targetLine, targetSymbol);
|
|
2185
|
+
});
|
|
2186
|
+
|
|
2187
|
+
item.appendChild(label);
|
|
2188
|
+
if (openUiState.showUnresolvedNote) {
|
|
2189
|
+
const fallbackNote = document.createElement('span');
|
|
2190
|
+
fallbackNote.className = 'vf-markdown-editor__mdx-note';
|
|
2191
|
+
fallbackNote.setAttribute(DATA_VF_IGNORE, 'true');
|
|
2192
|
+
fallbackNote.textContent = 'Unresolved import';
|
|
2193
|
+
item.appendChild(fallbackNote);
|
|
2194
|
+
}
|
|
2195
|
+
item.appendChild(openButton);
|
|
2196
|
+
markdownMdxBlocksRoot.appendChild(item);
|
|
2197
|
+
}
|
|
2198
|
+
|
|
2199
|
+
if (markdownMdxBlocksRoot.childNodes.length === 0) {
|
|
2200
|
+
markdownMdxBlocksRoot.style.display = 'none';
|
|
2201
|
+
}
|
|
2202
|
+
}
|
|
2203
|
+
|
|
2204
|
+
function getMarkdownBlockHoverIndexFromPointer(targetNode, clientX, clientY) {
|
|
2205
|
+
const blocks = getMarkdownTopLevelBlocks();
|
|
2206
|
+
if (blocks.length === 0) {
|
|
2207
|
+
return -1;
|
|
2208
|
+
}
|
|
2209
|
+
|
|
2210
|
+
const directBlock = getMarkdownBlockElementFromNode(targetNode);
|
|
2211
|
+
const directIndex = blocks.indexOf(directBlock);
|
|
2212
|
+
if (directIndex >= 0) {
|
|
2213
|
+
return directIndex;
|
|
2214
|
+
}
|
|
2215
|
+
|
|
2216
|
+
if (!markdownEditorSurface) {
|
|
2217
|
+
return -1;
|
|
2218
|
+
}
|
|
2219
|
+
const surfaceRect = markdownEditorSurface.getBoundingClientRect();
|
|
2220
|
+
const leftBoundary = surfaceRect.left - 44;
|
|
2221
|
+
const rightBoundary = surfaceRect.left + Math.min(96, surfaceRect.width * 0.35);
|
|
2222
|
+
if (clientX < leftBoundary || clientX > rightBoundary) {
|
|
2223
|
+
return -1;
|
|
2224
|
+
}
|
|
2225
|
+
|
|
2226
|
+
for (let i = 0; i < blocks.length; i += 1) {
|
|
2227
|
+
const rect = blocks[i].getBoundingClientRect();
|
|
2228
|
+
if (clientY >= rect.top - 4 && clientY <= rect.bottom + 4) {
|
|
2229
|
+
return i;
|
|
2230
|
+
}
|
|
2231
|
+
}
|
|
2232
|
+
|
|
2233
|
+
const firstRect = blocks[0].getBoundingClientRect();
|
|
2234
|
+
const lastRect = blocks[blocks.length - 1].getBoundingClientRect();
|
|
2235
|
+
if (clientY < firstRect.top) {
|
|
2236
|
+
return 0;
|
|
2237
|
+
}
|
|
2238
|
+
if (clientY > lastRect.bottom) {
|
|
2239
|
+
return blocks.length - 1;
|
|
2240
|
+
}
|
|
2241
|
+
return -1;
|
|
2242
|
+
}
|
|
2243
|
+
|
|
2244
|
+
function positionMarkdownBlockDragHandle(block, index) {
|
|
2245
|
+
if (!markdownBlockDragHandle || !block || !markdownEditorRoot || markdownEditorRoot.style.display !== 'block') {
|
|
2246
|
+
hideMarkdownBlockDragHandle();
|
|
2247
|
+
return;
|
|
2248
|
+
}
|
|
2249
|
+
|
|
2250
|
+
const rect = block.getBoundingClientRect();
|
|
2251
|
+
const surfaceRect = markdownEditorSurface ? markdownEditorSurface.getBoundingClientRect() : null;
|
|
2252
|
+
if (!surfaceRect || rect.width <= 0 || rect.height <= 0) {
|
|
2253
|
+
hideMarkdownBlockDragHandle();
|
|
2254
|
+
return;
|
|
2255
|
+
}
|
|
2256
|
+
|
|
2257
|
+
// Ignore blocks outside the visible scroll viewport.
|
|
2258
|
+
if (rect.bottom < surfaceRect.top || rect.top > surfaceRect.bottom) {
|
|
2259
|
+
hideMarkdownBlockDragHandle();
|
|
2260
|
+
return;
|
|
2261
|
+
}
|
|
2262
|
+
|
|
2263
|
+
const left = Math.max(6, rect.left - 36);
|
|
2264
|
+
const top = Math.max(6, rect.top + 1);
|
|
2265
|
+
markdownBlockDragHandle.style.left = left + 'px';
|
|
2266
|
+
markdownBlockDragHandle.style.top = top + 'px';
|
|
2267
|
+
markdownBlockDragHandle.style.display = 'block';
|
|
2268
|
+
markdownBlockDragHandle.setAttribute('data-block-index', String(index));
|
|
2269
|
+
markdownBlockHandleHoverIndex = index;
|
|
2270
|
+
}
|
|
2271
|
+
|
|
2272
|
+
function refreshMarkdownBlockDragHandlePosition() {
|
|
2273
|
+
if (markdownBlockDragActive || markdownBlockHandleHoverIndex < 0) {
|
|
2274
|
+
return;
|
|
2275
|
+
}
|
|
2276
|
+
const blocks = getMarkdownTopLevelBlocks();
|
|
2277
|
+
const block = blocks[markdownBlockHandleHoverIndex];
|
|
2278
|
+
if (!block) {
|
|
2279
|
+
hideMarkdownBlockDragHandle();
|
|
2280
|
+
return;
|
|
2281
|
+
}
|
|
2282
|
+
positionMarkdownBlockDragHandle(block, markdownBlockHandleHoverIndex);
|
|
2283
|
+
}
|
|
2284
|
+
|
|
2285
|
+
function getMarkdownDropSlotIndexFromPointer(clientY) {
|
|
2286
|
+
const blocks = getMarkdownTopLevelBlocks();
|
|
2287
|
+
if (blocks.length === 0) {
|
|
2288
|
+
return -1;
|
|
2289
|
+
}
|
|
2290
|
+
|
|
2291
|
+
for (let i = 0; i < blocks.length; i += 1) {
|
|
2292
|
+
const rect = blocks[i].getBoundingClientRect();
|
|
2293
|
+
const midpoint = rect.top + rect.height / 2;
|
|
2294
|
+
if (clientY < midpoint) {
|
|
2295
|
+
return i;
|
|
2296
|
+
}
|
|
2297
|
+
}
|
|
2298
|
+
return blocks.length;
|
|
2299
|
+
}
|
|
2300
|
+
|
|
2301
|
+
function autoScrollMarkdownSurfaceDuringDrag(clientY) {
|
|
2302
|
+
if (!markdownEditorSurface) {
|
|
2303
|
+
return;
|
|
2304
|
+
}
|
|
2305
|
+
|
|
2306
|
+
const rect = markdownEditorSurface.getBoundingClientRect();
|
|
2307
|
+
const threshold = 42;
|
|
2308
|
+
const maxStep = 20;
|
|
2309
|
+
let delta = 0;
|
|
2310
|
+
|
|
2311
|
+
if (clientY < rect.top + threshold) {
|
|
2312
|
+
const distance = rect.top + threshold - clientY;
|
|
2313
|
+
delta = -Math.min(maxStep, Math.max(2, Math.floor(distance / 3)));
|
|
2314
|
+
} else if (clientY > rect.bottom - threshold) {
|
|
2315
|
+
const distance = clientY - (rect.bottom - threshold);
|
|
2316
|
+
delta = Math.min(maxStep, Math.max(2, Math.floor(distance / 3)));
|
|
2317
|
+
}
|
|
2318
|
+
|
|
2319
|
+
if (delta !== 0) {
|
|
2320
|
+
markdownEditorSurface.scrollTop += delta;
|
|
2321
|
+
refreshMarkdownBlockDragHandlePosition();
|
|
2322
|
+
}
|
|
2323
|
+
}
|
|
2324
|
+
|
|
2325
|
+
function showMarkdownBlockDropIndicator(slotIndex) {
|
|
2326
|
+
if (!markdownBlockDropIndicator || !markdownEditorSurface) {
|
|
2327
|
+
return;
|
|
2328
|
+
}
|
|
2329
|
+
|
|
2330
|
+
const blocks = getMarkdownTopLevelBlocks();
|
|
2331
|
+
if (blocks.length === 0) {
|
|
2332
|
+
hideMarkdownBlockDropIndicator();
|
|
2333
|
+
return;
|
|
2334
|
+
}
|
|
2335
|
+
|
|
2336
|
+
const safeSlot = Math.max(0, Math.min(blocks.length, Math.trunc(slotIndex || 0)));
|
|
2337
|
+
const surfaceRect = markdownEditorSurface.getBoundingClientRect();
|
|
2338
|
+
let top = surfaceRect.top + 6;
|
|
2339
|
+
|
|
2340
|
+
if (safeSlot >= blocks.length) {
|
|
2341
|
+
const lastRect = blocks[blocks.length - 1].getBoundingClientRect();
|
|
2342
|
+
top = lastRect.bottom + 1;
|
|
2343
|
+
} else {
|
|
2344
|
+
const rect = blocks[safeSlot].getBoundingClientRect();
|
|
2345
|
+
top = rect.top - 1;
|
|
2346
|
+
}
|
|
2347
|
+
|
|
2348
|
+
markdownBlockDropIndicator.style.left = Math.max(8, surfaceRect.left + 8) + 'px';
|
|
2349
|
+
markdownBlockDropIndicator.style.top = Math.max(8, top) + 'px';
|
|
2350
|
+
markdownBlockDropIndicator.style.width = Math.max(40, surfaceRect.width - 16) + 'px';
|
|
2351
|
+
markdownBlockDropIndicator.style.display = 'block';
|
|
2352
|
+
markdownBlockDropSlotIndex = safeSlot;
|
|
2353
|
+
|
|
2354
|
+
const dropType = safeSlot >= blocks.length
|
|
2355
|
+
? { label: 'end of document', color: '#0284c7' }
|
|
2356
|
+
: getMarkdownBlockTypeInfo(blocks[safeSlot]);
|
|
2357
|
+
markdownBlockDropIndicator.style.background = dropType.color;
|
|
2358
|
+
markdownBlockDropIndicator.style.boxShadow = '0 1px 6px ' + dropType.color;
|
|
2359
|
+
|
|
2360
|
+
if (markdownBlockDropLabel) {
|
|
2361
|
+
markdownBlockDropLabel.textContent = safeSlot >= blocks.length
|
|
2362
|
+
? 'Drop at end'
|
|
2363
|
+
: 'Drop before ' + dropType.label;
|
|
2364
|
+
markdownBlockDropLabel.style.left = Math.max(8, surfaceRect.left + 8) + 'px';
|
|
2365
|
+
markdownBlockDropLabel.style.top = Math.max(8, top - 26) + 'px';
|
|
2366
|
+
markdownBlockDropLabel.style.borderColor = dropType.color;
|
|
2367
|
+
markdownBlockDropLabel.style.display = 'block';
|
|
2368
|
+
}
|
|
2369
|
+
}
|
|
2370
|
+
|
|
2371
|
+
function moveMarkdownLexicalBlock(sourceIndex, targetSlotIndex) {
|
|
2372
|
+
if (!markdownLexicalApi || !markdownLexicalApi.editor || !markdownLexicalApi.lexicalModule) {
|
|
2373
|
+
return false;
|
|
2374
|
+
}
|
|
2375
|
+
|
|
2376
|
+
const source = Math.trunc(sourceIndex);
|
|
2377
|
+
const targetSlot = Math.trunc(targetSlotIndex);
|
|
2378
|
+
if (!Number.isInteger(source) || !Number.isInteger(targetSlot)) {
|
|
2379
|
+
return false;
|
|
2380
|
+
}
|
|
2381
|
+
|
|
2382
|
+
let didMove = false;
|
|
2383
|
+
markdownLexicalApi.editor.update(function() {
|
|
2384
|
+
const root = markdownLexicalApi.lexicalModule.$getRoot();
|
|
2385
|
+
const children = root.getChildren();
|
|
2386
|
+
const maxSlot = children.length;
|
|
2387
|
+
if (source < 0 || source >= maxSlot || targetSlot < 0 || targetSlot > maxSlot) {
|
|
2388
|
+
return;
|
|
2389
|
+
}
|
|
2390
|
+
|
|
2391
|
+
let adjustedSlot = targetSlot;
|
|
2392
|
+
if (source < adjustedSlot) {
|
|
2393
|
+
adjustedSlot -= 1;
|
|
2394
|
+
}
|
|
2395
|
+
if (adjustedSlot === source) {
|
|
2396
|
+
return;
|
|
2397
|
+
}
|
|
2398
|
+
|
|
2399
|
+
const node = children[source];
|
|
2400
|
+
node.remove();
|
|
2401
|
+
const afterRemoval = root.getChildren();
|
|
2402
|
+
if (adjustedSlot >= afterRemoval.length) {
|
|
2403
|
+
root.append(node);
|
|
2404
|
+
} else {
|
|
2405
|
+
afterRemoval[adjustedSlot].insertBefore(node);
|
|
2406
|
+
}
|
|
2407
|
+
didMove = true;
|
|
2408
|
+
});
|
|
2409
|
+
|
|
2410
|
+
if (didMove) {
|
|
2411
|
+
scheduleMarkdownSelectionSync();
|
|
2412
|
+
scheduleMarkdownSelectionOverlayRender();
|
|
2413
|
+
scheduleMarkdownSlashMenuUpdate();
|
|
2414
|
+
scheduleMarkdownInlineToolbarUpdate();
|
|
2415
|
+
}
|
|
2416
|
+
return didMove;
|
|
2417
|
+
}
|
|
2418
|
+
|
|
2419
|
+
function getMarkdownCurrentBlockIndexFromSelection() {
|
|
2420
|
+
if (!markdownEditorSurface) {
|
|
2421
|
+
return -1;
|
|
2422
|
+
}
|
|
2423
|
+
const selection = window.getSelection();
|
|
2424
|
+
if (!selection || selection.rangeCount === 0) {
|
|
2425
|
+
return -1;
|
|
2426
|
+
}
|
|
2427
|
+
|
|
2428
|
+
const range = selection.getRangeAt(0);
|
|
2429
|
+
if (!markdownEditorSurface.contains(range.startContainer)) {
|
|
2430
|
+
return -1;
|
|
2431
|
+
}
|
|
2432
|
+
|
|
2433
|
+
const block = getMarkdownBlockElementFromNode(range.startContainer);
|
|
2434
|
+
if (!block) {
|
|
2435
|
+
return -1;
|
|
2436
|
+
}
|
|
2437
|
+
|
|
2438
|
+
const blocks = getMarkdownTopLevelBlocks();
|
|
2439
|
+
return blocks.indexOf(block);
|
|
2440
|
+
}
|
|
2441
|
+
|
|
2442
|
+
function moveMarkdownCurrentBlockByDelta(delta) {
|
|
2443
|
+
const blocks = getMarkdownTopLevelBlocks();
|
|
2444
|
+
if (blocks.length <= 1) {
|
|
2445
|
+
return false;
|
|
2446
|
+
}
|
|
2447
|
+
|
|
2448
|
+
const index = getMarkdownCurrentBlockIndexFromSelection();
|
|
2449
|
+
if (index < 0) {
|
|
2450
|
+
return false;
|
|
2451
|
+
}
|
|
2452
|
+
|
|
2453
|
+
const step = Math.sign(delta);
|
|
2454
|
+
if (step === 0) {
|
|
2455
|
+
return false;
|
|
2456
|
+
}
|
|
2457
|
+
|
|
2458
|
+
let targetSlot = index;
|
|
2459
|
+
if (step < 0) {
|
|
2460
|
+
targetSlot = Math.max(0, index - 1);
|
|
2461
|
+
} else {
|
|
2462
|
+
targetSlot = Math.min(blocks.length, index + 2);
|
|
2463
|
+
}
|
|
2464
|
+
|
|
2465
|
+
const moved = moveMarkdownLexicalBlock(index, targetSlot);
|
|
2466
|
+
if (moved) {
|
|
2467
|
+
setTimeout(function() {
|
|
2468
|
+
const nextBlocks = getMarkdownTopLevelBlocks();
|
|
2469
|
+
const nextIndex = Math.max(0, Math.min(nextBlocks.length - 1, index + step));
|
|
2470
|
+
const nextBlock = nextBlocks[nextIndex];
|
|
2471
|
+
if (nextBlock) {
|
|
2472
|
+
positionMarkdownBlockDragHandle(nextBlock, nextIndex);
|
|
2473
|
+
}
|
|
2474
|
+
}, 0);
|
|
2475
|
+
}
|
|
2476
|
+
return moved;
|
|
2477
|
+
}
|
|
2478
|
+
|
|
2479
|
+
function scheduleMarkdownSelectionSync() {
|
|
2480
|
+
if (!markdownFileId) {
|
|
2481
|
+
return;
|
|
2482
|
+
}
|
|
2483
|
+
|
|
2484
|
+
if (markdownSelectionSyncTimer) {
|
|
2485
|
+
clearTimeout(markdownSelectionSyncTimer);
|
|
2486
|
+
}
|
|
2487
|
+
|
|
2488
|
+
markdownSelectionSyncTimer = setTimeout(function() {
|
|
2489
|
+
const selection = getMarkdownEditorSelection();
|
|
2490
|
+
if (!selection) {
|
|
2491
|
+
return;
|
|
2492
|
+
}
|
|
2493
|
+
|
|
2494
|
+
const start = editorOffsetToSourceOffset(selection.start, 'start');
|
|
2495
|
+
const end = editorOffsetToSourceOffset(selection.end, 'end');
|
|
2496
|
+
|
|
2497
|
+
postToStudio({
|
|
2498
|
+
action: 'markdownSelectionChange',
|
|
2499
|
+
fileId: markdownFileId,
|
|
2500
|
+
filePath: PAGE_PATH,
|
|
2501
|
+
start: start,
|
|
2502
|
+
end: end
|
|
2503
|
+
});
|
|
2504
|
+
}, 80);
|
|
2505
|
+
}
|
|
2506
|
+
|
|
2507
|
+
function clearMarkdownSelectionSync() {
|
|
2508
|
+
if (!markdownFileId) {
|
|
2509
|
+
return;
|
|
2510
|
+
}
|
|
2511
|
+
postToStudio({
|
|
2512
|
+
action: 'markdownSelectionChange',
|
|
2513
|
+
fileId: markdownFileId,
|
|
2514
|
+
filePath: PAGE_PATH,
|
|
2515
|
+
start: -1,
|
|
2516
|
+
end: -1
|
|
2517
|
+
});
|
|
2518
|
+
}
|
|
2519
|
+
|
|
2520
|
+
function clearMarkdownSelectionOverlay() {
|
|
2521
|
+
if (!markdownSelectionOverlayRoot) {
|
|
2522
|
+
return;
|
|
2523
|
+
}
|
|
2524
|
+
markdownSelectionOverlayRoot.textContent = '';
|
|
2525
|
+
markdownSelectionOverlayRoot.style.display = 'none';
|
|
2526
|
+
}
|
|
2527
|
+
|
|
2528
|
+
function resolveMarkdownTextPoint(root, rawOffset) {
|
|
2529
|
+
const offset = Math.max(0, Math.trunc(rawOffset || 0));
|
|
2530
|
+
const walker = document.createTreeWalker(root, NodeFilter.SHOW_TEXT);
|
|
2531
|
+
let remaining = offset;
|
|
2532
|
+
let lastTextNode = null;
|
|
2533
|
+
let node = walker.nextNode();
|
|
2534
|
+
|
|
2535
|
+
while (node) {
|
|
2536
|
+
lastTextNode = node;
|
|
2537
|
+
const textLength = node.textContent ? node.textContent.length : 0;
|
|
2538
|
+
if (remaining <= textLength) {
|
|
2539
|
+
return { node: node, offset: remaining };
|
|
2540
|
+
}
|
|
2541
|
+
remaining -= textLength;
|
|
2542
|
+
node = walker.nextNode();
|
|
2543
|
+
}
|
|
2544
|
+
|
|
2545
|
+
if (lastTextNode) {
|
|
2546
|
+
const textLength = lastTextNode.textContent ? lastTextNode.textContent.length : 0;
|
|
2547
|
+
return { node: lastTextNode, offset: textLength };
|
|
2548
|
+
}
|
|
2549
|
+
|
|
2550
|
+
return {
|
|
2551
|
+
node: root,
|
|
2552
|
+
offset: offset > 0 ? root.childNodes.length : 0
|
|
2553
|
+
};
|
|
2554
|
+
}
|
|
2555
|
+
|
|
2556
|
+
function createMarkdownEditorRange(start, end) {
|
|
2557
|
+
if (!markdownEditorSurface) {
|
|
2558
|
+
return null;
|
|
2559
|
+
}
|
|
2560
|
+
|
|
2561
|
+
const safeStart = Math.max(0, Math.min(start, end));
|
|
2562
|
+
const safeEnd = Math.max(0, Math.max(start, end));
|
|
2563
|
+
const startPoint = resolveMarkdownTextPoint(markdownEditorSurface, safeStart);
|
|
2564
|
+
const endPoint = resolveMarkdownTextPoint(markdownEditorSurface, safeEnd);
|
|
2565
|
+
|
|
2566
|
+
try {
|
|
2567
|
+
const range = document.createRange();
|
|
2568
|
+
range.setStart(startPoint.node, startPoint.offset);
|
|
2569
|
+
range.setEnd(endPoint.node, endPoint.offset);
|
|
2570
|
+
return range;
|
|
2571
|
+
} catch {
|
|
2572
|
+
return null;
|
|
2573
|
+
}
|
|
2574
|
+
}
|
|
2575
|
+
|
|
2576
|
+
function toMarkdownOverlayRect(rect, surfaceRect) {
|
|
2577
|
+
const rawLeft = rect.left - surfaceRect.left;
|
|
2578
|
+
const rawTop = rect.top - surfaceRect.top;
|
|
2579
|
+
const rawRight = rawLeft + rect.width;
|
|
2580
|
+
const rawBottom = rawTop + rect.height;
|
|
2581
|
+
|
|
2582
|
+
const left = Math.max(0, rawLeft);
|
|
2583
|
+
const top = Math.max(0, rawTop);
|
|
2584
|
+
const right = Math.min(surfaceRect.width, rawRight);
|
|
2585
|
+
const bottom = Math.min(surfaceRect.height, rawBottom);
|
|
2586
|
+
const width = right - left;
|
|
2587
|
+
const height = bottom - top;
|
|
2588
|
+
|
|
2589
|
+
if (width <= 0 || height <= 0) {
|
|
2590
|
+
return null;
|
|
2591
|
+
}
|
|
2592
|
+
|
|
2593
|
+
return {
|
|
2594
|
+
left: left,
|
|
2595
|
+
top: top,
|
|
2596
|
+
width: width,
|
|
2597
|
+
height: height
|
|
2598
|
+
};
|
|
2599
|
+
}
|
|
2600
|
+
|
|
2601
|
+
function renderMarkdownSelectionOverlay() {
|
|
2602
|
+
if (!markdownSelectionOverlayRoot) {
|
|
2603
|
+
return;
|
|
2604
|
+
}
|
|
2605
|
+
|
|
2606
|
+
if (
|
|
2607
|
+
!markdownEditorRoot ||
|
|
2608
|
+
markdownEditorRoot.style.display !== 'block' ||
|
|
2609
|
+
!markdownEditorSurface ||
|
|
2610
|
+
!markdownLexicalApi ||
|
|
2611
|
+
!Array.isArray(markdownOverlaySelections) ||
|
|
2612
|
+
markdownOverlaySelections.length === 0
|
|
2613
|
+
) {
|
|
2614
|
+
clearMarkdownSelectionOverlay();
|
|
2615
|
+
return;
|
|
2616
|
+
}
|
|
2617
|
+
|
|
2618
|
+
const surfaceRect = markdownEditorSurface.getBoundingClientRect();
|
|
2619
|
+
if (surfaceRect.width <= 0 || surfaceRect.height <= 0) {
|
|
2620
|
+
clearMarkdownSelectionOverlay();
|
|
2621
|
+
return;
|
|
2622
|
+
}
|
|
2623
|
+
|
|
2624
|
+
const computedStyle = window.getComputedStyle(markdownEditorSurface);
|
|
2625
|
+
const lineHeight = Math.max(14, Number.parseFloat(computedStyle.lineHeight || '0') || 22);
|
|
2626
|
+
markdownSelectionOverlayRoot.textContent = '';
|
|
2627
|
+
markdownSelectionOverlayRoot.style.display = 'block';
|
|
2628
|
+
|
|
2629
|
+
for (const selection of markdownOverlaySelections) {
|
|
2630
|
+
if (!selection || typeof selection.start !== 'number' || typeof selection.end !== 'number') {
|
|
2631
|
+
continue;
|
|
2632
|
+
}
|
|
2633
|
+
|
|
2634
|
+
const range = createMarkdownEditorRange(selection.start, selection.end);
|
|
2635
|
+
if (!range) {
|
|
2636
|
+
continue;
|
|
2637
|
+
}
|
|
2638
|
+
|
|
2639
|
+
const color = typeof selection.color === 'string' && selection.color ? selection.color : '#6b7280';
|
|
2640
|
+
const name = typeof selection.name === 'string' && selection.name ? selection.name : 'Anonymous';
|
|
2641
|
+
let labelAnchor = null;
|
|
2642
|
+
|
|
2643
|
+
if (selection.start === selection.end) {
|
|
2644
|
+
const caretRect = range.getBoundingClientRect();
|
|
2645
|
+
const clippedCaret = toMarkdownOverlayRect(
|
|
2646
|
+
{
|
|
2647
|
+
left: caretRect.left,
|
|
2648
|
+
top: caretRect.top,
|
|
2649
|
+
width: 2,
|
|
2650
|
+
height: Math.max(caretRect.height, lineHeight)
|
|
2651
|
+
},
|
|
2652
|
+
surfaceRect
|
|
2653
|
+
);
|
|
2654
|
+
|
|
2655
|
+
if (!clippedCaret) {
|
|
2656
|
+
continue;
|
|
2657
|
+
}
|
|
2658
|
+
|
|
2659
|
+
const caret = document.createElement('div');
|
|
2660
|
+
caret.className = 'vf-markdown-editor__selection-caret';
|
|
2661
|
+
caret.setAttribute(DATA_VF_IGNORE, 'true');
|
|
2662
|
+
caret.style.left = clippedCaret.left + 'px';
|
|
2663
|
+
caret.style.top = clippedCaret.top + 'px';
|
|
2664
|
+
caret.style.height = clippedCaret.height + 'px';
|
|
2665
|
+
caret.style.background = color;
|
|
2666
|
+
markdownSelectionOverlayRoot.appendChild(caret);
|
|
2667
|
+
|
|
2668
|
+
labelAnchor = { left: clippedCaret.left, top: clippedCaret.top };
|
|
2669
|
+
} else {
|
|
2670
|
+
const rectList = Array.from(range.getClientRects());
|
|
2671
|
+
for (const rect of rectList) {
|
|
2672
|
+
const clippedRect = toMarkdownOverlayRect(rect, surfaceRect);
|
|
2673
|
+
if (!clippedRect) {
|
|
2674
|
+
continue;
|
|
2675
|
+
}
|
|
2676
|
+
|
|
2677
|
+
const highlight = document.createElement('div');
|
|
2678
|
+
highlight.className = 'vf-markdown-editor__selection-highlight';
|
|
2679
|
+
highlight.setAttribute(DATA_VF_IGNORE, 'true');
|
|
2680
|
+
highlight.style.left = clippedRect.left + 'px';
|
|
2681
|
+
highlight.style.top = clippedRect.top + 'px';
|
|
2682
|
+
highlight.style.width = clippedRect.width + 'px';
|
|
2683
|
+
highlight.style.height = clippedRect.height + 'px';
|
|
2684
|
+
highlight.style.background = color;
|
|
2685
|
+
markdownSelectionOverlayRoot.appendChild(highlight);
|
|
2686
|
+
|
|
2687
|
+
if (!labelAnchor) {
|
|
2688
|
+
labelAnchor = { left: clippedRect.left, top: clippedRect.top };
|
|
2689
|
+
}
|
|
2690
|
+
}
|
|
2691
|
+
}
|
|
2692
|
+
|
|
2693
|
+
if (!labelAnchor) {
|
|
2694
|
+
continue;
|
|
2695
|
+
}
|
|
2696
|
+
|
|
2697
|
+
const label = document.createElement('div');
|
|
2698
|
+
label.className = 'vf-markdown-editor__selection-label';
|
|
2699
|
+
label.setAttribute(DATA_VF_IGNORE, 'true');
|
|
2700
|
+
label.textContent = name;
|
|
2701
|
+
label.style.left = labelAnchor.left + 'px';
|
|
2702
|
+
label.style.top = labelAnchor.top + 'px';
|
|
2703
|
+
label.style.background = color;
|
|
2704
|
+
markdownSelectionOverlayRoot.appendChild(label);
|
|
2705
|
+
}
|
|
2706
|
+
|
|
2707
|
+
if (markdownSelectionOverlayRoot.childNodes.length === 0) {
|
|
2708
|
+
clearMarkdownSelectionOverlay();
|
|
2709
|
+
}
|
|
2710
|
+
}
|
|
2711
|
+
|
|
2712
|
+
function scheduleMarkdownSelectionOverlayRender() {
|
|
2713
|
+
if (markdownSelectionOverlayRenderFrame) {
|
|
2714
|
+
cancelAnimationFrame(markdownSelectionOverlayRenderFrame);
|
|
2715
|
+
}
|
|
2716
|
+
|
|
2717
|
+
markdownSelectionOverlayRenderFrame = requestAnimationFrame(function() {
|
|
2718
|
+
markdownSelectionOverlayRenderFrame = null;
|
|
2719
|
+
renderMarkdownSelectionOverlay();
|
|
2720
|
+
});
|
|
2721
|
+
}
|
|
2722
|
+
|
|
2723
|
+
function extractMarkdownParts(content) {
|
|
2724
|
+
if (typeof content !== 'string') {
|
|
2725
|
+
return {
|
|
2726
|
+
frontmatter: '',
|
|
2727
|
+
body: ''
|
|
2728
|
+
};
|
|
2729
|
+
}
|
|
2730
|
+
|
|
2731
|
+
const frontmatterPattern = new RegExp(
|
|
2732
|
+
'^---[ \\\\t]*\\\\r?\\\\n[\\\\s\\\\S]*?\\\\r?\\\\n---[ \\\\t]*(?:\\\\r?\\\\n)?'
|
|
2733
|
+
);
|
|
2734
|
+
const match = content.match(frontmatterPattern);
|
|
2735
|
+
if (!match) {
|
|
2736
|
+
return {
|
|
2737
|
+
frontmatter: '',
|
|
2738
|
+
body: content
|
|
2739
|
+
};
|
|
2740
|
+
}
|
|
2741
|
+
|
|
2742
|
+
return {
|
|
2743
|
+
frontmatter: match[0],
|
|
2744
|
+
body: content.slice(match[0].length)
|
|
2745
|
+
};
|
|
2746
|
+
}
|
|
2747
|
+
|
|
2748
|
+
function composeMarkdownContent(body) {
|
|
2749
|
+
const safeBody = typeof body === 'string' ? body : '';
|
|
2750
|
+
if (!markdownFrontmatter) {
|
|
2751
|
+
return safeBody;
|
|
2752
|
+
}
|
|
2753
|
+
if (!safeBody) {
|
|
2754
|
+
return markdownFrontmatter;
|
|
2755
|
+
}
|
|
2756
|
+
if (markdownFrontmatter.endsWith('\\n')) {
|
|
2757
|
+
return markdownFrontmatter + safeBody;
|
|
2758
|
+
}
|
|
2759
|
+
return markdownFrontmatter + '\\n' + safeBody;
|
|
2760
|
+
}
|
|
2761
|
+
|
|
2762
|
+
function extractRawBlocksForEditor(body, mdxImportMap) {
|
|
2763
|
+
const source = typeof body === 'string' ? body : '';
|
|
2764
|
+
const rawBlocks = [];
|
|
2765
|
+
const mdxBlocks = [];
|
|
2766
|
+
const tokenPrefix = 'VF_RAW_BLOCK_' + Date.now().toString(36) + '_' + Math.random().toString(36).slice(2, 8);
|
|
2767
|
+
const trackMdxBlocks = isMdxPage();
|
|
2768
|
+
const importMap = mdxImportMap && typeof mdxImportMap === 'object' ? mdxImportMap : {};
|
|
2769
|
+
const createToken = function(index) {
|
|
2770
|
+
return '[[' + tokenPrefix + '_' + index + ']]';
|
|
2771
|
+
};
|
|
2772
|
+
const registerMdxBlock = function(rawBlock, tokenIndex, offset, inputText) {
|
|
2773
|
+
if (!trackMdxBlocks) {
|
|
2774
|
+
return;
|
|
2775
|
+
}
|
|
2776
|
+
|
|
2777
|
+
const trimmed = String(rawBlock || '').trimStart();
|
|
2778
|
+
const fence = String.fromCharCode(96, 96, 96);
|
|
2779
|
+
const startsWithTsxFence = trimmed.startsWith(fence + 'tsx') || trimmed.startsWith(fence + 'jsx');
|
|
2780
|
+
const startsWithUpperTag = /^<\\s*[A-Z]/.test(trimmed);
|
|
2781
|
+
const hasTsxProps = trimmed.indexOf('{') >= 0 && trimmed.indexOf('}') >= 0;
|
|
2782
|
+
|
|
2783
|
+
if (!startsWithTsxFence && !startsWithUpperTag && !hasTsxProps) {
|
|
2784
|
+
return;
|
|
2785
|
+
}
|
|
2786
|
+
|
|
2787
|
+
const label = startsWithTsxFence
|
|
2788
|
+
? 'TSX block'
|
|
2789
|
+
: 'JSX ' + getMdxComponentName(trimmed);
|
|
2790
|
+
const componentName = getMdxComponentName(trimmed);
|
|
2791
|
+
const componentNamePattern = /^[A-Z][\\w$]*(?:\\.[A-Z][\\w$]*)*$/;
|
|
2792
|
+
const normalizedComponentName = componentNamePattern.test(componentName) ? componentName : '';
|
|
2793
|
+
const componentParts = normalizedComponentName ? normalizedComponentName.split('.') : [];
|
|
2794
|
+
const namespaceName = componentParts.length > 0 ? componentParts[0] : '';
|
|
2795
|
+
const fallbackSymbol = componentParts.length > 0 ? componentParts[componentParts.length - 1] : '';
|
|
2796
|
+
const directEntry = normalizedComponentName ? importMap[normalizedComponentName] : null;
|
|
2797
|
+
const namespaceEntry = !directEntry && namespaceName ? importMap[namespaceName] : null;
|
|
2798
|
+
const importEntry = directEntry || namespaceEntry || null;
|
|
2799
|
+
const entryPath = importEntry && typeof importEntry.filePath === 'string'
|
|
2800
|
+
? importEntry.filePath
|
|
2801
|
+
: (
|
|
2802
|
+
typeof importEntry === 'string'
|
|
2803
|
+
? importEntry
|
|
2804
|
+
: ''
|
|
2805
|
+
);
|
|
2806
|
+
const entrySymbol = importEntry && typeof importEntry.symbolName === 'string'
|
|
2807
|
+
? importEntry.symbolName
|
|
2808
|
+
: '';
|
|
2809
|
+
const entryKind = importEntry && typeof importEntry.importKind === 'string'
|
|
2810
|
+
? importEntry.importKind
|
|
2811
|
+
: '';
|
|
2812
|
+
const componentPath = entryPath || '';
|
|
2813
|
+
let componentSymbol = fallbackSymbol;
|
|
2814
|
+
if (entrySymbol) {
|
|
2815
|
+
componentSymbol = entrySymbol;
|
|
2816
|
+
} else if (entryKind === 'namespace' && componentParts.length > 1) {
|
|
2817
|
+
componentSymbol = componentParts[componentParts.length - 1];
|
|
2818
|
+
}
|
|
2819
|
+
mdxBlocks.push({
|
|
2820
|
+
tokenIndex: tokenIndex,
|
|
2821
|
+
label: label,
|
|
2822
|
+
lineNumber: getLineNumberForOffset(inputText, offset),
|
|
2823
|
+
filePath: componentPath,
|
|
2824
|
+
symbolName: componentSymbol
|
|
2825
|
+
});
|
|
2826
|
+
};
|
|
2827
|
+
const replaceWithToken = function(match, leadingNewline, offset, inputText) {
|
|
2828
|
+
const safeLeading = typeof leadingNewline === 'string' ? leadingNewline : '';
|
|
2829
|
+
const tokenIndex = rawBlocks.length;
|
|
2830
|
+
const rawBlock = typeof match === 'string' ? match.trimStart() : '';
|
|
2831
|
+
rawBlocks.push(rawBlock);
|
|
2832
|
+
registerMdxBlock(rawBlock, tokenIndex, Math.max(0, (offset || 0) + safeLeading.length), inputText || source);
|
|
2833
|
+
return safeLeading + createToken(tokenIndex);
|
|
2834
|
+
};
|
|
2835
|
+
|
|
2836
|
+
const mermaidFencePattern = new RegExp(
|
|
2837
|
+
'(^|\\\\n)\\\\x60\\\\x60\\\\x60mermaid[^\\\\n]*\\\\n[\\\\s\\\\S]*?\\\\n\\\\x60\\\\x60\\\\x60(?=\\\\n|$)',
|
|
2838
|
+
'g'
|
|
2839
|
+
);
|
|
2840
|
+
const tsxFencePattern = new RegExp(
|
|
2841
|
+
'(^|\\\\n)\\\\x60\\\\x60\\\\x60(?:tsx|jsx)[^\\\\n]*\\\\n[\\\\s\\\\S]*?\\\\n\\\\x60\\\\x60\\\\x60(?=\\\\n|$)',
|
|
2842
|
+
'g'
|
|
2843
|
+
);
|
|
2844
|
+
const htmlBlockPattern = new RegExp(
|
|
2845
|
+
'(^|\\\\n)<[A-Za-z][\\\\w:-]*(?:\\\\s[^>\\\\n]*)?>[\\\\s\\\\S]*?<\\\\/[A-Za-z][\\\\w:-]*>(?=\\\\n|$)',
|
|
2846
|
+
'g'
|
|
2847
|
+
);
|
|
2848
|
+
const htmlSelfClosingPattern = new RegExp(
|
|
2849
|
+
'(^|\\\\n)<[A-Za-z][\\\\w:-]*(?:\\\\s[^>\\\\n]*)?\\\\/>(?=\\\\n|$)',
|
|
2850
|
+
'g'
|
|
2851
|
+
);
|
|
2852
|
+
|
|
2853
|
+
let editorBody = source.replace(
|
|
2854
|
+
mermaidFencePattern,
|
|
2855
|
+
replaceWithToken
|
|
2856
|
+
);
|
|
2857
|
+
|
|
2858
|
+
editorBody = editorBody.replace(
|
|
2859
|
+
tsxFencePattern,
|
|
2860
|
+
replaceWithToken
|
|
2861
|
+
);
|
|
2862
|
+
|
|
2863
|
+
editorBody = editorBody.replace(
|
|
2864
|
+
htmlBlockPattern,
|
|
2865
|
+
replaceWithToken
|
|
2866
|
+
);
|
|
2867
|
+
|
|
2868
|
+
editorBody = editorBody.replace(
|
|
2869
|
+
htmlSelfClosingPattern,
|
|
2870
|
+
replaceWithToken
|
|
2871
|
+
);
|
|
2872
|
+
|
|
2873
|
+
return {
|
|
2874
|
+
editorBody: editorBody,
|
|
2875
|
+
rawBlocks: rawBlocks,
|
|
2876
|
+
mdxBlocks: mdxBlocks,
|
|
2877
|
+
tokenPrefix: tokenPrefix
|
|
2878
|
+
};
|
|
2879
|
+
}
|
|
2880
|
+
|
|
2881
|
+
function restoreRawBlocksFromEditor(editorBody) {
|
|
2882
|
+
const source = typeof editorBody === 'string' ? editorBody : '';
|
|
2883
|
+
if (!source || markdownRawBlocks.length === 0) {
|
|
2884
|
+
return source;
|
|
2885
|
+
}
|
|
2886
|
+
const rawBlockTokenPattern = getMarkdownRawBlockTokenPattern();
|
|
2887
|
+
|
|
2888
|
+
return source.replace(rawBlockTokenPattern, function(match, indexText) {
|
|
2889
|
+
const index = Number(indexText);
|
|
2890
|
+
if (!Number.isInteger(index) || index < 0 || index >= markdownRawBlocks.length) {
|
|
2891
|
+
return match;
|
|
2892
|
+
}
|
|
2893
|
+
const rawBlock = markdownRawBlocks[index];
|
|
2894
|
+
return typeof rawBlock === 'string' ? rawBlock : match;
|
|
2895
|
+
});
|
|
2896
|
+
}
|
|
2897
|
+
|
|
2898
|
+
function handleMarkdownLocalChange(content) {
|
|
2899
|
+
if (typeof content !== 'string') {
|
|
2900
|
+
return;
|
|
2901
|
+
}
|
|
2902
|
+
|
|
2903
|
+
markdownCurrentEditorContent = content;
|
|
2904
|
+
const restoredBody = restoreRawBlocksFromEditor(content);
|
|
2905
|
+
const fullContent = composeMarkdownContent(restoredBody);
|
|
2906
|
+
if (fullContent === markdownCurrentContent) {
|
|
2907
|
+
return;
|
|
2908
|
+
}
|
|
2909
|
+
markdownCurrentContent = fullContent;
|
|
2910
|
+
markdownHasUnsavedChanges = true;
|
|
2911
|
+
scheduleMarkdownSync(fullContent);
|
|
2912
|
+
scheduleMarkdownSelectionOverlayRender();
|
|
2913
|
+
}
|
|
2914
|
+
|
|
2915
|
+
function saveMarkdownContent() {
|
|
2916
|
+
if (!markdownHasUnsavedChanges) {
|
|
2917
|
+
return;
|
|
2918
|
+
}
|
|
2919
|
+
setMarkdownPersistStatus('saving');
|
|
2920
|
+
if (markdownSyncTimer) {
|
|
2921
|
+
clearTimeout(markdownSyncTimer);
|
|
2922
|
+
markdownSyncTimer = null;
|
|
2923
|
+
}
|
|
2924
|
+
postToStudio({
|
|
2925
|
+
action: 'markdownContentChange',
|
|
2926
|
+
fileId: markdownFileId,
|
|
2927
|
+
filePath: PAGE_PATH,
|
|
2928
|
+
content: markdownCurrentContent,
|
|
2929
|
+
save: true
|
|
2930
|
+
});
|
|
2931
|
+
markdownHasUnsavedChanges = false;
|
|
2932
|
+
}
|
|
2933
|
+
|
|
2934
|
+
function setupMarkdownLexicalEditor() {
|
|
2935
|
+
if (!markdownEditorSurface || markdownLexicalApi || markdownLexicalSetupPromise) {
|
|
2936
|
+
return;
|
|
2937
|
+
}
|
|
2938
|
+
|
|
2939
|
+
markdownLexicalSetupPromise = Promise.all([
|
|
2940
|
+
import('https://esm.sh/lexical@0.21.0?target=es2022'),
|
|
2941
|
+
import('https://esm.sh/@lexical/rich-text@0.21.0?target=es2022'),
|
|
2942
|
+
import('https://esm.sh/@lexical/list@0.21.0?target=es2022'),
|
|
2943
|
+
import('https://esm.sh/@lexical/markdown@0.21.0?target=es2022'),
|
|
2944
|
+
import('https://esm.sh/@lexical/history@0.21.0?target=es2022')
|
|
2945
|
+
]).then(function(modules) {
|
|
2946
|
+
if (!markdownEditorSurface) {
|
|
2947
|
+
return;
|
|
2948
|
+
}
|
|
2949
|
+
|
|
2950
|
+
const lexicalModule = modules[0];
|
|
2951
|
+
const richTextModule = modules[1];
|
|
2952
|
+
const listModule = modules[2];
|
|
2953
|
+
const markdownModule = modules[3];
|
|
2954
|
+
const historyModule = modules[4];
|
|
2955
|
+
|
|
2956
|
+
const editor = lexicalModule.createEditor({
|
|
2957
|
+
namespace: 'veryfront-markdown-preview',
|
|
2958
|
+
nodes: [
|
|
2959
|
+
richTextModule.HeadingNode,
|
|
2960
|
+
richTextModule.QuoteNode,
|
|
2961
|
+
listModule.ListNode,
|
|
2962
|
+
listModule.ListItemNode
|
|
2963
|
+
],
|
|
2964
|
+
onError: function(error) {
|
|
2965
|
+
console.error('[StudioBridge] Markdown Lexical error', error);
|
|
2966
|
+
}
|
|
2967
|
+
});
|
|
2968
|
+
|
|
2969
|
+
const unregisterRichText = richTextModule.registerRichText(editor);
|
|
2970
|
+
const unregisterList = listModule.registerList(editor);
|
|
2971
|
+
const unregisterHistory = historyModule.registerHistory(
|
|
2972
|
+
editor,
|
|
2973
|
+
historyModule.createEmptyHistoryState(),
|
|
2974
|
+
1000
|
|
2975
|
+
);
|
|
2976
|
+
const unregisterUpdate = editor.registerUpdateListener(function(update) {
|
|
2977
|
+
if (markdownApplyingRemoteUpdate) {
|
|
2978
|
+
return;
|
|
2979
|
+
}
|
|
2980
|
+
|
|
2981
|
+
let nextContent = '';
|
|
2982
|
+
update.editorState.read(function() {
|
|
2983
|
+
nextContent = markdownModule.$convertToMarkdownString(markdownModule.TRANSFORMERS, true);
|
|
2984
|
+
});
|
|
2985
|
+
const restoredBody = restoreRawBlocksFromEditor(nextContent);
|
|
2986
|
+
const fullContent = composeMarkdownContent(restoredBody);
|
|
2987
|
+
|
|
2988
|
+
if (fullContent === markdownLexicalRenderedContent) {
|
|
2989
|
+
return;
|
|
2990
|
+
}
|
|
2991
|
+
markdownLexicalRenderedContent = fullContent;
|
|
2992
|
+
|
|
2993
|
+
handleMarkdownLocalChange(nextContent);
|
|
2994
|
+
scheduleMarkdownSlashMenuUpdate();
|
|
2995
|
+
scheduleMarkdownInlineToolbarUpdate();
|
|
2996
|
+
});
|
|
2997
|
+
|
|
2998
|
+
editor.setRootElement(markdownEditorSurface);
|
|
2999
|
+
editor.update(function() {
|
|
3000
|
+
const root = lexicalModule.$getRoot();
|
|
3001
|
+
if (root.getChildrenSize() === 0) {
|
|
3002
|
+
root.append(lexicalModule.$createParagraphNode());
|
|
3003
|
+
}
|
|
3004
|
+
});
|
|
3005
|
+
markdownLexicalApi = {
|
|
3006
|
+
editor: editor,
|
|
3007
|
+
lexicalModule: lexicalModule,
|
|
3008
|
+
markdownModule: markdownModule,
|
|
3009
|
+
unregisterRichText: unregisterRichText,
|
|
3010
|
+
unregisterList: unregisterList,
|
|
3011
|
+
unregisterHistory: unregisterHistory,
|
|
3012
|
+
unregisterUpdate: unregisterUpdate
|
|
3013
|
+
};
|
|
3014
|
+
|
|
3015
|
+
markdownEditorSurface.style.display = 'block';
|
|
3016
|
+
if (markdownEditorTextarea) {
|
|
3017
|
+
markdownEditorTextarea.style.display = 'none';
|
|
3018
|
+
}
|
|
3019
|
+
|
|
3020
|
+
applyMarkdownContent(markdownCurrentContent);
|
|
3021
|
+
hideMarkdownBlockDropIndicator();
|
|
3022
|
+
}).catch(function(error) {
|
|
3023
|
+
console.warn(
|
|
3024
|
+
'[StudioBridge] Failed to load Lexical markdown editor; falling back to textarea',
|
|
3025
|
+
error
|
|
3026
|
+
);
|
|
3027
|
+
if (markdownEditorSurface) {
|
|
3028
|
+
markdownEditorSurface.style.display = 'none';
|
|
3029
|
+
}
|
|
3030
|
+
if (markdownEditorTextarea) {
|
|
3031
|
+
markdownEditorTextarea.style.display = 'block';
|
|
3032
|
+
}
|
|
3033
|
+
hideMarkdownSlashMenu();
|
|
3034
|
+
hideMarkdownInlineToolbar();
|
|
3035
|
+
hideMarkdownBlockDragUi();
|
|
3036
|
+
clearMarkdownSelectionOverlay();
|
|
3037
|
+
});
|
|
3038
|
+
}
|
|
3039
|
+
|
|
3040
|
+
function focusMarkdownEditor() {
|
|
3041
|
+
if (markdownLexicalApi && markdownEditorSurface) {
|
|
3042
|
+
markdownEditorSurface.focus();
|
|
3043
|
+
return;
|
|
3044
|
+
}
|
|
3045
|
+
if (markdownEditorTextarea) {
|
|
3046
|
+
markdownEditorTextarea.focus();
|
|
3047
|
+
}
|
|
3048
|
+
}
|
|
3049
|
+
|
|
3050
|
+
function applyMarkdownHistoryCommand(command) {
|
|
3051
|
+
if (!markdownLexicalApi || !markdownLexicalApi.editor || !markdownLexicalApi.lexicalModule) {
|
|
3052
|
+
return;
|
|
3053
|
+
}
|
|
3054
|
+
if (!command) {
|
|
3055
|
+
return;
|
|
3056
|
+
}
|
|
3057
|
+
|
|
3058
|
+
markdownLexicalApi.editor.focus();
|
|
3059
|
+
markdownLexicalApi.editor.dispatchCommand(command, undefined);
|
|
3060
|
+
scheduleMarkdownSelectionSync();
|
|
3061
|
+
scheduleMarkdownSelectionOverlayRender();
|
|
3062
|
+
scheduleMarkdownSlashMenuUpdate();
|
|
3063
|
+
scheduleMarkdownInlineToolbarUpdate();
|
|
3064
|
+
}
|
|
3065
|
+
|
|
3066
|
+
function applyMarkdownContent(content) {
|
|
3067
|
+
if (typeof content !== 'string') {
|
|
3068
|
+
return;
|
|
3069
|
+
}
|
|
3070
|
+
|
|
3071
|
+
if (markdownLexicalApi && markdownLexicalRenderedContent === content) {
|
|
3072
|
+
markdownCurrentContent = content;
|
|
3073
|
+
scheduleMarkdownSelectionOverlayRender();
|
|
3074
|
+
scheduleMarkdownSlashMenuUpdate();
|
|
3075
|
+
scheduleMarkdownInlineToolbarUpdate();
|
|
3076
|
+
hideMarkdownBlockDropIndicator();
|
|
3077
|
+
return;
|
|
3078
|
+
}
|
|
3079
|
+
|
|
3080
|
+
const mdxImportMap = parseMdxImportMap(content);
|
|
3081
|
+
const parts = extractMarkdownParts(content);
|
|
3082
|
+
const extracted = extractRawBlocksForEditor(parts.body, mdxImportMap);
|
|
3083
|
+
const editorContent = extracted.editorBody;
|
|
3084
|
+
const mdxBlocks = Array.isArray(extracted.mdxBlocks) ? extracted.mdxBlocks : [];
|
|
3085
|
+
markdownFrontmatter = parts.frontmatter;
|
|
3086
|
+
markdownRawBlocks = extracted.rawBlocks;
|
|
3087
|
+
markdownRawBlockTokenPrefix = extracted.tokenPrefix;
|
|
3088
|
+
markdownLatestMdxImportMap = mdxImportMap;
|
|
3089
|
+
setMarkdownMdxBlocks(mdxBlocks);
|
|
3090
|
+
|
|
3091
|
+
markdownCurrentContent = content;
|
|
3092
|
+
markdownCurrentEditorContent = editorContent;
|
|
3093
|
+
|
|
3094
|
+
if (markdownLexicalApi) {
|
|
3095
|
+
markdownApplyingRemoteUpdate = true;
|
|
3096
|
+
try {
|
|
3097
|
+
markdownLexicalRenderedContent = content;
|
|
3098
|
+
markdownLexicalApi.editor.update(function() {
|
|
3099
|
+
const lexicalModule = markdownLexicalApi.lexicalModule;
|
|
3100
|
+
const markdownModule = markdownLexicalApi.markdownModule;
|
|
3101
|
+
const root = lexicalModule.$getRoot();
|
|
3102
|
+
root.clear();
|
|
3103
|
+
markdownModule.$convertFromMarkdownString(editorContent, markdownModule.TRANSFORMERS, true);
|
|
3104
|
+
if (root.getChildrenSize() === 0) {
|
|
3105
|
+
root.append(lexicalModule.$createParagraphNode());
|
|
3106
|
+
}
|
|
3107
|
+
}, { discrete: true });
|
|
3108
|
+
} finally {
|
|
3109
|
+
markdownApplyingRemoteUpdate = false;
|
|
3110
|
+
}
|
|
3111
|
+
scheduleMarkdownSelectionOverlayRender();
|
|
3112
|
+
scheduleMarkdownSlashMenuUpdate();
|
|
3113
|
+
scheduleMarkdownInlineToolbarUpdate();
|
|
3114
|
+
hideMarkdownBlockDropIndicator();
|
|
3115
|
+
return;
|
|
3116
|
+
}
|
|
3117
|
+
|
|
3118
|
+
if (!markdownEditorTextarea || markdownEditorTextarea.value === editorContent) {
|
|
3119
|
+
clearMarkdownSelectionOverlay();
|
|
3120
|
+
hideMarkdownSlashMenu();
|
|
3121
|
+
hideMarkdownInlineToolbar();
|
|
3122
|
+
hideMarkdownBlockDragUi();
|
|
3123
|
+
return;
|
|
3124
|
+
}
|
|
3125
|
+
|
|
3126
|
+
const selectionStart = markdownEditorTextarea.selectionStart;
|
|
3127
|
+
const selectionEnd = markdownEditorTextarea.selectionEnd;
|
|
3128
|
+
markdownEditorTextarea.value = editorContent;
|
|
3129
|
+
|
|
3130
|
+
if (typeof selectionStart === 'number' && typeof selectionEnd === 'number') {
|
|
3131
|
+
const max = markdownEditorTextarea.value.length;
|
|
3132
|
+
markdownEditorTextarea.setSelectionRange(Math.min(selectionStart, max), Math.min(selectionEnd, max));
|
|
3133
|
+
}
|
|
3134
|
+
clearMarkdownSelectionOverlay();
|
|
3135
|
+
hideMarkdownSlashMenu();
|
|
3136
|
+
hideMarkdownInlineToolbar();
|
|
3137
|
+
hideMarkdownBlockDragUi();
|
|
3138
|
+
}
|
|
3139
|
+
|
|
3140
|
+
function setMarkdownPersistStatus(status) {
|
|
3141
|
+
if (!markdownPersistStatus) {
|
|
3142
|
+
return;
|
|
3143
|
+
}
|
|
3144
|
+
|
|
3145
|
+
const nextStatus = status === 'saving' || status === 'saved' || status === 'error'
|
|
3146
|
+
? status
|
|
3147
|
+
: 'saved';
|
|
3148
|
+
|
|
3149
|
+
markdownPersistStatus.setAttribute('data-state', nextStatus);
|
|
3150
|
+
if (nextStatus === 'saving') {
|
|
3151
|
+
markdownPersistStatus.textContent = 'Saving...';
|
|
3152
|
+
return;
|
|
3153
|
+
}
|
|
3154
|
+
if (nextStatus === 'error') {
|
|
3155
|
+
markdownPersistStatus.textContent = 'Save failed';
|
|
3156
|
+
return;
|
|
3157
|
+
}
|
|
3158
|
+
markdownPersistStatus.textContent = 'Saved';
|
|
3159
|
+
}
|
|
3160
|
+
|
|
3161
|
+
function setMarkdownPresence(users) {
|
|
3162
|
+
markdownLatestPresenceUsers = Array.isArray(users) ? users : [];
|
|
3163
|
+
if (!markdownPresenceRoot) {
|
|
3164
|
+
return;
|
|
3165
|
+
}
|
|
3166
|
+
|
|
3167
|
+
markdownPresenceRoot.textContent = '';
|
|
3168
|
+
if (!Array.isArray(users) || users.length === 0) {
|
|
3169
|
+
markdownPresenceRoot.style.display = 'none';
|
|
3170
|
+
return;
|
|
3171
|
+
}
|
|
3172
|
+
|
|
3173
|
+
const visibleUsers = users.filter(function(user) {
|
|
3174
|
+
return user && typeof user.name === 'string';
|
|
3175
|
+
}).slice(0, 4);
|
|
3176
|
+
|
|
3177
|
+
if (visibleUsers.length === 0) {
|
|
3178
|
+
markdownPresenceRoot.style.display = 'none';
|
|
3179
|
+
return;
|
|
3180
|
+
}
|
|
3181
|
+
|
|
3182
|
+
markdownPresenceRoot.style.display = 'inline-flex';
|
|
3183
|
+
|
|
3184
|
+
for (const user of visibleUsers) {
|
|
3185
|
+
const pill = document.createElement('div');
|
|
3186
|
+
pill.className = 'vf-markdown-editor__presence-pill';
|
|
3187
|
+
pill.setAttribute(DATA_VF_IGNORE, 'true');
|
|
3188
|
+
pill.setAttribute('data-current', user.isCurrentUser ? 'true' : 'false');
|
|
3189
|
+
pill.setAttribute('data-agent', user.isAgent ? 'true' : 'false');
|
|
3190
|
+
pill.textContent = user.isCurrentUser ? 'You' : user.name;
|
|
3191
|
+
|
|
3192
|
+
const color = typeof user.color === 'string' && user.color ? user.color : '#6b7280';
|
|
3193
|
+
pill.style.borderLeftColor = color;
|
|
3194
|
+
|
|
3195
|
+
markdownPresenceRoot.appendChild(pill);
|
|
3196
|
+
}
|
|
3197
|
+
|
|
3198
|
+
if (users.length > visibleUsers.length) {
|
|
3199
|
+
const extra = document.createElement('div');
|
|
3200
|
+
extra.className = 'vf-markdown-editor__presence-pill';
|
|
3201
|
+
extra.setAttribute(DATA_VF_IGNORE, 'true');
|
|
3202
|
+
extra.textContent = '+' + String(users.length - visibleUsers.length);
|
|
3203
|
+
markdownPresenceRoot.appendChild(extra);
|
|
3204
|
+
}
|
|
3205
|
+
}
|
|
3206
|
+
|
|
3207
|
+
function setMarkdownSelections(selections) {
|
|
3208
|
+
markdownLatestSelections = Array.isArray(selections) ? selections : [];
|
|
3209
|
+
if (!markdownSelectionsRoot) {
|
|
3210
|
+
return;
|
|
3211
|
+
}
|
|
3212
|
+
|
|
3213
|
+
markdownSelectionsRoot.textContent = '';
|
|
3214
|
+
markdownOverlaySelections = [];
|
|
3215
|
+
if (!Array.isArray(selections) || selections.length === 0) {
|
|
3216
|
+
markdownSelectionsRoot.style.display = 'none';
|
|
3217
|
+
clearMarkdownSelectionOverlay();
|
|
3218
|
+
return;
|
|
3219
|
+
}
|
|
3220
|
+
|
|
3221
|
+
const visibleSelections = selections.filter(function(selection) {
|
|
3222
|
+
return (
|
|
3223
|
+
selection &&
|
|
3224
|
+
typeof selection.name === 'string' &&
|
|
3225
|
+
typeof selection.start === 'number' &&
|
|
3226
|
+
typeof selection.end === 'number'
|
|
3227
|
+
);
|
|
3228
|
+
}).slice(0, 4);
|
|
3229
|
+
|
|
3230
|
+
if (visibleSelections.length === 0) {
|
|
3231
|
+
markdownSelectionsRoot.style.display = 'none';
|
|
3232
|
+
clearMarkdownSelectionOverlay();
|
|
3233
|
+
return;
|
|
3234
|
+
}
|
|
3235
|
+
|
|
3236
|
+
markdownSelectionsRoot.style.display = 'inline-flex';
|
|
3237
|
+
|
|
3238
|
+
for (const selection of visibleSelections) {
|
|
3239
|
+
const pill = document.createElement('div');
|
|
3240
|
+
pill.className = 'vf-markdown-editor__selection-pill';
|
|
3241
|
+
pill.setAttribute(DATA_VF_IGNORE, 'true');
|
|
3242
|
+
const color = typeof selection.color === 'string' && selection.color ? selection.color : '#6b7280';
|
|
3243
|
+
const displayName = selection.isCurrentUser ? 'You' : selection.name;
|
|
3244
|
+
pill.style.borderLeftColor = color;
|
|
3245
|
+
|
|
3246
|
+
const start = Math.max(0, Math.trunc(selection.start));
|
|
3247
|
+
const end = Math.max(0, Math.trunc(selection.end));
|
|
3248
|
+
const rangeLabel = start === end ? '@' + String(start) : String(start) + '-' + String(end);
|
|
3249
|
+
pill.textContent = displayName + ' ' + rangeLabel;
|
|
3250
|
+
markdownSelectionsRoot.appendChild(pill);
|
|
3251
|
+
|
|
3252
|
+
const editorRange = sourceSelectionToEditorRange(start, end);
|
|
3253
|
+
if (!editorRange) {
|
|
3254
|
+
continue;
|
|
3255
|
+
}
|
|
3256
|
+
|
|
3257
|
+
markdownOverlaySelections.push({
|
|
3258
|
+
name: displayName,
|
|
3259
|
+
color: color,
|
|
3260
|
+
start: editorRange.start,
|
|
3261
|
+
end: editorRange.end
|
|
3262
|
+
});
|
|
3263
|
+
}
|
|
3264
|
+
|
|
3265
|
+
if (selections.length > visibleSelections.length) {
|
|
3266
|
+
const extra = document.createElement('div');
|
|
3267
|
+
extra.className = 'vf-markdown-editor__selection-pill';
|
|
3268
|
+
extra.setAttribute(DATA_VF_IGNORE, 'true');
|
|
3269
|
+
extra.textContent = '+' + String(selections.length - visibleSelections.length);
|
|
3270
|
+
markdownSelectionsRoot.appendChild(extra);
|
|
3271
|
+
}
|
|
3272
|
+
|
|
3273
|
+
scheduleMarkdownSelectionOverlayRender();
|
|
3274
|
+
}
|
|
3275
|
+
|
|
3276
|
+
function ensureMarkdownEditor() {
|
|
3277
|
+
if (markdownEditorRoot) {
|
|
3278
|
+
return markdownEditorRoot;
|
|
3279
|
+
}
|
|
3280
|
+
|
|
3281
|
+
const editorRoot = document.createElement('div');
|
|
3282
|
+
editorRoot.className = 'vf-markdown-editor';
|
|
3283
|
+
editorRoot.setAttribute(DATA_VF_IGNORE, 'true');
|
|
3284
|
+
|
|
3285
|
+
const toolbar = document.createElement('div');
|
|
3286
|
+
toolbar.className = 'vf-markdown-editor__toolbar';
|
|
3287
|
+
toolbar.setAttribute(DATA_VF_IGNORE, 'true');
|
|
3288
|
+
|
|
3289
|
+
const title = document.createElement('div');
|
|
3290
|
+
title.className = 'vf-markdown-editor__title';
|
|
3291
|
+
title.setAttribute(DATA_VF_IGNORE, 'true');
|
|
3292
|
+
|
|
3293
|
+
const titleMain = document.createElement('div');
|
|
3294
|
+
titleMain.className = 'vf-markdown-editor__title-main';
|
|
3295
|
+
titleMain.setAttribute(DATA_VF_IGNORE, 'true');
|
|
3296
|
+
titleMain.textContent = 'Markdown editor';
|
|
3297
|
+
|
|
3298
|
+
const titleHints = document.createElement('div');
|
|
3299
|
+
titleHints.className = 'vf-markdown-editor__title-hints';
|
|
3300
|
+
titleHints.setAttribute(DATA_VF_IGNORE, 'true');
|
|
3301
|
+
titleHints.textContent = '/ commands | Shift+Alt+Up/Down move block | Undo/Redo';
|
|
3302
|
+
|
|
3303
|
+
title.appendChild(titleMain);
|
|
3304
|
+
title.appendChild(titleHints);
|
|
3305
|
+
|
|
3306
|
+
const actions = document.createElement('div');
|
|
3307
|
+
actions.className = 'vf-markdown-editor__actions';
|
|
3308
|
+
actions.setAttribute(DATA_VF_IGNORE, 'true');
|
|
3309
|
+
|
|
3310
|
+
const status = document.createElement('div');
|
|
3311
|
+
status.className = 'vf-markdown-editor__status';
|
|
3312
|
+
status.setAttribute(DATA_VF_IGNORE, 'true');
|
|
3313
|
+
status.textContent = '';
|
|
3314
|
+
status.setAttribute('data-state', '');
|
|
3315
|
+
|
|
3316
|
+
const presence = document.createElement('div');
|
|
3317
|
+
presence.className = 'vf-markdown-editor__presence';
|
|
3318
|
+
presence.setAttribute(DATA_VF_IGNORE, 'true');
|
|
3319
|
+
|
|
3320
|
+
const selections = document.createElement('div');
|
|
3321
|
+
selections.className = 'vf-markdown-editor__selections';
|
|
3322
|
+
selections.setAttribute(DATA_VF_IGNORE, 'true');
|
|
3323
|
+
|
|
3324
|
+
const undoButton = document.createElement('button');
|
|
3325
|
+
undoButton.type = 'button';
|
|
3326
|
+
undoButton.className = 'vf-markdown-editor__history';
|
|
3327
|
+
undoButton.setAttribute(DATA_VF_IGNORE, 'true');
|
|
3328
|
+
undoButton.setAttribute('title', 'Undo');
|
|
3329
|
+
undoButton.textContent = 'Undo';
|
|
3330
|
+
undoButton.addEventListener('click', function() {
|
|
3331
|
+
if (!markdownLexicalApi || !markdownLexicalApi.lexicalModule) {
|
|
3332
|
+
return;
|
|
3333
|
+
}
|
|
3334
|
+
applyMarkdownHistoryCommand(markdownLexicalApi.lexicalModule.UNDO_COMMAND);
|
|
3335
|
+
});
|
|
3336
|
+
|
|
3337
|
+
const redoButton = document.createElement('button');
|
|
3338
|
+
redoButton.type = 'button';
|
|
3339
|
+
redoButton.className = 'vf-markdown-editor__history';
|
|
3340
|
+
redoButton.setAttribute(DATA_VF_IGNORE, 'true');
|
|
3341
|
+
redoButton.setAttribute('title', 'Redo');
|
|
3342
|
+
redoButton.textContent = 'Redo';
|
|
3343
|
+
redoButton.addEventListener('click', function() {
|
|
3344
|
+
if (!markdownLexicalApi || !markdownLexicalApi.lexicalModule) {
|
|
3345
|
+
return;
|
|
3346
|
+
}
|
|
3347
|
+
applyMarkdownHistoryCommand(markdownLexicalApi.lexicalModule.REDO_COMMAND);
|
|
3348
|
+
});
|
|
3349
|
+
|
|
3350
|
+
const openStudioButton = document.createElement('button');
|
|
3351
|
+
openStudioButton.type = 'button';
|
|
3352
|
+
openStudioButton.className = 'vf-markdown-editor__history';
|
|
3353
|
+
openStudioButton.setAttribute(DATA_VF_IGNORE, 'true');
|
|
3354
|
+
openStudioButton.setAttribute('title', 'Open file in Studio');
|
|
3355
|
+
openStudioButton.textContent = 'Open';
|
|
3356
|
+
openStudioButton.addEventListener('click', function() {
|
|
3357
|
+
openMarkdownSourceInStudio(1);
|
|
3358
|
+
});
|
|
3359
|
+
|
|
3360
|
+
const exitButton = document.createElement('button');
|
|
3361
|
+
exitButton.type = 'button';
|
|
3362
|
+
exitButton.className = 'vf-markdown-editor__exit';
|
|
3363
|
+
exitButton.setAttribute(DATA_VF_IGNORE, 'true');
|
|
3364
|
+
exitButton.textContent = 'Done';
|
|
3365
|
+
exitButton.addEventListener('click', function() {
|
|
3366
|
+
setMarkdownEditMode(false);
|
|
3367
|
+
});
|
|
3368
|
+
|
|
3369
|
+
actions.appendChild(status);
|
|
3370
|
+
actions.appendChild(presence);
|
|
3371
|
+
actions.appendChild(selections);
|
|
3372
|
+
actions.appendChild(undoButton);
|
|
3373
|
+
actions.appendChild(redoButton);
|
|
3374
|
+
actions.appendChild(openStudioButton);
|
|
3375
|
+
actions.appendChild(exitButton);
|
|
3376
|
+
|
|
3377
|
+
toolbar.appendChild(title);
|
|
3378
|
+
toolbar.appendChild(actions);
|
|
3379
|
+
|
|
3380
|
+
const mdxBlocks = document.createElement('div');
|
|
3381
|
+
mdxBlocks.className = 'vf-markdown-editor__mdx-blocks';
|
|
3382
|
+
mdxBlocks.setAttribute(DATA_VF_IGNORE, 'true');
|
|
3383
|
+
|
|
3384
|
+
const surface = document.createElement('div');
|
|
3385
|
+
surface.className = 'vf-markdown-editor__surface markdown-body';
|
|
3386
|
+
surface.setAttribute(DATA_VF_IGNORE, 'true');
|
|
3387
|
+
surface.setAttribute('contenteditable', 'true');
|
|
3388
|
+
surface.setAttribute('aria-label', 'Markdown editor');
|
|
3389
|
+
surface.addEventListener('keyup', scheduleMarkdownSelectionSync);
|
|
3390
|
+
surface.addEventListener('mouseup', scheduleMarkdownSelectionSync);
|
|
3391
|
+
surface.addEventListener('input', function() {
|
|
3392
|
+
scheduleMarkdownSelectionSync();
|
|
3393
|
+
scheduleMarkdownSlashMenuUpdate();
|
|
3394
|
+
});
|
|
3395
|
+
surface.addEventListener('keyup', scheduleMarkdownSlashMenuUpdate);
|
|
3396
|
+
surface.addEventListener('mouseup', scheduleMarkdownSlashMenuUpdate);
|
|
3397
|
+
surface.addEventListener('keydown', handleMarkdownSlashMenuKeydown);
|
|
3398
|
+
surface.addEventListener('keydown', function(event) {
|
|
3399
|
+
if (!event.shiftKey || !event.altKey) {
|
|
3400
|
+
return;
|
|
3401
|
+
}
|
|
3402
|
+
if (event.key === 'ArrowUp') {
|
|
3403
|
+
const moved = moveMarkdownCurrentBlockByDelta(-1);
|
|
3404
|
+
if (moved) {
|
|
3405
|
+
event.preventDefault();
|
|
3406
|
+
}
|
|
3407
|
+
return;
|
|
3408
|
+
}
|
|
3409
|
+
if (event.key === 'ArrowDown') {
|
|
3410
|
+
const moved = moveMarkdownCurrentBlockByDelta(1);
|
|
3411
|
+
if (moved) {
|
|
3412
|
+
event.preventDefault();
|
|
3413
|
+
}
|
|
3414
|
+
}
|
|
3415
|
+
});
|
|
3416
|
+
surface.addEventListener('keydown', function(event) {
|
|
3417
|
+
if ((event.metaKey || event.ctrlKey) && event.key === 's') {
|
|
3418
|
+
event.preventDefault();
|
|
3419
|
+
saveMarkdownContent();
|
|
3420
|
+
}
|
|
3421
|
+
});
|
|
3422
|
+
surface.addEventListener('scroll', scheduleMarkdownSelectionOverlayRender);
|
|
3423
|
+
surface.addEventListener('scroll', scheduleMarkdownSlashMenuUpdate);
|
|
3424
|
+
surface.addEventListener('keyup', scheduleMarkdownInlineToolbarUpdate);
|
|
3425
|
+
surface.addEventListener('mouseup', scheduleMarkdownInlineToolbarUpdate);
|
|
3426
|
+
surface.addEventListener('input', scheduleMarkdownInlineToolbarUpdate);
|
|
3427
|
+
surface.addEventListener('scroll', scheduleMarkdownInlineToolbarUpdate);
|
|
3428
|
+
surface.addEventListener('scroll', refreshMarkdownBlockDragHandlePosition);
|
|
3429
|
+
|
|
3430
|
+
const surfaceWrap = document.createElement('div');
|
|
3431
|
+
surfaceWrap.className = 'vf-markdown-editor__surface-wrap';
|
|
3432
|
+
surfaceWrap.setAttribute(DATA_VF_IGNORE, 'true');
|
|
3433
|
+
|
|
3434
|
+
const selectionOverlay = document.createElement('div');
|
|
3435
|
+
selectionOverlay.className = 'vf-markdown-editor__selection-overlay';
|
|
3436
|
+
selectionOverlay.setAttribute(DATA_VF_IGNORE, 'true');
|
|
3437
|
+
|
|
3438
|
+
const slashMenu = document.createElement('div');
|
|
3439
|
+
slashMenu.className = 'vf-markdown-editor__slash-menu';
|
|
3440
|
+
slashMenu.setAttribute(DATA_VF_IGNORE, 'true');
|
|
3441
|
+
|
|
3442
|
+
const inlineToolbar = document.createElement('div');
|
|
3443
|
+
inlineToolbar.className = 'vf-markdown-editor__inline-toolbar';
|
|
3444
|
+
inlineToolbar.setAttribute(DATA_VF_IGNORE, 'true');
|
|
3445
|
+
|
|
3446
|
+
const boldButton = document.createElement('button');
|
|
3447
|
+
boldButton.type = 'button';
|
|
3448
|
+
boldButton.className = 'vf-markdown-editor__inline-button';
|
|
3449
|
+
boldButton.setAttribute(DATA_VF_IGNORE, 'true');
|
|
3450
|
+
boldButton.textContent = 'B';
|
|
3451
|
+
boldButton.addEventListener('mousedown', function(event) {
|
|
3452
|
+
event.preventDefault();
|
|
3453
|
+
});
|
|
3454
|
+
boldButton.addEventListener('click', function(event) {
|
|
3455
|
+
event.preventDefault();
|
|
3456
|
+
toggleMarkdownInlineFormat('bold');
|
|
3457
|
+
});
|
|
3458
|
+
|
|
3459
|
+
const italicButton = document.createElement('button');
|
|
3460
|
+
italicButton.type = 'button';
|
|
3461
|
+
italicButton.className = 'vf-markdown-editor__inline-button';
|
|
3462
|
+
italicButton.setAttribute(DATA_VF_IGNORE, 'true');
|
|
3463
|
+
italicButton.textContent = 'I';
|
|
3464
|
+
italicButton.addEventListener('mousedown', function(event) {
|
|
3465
|
+
event.preventDefault();
|
|
3466
|
+
});
|
|
3467
|
+
italicButton.addEventListener('click', function(event) {
|
|
3468
|
+
event.preventDefault();
|
|
3469
|
+
toggleMarkdownInlineFormat('italic');
|
|
3470
|
+
});
|
|
3471
|
+
|
|
3472
|
+
const codeButton = document.createElement('button');
|
|
3473
|
+
codeButton.type = 'button';
|
|
3474
|
+
codeButton.className = 'vf-markdown-editor__inline-button';
|
|
3475
|
+
codeButton.setAttribute(DATA_VF_IGNORE, 'true');
|
|
3476
|
+
codeButton.textContent = '</>';
|
|
3477
|
+
codeButton.addEventListener('mousedown', function(event) {
|
|
3478
|
+
event.preventDefault();
|
|
3479
|
+
});
|
|
3480
|
+
codeButton.addEventListener('click', function(event) {
|
|
3481
|
+
event.preventDefault();
|
|
3482
|
+
toggleMarkdownInlineFormat('code');
|
|
3483
|
+
});
|
|
3484
|
+
|
|
3485
|
+
inlineToolbar.appendChild(boldButton);
|
|
3486
|
+
inlineToolbar.appendChild(italicButton);
|
|
3487
|
+
inlineToolbar.appendChild(codeButton);
|
|
3488
|
+
|
|
3489
|
+
const blockDragHandle = document.createElement('button');
|
|
3490
|
+
blockDragHandle.type = 'button';
|
|
3491
|
+
blockDragHandle.className = 'vf-markdown-editor__block-handle';
|
|
3492
|
+
blockDragHandle.setAttribute(DATA_VF_IGNORE, 'true');
|
|
3493
|
+
blockDragHandle.textContent = '::';
|
|
3494
|
+
blockDragHandle.draggable = true;
|
|
3495
|
+
blockDragHandle.setAttribute('data-dragging', 'false');
|
|
3496
|
+
blockDragHandle.addEventListener('dragstart', function(event) {
|
|
3497
|
+
const indexText = blockDragHandle.getAttribute('data-block-index');
|
|
3498
|
+
const index = Number(indexText);
|
|
3499
|
+
if (!Number.isInteger(index)) {
|
|
3500
|
+
event.preventDefault();
|
|
3501
|
+
return;
|
|
3502
|
+
}
|
|
3503
|
+
markdownBlockDragSourceIndex = index;
|
|
3504
|
+
markdownBlockDragActive = true;
|
|
3505
|
+
blockDragHandle.setAttribute('data-dragging', 'true');
|
|
3506
|
+
if (event.dataTransfer) {
|
|
3507
|
+
event.dataTransfer.effectAllowed = 'move';
|
|
3508
|
+
event.dataTransfer.setData('text/plain', String(index));
|
|
3509
|
+
|
|
3510
|
+
const blocks = getMarkdownTopLevelBlocks();
|
|
3511
|
+
const block = blocks[index];
|
|
3512
|
+
removeMarkdownDragGhost();
|
|
3513
|
+
if (block) {
|
|
3514
|
+
const ghost = createMarkdownDragGhost(block);
|
|
3515
|
+
document.body.appendChild(ghost);
|
|
3516
|
+
markdownBlockDragGhost = ghost;
|
|
3517
|
+
event.dataTransfer.setDragImage(ghost, 14, 14);
|
|
3518
|
+
}
|
|
3519
|
+
}
|
|
3520
|
+
showMarkdownBlockDropIndicator(index);
|
|
3521
|
+
});
|
|
3522
|
+
blockDragHandle.addEventListener('mouseenter', function() {
|
|
3523
|
+
if (markdownBlockHandleHoverIndex >= 0) {
|
|
3524
|
+
blockDragHandle.style.display = 'block';
|
|
3525
|
+
}
|
|
3526
|
+
});
|
|
3527
|
+
blockDragHandle.addEventListener('mouseleave', function(event) {
|
|
3528
|
+
if (markdownBlockDragActive) {
|
|
3529
|
+
return;
|
|
3530
|
+
}
|
|
3531
|
+
const next = event.relatedTarget;
|
|
3532
|
+
if (next && markdownEditorSurface && markdownEditorSurface.contains(next)) {
|
|
3533
|
+
return;
|
|
3534
|
+
}
|
|
3535
|
+
hideMarkdownBlockDragHandle();
|
|
3536
|
+
});
|
|
3537
|
+
blockDragHandle.addEventListener('dragend', function() {
|
|
3538
|
+
hideMarkdownBlockDragUi();
|
|
3539
|
+
});
|
|
3540
|
+
|
|
3541
|
+
const blockDropIndicator = document.createElement('div');
|
|
3542
|
+
blockDropIndicator.className = 'vf-markdown-editor__block-drop-indicator';
|
|
3543
|
+
blockDropIndicator.setAttribute(DATA_VF_IGNORE, 'true');
|
|
3544
|
+
|
|
3545
|
+
const blockDropLabel = document.createElement('div');
|
|
3546
|
+
blockDropLabel.className = 'vf-markdown-editor__block-drop-label';
|
|
3547
|
+
blockDropLabel.setAttribute(DATA_VF_IGNORE, 'true');
|
|
3548
|
+
|
|
3549
|
+
surfaceWrap.appendChild(surface);
|
|
3550
|
+
surfaceWrap.appendChild(selectionOverlay);
|
|
3551
|
+
|
|
3552
|
+
const textarea = document.createElement('textarea');
|
|
3553
|
+
textarea.className = 'vf-markdown-editor__textarea';
|
|
3554
|
+
textarea.setAttribute(DATA_VF_IGNORE, 'true');
|
|
3555
|
+
textarea.setAttribute('aria-label', 'Markdown editor');
|
|
3556
|
+
textarea.spellcheck = false;
|
|
3557
|
+
textarea.addEventListener('input', function() {
|
|
3558
|
+
handleMarkdownLocalChange(textarea.value);
|
|
3559
|
+
scheduleMarkdownSelectionSync();
|
|
3560
|
+
hideMarkdownSlashMenu();
|
|
3561
|
+
});
|
|
3562
|
+
textarea.addEventListener('select', scheduleMarkdownSelectionSync);
|
|
3563
|
+
textarea.addEventListener('keyup', scheduleMarkdownSelectionSync);
|
|
3564
|
+
textarea.addEventListener('click', scheduleMarkdownSelectionSync);
|
|
3565
|
+
textarea.addEventListener('input', clearMarkdownSelectionOverlay);
|
|
3566
|
+
textarea.addEventListener('keydown', function() {
|
|
3567
|
+
hideMarkdownSlashMenu();
|
|
3568
|
+
hideMarkdownInlineToolbar();
|
|
3569
|
+
hideMarkdownBlockDragUi();
|
|
3570
|
+
});
|
|
3571
|
+
|
|
3572
|
+
surface.addEventListener('mousemove', function(event) {
|
|
3573
|
+
if (markdownBlockDragActive) {
|
|
3574
|
+
return;
|
|
3575
|
+
}
|
|
3576
|
+
|
|
3577
|
+
const index = getMarkdownBlockHoverIndexFromPointer(event.target, event.clientX, event.clientY);
|
|
3578
|
+
if (index < 0) {
|
|
3579
|
+
hideMarkdownBlockDragHandle();
|
|
3580
|
+
return;
|
|
3581
|
+
}
|
|
3582
|
+
const blocks = getMarkdownTopLevelBlocks();
|
|
3583
|
+
const block = blocks[index];
|
|
3584
|
+
if (!block) {
|
|
3585
|
+
hideMarkdownBlockDragHandle();
|
|
3586
|
+
return;
|
|
3587
|
+
}
|
|
3588
|
+
positionMarkdownBlockDragHandle(block, index);
|
|
3589
|
+
});
|
|
3590
|
+
|
|
3591
|
+
surface.addEventListener('mouseleave', function(event) {
|
|
3592
|
+
if (!markdownBlockDragActive) {
|
|
3593
|
+
const next = event.relatedTarget;
|
|
3594
|
+
if (next && markdownBlockDragHandle && (next === markdownBlockDragHandle || markdownBlockDragHandle.contains(next))) {
|
|
3595
|
+
return;
|
|
3596
|
+
}
|
|
3597
|
+
hideMarkdownBlockDragHandle();
|
|
3598
|
+
}
|
|
3599
|
+
});
|
|
3600
|
+
|
|
3601
|
+
surface.addEventListener('dragover', function(event) {
|
|
3602
|
+
if (!markdownBlockDragActive) {
|
|
3603
|
+
return;
|
|
3604
|
+
}
|
|
3605
|
+
event.preventDefault();
|
|
3606
|
+
if (event.dataTransfer) {
|
|
3607
|
+
event.dataTransfer.dropEffect = 'move';
|
|
3608
|
+
}
|
|
3609
|
+
autoScrollMarkdownSurfaceDuringDrag(event.clientY);
|
|
3610
|
+
const slotIndex = getMarkdownDropSlotIndexFromPointer(event.clientY);
|
|
3611
|
+
if (slotIndex >= 0) {
|
|
3612
|
+
showMarkdownBlockDropIndicator(slotIndex);
|
|
3613
|
+
}
|
|
3614
|
+
});
|
|
3615
|
+
|
|
3616
|
+
surface.addEventListener('drop', function(event) {
|
|
3617
|
+
if (!markdownBlockDragActive) {
|
|
3618
|
+
return;
|
|
3619
|
+
}
|
|
3620
|
+
event.preventDefault();
|
|
3621
|
+
|
|
3622
|
+
const fallbackSlot = getMarkdownDropSlotIndexFromPointer(event.clientY);
|
|
3623
|
+
const slotIndex = markdownBlockDropSlotIndex >= 0 ? markdownBlockDropSlotIndex : fallbackSlot;
|
|
3624
|
+
const sourceIndex = markdownBlockDragSourceIndex;
|
|
3625
|
+
hideMarkdownBlockDragUi();
|
|
3626
|
+
if (sourceIndex < 0 || slotIndex < 0) {
|
|
3627
|
+
return;
|
|
3628
|
+
}
|
|
3629
|
+
moveMarkdownLexicalBlock(sourceIndex, slotIndex);
|
|
3630
|
+
});
|
|
3631
|
+
|
|
3632
|
+
document.addEventListener('selectionchange', function() {
|
|
3633
|
+
if (!markdownEditorRoot || markdownEditorRoot.style.display !== 'block') {
|
|
3634
|
+
return;
|
|
3635
|
+
}
|
|
3636
|
+
scheduleMarkdownSelectionSync();
|
|
3637
|
+
scheduleMarkdownSelectionOverlayRender();
|
|
3638
|
+
scheduleMarkdownSlashMenuUpdate();
|
|
3639
|
+
scheduleMarkdownInlineToolbarUpdate();
|
|
3640
|
+
});
|
|
3641
|
+
|
|
3642
|
+
window.addEventListener('resize', scheduleMarkdownSelectionOverlayRender);
|
|
3643
|
+
window.addEventListener('resize', scheduleMarkdownSlashMenuUpdate);
|
|
3644
|
+
window.addEventListener('resize', scheduleMarkdownInlineToolbarUpdate);
|
|
3645
|
+
window.addEventListener('resize', hideMarkdownBlockDragUi);
|
|
3646
|
+
|
|
3647
|
+
editorRoot.appendChild(toolbar);
|
|
3648
|
+
editorRoot.appendChild(mdxBlocks);
|
|
3649
|
+
editorRoot.appendChild(surfaceWrap);
|
|
3650
|
+
editorRoot.appendChild(textarea);
|
|
3651
|
+
editorRoot.appendChild(slashMenu);
|
|
3652
|
+
editorRoot.appendChild(inlineToolbar);
|
|
3653
|
+
editorRoot.appendChild(blockDragHandle);
|
|
3654
|
+
editorRoot.appendChild(blockDropIndicator);
|
|
3655
|
+
editorRoot.appendChild(blockDropLabel);
|
|
3656
|
+
document.body.appendChild(editorRoot);
|
|
3657
|
+
|
|
3658
|
+
markdownEditorRoot = editorRoot;
|
|
3659
|
+
markdownEditorSurface = surface;
|
|
3660
|
+
markdownEditorTextarea = textarea;
|
|
3661
|
+
markdownPersistStatus = status;
|
|
3662
|
+
markdownPresenceRoot = presence;
|
|
3663
|
+
markdownSelectionsRoot = selections;
|
|
3664
|
+
markdownMdxBlocksRoot = mdxBlocks;
|
|
3665
|
+
markdownSelectionOverlayRoot = selectionOverlay;
|
|
3666
|
+
markdownSlashMenuRoot = slashMenu;
|
|
3667
|
+
markdownInlineToolbarRoot = inlineToolbar;
|
|
3668
|
+
markdownBlockDragHandle = blockDragHandle;
|
|
3669
|
+
markdownBlockDropIndicator = blockDropIndicator;
|
|
3670
|
+
markdownBlockDropLabel = blockDropLabel;
|
|
3671
|
+
setMarkdownMdxBlocks(markdownLatestMdxBlocks);
|
|
3672
|
+
setMarkdownPresence(markdownLatestPresenceUsers);
|
|
3673
|
+
setMarkdownSelections(markdownLatestSelections);
|
|
3674
|
+
setupMarkdownLexicalEditor();
|
|
3675
|
+
applyMarkdownContent(markdownCurrentContent);
|
|
3676
|
+
|
|
3677
|
+
return editorRoot;
|
|
3678
|
+
}
|
|
3679
|
+
|
|
3680
|
+
function setMarkdownEditMode(enabled) {
|
|
3681
|
+
const markdownBody = document.getElementById('markdown-body');
|
|
3682
|
+
if (!markdownBody || !isMarkdownPage()) {
|
|
3683
|
+
return;
|
|
3684
|
+
}
|
|
3685
|
+
|
|
3686
|
+
if (enabled) {
|
|
3687
|
+
ensureMarkdownEditor();
|
|
3688
|
+
setupMarkdownLexicalEditor();
|
|
3689
|
+
markdownBody.style.display = 'none';
|
|
3690
|
+
markdownEditorRoot.style.display = 'block';
|
|
3691
|
+
markdownHasUnsavedChanges = false;
|
|
3692
|
+
focusMarkdownEditor();
|
|
3693
|
+
scheduleMarkdownSelectionSync();
|
|
3694
|
+
scheduleMarkdownSelectionOverlayRender();
|
|
3695
|
+
scheduleMarkdownSlashMenuUpdate();
|
|
3696
|
+
scheduleMarkdownInlineToolbarUpdate();
|
|
3697
|
+
postMarkdownEditorReady();
|
|
3698
|
+
} else {
|
|
3699
|
+
markdownBody.style.display = '';
|
|
3700
|
+
if (markdownEditorRoot) {
|
|
3701
|
+
markdownEditorRoot.style.display = 'none';
|
|
3702
|
+
}
|
|
3703
|
+
hideMarkdownSlashMenu();
|
|
3704
|
+
hideMarkdownInlineToolbar();
|
|
3705
|
+
hideMarkdownBlockDragUi();
|
|
3706
|
+
markdownOverlaySelections = [];
|
|
3707
|
+
clearMarkdownSelectionOverlay();
|
|
3708
|
+
clearMarkdownSelectionSync();
|
|
3709
|
+
}
|
|
3710
|
+
|
|
3711
|
+
const nextUrl = new URL(window.location.href);
|
|
3712
|
+
if (enabled) {
|
|
3713
|
+
nextUrl.searchParams.set('edit', 'true');
|
|
3714
|
+
} else {
|
|
3715
|
+
nextUrl.searchParams.delete('edit');
|
|
3716
|
+
}
|
|
3717
|
+
window.history.replaceState(window.history.state, '', nextUrl.toString());
|
|
3718
|
+
}
|
|
3719
|
+
|
|
3720
|
+
function ensureMarkdownEditButton() {
|
|
3721
|
+
if (markdownEditButton || !isMarkdownPage()) {
|
|
3722
|
+
return;
|
|
3723
|
+
}
|
|
3724
|
+
|
|
3725
|
+
const button = document.createElement('button');
|
|
3726
|
+
button.type = 'button';
|
|
3727
|
+
button.className = 'vf-markdown-edit-button';
|
|
3728
|
+
button.textContent = 'Edit';
|
|
3729
|
+
button.setAttribute(DATA_VF_IGNORE, 'true');
|
|
3730
|
+
button.addEventListener('click', function() {
|
|
3731
|
+
setMarkdownEditMode(true);
|
|
3732
|
+
});
|
|
3733
|
+
|
|
3734
|
+
document.body.appendChild(button);
|
|
3735
|
+
markdownEditButton = button;
|
|
3736
|
+
}
|
|
3737
|
+
|
|
3738
|
+
function setupMarkdownEditor(params) {
|
|
3739
|
+
if (!isMarkdownPage()) {
|
|
3740
|
+
return;
|
|
3741
|
+
}
|
|
3742
|
+
|
|
3743
|
+
markdownFileId = params.get('vf_file_id') || PAGE_ID || null;
|
|
3744
|
+
ensureMarkdownEditButton();
|
|
3745
|
+
|
|
3746
|
+
if (params.get('edit') === 'true') {
|
|
3747
|
+
setMarkdownEditMode(true);
|
|
3748
|
+
}
|
|
3749
|
+
}
|
|
3750
|
+
|
|
3751
|
+
let html2canvasLoaded = false;
|
|
3752
|
+
let html2canvasPromise = null;
|
|
3753
|
+
|
|
3754
|
+
function loadHtml2Canvas() {
|
|
3755
|
+
if (html2canvasLoaded) return Promise.resolve();
|
|
3756
|
+
if (html2canvasPromise) return html2canvasPromise;
|
|
3757
|
+
|
|
3758
|
+
html2canvasPromise = new Promise((resolve, reject) => {
|
|
3759
|
+
const script = document.createElement('script');
|
|
3760
|
+
script.src = 'https://cdn.jsdelivr.net/npm/html2canvas-pro@2.0.0/dist/html2canvas-pro.min.js';
|
|
3761
|
+
script.onload = () => {
|
|
3762
|
+
html2canvasLoaded = true;
|
|
3763
|
+
resolve();
|
|
3764
|
+
};
|
|
3765
|
+
script.onerror = (event) => {
|
|
3766
|
+
console.warn(
|
|
3767
|
+
'[StudioBridge] Failed to load html2canvas script. This may be caused by CSP script-src restrictions.',
|
|
3768
|
+
event
|
|
3769
|
+
);
|
|
3770
|
+
reject(new Error('Failed to load html2canvas script'));
|
|
3771
|
+
};
|
|
3772
|
+
try {
|
|
3773
|
+
document.head.appendChild(script);
|
|
3774
|
+
} catch (error) {
|
|
3775
|
+
console.warn(
|
|
3776
|
+
'[StudioBridge] Failed to append html2canvas script element. This may be caused by CSP script-src restrictions.',
|
|
3777
|
+
error
|
|
3778
|
+
);
|
|
3779
|
+
reject(error instanceof Error ? error : new Error('Failed to append html2canvas script element'));
|
|
3780
|
+
}
|
|
3781
|
+
});
|
|
3782
|
+
|
|
3783
|
+
return html2canvasPromise;
|
|
3784
|
+
}
|
|
3785
|
+
|
|
3786
|
+
async function captureScreenshot(options) {
|
|
3787
|
+
const { scrollTo, fullPage, quality = 0.8 } = options || {};
|
|
3788
|
+
const originalScrollY = window.scrollY;
|
|
3789
|
+
|
|
3790
|
+
try {
|
|
3791
|
+
await loadHtml2Canvas();
|
|
3792
|
+
|
|
3793
|
+
if (typeof scrollTo === 'number') {
|
|
3794
|
+
window.scrollTo(0, scrollTo);
|
|
3795
|
+
await new Promise(r => setTimeout(r, 150));
|
|
3796
|
+
}
|
|
3797
|
+
|
|
3798
|
+
const canvasOptions = {
|
|
3799
|
+
useCORS: true,
|
|
3800
|
+
logging: false,
|
|
3801
|
+
scale: window.devicePixelRatio || 1
|
|
3802
|
+
};
|
|
3803
|
+
|
|
3804
|
+
if (fullPage) {
|
|
3805
|
+
canvasOptions.height = document.documentElement.scrollHeight;
|
|
3806
|
+
canvasOptions.windowHeight = document.documentElement.scrollHeight;
|
|
3807
|
+
canvasOptions.y = 0;
|
|
3808
|
+
window.scrollTo(0, 0);
|
|
3809
|
+
await new Promise(r => setTimeout(r, 100));
|
|
3810
|
+
}
|
|
3811
|
+
|
|
3812
|
+
const html2canvasFn = window.html2canvas.default || window.html2canvas;
|
|
3813
|
+
const canvas = await html2canvasFn(document.body, canvasOptions);
|
|
3814
|
+
|
|
3815
|
+
if (!canvas || canvas.width === 0 || canvas.height === 0) {
|
|
3816
|
+
console.error('[bridge] html2canvas produced empty canvas:', canvas?.width, 'x', canvas?.height);
|
|
3817
|
+
window.scrollTo(0, originalScrollY);
|
|
531
3818
|
return {
|
|
532
3819
|
success: false,
|
|
533
3820
|
error: 'html2canvas produced empty canvas (0x0 dimensions)'
|
|
@@ -642,7 +3929,47 @@ export function generateStudioBridgeScript(options) {
|
|
|
642
3929
|
if (!inspectMode) showHoverOverlay(message.id);
|
|
643
3930
|
return;
|
|
644
3931
|
|
|
645
|
-
case '
|
|
3932
|
+
case 'setMarkdownContent':
|
|
3933
|
+
if (!isMarkdownPage()) {
|
|
3934
|
+
return;
|
|
3935
|
+
}
|
|
3936
|
+
if (message.fileId && markdownFileId && message.fileId !== markdownFileId) {
|
|
3937
|
+
return;
|
|
3938
|
+
}
|
|
3939
|
+
applyMarkdownContent(message.content || '');
|
|
3940
|
+
return;
|
|
3941
|
+
|
|
3942
|
+
case 'setMarkdownPersistState':
|
|
3943
|
+
if (!isMarkdownPage()) {
|
|
3944
|
+
return;
|
|
3945
|
+
}
|
|
3946
|
+
if (message.fileId && markdownFileId && message.fileId !== markdownFileId) {
|
|
3947
|
+
return;
|
|
3948
|
+
}
|
|
3949
|
+
setMarkdownPersistStatus(message.status || 'saved');
|
|
3950
|
+
if (message.status === 'saved') {
|
|
3951
|
+
markdownHasUnsavedChanges = false;
|
|
3952
|
+
}
|
|
3953
|
+
return;
|
|
3954
|
+
|
|
3955
|
+
case 'setMarkdownPresence':
|
|
3956
|
+
if (!isMarkdownPage()) {
|
|
3957
|
+
return;
|
|
3958
|
+
}
|
|
3959
|
+
if (message.fileId && markdownFileId && message.fileId !== markdownFileId) {
|
|
3960
|
+
return;
|
|
3961
|
+
}
|
|
3962
|
+
setMarkdownPresence(message.users);
|
|
3963
|
+
return;
|
|
3964
|
+
|
|
3965
|
+
case 'setMarkdownSelections':
|
|
3966
|
+
if (!isMarkdownPage()) {
|
|
3967
|
+
return;
|
|
3968
|
+
}
|
|
3969
|
+
if (message.fileId && markdownFileId && message.fileId !== markdownFileId) {
|
|
3970
|
+
return;
|
|
3971
|
+
}
|
|
3972
|
+
setMarkdownSelections(message.selections);
|
|
646
3973
|
return;
|
|
647
3974
|
|
|
648
3975
|
case 'screenshot':
|
|
@@ -717,6 +4044,7 @@ export function generateStudioBridgeScript(options) {
|
|
|
717
4044
|
setupConsoleCapture();
|
|
718
4045
|
setupErrorHandling();
|
|
719
4046
|
setupInspectMode();
|
|
4047
|
+
setupMarkdownEditor(params);
|
|
720
4048
|
|
|
721
4049
|
window.addEventListener('message', handleStudioMessage);
|
|
722
4050
|
|
|
@@ -747,6 +4075,16 @@ export function generateStudioBridgeScript(options) {
|
|
|
747
4075
|
console.debug('[StudioBridge] Initialized successfully');
|
|
748
4076
|
}
|
|
749
4077
|
|
|
750
|
-
|
|
4078
|
+
if (DEBUG_EXPOSE_INTERNALS && typeof window !== 'undefined') {
|
|
4079
|
+
window.__VF_STUDIO_BRIDGE_DEBUG = {
|
|
4080
|
+
parseMdxImportMap: parseMdxImportMap,
|
|
4081
|
+
extractRawBlocksForEditor: extractRawBlocksForEditor,
|
|
4082
|
+
getMdxBlockOpenUiState: getMdxBlockOpenUiState
|
|
4083
|
+
};
|
|
4084
|
+
}
|
|
4085
|
+
|
|
4086
|
+
if (!DEBUG_SKIP_INIT) {
|
|
4087
|
+
init();
|
|
4088
|
+
}
|
|
751
4089
|
})();`;
|
|
752
4090
|
}
|