@jxsuite/studio 0.23.2 → 0.24.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/studio.js +2230 -714
- package/dist/studio.js.map +121 -104
- package/package.json +8 -7
- package/src/browse/{browse-modal.js → browse-modal.ts} +9 -10
- package/src/browse/{browse.js → browse.ts} +109 -84
- package/src/canvas/{canvas-diff.js → canvas-diff.ts} +42 -35
- package/src/canvas/{canvas-helpers.js → canvas-helpers.ts} +25 -19
- package/src/canvas/{canvas-live-render.js → canvas-live-render.ts} +53 -63
- package/src/canvas/{canvas-render.js → canvas-render.ts} +110 -121
- package/src/canvas/{canvas-utils.js → canvas-utils.ts} +48 -44
- package/src/canvas/{nested-site-style.js → nested-site-style.ts} +3 -3
- package/src/editor/{component-inline-edit.js → component-inline-edit.ts} +45 -33
- package/src/editor/{content-inline-edit.js → content-inline-edit.ts} +27 -45
- package/src/editor/{context-menu.js → context-menu.ts} +38 -36
- package/src/editor/{convert-targets.js → convert-targets.ts} +15 -12
- package/src/editor/{convert-to-component.js → convert-to-component.ts} +25 -26
- package/src/editor/{convert-to-repeater.js → convert-to-repeater.ts} +60 -40
- package/src/editor/{inline-edit.js → inline-edit.ts} +91 -55
- package/src/editor/{inline-format.js → inline-format.ts} +46 -51
- package/src/editor/{insertion-helper.js → insertion-helper.ts} +47 -63
- package/src/editor/{shortcuts.js → shortcuts.ts} +45 -31
- package/src/editor/{slash-menu.js → slash-menu.ts} +34 -35
- package/src/files/{components.js → components.ts} +12 -15
- package/src/files/{file-ops.js → file-ops.ts} +36 -29
- package/src/files/{files.js → files.ts} +102 -84
- package/src/github/{github-auth.js → github-auth.ts} +3 -3
- package/src/github/{github-publish.js → github-publish.ts} +20 -18
- package/src/markdown/{md-allowlist.js → md-allowlist.ts} +5 -2
- package/src/markdown/{md-convert.js → md-convert.ts} +146 -169
- package/src/new-project/{new-project-modal.js → new-project-modal.ts} +16 -18
- package/src/panels/{activity-bar.js → activity-bar.ts} +26 -34
- package/src/panels/{ai-panel.js → ai-panel.ts} +30 -30
- package/src/panels/{block-action-bar.js → block-action-bar.ts} +64 -73
- package/src/panels/{canvas-dnd.js → canvas-dnd.ts} +44 -49
- package/src/panels/{data-explorer.js → data-explorer.ts} +21 -15
- package/src/panels/dnd.ts +417 -0
- package/src/panels/{editors.js → editors.ts} +43 -50
- package/src/panels/{elements-panel.js → elements-panel.ts} +30 -27
- package/src/panels/{events-panel.js → events-panel.ts} +33 -25
- package/src/panels/{git-panel.js → git-panel.ts} +97 -83
- package/src/panels/{head-panel.js → head-panel.ts} +117 -139
- package/src/panels/{imports-panel.js → imports-panel.ts} +69 -89
- package/src/panels/{layers-panel.js → layers-panel.ts} +61 -57
- package/src/panels/{left-panel.js → left-panel.ts} +56 -63
- package/src/panels/{overlays.js → overlays.ts} +28 -36
- package/src/panels/{panel-events.js → panel-events.ts} +53 -51
- package/src/panels/{preview-render.js → preview-render.ts} +20 -15
- package/src/panels/{properties-panel.js → properties-panel.ts} +212 -246
- package/src/panels/{pseudo-preview.js → pseudo-preview.ts} +9 -8
- package/src/panels/{quick-search.js → quick-search.ts} +16 -17
- package/src/panels/{right-panel.js → right-panel.ts} +41 -59
- package/src/panels/{shared.js → shared.ts} +7 -5
- package/src/panels/{signals-panel.js → signals-panel.ts} +322 -362
- package/src/panels/{statusbar.js → statusbar.ts} +14 -14
- package/src/panels/{style-inputs.js → style-inputs.ts} +66 -48
- package/src/panels/{style-panel.js → style-panel.ts} +235 -290
- package/src/panels/{style-utils.js → style-utils.ts} +32 -31
- package/src/panels/{stylebook-layers-panel.js → stylebook-layers-panel.ts} +22 -14
- package/src/panels/{stylebook-panel.js → stylebook-panel.ts} +127 -140
- package/src/panels/{tab-strip.js → tab-strip.ts} +12 -14
- package/src/panels/{toolbar.js → toolbar.ts} +68 -70
- package/src/panels/{welcome-screen.js → welcome-screen.ts} +14 -16
- package/src/{platform.js → platform.ts} +5 -3
- package/src/platforms/{devserver.js → devserver.ts} +53 -44
- package/src/{recent-projects.js → recent-projects.ts} +17 -12
- package/src/{resize-edges.js → resize-edges.ts} +18 -13
- package/src/services/{cem-export.js → cem-export.ts} +14 -7
- package/src/services/{code-services.js → code-services.ts} +29 -20
- package/src/services/{monaco-setup.js → monaco-setup.ts} +2 -3
- package/src/settings/{content-types-editor.js → content-types-editor.ts} +88 -67
- package/src/settings/{css-vars-editor.js → css-vars-editor.ts} +78 -51
- package/src/settings/{defs-editor.js → defs-editor.ts} +70 -56
- package/src/settings/{general-settings.js → general-settings.ts} +29 -28
- package/src/settings/{head-editor.js → head-editor.ts} +37 -35
- package/src/settings/{schema-field-ui.js → schema-field-ui.ts} +86 -76
- package/src/settings/{settings-modal.js → settings-modal.ts} +14 -16
- package/src/{site-context.js → site-context.ts} +35 -32
- package/src/{state.js → state.ts} +95 -78
- package/src/{store.js → store.ts} +60 -63
- package/src/{studio.js → studio.ts} +117 -119
- package/src/tabs/tab.ts +196 -0
- package/src/tabs/{transact.js → transact.ts} +107 -60
- package/src/types.ts +185 -0
- package/src/ui/{button-group.js → button-group.ts} +21 -21
- package/src/ui/{color-selector.js → color-selector.ts} +44 -37
- package/src/ui/{expression-editor.js → expression-editor.ts} +65 -40
- package/src/ui/{field-row.js → field-row.ts} +19 -2
- package/src/ui/{icons.js → icons.ts} +2 -2
- package/src/ui/{layers.js → layers.ts} +33 -24
- package/src/ui/{media-picker.js → media-picker.ts} +13 -9
- package/src/ui/{panel-resize.js → panel-resize.ts} +9 -4
- package/src/ui/{spectrum.js → spectrum.ts} +4 -4
- package/src/ui/{unit-selector.js → unit-selector.ts} +16 -19
- package/src/ui/{value-selector.js → value-selector.ts} +28 -28
- package/src/ui/{widgets.js → widgets.ts} +62 -15
- package/src/utils/{canvas-media.js → canvas-media.ts} +29 -14
- package/src/utils/{edit-display.js → edit-display.ts} +23 -20
- package/src/utils/{google-fonts.js → google-fonts.ts} +14 -13
- package/src/utils/{inherited-style.js → inherited-style.ts} +15 -10
- package/src/utils/{studio-utils.js → studio-utils.ts} +35 -23
- package/src/{view.js → view.ts} +53 -55
- package/src/workspace/{workspace.js → workspace.ts} +44 -33
- package/src/panels/dnd.js +0 -426
- package/src/tabs/tab.js +0 -192
- /package/src/{reactivity.js → reactivity.ts} +0 -0
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
/// <reference lib="dom" />
|
|
1
2
|
/**
|
|
2
3
|
* Shortcuts.js — Keyboard shortcuts for Jx Studio
|
|
3
4
|
*
|
|
@@ -5,8 +6,8 @@
|
|
|
5
6
|
* shortcuts on the canvas / document.
|
|
6
7
|
*/
|
|
7
8
|
|
|
8
|
-
import { getNodeAtPath, parentElementPath, childIndex, canvasWrap } from "../store
|
|
9
|
-
import { activeTab, workspace, closeTab } from "../workspace/workspace
|
|
9
|
+
import { getNodeAtPath, parentElementPath, childIndex, canvasWrap } from "../store";
|
|
10
|
+
import { activeTab, workspace, closeTab } from "../workspace/workspace";
|
|
10
11
|
import {
|
|
11
12
|
transactDoc,
|
|
12
13
|
mutateInsertNode,
|
|
@@ -14,13 +15,13 @@ import {
|
|
|
14
15
|
mutateDuplicateNode,
|
|
15
16
|
undo as tabUndo,
|
|
16
17
|
redo as tabRedo,
|
|
17
|
-
} from "../tabs/transact
|
|
18
|
-
import { isEditing, stopEditing } from "./inline-edit
|
|
19
|
-
import { copyNode, cutNode, pasteNode } from "./context-menu
|
|
20
|
-
import { openQuickSearch } from "../panels/quick-search
|
|
21
|
-
import { showConfirmDialog } from "../ui/layers
|
|
18
|
+
} from "../tabs/transact";
|
|
19
|
+
import { isEditing, stopEditing } from "./inline-edit";
|
|
20
|
+
import { copyNode, cutNode, pasteNode } from "./context-menu";
|
|
21
|
+
import { openQuickSearch } from "../panels/quick-search";
|
|
22
|
+
import { showConfirmDialog } from "../ui/layers";
|
|
22
23
|
|
|
23
|
-
|
|
24
|
+
import type { JxPath } from "../state";
|
|
24
25
|
|
|
25
26
|
/**
|
|
26
27
|
* Initialise all keyboard (and wheel/pointer) shortcuts.
|
|
@@ -38,11 +39,24 @@ import { showConfirmDialog } from "../ui/layers.js";
|
|
|
38
39
|
* enterEditOnPath: (path: JxPath) => void;
|
|
39
40
|
* }} getContext
|
|
40
41
|
*/
|
|
41
|
-
export function initShortcuts(
|
|
42
|
+
export function initShortcuts(
|
|
43
|
+
getContext: () => {
|
|
44
|
+
canvasMode: string;
|
|
45
|
+
panX: number;
|
|
46
|
+
panY: number;
|
|
47
|
+
setPan: (x: number, y: number) => void;
|
|
48
|
+
applyTransform: () => void;
|
|
49
|
+
positionZoomIndicator: () => void;
|
|
50
|
+
componentInlineEdit: Record<string, unknown> | null;
|
|
51
|
+
saveFile: () => void;
|
|
52
|
+
openProject: () => void;
|
|
53
|
+
enterEditOnPath: (path: JxPath) => void;
|
|
54
|
+
},
|
|
55
|
+
) {
|
|
42
56
|
// Wheel handler: Ctrl+Scroll = zoom (cursor-centered), plain scroll = pan
|
|
43
57
|
canvasWrap.addEventListener(
|
|
44
58
|
"wheel",
|
|
45
|
-
(
|
|
59
|
+
(e: WheelEvent) => {
|
|
46
60
|
const { canvasMode, panX, panY, setPan, applyTransform } = getContext();
|
|
47
61
|
// Edit (content) mode: let the scroll container handle scrolling natively
|
|
48
62
|
if (canvasMode === "edit") return;
|
|
@@ -60,7 +74,7 @@ export function initShortcuts(getContext) {
|
|
|
60
74
|
const ratio = newZoom / oldZoom;
|
|
61
75
|
// Adjust pan so the point under cursor stays stationary
|
|
62
76
|
setPan(cursorX - (cursorX - panX) * ratio, cursorY - (cursorY - panY) * ratio);
|
|
63
|
-
activeTab.value
|
|
77
|
+
activeTab.value!.session.ui.zoom = newZoom;
|
|
64
78
|
} else if (e.shiftKey) {
|
|
65
79
|
// Shift+scroll = horizontal pan
|
|
66
80
|
setPan(panX - e.deltaY, panY);
|
|
@@ -74,7 +88,7 @@ export function initShortcuts(getContext) {
|
|
|
74
88
|
);
|
|
75
89
|
|
|
76
90
|
// Middle-mouse drag panning
|
|
77
|
-
canvasWrap.addEventListener("pointerdown", (
|
|
91
|
+
canvasWrap.addEventListener("pointerdown", (e: PointerEvent) => {
|
|
78
92
|
const ctx = getContext();
|
|
79
93
|
if (ctx.canvasMode === "edit") return; // no panning in edit mode
|
|
80
94
|
if (e.button !== 1) return; // middle button only
|
|
@@ -82,7 +96,7 @@ export function initShortcuts(getContext) {
|
|
|
82
96
|
canvasWrap.setPointerCapture(e.pointerId);
|
|
83
97
|
let lastX = e.clientX,
|
|
84
98
|
lastY = e.clientY;
|
|
85
|
-
const onMove = (
|
|
99
|
+
const onMove = (ev: PointerEvent) => {
|
|
86
100
|
const { panX, panY, setPan, applyTransform } = getContext();
|
|
87
101
|
setPan(panX + (ev.clientX - lastX), panY + (ev.clientY - lastY));
|
|
88
102
|
lastX = ev.clientX;
|
|
@@ -187,8 +201,8 @@ export function initShortcuts(getContext) {
|
|
|
187
201
|
break;
|
|
188
202
|
case "z":
|
|
189
203
|
e.preventDefault();
|
|
190
|
-
if (e.shiftKey) tabRedo(activeTab.value);
|
|
191
|
-
else tabUndo(activeTab.value);
|
|
204
|
+
if (e.shiftKey) tabRedo(activeTab.value!);
|
|
205
|
+
else tabUndo(activeTab.value!);
|
|
192
206
|
break;
|
|
193
207
|
case "d":
|
|
194
208
|
e.preventDefault();
|
|
@@ -212,7 +226,7 @@ export function initShortcuts(getContext) {
|
|
|
212
226
|
case "0":
|
|
213
227
|
if (canvasMode === "edit") break;
|
|
214
228
|
e.preventDefault();
|
|
215
|
-
activeTab.value
|
|
229
|
+
activeTab.value!.session.ui.zoom = 1;
|
|
216
230
|
setPan(16, 16);
|
|
217
231
|
applyTransform();
|
|
218
232
|
break;
|
|
@@ -220,13 +234,13 @@ export function initShortcuts(getContext) {
|
|
|
220
234
|
case "+":
|
|
221
235
|
if (canvasMode === "edit") break;
|
|
222
236
|
e.preventDefault();
|
|
223
|
-
activeTab.value
|
|
237
|
+
activeTab.value!.session.ui.zoom = Math.min(5.0, (tab?.session.ui.zoom ?? 1) * 1.2);
|
|
224
238
|
applyTransform();
|
|
225
239
|
break;
|
|
226
240
|
case "-":
|
|
227
241
|
if (canvasMode === "edit") break;
|
|
228
242
|
e.preventDefault();
|
|
229
|
-
activeTab.value
|
|
243
|
+
activeTab.value!.session.ui.zoom = Math.max(0.05, (tab?.session.ui.zoom ?? 1) / 1.2);
|
|
230
244
|
applyTransform();
|
|
231
245
|
break;
|
|
232
246
|
}
|
|
@@ -243,13 +257,13 @@ export function initShortcuts(getContext) {
|
|
|
243
257
|
}
|
|
244
258
|
break;
|
|
245
259
|
case "Escape":
|
|
246
|
-
activeTab.value
|
|
260
|
+
activeTab.value!.session.selection = null;
|
|
247
261
|
break;
|
|
248
262
|
case "Enter":
|
|
249
263
|
if (tab?.session.selection && tab.session.selection.length >= 2) {
|
|
250
264
|
e.preventDefault();
|
|
251
|
-
const pp =
|
|
252
|
-
const idx =
|
|
265
|
+
const pp = parentElementPath(tab.session.selection) as JxPath;
|
|
266
|
+
const idx = childIndex(tab.session.selection) as number;
|
|
253
267
|
const newPath = [...pp, "children", idx + 1];
|
|
254
268
|
transactDoc(tab, (t) => {
|
|
255
269
|
mutateInsertNode(t, pp, idx + 1, { tagName: "p", textContent: "" });
|
|
@@ -269,7 +283,7 @@ export function initShortcuts(getContext) {
|
|
|
269
283
|
case "ArrowLeft":
|
|
270
284
|
e.preventDefault();
|
|
271
285
|
if (tab?.session.selection && tab.session.selection.length >= 2) {
|
|
272
|
-
activeTab.value
|
|
286
|
+
activeTab.value!.session.selection = parentElementPath(tab.session.selection);
|
|
273
287
|
}
|
|
274
288
|
break;
|
|
275
289
|
case "ArrowRight":
|
|
@@ -277,7 +291,7 @@ export function initShortcuts(getContext) {
|
|
|
277
291
|
if (tab?.session.selection) {
|
|
278
292
|
const node = getNodeAtPath(tab.doc.document, tab.session.selection);
|
|
279
293
|
if ((node?.children?.length ?? 0) > 0) {
|
|
280
|
-
activeTab.value
|
|
294
|
+
activeTab.value!.session.selection = [...tab.session.selection, "children", 0];
|
|
281
295
|
}
|
|
282
296
|
}
|
|
283
297
|
break;
|
|
@@ -287,8 +301,8 @@ export function initShortcuts(getContext) {
|
|
|
287
301
|
// Block ctrl+scroll (browser zoom) on all non-canvas areas
|
|
288
302
|
document.addEventListener(
|
|
289
303
|
"wheel",
|
|
290
|
-
(
|
|
291
|
-
if ((e.ctrlKey || e.metaKey) && !canvasWrap.contains(
|
|
304
|
+
(e: WheelEvent) => {
|
|
305
|
+
if ((e.ctrlKey || e.metaKey) && !canvasWrap.contains(e.target as Node)) {
|
|
292
306
|
e.preventDefault();
|
|
293
307
|
}
|
|
294
308
|
},
|
|
@@ -297,23 +311,23 @@ export function initShortcuts(getContext) {
|
|
|
297
311
|
}
|
|
298
312
|
|
|
299
313
|
/** @param {number} [direction] */
|
|
300
|
-
function navigateSelection(direction = -1) {
|
|
314
|
+
function navigateSelection(direction: number = -1) {
|
|
301
315
|
const tab = activeTab.value;
|
|
302
316
|
if (!tab?.session.selection) {
|
|
303
|
-
activeTab.value
|
|
317
|
+
activeTab.value!.session.selection = [];
|
|
304
318
|
return;
|
|
305
319
|
}
|
|
306
320
|
if (tab.session.selection.length < 2) return;
|
|
307
321
|
|
|
308
322
|
const parent = getNodeAtPath(
|
|
309
323
|
tab.doc.document,
|
|
310
|
-
|
|
324
|
+
parentElementPath(tab.session.selection) as JxPath,
|
|
311
325
|
);
|
|
312
|
-
const idx =
|
|
326
|
+
const idx = childIndex(tab.session.selection) as number;
|
|
313
327
|
const newIdx = idx + direction;
|
|
314
328
|
if (parent?.children && newIdx >= 0 && newIdx < parent.children.length) {
|
|
315
|
-
activeTab.value
|
|
316
|
-
|
|
329
|
+
activeTab.value!.session.selection = [
|
|
330
|
+
...(parentElementPath(tab.session.selection) as JxPath),
|
|
317
331
|
"children",
|
|
318
332
|
newIdx,
|
|
319
333
|
];
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
/// <reference lib="dom" />
|
|
1
2
|
/**
|
|
2
3
|
* Slash-menu.js — Shared slash command menu for element insertion
|
|
3
4
|
*
|
|
@@ -9,7 +10,13 @@
|
|
|
9
10
|
|
|
10
11
|
import { html, render as litRender, nothing } from "lit-html";
|
|
11
12
|
import { ref } from "lit-html/directives/ref.js";
|
|
12
|
-
import { getLayerSlot } from "../ui/layers
|
|
13
|
+
import { getLayerSlot } from "../ui/layers";
|
|
14
|
+
|
|
15
|
+
interface SlashCommand {
|
|
16
|
+
label: string;
|
|
17
|
+
tag: string;
|
|
18
|
+
description: string;
|
|
19
|
+
}
|
|
13
20
|
|
|
14
21
|
// ─── Commands ─────────────────────────────────────────────────────────────────
|
|
15
22
|
|
|
@@ -31,30 +38,20 @@ const SLASH_COMMANDS = [
|
|
|
31
38
|
{ label: "Section", tag: "section", description: "Section container" },
|
|
32
39
|
];
|
|
33
40
|
|
|
34
|
-
/** @typedef {{ label: string; tag: string; description: string }} SlashCommand */
|
|
35
|
-
|
|
36
41
|
// ─── State ────────────────────────────────────────────────────────────────────
|
|
37
42
|
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
* } | null}
|
|
44
|
-
*/
|
|
45
|
-
let callbacks = null;
|
|
43
|
+
let callbacks: {
|
|
44
|
+
onSelect: (cmd: SlashCommand) => void;
|
|
45
|
+
showFilter?: boolean;
|
|
46
|
+
commands?: SlashCommand[];
|
|
47
|
+
} | null = null;
|
|
46
48
|
let activeIdx = 0;
|
|
47
|
-
|
|
48
|
-
let filteredItems = [];
|
|
49
|
+
let filteredItems: SlashCommand[] = [];
|
|
49
50
|
let open = false;
|
|
50
|
-
|
|
51
|
-
let
|
|
52
|
-
|
|
53
|
-
let
|
|
54
|
-
/** @type {HTMLInputElement | null} */
|
|
55
|
-
let _filterEl = null;
|
|
56
|
-
/** @type {HTMLElement | null} */
|
|
57
|
-
let _popoverEl = null;
|
|
51
|
+
let _anchorEl: HTMLElement | null = null;
|
|
52
|
+
let _anchorRect: DOMRect | null = null;
|
|
53
|
+
let _filterEl: HTMLInputElement | null = null;
|
|
54
|
+
let _popoverEl: HTMLElement | null = null;
|
|
58
55
|
|
|
59
56
|
/** @returns {HTMLElement} */
|
|
60
57
|
function getHost() {
|
|
@@ -79,7 +76,11 @@ export function isSlashMenuOpen() {
|
|
|
79
76
|
* commands?: SlashCommand[];
|
|
80
77
|
* }} cbs
|
|
81
78
|
*/
|
|
82
|
-
export function showSlashMenu(
|
|
79
|
+
export function showSlashMenu(
|
|
80
|
+
anchorEl: HTMLElement,
|
|
81
|
+
filter: string,
|
|
82
|
+
cbs: { onSelect: (cmd: SlashCommand) => void; showFilter?: boolean; commands?: SlashCommand[] },
|
|
83
|
+
) {
|
|
83
84
|
callbacks = cbs;
|
|
84
85
|
_anchorEl = anchorEl;
|
|
85
86
|
_anchorRect = anchorEl.getBoundingClientRect();
|
|
@@ -135,7 +136,7 @@ export function dismissSlashMenu() {
|
|
|
135
136
|
* @param {HTMLElement} anchorEl
|
|
136
137
|
* @param {boolean} showFilter
|
|
137
138
|
*/
|
|
138
|
-
function render(anchorEl, showFilter) {
|
|
139
|
+
function render(anchorEl: HTMLElement, showFilter: boolean) {
|
|
139
140
|
const rect = _anchorRect || anchorEl.getBoundingClientRect();
|
|
140
141
|
|
|
141
142
|
litRender(
|
|
@@ -143,7 +144,7 @@ function render(anchorEl, showFilter) {
|
|
|
143
144
|
<sp-popover
|
|
144
145
|
open
|
|
145
146
|
${ref((el) => {
|
|
146
|
-
_popoverEl =
|
|
147
|
+
_popoverEl = (el as HTMLElement | undefined) || null;
|
|
147
148
|
})}
|
|
148
149
|
style="position:fixed;left:${rect.left}px;top:${rect.bottom +
|
|
149
150
|
4}px;z-index:9999;max-height:320px;overflow-y:auto"
|
|
@@ -156,7 +157,7 @@ function render(anchorEl, showFilter) {
|
|
|
156
157
|
autocomplete="off"
|
|
157
158
|
style="display:block;width:100%;box-sizing:border-box;padding:6px 10px;border:none;border-bottom:1px solid var(--border, #444);outline:none;font-size:13px;background:transparent;color:inherit"
|
|
158
159
|
${ref((el) => {
|
|
159
|
-
_filterEl =
|
|
160
|
+
_filterEl = (el as HTMLInputElement | undefined) || null;
|
|
160
161
|
})}
|
|
161
162
|
@input=${onFilterInput}
|
|
162
163
|
/>`
|
|
@@ -167,7 +168,7 @@ function render(anchorEl, showFilter) {
|
|
|
167
168
|
(cmd, i) => html`
|
|
168
169
|
<sp-menu-item
|
|
169
170
|
?focused=${i === 0}
|
|
170
|
-
@click=${(
|
|
171
|
+
@click=${(e: Event) => {
|
|
171
172
|
e.preventDefault();
|
|
172
173
|
e.stopPropagation();
|
|
173
174
|
select(cmd);
|
|
@@ -189,22 +190,22 @@ function render(anchorEl, showFilter) {
|
|
|
189
190
|
}
|
|
190
191
|
|
|
191
192
|
/** @param {MouseEvent} e */
|
|
192
|
-
function onOutsideClick(e) {
|
|
193
|
-
if (_popoverEl && !_popoverEl.contains(
|
|
193
|
+
function onOutsideClick(e: MouseEvent) {
|
|
194
|
+
if (_popoverEl && !_popoverEl.contains(e.target as Node)) {
|
|
194
195
|
dismissSlashMenu();
|
|
195
196
|
}
|
|
196
197
|
}
|
|
197
198
|
|
|
198
199
|
/** @param {SlashCommand} cmd */
|
|
199
|
-
function select(cmd) {
|
|
200
|
+
function select(cmd: SlashCommand) {
|
|
200
201
|
const cbs = callbacks;
|
|
201
202
|
dismissSlashMenu();
|
|
202
203
|
cbs?.onSelect(cmd);
|
|
203
204
|
}
|
|
204
205
|
|
|
205
206
|
/** @param {Event} e */
|
|
206
|
-
function onFilterInput(e) {
|
|
207
|
-
const input =
|
|
207
|
+
function onFilterInput(e: Event) {
|
|
208
|
+
const input = e.target as HTMLInputElement;
|
|
208
209
|
const filter = input.value.toLowerCase();
|
|
209
210
|
|
|
210
211
|
const source = callbacks?.commands || SLASH_COMMANDS;
|
|
@@ -227,12 +228,10 @@ function onFilterInput(e) {
|
|
|
227
228
|
}
|
|
228
229
|
|
|
229
230
|
/** @param {KeyboardEvent} e */
|
|
230
|
-
function onKeydown(e) {
|
|
231
|
+
function onKeydown(e: KeyboardEvent) {
|
|
231
232
|
if (!open) return;
|
|
232
233
|
|
|
233
|
-
const items =
|
|
234
|
-
getHost().querySelectorAll("sp-menu-item:not([disabled])")
|
|
235
|
-
);
|
|
234
|
+
const items = getHost().querySelectorAll("sp-menu-item:not([disabled])") as NodeListOf<Element>;
|
|
236
235
|
|
|
237
236
|
if (e.key === "ArrowDown") {
|
|
238
237
|
e.preventDefault();
|
|
@@ -1,21 +1,18 @@
|
|
|
1
1
|
/** Component registry — cached list of project components discovered via the platform. */
|
|
2
2
|
|
|
3
|
-
import { getPlatform } from "../platform
|
|
4
|
-
import { projectState } from "../store
|
|
3
|
+
import { getPlatform } from "../platform";
|
|
4
|
+
import { projectState } from "../store";
|
|
5
5
|
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
* }} ComponentEntry
|
|
15
|
-
*/
|
|
6
|
+
export interface ComponentEntry {
|
|
7
|
+
tagName: string;
|
|
8
|
+
path: string;
|
|
9
|
+
props?: { name: string; type?: string; [k: string]: unknown }[];
|
|
10
|
+
source?: string;
|
|
11
|
+
package?: string;
|
|
12
|
+
modulePath?: string;
|
|
13
|
+
}
|
|
16
14
|
|
|
17
|
-
|
|
18
|
-
export let componentRegistry = [];
|
|
15
|
+
export let componentRegistry: ComponentEntry[] = [];
|
|
19
16
|
export let _componentRegistryLoaded = false;
|
|
20
17
|
|
|
21
18
|
export async function loadComponentRegistry() {
|
|
@@ -32,7 +29,7 @@ export async function loadComponentRegistry() {
|
|
|
32
29
|
* @param {string | null} fromDocPath
|
|
33
30
|
* @param {string} toCompPath
|
|
34
31
|
*/
|
|
35
|
-
export function computeRelativePath(fromDocPath, toCompPath) {
|
|
32
|
+
export function computeRelativePath(fromDocPath: string | null, toCompPath: string) {
|
|
36
33
|
if (!fromDocPath) return `./${toCompPath}`;
|
|
37
34
|
const from = fromDocPath.replaceAll("\\", "/");
|
|
38
35
|
const to = toCompPath.replaceAll("\\", "/");
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
/// <reference lib="dom" />
|
|
1
2
|
/**
|
|
2
3
|
* File Operations — open, save, export documents.
|
|
3
4
|
*
|
|
@@ -9,19 +10,21 @@ import remarkStringify from "remark-stringify";
|
|
|
9
10
|
import remarkDirective from "remark-directive";
|
|
10
11
|
import remarkGfm from "remark-gfm";
|
|
11
12
|
import { stringify as stringifyYaml } from "yaml";
|
|
12
|
-
import { jxToMd, jxDocToMd } from "../markdown/md-convert
|
|
13
|
-
import { locateDocument } from "../services/code-services
|
|
14
|
-
import { statusMessage } from "../panels/statusbar
|
|
15
|
-
import { getPlatform } from "../platform
|
|
16
|
-
import { activeTab, openTab } from "../workspace/workspace
|
|
17
|
-
import { isEditing, stopEditing } from "../editor/inline-edit
|
|
13
|
+
import { jxToMd, jxDocToMd } from "../markdown/md-convert";
|
|
14
|
+
import { locateDocument } from "../services/code-services";
|
|
15
|
+
import { statusMessage } from "../panels/statusbar";
|
|
16
|
+
import { getPlatform } from "../platform";
|
|
17
|
+
import { activeTab, openTab } from "../workspace/workspace";
|
|
18
|
+
import { isEditing, stopEditing } from "../editor/inline-edit";
|
|
19
|
+
|
|
20
|
+
import type { JxElement, JxMutableNode } from "@jxsuite/schema/types";
|
|
18
21
|
|
|
19
22
|
/** Open a file via the File System Access API (or fallback input). */
|
|
20
23
|
export async function openFile() {
|
|
21
24
|
try {
|
|
22
25
|
if ("showOpenFilePicker" in window) {
|
|
23
|
-
const [handle] = await
|
|
24
|
-
|
|
26
|
+
const [handle] = await (
|
|
27
|
+
window as unknown as { showOpenFilePicker: Function }
|
|
25
28
|
).showOpenFilePicker({
|
|
26
29
|
types: [
|
|
27
30
|
{ description: "Jx Component", accept: { "application/json": [".json"] } },
|
|
@@ -70,9 +73,8 @@ export async function openFile() {
|
|
|
70
73
|
};
|
|
71
74
|
input.click();
|
|
72
75
|
}
|
|
73
|
-
} catch (
|
|
74
|
-
if (
|
|
75
|
-
statusMessage(`Error: ${/** @type {Error} */ (e).message}`);
|
|
76
|
+
} catch (e) {
|
|
77
|
+
if ((e as Error).name !== "AbortError") statusMessage(`Error: ${(e as Error).message}`);
|
|
76
78
|
}
|
|
77
79
|
}
|
|
78
80
|
|
|
@@ -82,9 +84,9 @@ export async function openFile() {
|
|
|
82
84
|
* @param {string} source Markdown text
|
|
83
85
|
* @returns {Promise<{ document: JxMutableNode; frontmatter: Record<string, unknown> }>}
|
|
84
86
|
*/
|
|
85
|
-
export async function loadMarkdown(source) {
|
|
87
|
+
export async function loadMarkdown(source: string) {
|
|
86
88
|
const { transpileJxMarkdown } = await import("@jxsuite/parser/transpile");
|
|
87
|
-
const doc =
|
|
89
|
+
const doc = transpileJxMarkdown(source) as JxMutableNode;
|
|
88
90
|
|
|
89
91
|
const isComponent = doc.tagName && String(doc.tagName).includes("-");
|
|
90
92
|
|
|
@@ -95,12 +97,18 @@ export async function loadMarkdown(source) {
|
|
|
95
97
|
// Content markdown — children form the root-level document body
|
|
96
98
|
const children = doc.children ?? [];
|
|
97
99
|
if (children.length === 0) children.push({ tagName: "p", children: [] });
|
|
98
|
-
const contentDoc = { children };
|
|
99
100
|
|
|
100
|
-
|
|
101
|
-
const
|
|
101
|
+
const documentKeys = new Set(["state", "imports"]);
|
|
102
|
+
const contentDoc: Record<string, unknown> = { children };
|
|
103
|
+
|
|
104
|
+
const frontmatter: Record<string, unknown> = {};
|
|
102
105
|
for (const [key, value] of Object.entries(doc)) {
|
|
103
|
-
if (key
|
|
106
|
+
if (key === "children") continue;
|
|
107
|
+
if (documentKeys.has(key)) {
|
|
108
|
+
contentDoc[key] = value;
|
|
109
|
+
} else {
|
|
110
|
+
frontmatter[key] = value;
|
|
111
|
+
}
|
|
104
112
|
}
|
|
105
113
|
|
|
106
114
|
return { document: contentDoc, frontmatter };
|
|
@@ -136,9 +144,8 @@ export async function saveFile() {
|
|
|
136
144
|
} else {
|
|
137
145
|
statusMessage("No save target — use Export");
|
|
138
146
|
}
|
|
139
|
-
} catch (
|
|
140
|
-
if (
|
|
141
|
-
statusMessage(`Save error: ${/** @type {Error} */ (e).message}`);
|
|
147
|
+
} catch (e) {
|
|
148
|
+
if ((e as Error).name !== "AbortError") statusMessage(`Save error: ${(e as Error).message}`);
|
|
142
149
|
}
|
|
143
150
|
}
|
|
144
151
|
|
|
@@ -159,8 +166,8 @@ export async function exportFile() {
|
|
|
159
166
|
: isContent
|
|
160
167
|
? "content.md"
|
|
161
168
|
: "component.json";
|
|
162
|
-
const handle = await
|
|
163
|
-
|
|
169
|
+
const handle = await (
|
|
170
|
+
window as unknown as { showSaveFilePicker: Function }
|
|
164
171
|
).showSaveFilePicker({
|
|
165
172
|
suggestedName,
|
|
166
173
|
types: [{ description, accept: { [mimeType]: [ext] } }],
|
|
@@ -182,9 +189,8 @@ export async function exportFile() {
|
|
|
182
189
|
tab.doc.dirty = false;
|
|
183
190
|
statusMessage("Downloaded");
|
|
184
191
|
}
|
|
185
|
-
} catch (
|
|
186
|
-
if (
|
|
187
|
-
statusMessage(`Export error: ${/** @type {Error} */ (e).message}`);
|
|
192
|
+
} catch (e) {
|
|
193
|
+
if ((e as Error).name !== "AbortError") statusMessage(`Export error: ${(e as Error).message}`);
|
|
188
194
|
}
|
|
189
195
|
}
|
|
190
196
|
|
|
@@ -194,19 +200,20 @@ export async function exportFile() {
|
|
|
194
200
|
* @param {import("../tabs/tab.js").Tab} tab
|
|
195
201
|
* @returns {string}
|
|
196
202
|
*/
|
|
197
|
-
export function serializeDocument(tab) {
|
|
203
|
+
export function serializeDocument(tab: import("../tabs/tab.js").Tab) {
|
|
198
204
|
if (tab.doc.sourceFormat === "md") {
|
|
199
205
|
const fm = tab.doc.content?.frontmatter || {};
|
|
200
|
-
const
|
|
206
|
+
const doc = tab.doc.document;
|
|
207
|
+
const fullDoc = { ...fm, ...doc, children: doc.children ?? [] };
|
|
201
208
|
return jxDocToMd(/** @type {JxMutableNode} */ (fullDoc));
|
|
202
209
|
}
|
|
203
210
|
if (tab.doc.mode === "content") {
|
|
204
|
-
const mdast = jxToMd(
|
|
211
|
+
const mdast = jxToMd(tab.doc.document as JxElement);
|
|
205
212
|
const md = unified()
|
|
206
213
|
.use(remarkGfm)
|
|
207
214
|
.use(remarkDirective)
|
|
208
215
|
.use(remarkStringify, { bullet: "-", emphasis: "*", strong: "*" })
|
|
209
|
-
.stringify(
|
|
216
|
+
.stringify(mdast as unknown as import("mdast").Root);
|
|
210
217
|
const fm = tab.doc.content?.frontmatter;
|
|
211
218
|
const hasFrontmatter = fm && Object.keys(fm).length > 0;
|
|
212
219
|
return hasFrontmatter ? `---\n${stringifyYaml(fm).trim()}\n---\n\n${md}` : md;
|