@jxsuite/studio 0.28.2 → 0.28.4
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/dist/studio.js +86144 -83971
- package/dist/studio.js.map +229 -219
- package/package.json +5 -5
- package/src/browse/browse-modal.ts +9 -3
- package/src/browse/browse.ts +139 -82
- package/src/canvas/canvas-diff.ts +35 -18
- package/src/canvas/canvas-helpers.ts +55 -21
- package/src/canvas/canvas-live-render.ts +140 -89
- package/src/canvas/canvas-render.ts +127 -82
- package/src/canvas/canvas-utils.ts +68 -42
- package/src/canvas/nested-site-style.ts +15 -7
- package/src/editor/component-inline-edit.ts +90 -51
- package/src/editor/content-inline-edit.ts +75 -72
- package/src/editor/context-menu.ts +125 -71
- package/src/editor/convert-targets.ts +17 -13
- package/src/editor/convert-to-component.ts +51 -28
- package/src/editor/convert-to-repeater.ts +44 -19
- package/src/editor/inline-edit.ts +107 -52
- package/src/editor/inline-format.ts +128 -60
- package/src/editor/insertion-helper.ts +26 -14
- package/src/editor/shortcuts.ts +89 -43
- package/src/editor/slash-menu.ts +41 -26
- package/src/files/components.ts +9 -7
- package/src/files/file-ops.ts +53 -33
- package/src/files/files.ts +246 -143
- package/src/format/constraints.ts +25 -15
- package/src/format/format-host.ts +33 -10
- package/src/github/github-auth.ts +24 -14
- package/src/github/github-publish.ts +20 -14
- package/src/new-project/new-project-modal.ts +27 -18
- package/src/panels/activity-bar.ts +27 -26
- package/src/panels/ai-panel.ts +100 -44
- package/src/panels/block-action-bar.ts +84 -52
- package/src/panels/canvas-dnd.ts +47 -28
- package/src/panels/data-explorer.ts +24 -11
- package/src/panels/dnd.ts +148 -100
- package/src/panels/editors.ts +54 -37
- package/src/panels/elements-panel.ts +23 -13
- package/src/panels/events-panel.ts +48 -38
- package/src/panels/git-panel.ts +95 -60
- package/src/panels/head-panel.ts +138 -84
- package/src/panels/imports-panel.ts +66 -29
- package/src/panels/layers-panel.ts +80 -36
- package/src/panels/left-panel.ts +86 -53
- package/src/panels/overlays.ts +36 -19
- package/src/panels/panel-events.ts +48 -27
- package/src/panels/panel-scheduler.ts +41 -23
- package/src/panels/preview-render.ts +26 -26
- package/src/panels/properties-panel.ts +155 -118
- package/src/panels/pseudo-preview.ts +31 -15
- package/src/panels/quick-search.ts +28 -13
- package/src/panels/right-panel.ts +35 -24
- package/src/panels/shared.ts +46 -29
- package/src/panels/signals-panel.ts +303 -186
- package/src/panels/statusbar.ts +27 -12
- package/src/panels/style-inputs.ts +30 -24
- package/src/panels/style-panel.ts +130 -90
- package/src/panels/style-utils.ts +63 -23
- package/src/panels/stylebook-layers-panel.ts +27 -30
- package/src/panels/stylebook-panel.ts +140 -72
- package/src/panels/tab-strip.ts +21 -10
- package/src/panels/toolbar.ts +52 -40
- package/src/panels/welcome-screen.ts +1 -1
- package/src/platform.ts +2 -1
- package/src/platforms/devserver.ts +221 -105
- package/src/recent-projects.ts +15 -7
- package/src/resize-edges.ts +7 -5
- package/src/services/cem-export.ts +73 -57
- package/src/services/code-services.ts +52 -25
- package/src/services/monaco-setup.ts +5 -5
- package/src/settings/content-types-editor.ts +133 -62
- package/src/settings/css-vars-editor.ts +31 -14
- package/src/settings/defs-editor.ts +125 -63
- package/src/settings/general-settings.ts +11 -6
- package/src/settings/head-editor.ts +30 -15
- package/src/settings/schema-field-ui.ts +75 -35
- package/src/settings/settings-modal.ts +35 -17
- package/src/site-context.ts +80 -47
- package/src/state.ts +98 -65
- package/src/store.ts +42 -23
- package/src/studio.ts +165 -144
- package/src/tabs/tab.ts +51 -45
- package/src/tabs/transact.ts +230 -109
- package/src/types.ts +60 -51
- package/src/ui/button-group.ts +3 -2
- package/src/ui/color-selector.ts +33 -15
- package/src/ui/expression-editor.ts +139 -86
- package/src/ui/field-input.ts +35 -18
- package/src/ui/field-row.ts +1 -1
- package/src/ui/icons.ts +5 -6
- package/src/ui/layers.ts +33 -26
- package/src/ui/media-picker.ts +13 -7
- package/src/ui/panel-resize.ts +39 -20
- package/src/ui/spectrum.ts +4 -2
- package/src/ui/unit-selector.ts +20 -11
- package/src/ui/value-selector.ts +13 -7
- package/src/ui/widgets.ts +34 -18
- package/src/utils/canvas-media.ts +66 -33
- package/src/utils/edit-display.ts +43 -44
- package/src/utils/google-fonts.ts +10 -6
- package/src/utils/inherited-style.ts +22 -8
- package/src/utils/studio-utils.ts +88 -45
- package/src/view.ts +9 -5
- package/src/workspace/workspace.ts +49 -29
|
@@ -8,9 +8,16 @@
|
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
10
|
import elementsMeta from "../../data/elements-meta.json";
|
|
11
|
-
import {
|
|
11
|
+
import { normalizeInlineContent, toggleInlineFormat } from "./inline-format";
|
|
12
12
|
import type { JxPath } from "../state";
|
|
13
13
|
import type { JxMutableNode } from "@jxsuite/schema/types";
|
|
14
|
+
|
|
15
|
+
import {
|
|
16
|
+
showSlashMenu as sharedShowSlashMenu,
|
|
17
|
+
dismissSlashMenu as sharedDismissSlashMenu,
|
|
18
|
+
isSlashMenuOpen,
|
|
19
|
+
} from "./slash-menu";
|
|
20
|
+
|
|
14
21
|
export interface InlineAction {
|
|
15
22
|
tag: string;
|
|
16
23
|
label: string;
|
|
@@ -35,12 +42,6 @@ export interface JxContentResult {
|
|
|
35
42
|
children?: (JxMutableNode | string)[];
|
|
36
43
|
}
|
|
37
44
|
|
|
38
|
-
import {
|
|
39
|
-
showSlashMenu as sharedShowSlashMenu,
|
|
40
|
-
dismissSlashMenu as sharedDismissSlashMenu,
|
|
41
|
-
isSlashMenuOpen,
|
|
42
|
-
} from "./slash-menu";
|
|
43
|
-
|
|
44
45
|
// ─── Inline tag set (tags that represent rich text formatting) ─────────────
|
|
45
46
|
|
|
46
47
|
/** Fallback set — used when parent context is unknown */
|
|
@@ -90,9 +91,13 @@ const EDITABLE_BLOCKS = new Set([
|
|
|
90
91
|
* @returns {boolean}
|
|
91
92
|
*/
|
|
92
93
|
export function isInlineInContext(childTag: string, parentTag: string) {
|
|
93
|
-
if (!parentTag)
|
|
94
|
+
if (!parentTag) {
|
|
95
|
+
return INLINE_TAGS.has(childTag);
|
|
96
|
+
}
|
|
94
97
|
const parentDef = (elementsMeta.$defs as Record<string, ElementDef>)[parentTag];
|
|
95
|
-
if (!parentDef || !parentDef.$inlineChildren)
|
|
98
|
+
if (!parentDef || !parentDef.$inlineChildren) {
|
|
99
|
+
return false;
|
|
100
|
+
}
|
|
96
101
|
return parentDef.$inlineChildren.includes(childTag);
|
|
97
102
|
}
|
|
98
103
|
|
|
@@ -105,19 +110,23 @@ export function isInlineInContext(childTag: string, parentTag: string) {
|
|
|
105
110
|
*/
|
|
106
111
|
export function getInlineActions(tag: string) {
|
|
107
112
|
const def = (elementsMeta.$defs as Record<string, ElementDef>)[tag];
|
|
108
|
-
if (!def)
|
|
113
|
+
if (!def) {
|
|
114
|
+
return null;
|
|
115
|
+
}
|
|
109
116
|
let actions = def.$inlineActions;
|
|
110
117
|
if (typeof actions === "string") {
|
|
111
118
|
const refDef = (elementsMeta.$defs as Record<string, ElementDef>)[actions];
|
|
112
119
|
actions = refDef?.$inlineActions ?? undefined;
|
|
113
120
|
}
|
|
114
|
-
if (!Array.isArray(actions))
|
|
121
|
+
if (!Array.isArray(actions)) {
|
|
122
|
+
return null;
|
|
123
|
+
}
|
|
115
124
|
return actions;
|
|
116
125
|
}
|
|
117
126
|
|
|
118
127
|
// ─── Editing state ─────────────────────────────────────────────────────────
|
|
119
128
|
|
|
120
|
-
let activeEl: HTMLElement | null = null; //
|
|
129
|
+
let activeEl: HTMLElement | null = null; // Currently contenteditable element
|
|
121
130
|
let activePath: JxPath | null = null; // JSON path to the active element
|
|
122
131
|
/**
|
|
123
132
|
* @type {((
|
|
@@ -133,22 +142,22 @@ let commitFn:
|
|
|
133
142
|
children: (JxMutableNode | string)[] | null,
|
|
134
143
|
textContent: string | null,
|
|
135
144
|
) => void)
|
|
136
|
-
| null = null; //
|
|
145
|
+
| null = null; // Function(path, newChildren, newTextContent) to commit changes
|
|
137
146
|
/**
|
|
138
147
|
* @type {((path: JxPath, beforeChildren: JxContentResult, afterChildren: JxContentResult) => void)
|
|
139
148
|
* | null}
|
|
140
149
|
*/
|
|
141
150
|
let splitFn:
|
|
142
151
|
| ((path: JxPath, beforeChildren: JxContentResult, afterChildren: JxContentResult) => void)
|
|
143
|
-
| null = null; //
|
|
152
|
+
| null = null; // Function(path, beforeChildren, afterChildren) to split paragraph
|
|
144
153
|
/**
|
|
145
154
|
* @type {((path: JxPath, elementDef: SlashCommand, commitData?: JxContentResult) => void)
|
|
146
155
|
* | null}
|
|
147
156
|
*/
|
|
148
157
|
let insertFn:
|
|
149
158
|
| ((path: JxPath, elementDef: SlashCommand, commitData?: JxContentResult) => void)
|
|
150
|
-
| null = null; //
|
|
151
|
-
let endFn: (() => void) | null = null; //
|
|
159
|
+
| null = null; // Function(path, elementDef, commitData?) to insert after current block
|
|
160
|
+
let endFn: (() => void) | null = null; // Function() called when editing stops
|
|
152
161
|
|
|
153
162
|
/**
|
|
154
163
|
* Check if an element is a text-bearing editable block.
|
|
@@ -169,7 +178,9 @@ export function isEditableBlock(el: HTMLElement) {
|
|
|
169
178
|
* @returns {boolean}
|
|
170
179
|
*/
|
|
171
180
|
export function isInlineElement(node: JxMutableNode, parentNode?: JxMutableNode) {
|
|
172
|
-
if (!node || typeof node !== "object")
|
|
181
|
+
if (!node || typeof node !== "object") {
|
|
182
|
+
return false;
|
|
183
|
+
}
|
|
173
184
|
const childTag = (node.tagName ?? "div").toLowerCase();
|
|
174
185
|
if (parentNode) {
|
|
175
186
|
const parentTag = (parentNode.tagName ?? "div").toLowerCase();
|
|
@@ -216,7 +227,9 @@ export function startEditing(
|
|
|
216
227
|
onEnd: () => void;
|
|
217
228
|
},
|
|
218
229
|
) {
|
|
219
|
-
if (activeEl)
|
|
230
|
+
if (activeEl) {
|
|
231
|
+
stopEditing();
|
|
232
|
+
}
|
|
220
233
|
|
|
221
234
|
activeEl = el;
|
|
222
235
|
activePath = path;
|
|
@@ -251,7 +264,9 @@ export function startEditing(
|
|
|
251
264
|
|
|
252
265
|
/** Stop editing and commit changes. */
|
|
253
266
|
export function stopEditing() {
|
|
254
|
-
if (!activeEl)
|
|
267
|
+
if (!activeEl) {
|
|
268
|
+
return;
|
|
269
|
+
}
|
|
255
270
|
|
|
256
271
|
commitChanges();
|
|
257
272
|
sharedDismissSlashMenu();
|
|
@@ -310,7 +325,9 @@ function handleKeydown(e: KeyboardEvent) {
|
|
|
310
325
|
}
|
|
311
326
|
|
|
312
327
|
if (e.key === "Enter" && !e.shiftKey) {
|
|
313
|
-
if (isSlashMenuOpen())
|
|
328
|
+
if (isSlashMenuOpen()) {
|
|
329
|
+
return;
|
|
330
|
+
} // Shared slash menu captures Enter
|
|
314
331
|
e.preventDefault();
|
|
315
332
|
e.stopPropagation();
|
|
316
333
|
handleEnterKey();
|
|
@@ -335,18 +352,24 @@ function handleKeydown(e: KeyboardEvent) {
|
|
|
335
352
|
// Rich text shortcuts
|
|
336
353
|
if (e.ctrlKey || e.metaKey) {
|
|
337
354
|
switch (e.key) {
|
|
338
|
-
case "b":
|
|
355
|
+
case "b": {
|
|
339
356
|
e.preventDefault();
|
|
340
357
|
toggleInlineFormat("strong", activeEl);
|
|
341
358
|
break;
|
|
342
|
-
|
|
359
|
+
}
|
|
360
|
+
case "i": {
|
|
343
361
|
e.preventDefault();
|
|
344
362
|
toggleInlineFormat("em", activeEl);
|
|
345
363
|
break;
|
|
346
|
-
|
|
364
|
+
}
|
|
365
|
+
case "`": {
|
|
347
366
|
e.preventDefault();
|
|
348
367
|
toggleInlineFormat("code", activeEl);
|
|
349
368
|
break;
|
|
369
|
+
}
|
|
370
|
+
default: {
|
|
371
|
+
break;
|
|
372
|
+
}
|
|
350
373
|
}
|
|
351
374
|
}
|
|
352
375
|
|
|
@@ -369,7 +392,9 @@ function handleInput() {
|
|
|
369
392
|
/** @param {FocusEvent} _e */
|
|
370
393
|
function handleBlur(_e: FocusEvent) {
|
|
371
394
|
// Don't close if focus moved to slash menu
|
|
372
|
-
if (isSlashMenuOpen())
|
|
395
|
+
if (isSlashMenuOpen()) {
|
|
396
|
+
return;
|
|
397
|
+
}
|
|
373
398
|
|
|
374
399
|
// Delay to allow click events to fire
|
|
375
400
|
setTimeout(() => {
|
|
@@ -390,10 +415,14 @@ function handlePaste(e: ClipboardEvent) {
|
|
|
390
415
|
// ─── Enter key: split paragraph ────────────────────────────────────────────
|
|
391
416
|
|
|
392
417
|
function handleEnterKey() {
|
|
393
|
-
if (!splitFn || !activeEl || !activePath)
|
|
418
|
+
if (!splitFn || !activeEl || !activePath) {
|
|
419
|
+
return;
|
|
420
|
+
}
|
|
394
421
|
|
|
395
422
|
const sel = window.getSelection();
|
|
396
|
-
if (!sel || !sel.rangeCount)
|
|
423
|
+
if (!sel || !sel.rangeCount) {
|
|
424
|
+
return;
|
|
425
|
+
}
|
|
397
426
|
|
|
398
427
|
const range = sel.getRangeAt(0);
|
|
399
428
|
|
|
@@ -440,7 +469,9 @@ function handleEnterKey() {
|
|
|
440
469
|
// ─── Content sync: DOM → Jx ────────────────────────────────────────────
|
|
441
470
|
|
|
442
471
|
function commitChanges() {
|
|
443
|
-
if (!commitFn || !activeEl || !activePath)
|
|
472
|
+
if (!commitFn || !activeEl || !activePath) {
|
|
473
|
+
return;
|
|
474
|
+
}
|
|
444
475
|
|
|
445
476
|
normalizeInlineContent(activeEl);
|
|
446
477
|
const result = elementToJx(activeEl);
|
|
@@ -455,16 +486,14 @@ function commitChanges() {
|
|
|
455
486
|
* @returns {JxContentResult}
|
|
456
487
|
*/
|
|
457
488
|
export function normalizeChildren(node: { children?: (JxMutableNode | string)[] }) {
|
|
458
|
-
if (!Array.isArray(node.children) || node.children.length === 0)
|
|
489
|
+
if (!Array.isArray(node.children) || node.children.length === 0) {
|
|
490
|
+
return { textContent: "" };
|
|
491
|
+
}
|
|
459
492
|
|
|
460
493
|
// Step 1: Merge adjacent text nodes
|
|
461
494
|
const merged = [];
|
|
462
495
|
for (const child of node.children) {
|
|
463
|
-
if (
|
|
464
|
-
typeof child === "string" &&
|
|
465
|
-
merged.length > 0 &&
|
|
466
|
-
typeof merged[merged.length - 1] === "string"
|
|
467
|
-
) {
|
|
496
|
+
if (typeof child === "string" && merged.length > 0 && typeof merged.at(-1) === "string") {
|
|
468
497
|
merged[merged.length - 1] += child;
|
|
469
498
|
} else {
|
|
470
499
|
merged.push(child);
|
|
@@ -490,7 +519,9 @@ function elementToJx(el: HTMLElement): JxContentResult {
|
|
|
490
519
|
const nodes = el.childNodes;
|
|
491
520
|
|
|
492
521
|
// If just a single text node, use textContent
|
|
493
|
-
if (nodes.length === 0)
|
|
522
|
+
if (nodes.length === 0) {
|
|
523
|
+
return { textContent: "" };
|
|
524
|
+
}
|
|
494
525
|
if (nodes.length === 1 && nodes[0].nodeType === Node.TEXT_NODE) {
|
|
495
526
|
return { textContent: nodes[0].textContent };
|
|
496
527
|
}
|
|
@@ -499,7 +530,9 @@ function elementToJx(el: HTMLElement): JxContentResult {
|
|
|
499
530
|
const children: (JxMutableNode | string)[] = [];
|
|
500
531
|
for (const child of nodes) {
|
|
501
532
|
const jsx = domNodeToJx(child);
|
|
502
|
-
if (jsx !== null && jsx !== undefined)
|
|
533
|
+
if (jsx !== null && jsx !== undefined) {
|
|
534
|
+
children.push(jsx);
|
|
535
|
+
}
|
|
503
536
|
}
|
|
504
537
|
|
|
505
538
|
// Normalize: merge adjacent text nodes + fold all-text to textContent
|
|
@@ -515,11 +548,15 @@ function elementToJx(el: HTMLElement): JxContentResult {
|
|
|
515
548
|
function domNodeToJx(node: Node) {
|
|
516
549
|
if (node.nodeType === Node.TEXT_NODE) {
|
|
517
550
|
const text = node.textContent;
|
|
518
|
-
if (!text)
|
|
551
|
+
if (!text) {
|
|
552
|
+
return null;
|
|
553
|
+
}
|
|
519
554
|
return text; // Bare string — text node child
|
|
520
555
|
}
|
|
521
556
|
|
|
522
|
-
if (node.nodeType !== Node.ELEMENT_NODE)
|
|
557
|
+
if (node.nodeType !== Node.ELEMENT_NODE) {
|
|
558
|
+
return null;
|
|
559
|
+
}
|
|
523
560
|
|
|
524
561
|
const el = node as HTMLElement;
|
|
525
562
|
const tag = el.tagName.toLowerCase();
|
|
@@ -532,12 +569,16 @@ function domNodeToJx(node: Node) {
|
|
|
532
569
|
s: "del",
|
|
533
570
|
strike: "del",
|
|
534
571
|
};
|
|
535
|
-
if (tagMap[tag])
|
|
572
|
+
if (tagMap[tag]) {
|
|
573
|
+
result.tagName = tagMap[tag];
|
|
574
|
+
}
|
|
536
575
|
|
|
537
576
|
// Attributes
|
|
538
577
|
if (tag === "a" && (el as HTMLAnchorElement).href) {
|
|
539
|
-
result.attributes = { href: el.getAttribute("href") };
|
|
540
|
-
if ((el as HTMLAnchorElement).title)
|
|
578
|
+
result.attributes = { href: el.getAttribute("href") ?? "" };
|
|
579
|
+
if ((el as HTMLAnchorElement).title) {
|
|
580
|
+
result.attributes.title = (el as HTMLAnchorElement).title;
|
|
581
|
+
}
|
|
541
582
|
}
|
|
542
583
|
if (tag === "code") {
|
|
543
584
|
result.textContent = el.textContent;
|
|
@@ -545,7 +586,7 @@ function domNodeToJx(node: Node) {
|
|
|
545
586
|
}
|
|
546
587
|
|
|
547
588
|
// Recurse children
|
|
548
|
-
const childNodes = el
|
|
589
|
+
const { childNodes } = el;
|
|
549
590
|
if (childNodes.length === 0) {
|
|
550
591
|
result.textContent = "";
|
|
551
592
|
} else if (childNodes.length === 1 && childNodes[0].nodeType === Node.TEXT_NODE) {
|
|
@@ -554,7 +595,9 @@ function domNodeToJx(node: Node) {
|
|
|
554
595
|
result.children = [];
|
|
555
596
|
for (const child of childNodes) {
|
|
556
597
|
const jsx = domNodeToJx(child);
|
|
557
|
-
if (jsx)
|
|
598
|
+
if (jsx) {
|
|
599
|
+
result.children.push(/** @type {JxMutableNode} */ jsx);
|
|
600
|
+
}
|
|
558
601
|
}
|
|
559
602
|
}
|
|
560
603
|
|
|
@@ -569,7 +612,9 @@ function domNodeToJx(node: Node) {
|
|
|
569
612
|
*/
|
|
570
613
|
function fragmentToJx(frag: DocumentFragment) {
|
|
571
614
|
const nodes = frag.childNodes;
|
|
572
|
-
if (nodes.length === 0)
|
|
615
|
+
if (nodes.length === 0) {
|
|
616
|
+
return { textContent: "" };
|
|
617
|
+
}
|
|
573
618
|
if (nodes.length === 1 && nodes[0].nodeType === Node.TEXT_NODE) {
|
|
574
619
|
return { textContent: nodes[0].textContent };
|
|
575
620
|
}
|
|
@@ -577,14 +622,16 @@ function fragmentToJx(frag: DocumentFragment) {
|
|
|
577
622
|
const children: (JxMutableNode | string)[] = [];
|
|
578
623
|
for (const child of nodes) {
|
|
579
624
|
const jsx = domNodeToJx(child);
|
|
580
|
-
if (jsx)
|
|
625
|
+
if (jsx) {
|
|
626
|
+
children.push(jsx);
|
|
627
|
+
}
|
|
581
628
|
}
|
|
582
629
|
|
|
583
630
|
if (
|
|
584
631
|
children.length === 1 &&
|
|
585
632
|
typeof children[0] !== "string" &&
|
|
586
633
|
children[0].tagName === "span" &&
|
|
587
|
-
children[0].textContent
|
|
634
|
+
typeof children[0].textContent === "string"
|
|
588
635
|
) {
|
|
589
636
|
return { textContent: children[0].textContent };
|
|
590
637
|
}
|
|
@@ -611,10 +658,14 @@ function getTextBeforeCursor(range: Range) {
|
|
|
611
658
|
let _slashFilterStart = 0;
|
|
612
659
|
|
|
613
660
|
function openSlashMenu() {
|
|
614
|
-
if (!activeEl || !insertFn || !activePath)
|
|
661
|
+
if (!activeEl || !insertFn || !activePath) {
|
|
662
|
+
return;
|
|
663
|
+
}
|
|
615
664
|
|
|
616
665
|
const sel = window.getSelection();
|
|
617
|
-
if (!sel || !sel.rangeCount)
|
|
666
|
+
if (!sel || !sel.rangeCount) {
|
|
667
|
+
return;
|
|
668
|
+
}
|
|
618
669
|
const range = sel.getRangeAt(0);
|
|
619
670
|
_slashFilterStart = getTextBeforeCursor(range).length;
|
|
620
671
|
|
|
@@ -622,7 +673,9 @@ function openSlashMenu() {
|
|
|
622
673
|
}
|
|
623
674
|
|
|
624
675
|
function updateSlashMenu() {
|
|
625
|
-
if (!activeEl)
|
|
676
|
+
if (!activeEl) {
|
|
677
|
+
return;
|
|
678
|
+
}
|
|
626
679
|
|
|
627
680
|
const sel = window.getSelection();
|
|
628
681
|
if (!sel || !sel.rangeCount) {
|
|
@@ -634,7 +687,7 @@ function updateSlashMenu() {
|
|
|
634
687
|
const fullText = getTextBeforeCursor(range);
|
|
635
688
|
const slashIdx = fullText.lastIndexOf("/");
|
|
636
689
|
|
|
637
|
-
if (slashIdx
|
|
690
|
+
if (slashIdx === -1 || fullText.length < _slashFilterStart - 1) {
|
|
638
691
|
sharedDismissSlashMenu();
|
|
639
692
|
return;
|
|
640
693
|
}
|
|
@@ -645,7 +698,9 @@ function updateSlashMenu() {
|
|
|
645
698
|
|
|
646
699
|
/** @param {SlashCommand} cmd */
|
|
647
700
|
function handleSlashSelect(cmd: SlashCommand) {
|
|
648
|
-
if (!activeEl || !insertFn || !activePath)
|
|
701
|
+
if (!activeEl || !insertFn || !activePath) {
|
|
702
|
+
return;
|
|
703
|
+
}
|
|
649
704
|
|
|
650
705
|
// Remove the /command text from the element
|
|
651
706
|
const sel = window.getSelection();
|
|
@@ -653,7 +708,7 @@ function handleSlashSelect(cmd: SlashCommand) {
|
|
|
653
708
|
const range = sel.getRangeAt(0);
|
|
654
709
|
const fullText = getTextBeforeCursor(range);
|
|
655
710
|
const slashIdx = fullText.lastIndexOf("/");
|
|
656
|
-
if (slashIdx
|
|
711
|
+
if (slashIdx !== -1) {
|
|
657
712
|
const walker = document.createTreeWalker(activeEl, NodeFilter.SHOW_TEXT);
|
|
658
713
|
let charCount = 0;
|
|
659
714
|
let slashNode: Text | null = null;
|
|
@@ -677,7 +732,7 @@ function handleSlashSelect(cmd: SlashCommand) {
|
|
|
677
732
|
}
|
|
678
733
|
|
|
679
734
|
// Compute commit data inline instead of calling commitChanges() — avoids a separate
|
|
680
|
-
//
|
|
735
|
+
// Update() call that would race with the insertFn update() (two concurrent async renders).
|
|
681
736
|
normalizeInlineContent(activeEl);
|
|
682
737
|
const commitResult = elementToJx(activeEl);
|
|
683
738
|
|