@jxsuite/studio 0.23.1 → 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 +2231 -715
- 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} +110 -85
- 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} +38 -26
- 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
|
* Site context helpers - merge site-level definitions with file-level.
|
|
3
4
|
*
|
|
@@ -5,8 +6,16 @@
|
|
|
5
6
|
* definitions merge on top (file wins on conflict).
|
|
6
7
|
*/
|
|
7
8
|
|
|
8
|
-
import { projectState, setProjectState, requireProjectState } from "./store
|
|
9
|
-
import { getPlatform } from "./platform
|
|
9
|
+
import { projectState, setProjectState, requireProjectState } from "./store";
|
|
10
|
+
import { getPlatform } from "./platform";
|
|
11
|
+
|
|
12
|
+
import type {
|
|
13
|
+
JxElement,
|
|
14
|
+
JxMutableNode,
|
|
15
|
+
JxStyle,
|
|
16
|
+
JxHeadEntry,
|
|
17
|
+
ProjectConfig,
|
|
18
|
+
} from "@jxsuite/schema/types";
|
|
10
19
|
|
|
11
20
|
/**
|
|
12
21
|
* Merge site $media with document $media. Document keys win on conflict.
|
|
@@ -15,7 +24,7 @@ import { getPlatform } from "./platform.js";
|
|
|
15
24
|
* undefined)
|
|
16
25
|
* @returns {Record<string, string>}
|
|
17
26
|
*/
|
|
18
|
-
export function getEffectiveMedia(docMedia) {
|
|
27
|
+
export function getEffectiveMedia(docMedia: Record<string, string> | undefined) {
|
|
19
28
|
const siteMedia = projectState?.projectConfig?.$media;
|
|
20
29
|
if (!siteMedia) return docMedia || {};
|
|
21
30
|
if (!docMedia) return { ...siteMedia };
|
|
@@ -29,7 +38,7 @@ export function getEffectiveMedia(docMedia) {
|
|
|
29
38
|
* @param {JxStyle | undefined} docStyle - The current document's style (may be undefined)
|
|
30
39
|
* @returns {JxStyle}
|
|
31
40
|
*/
|
|
32
|
-
export function getEffectiveStyle(docStyle) {
|
|
41
|
+
export function getEffectiveStyle(docStyle: JxStyle | undefined) {
|
|
33
42
|
const siteStyle = projectState?.projectConfig?.style;
|
|
34
43
|
if (!siteStyle) return docStyle || {};
|
|
35
44
|
if (!docStyle) return { ...siteStyle };
|
|
@@ -41,7 +50,7 @@ export function getEffectiveStyle(docStyle) {
|
|
|
41
50
|
typeof merged[k] === "object" &&
|
|
42
51
|
merged[k] !== null
|
|
43
52
|
) {
|
|
44
|
-
merged[k] = {
|
|
53
|
+
merged[k] = { ...(merged[k] as JxStyle), ...(v as JxStyle) };
|
|
45
54
|
} else {
|
|
46
55
|
merged[k] = v;
|
|
47
56
|
}
|
|
@@ -56,7 +65,7 @@ export function getEffectiveStyle(docStyle) {
|
|
|
56
65
|
* undefined)
|
|
57
66
|
* @returns {Record<string, string>}
|
|
58
67
|
*/
|
|
59
|
-
export function getEffectiveImports(docImports) {
|
|
68
|
+
export function getEffectiveImports(docImports: Record<string, string> | undefined) {
|
|
60
69
|
const siteImports = projectState?.projectConfig?.imports;
|
|
61
70
|
if (!siteImports) return docImports || {};
|
|
62
71
|
if (!docImports) return { ...siteImports };
|
|
@@ -70,14 +79,12 @@ export function getEffectiveImports(docImports) {
|
|
|
70
79
|
* undefined)
|
|
71
80
|
* @returns {(JxElement | string)[]}
|
|
72
81
|
*/
|
|
73
|
-
export function getEffectiveElements(docElements) {
|
|
82
|
+
export function getEffectiveElements(docElements?: (JxElement | string)[]) {
|
|
74
83
|
const siteElements = projectState?.projectConfig?.$elements;
|
|
75
84
|
if (!siteElements?.length) return docElements || [];
|
|
76
85
|
if (!docElements?.length) return [...siteElements];
|
|
77
|
-
|
|
78
|
-
const
|
|
79
|
-
/** @type {(JxElement | string)[]} */
|
|
80
|
-
const merged = [];
|
|
86
|
+
const seen: Set<string> = new Set();
|
|
87
|
+
const merged: (JxElement | string)[] = [];
|
|
81
88
|
for (const entry of [...siteElements, ...docElements]) {
|
|
82
89
|
const key = typeof entry === "string" ? entry : entry?.$ref;
|
|
83
90
|
if (key && !seen.has(key)) {
|
|
@@ -94,14 +101,12 @@ export function getEffectiveElements(docElements) {
|
|
|
94
101
|
* @param {JxHeadEntry[]} [docHead] - The current document's $head (may be undefined)
|
|
95
102
|
* @returns {JxHeadEntry[]}
|
|
96
103
|
*/
|
|
97
|
-
export function getEffectiveHead(docHead) {
|
|
104
|
+
export function getEffectiveHead(docHead?: JxHeadEntry[]) {
|
|
98
105
|
const siteHead = projectState?.projectConfig?.$head;
|
|
99
106
|
if (!siteHead?.length) return docHead || [];
|
|
100
107
|
if (!docHead?.length) return [...siteHead];
|
|
101
|
-
|
|
102
|
-
const
|
|
103
|
-
/** @type {JxHeadEntry[]} */
|
|
104
|
-
const merged = [];
|
|
108
|
+
const seen: Set<string> = new Set();
|
|
109
|
+
const merged: JxHeadEntry[] = [];
|
|
105
110
|
for (const entry of [...siteHead, ...docHead]) {
|
|
106
111
|
const key =
|
|
107
112
|
String(entry?.attributes?.href || entry?.attributes?.src || "") || JSON.stringify(entry);
|
|
@@ -115,8 +120,7 @@ export function getEffectiveHead(docHead) {
|
|
|
115
120
|
|
|
116
121
|
// ─── Layout resolution ──────────────────────────────────────────────────────
|
|
117
122
|
|
|
118
|
-
|
|
119
|
-
const layoutCache = new Map();
|
|
123
|
+
const layoutCache: Map<string, JxMutableNode> = new Map();
|
|
120
124
|
|
|
121
125
|
export function invalidateLayoutCache() {
|
|
122
126
|
layoutCache.clear();
|
|
@@ -129,7 +133,7 @@ export function invalidateLayoutCache() {
|
|
|
129
133
|
* or undefined)
|
|
130
134
|
* @returns {string | null} The layout path, or null if no layout applies
|
|
131
135
|
*/
|
|
132
|
-
export function getEffectiveLayoutPath(docLayout) {
|
|
136
|
+
export function getEffectiveLayoutPath(docLayout: string | false | undefined) {
|
|
133
137
|
if (docLayout === false) return null;
|
|
134
138
|
const defaultLayout = projectState?.projectConfig?.defaults?.layout;
|
|
135
139
|
return docLayout || defaultLayout || null;
|
|
@@ -141,10 +145,10 @@ export function getEffectiveLayoutPath(docLayout) {
|
|
|
141
145
|
* @param {string} layoutPath - Relative path to the layout file (e.g., "./layouts/base.json")
|
|
142
146
|
* @returns {Promise<JxMutableNode | null>} The parsed layout document, or null on failure
|
|
143
147
|
*/
|
|
144
|
-
export async function resolveLayoutDoc(layoutPath) {
|
|
148
|
+
export async function resolveLayoutDoc(layoutPath: string) {
|
|
145
149
|
const normalized = layoutPath.replace(/^\.\//, "");
|
|
146
150
|
if (layoutCache.has(normalized))
|
|
147
|
-
return structuredClone(
|
|
151
|
+
return structuredClone(layoutCache.get(normalized) as JxMutableNode);
|
|
148
152
|
|
|
149
153
|
try {
|
|
150
154
|
const platform = getPlatform();
|
|
@@ -165,7 +169,7 @@ export async function resolveLayoutDoc(layoutPath) {
|
|
|
165
169
|
* @param {JxMutableNode} pageDoc - The page document
|
|
166
170
|
* @returns {JxMutableNode} The merged document
|
|
167
171
|
*/
|
|
168
|
-
export function distributePageIntoLayout(layoutDoc, pageDoc) {
|
|
172
|
+
export function distributePageIntoLayout(layoutDoc: JxMutableNode, pageDoc: JxMutableNode) {
|
|
169
173
|
const pageChildren = pageDoc.children ?? [];
|
|
170
174
|
const children = typeof pageChildren === "string" ? [pageChildren] : pageChildren;
|
|
171
175
|
|
|
@@ -193,20 +197,19 @@ export function distributePageIntoLayout(layoutDoc, pageDoc) {
|
|
|
193
197
|
}
|
|
194
198
|
|
|
195
199
|
function fillSlots(
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
200
|
+
node: JxMutableNode,
|
|
201
|
+
named: Map<string, JxMutableNode[]>,
|
|
202
|
+
defaults: (JxMutableNode | string)[],
|
|
199
203
|
) {
|
|
200
204
|
if (!node || typeof node !== "object") return;
|
|
201
205
|
if (!Array.isArray(node.children)) return;
|
|
202
206
|
|
|
203
|
-
|
|
204
|
-
const newChildren = [];
|
|
207
|
+
const newChildren: (JxMutableNode | string)[] = [];
|
|
205
208
|
for (const child of node.children) {
|
|
206
209
|
if (child && typeof child === "object" && child.tagName === "slot") {
|
|
207
210
|
const slotName = child.attributes?.name;
|
|
208
211
|
if (slotName && named.has(slotName)) {
|
|
209
|
-
newChildren.push(
|
|
212
|
+
newChildren.push(...(named.get(slotName) as JxMutableNode[]));
|
|
210
213
|
named.delete(slotName);
|
|
211
214
|
} else if (!slotName && defaults.length > 0) {
|
|
212
215
|
newChildren.push(...defaults);
|
|
@@ -218,7 +221,7 @@ function fillSlots(
|
|
|
218
221
|
newChildren.push(child);
|
|
219
222
|
}
|
|
220
223
|
}
|
|
221
|
-
node.children =
|
|
224
|
+
node.children = newChildren as JxMutableNode[];
|
|
222
225
|
}
|
|
223
226
|
|
|
224
227
|
/**
|
|
@@ -226,12 +229,12 @@ function fillSlots(
|
|
|
226
229
|
*
|
|
227
230
|
* @param {Partial<ProjectConfig>} patch - Fields to merge into the current projectConfig
|
|
228
231
|
*/
|
|
229
|
-
export async function updateSiteConfig(patch) {
|
|
232
|
+
export async function updateSiteConfig(patch: Partial<ProjectConfig>) {
|
|
230
233
|
const platform = getPlatform();
|
|
231
|
-
const config =
|
|
234
|
+
const config = {
|
|
232
235
|
...requireProjectState().projectConfig,
|
|
233
236
|
...patch,
|
|
234
|
-
}
|
|
237
|
+
} as ProjectConfig;
|
|
235
238
|
await platform.writeFile("project.json", JSON.stringify(config, null, 2));
|
|
236
239
|
setProjectState({ ...requireProjectState(), projectConfig: config });
|
|
237
240
|
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
/// <reference lib="dom" />
|
|
1
2
|
/**
|
|
2
3
|
* State.js — Builder state model and mutation API
|
|
3
4
|
*
|
|
@@ -8,39 +9,43 @@
|
|
|
8
9
|
* = third child of first child
|
|
9
10
|
*/
|
|
10
11
|
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
12
|
+
import type { ProjectState } from "./types";
|
|
13
|
+
import type { JxMutableNode } from "@jxsuite/schema/types";
|
|
14
|
+
|
|
15
|
+
export type JxPath = (string | number)[];
|
|
16
|
+
|
|
17
|
+
interface HistorySnapshot {
|
|
18
|
+
document: JxMutableNode;
|
|
19
|
+
selection: JxPath | null;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export interface StudioState {
|
|
23
|
+
document: JxMutableNode;
|
|
24
|
+
selection: JxPath | null;
|
|
25
|
+
hover: JxPath | null;
|
|
26
|
+
history: HistorySnapshot[];
|
|
27
|
+
historyIndex: number;
|
|
28
|
+
dirty: boolean;
|
|
29
|
+
fileHandle: FileSystemFileHandle | null;
|
|
30
|
+
documentPath: string | null;
|
|
31
|
+
documentStack: StudioStackFrame[];
|
|
32
|
+
handlersSource: string | null;
|
|
33
|
+
mode: string;
|
|
34
|
+
content: { frontmatter: Record<string, unknown> };
|
|
35
|
+
ui: Record<string, unknown>;
|
|
36
|
+
canvas: { status: string; scope: Record<string, unknown> | null; error: string | null };
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
interface StudioStackFrame {
|
|
40
|
+
document: JxMutableNode;
|
|
41
|
+
selection: JxPath | null;
|
|
42
|
+
fileHandle: FileSystemFileHandle | null;
|
|
43
|
+
documentPath: string | null;
|
|
44
|
+
dirty: boolean;
|
|
45
|
+
history: HistorySnapshot[];
|
|
46
|
+
historyIndex: number;
|
|
47
|
+
mode: string;
|
|
48
|
+
}
|
|
44
49
|
|
|
45
50
|
// ─── Path utilities ───────────────────────────────────────────────────────────
|
|
46
51
|
|
|
@@ -51,10 +56,10 @@
|
|
|
51
56
|
* @param {JxPath} path
|
|
52
57
|
* @returns {JxMutableNode}
|
|
53
58
|
*/
|
|
54
|
-
export function getNodeAtPath(doc, path) {
|
|
59
|
+
export function getNodeAtPath(doc: JxMutableNode, path: JxPath) {
|
|
55
60
|
let node = doc;
|
|
56
61
|
for (const key of path) {
|
|
57
|
-
if (node == null) return
|
|
62
|
+
if (node == null) return undefined as unknown as JxMutableNode;
|
|
58
63
|
node = node[key];
|
|
59
64
|
}
|
|
60
65
|
return node;
|
|
@@ -66,7 +71,7 @@ export function getNodeAtPath(doc, path) {
|
|
|
66
71
|
* @param {JxPath} path
|
|
67
72
|
* @returns {JxPath | null}
|
|
68
73
|
*/
|
|
69
|
-
export function parentElementPath(path) {
|
|
74
|
+
export function parentElementPath(path: JxPath) {
|
|
70
75
|
return path.length >= 2 ? path.slice(0, -2) : null;
|
|
71
76
|
}
|
|
72
77
|
|
|
@@ -76,7 +81,7 @@ export function parentElementPath(path) {
|
|
|
76
81
|
* @param {JxPath} path
|
|
77
82
|
* @returns {string | number}
|
|
78
83
|
*/
|
|
79
|
-
export function childIndex(path) {
|
|
84
|
+
export function childIndex(path: JxPath) {
|
|
80
85
|
return path[path.length - 1];
|
|
81
86
|
}
|
|
82
87
|
|
|
@@ -86,7 +91,7 @@ export function childIndex(path) {
|
|
|
86
91
|
* @param {JxPath} path
|
|
87
92
|
* @returns {string}
|
|
88
93
|
*/
|
|
89
|
-
export function pathKey(path) {
|
|
94
|
+
export function pathKey(path: JxPath) {
|
|
90
95
|
return path.join("/");
|
|
91
96
|
}
|
|
92
97
|
|
|
@@ -97,7 +102,7 @@ export function pathKey(path) {
|
|
|
97
102
|
* @param {JxPath | null} b
|
|
98
103
|
* @returns {boolean}
|
|
99
104
|
*/
|
|
100
|
-
export function pathsEqual(a, b) {
|
|
105
|
+
export function pathsEqual(a: JxPath | null, b: JxPath | null) {
|
|
101
106
|
if (a === b) return true;
|
|
102
107
|
if (!a || !b || a.length !== b.length) return false;
|
|
103
108
|
return a.every((v, i) => v === b[i]);
|
|
@@ -110,7 +115,7 @@ export function pathsEqual(a, b) {
|
|
|
110
115
|
* @param {JxPath} descendant
|
|
111
116
|
* @returns {boolean}
|
|
112
117
|
*/
|
|
113
|
-
export function isAncestor(path, descendant) {
|
|
118
|
+
export function isAncestor(path: JxPath, descendant: JxPath) {
|
|
114
119
|
if (path.length > descendant.length) return false;
|
|
115
120
|
return path.every((v, i) => v === descendant[i]);
|
|
116
121
|
}
|
|
@@ -133,21 +138,24 @@ export function isAncestor(path, descendant) {
|
|
|
133
138
|
* nodeType: string;
|
|
134
139
|
* }[]}
|
|
135
140
|
*/
|
|
136
|
-
export
|
|
141
|
+
export type FlatRow = {
|
|
142
|
+
node: JxMutableNode | string | number | boolean;
|
|
143
|
+
path: JxPath;
|
|
144
|
+
depth: number;
|
|
145
|
+
nodeType: string;
|
|
146
|
+
};
|
|
147
|
+
|
|
148
|
+
export function flattenTree(
|
|
149
|
+
doc: JxMutableNode | string | number | boolean,
|
|
150
|
+
path: JxPath = [],
|
|
151
|
+
depth: number = 0,
|
|
152
|
+
): FlatRow[] {
|
|
137
153
|
// Text node children: bare primitives get a "text" row
|
|
138
154
|
if (typeof doc === "string" || typeof doc === "number" || typeof doc === "boolean") {
|
|
139
155
|
return [{ node: doc, path, depth, nodeType: "text" }];
|
|
140
156
|
}
|
|
141
157
|
|
|
142
|
-
|
|
143
|
-
* @type {{
|
|
144
|
-
* node: JxMutableNode | string | number | boolean;
|
|
145
|
-
* path: JxPath;
|
|
146
|
-
* depth: number;
|
|
147
|
-
* nodeType: string;
|
|
148
|
-
* }[]}
|
|
149
|
-
*/
|
|
150
|
-
const rows = [{ node: doc, path, depth, nodeType: "element" }];
|
|
158
|
+
const rows: FlatRow[] = [{ node: doc, path, depth, nodeType: "element" }];
|
|
151
159
|
|
|
152
160
|
// Custom component instances without user-authored children are atomic in the layer tree
|
|
153
161
|
if (doc.$props && (doc.tagName || "").includes("-") && !Array.isArray(doc.children)) {
|
|
@@ -164,19 +172,18 @@ export function flattenTree(doc, path = [], depth = 0) {
|
|
|
164
172
|
} else if (
|
|
165
173
|
children &&
|
|
166
174
|
typeof children === "object" &&
|
|
167
|
-
|
|
175
|
+
(children as JxMutableNode).$prototype === "Array"
|
|
168
176
|
) {
|
|
169
177
|
// $map — emit the map container, then recurse into the template
|
|
170
|
-
rows.push({
|
|
171
|
-
|
|
178
|
+
rows.push({
|
|
179
|
+
node: children as JxMutableNode,
|
|
180
|
+
path: [...path, "children"],
|
|
181
|
+
depth: depth + 1,
|
|
182
|
+
nodeType: "map",
|
|
183
|
+
});
|
|
184
|
+
const mapDef = (children as JxMutableNode).map;
|
|
172
185
|
if (mapDef && typeof mapDef === "object") {
|
|
173
|
-
rows.push(
|
|
174
|
-
...flattenTree(
|
|
175
|
-
/** @type {JxMutableNode} */ (mapDef),
|
|
176
|
-
[...path, "children", "map"],
|
|
177
|
-
depth + 2,
|
|
178
|
-
),
|
|
179
|
-
);
|
|
186
|
+
rows.push(...flattenTree(mapDef as JxMutableNode, [...path, "children", "map"], depth + 2));
|
|
180
187
|
}
|
|
181
188
|
}
|
|
182
189
|
|
|
@@ -184,12 +191,22 @@ export function flattenTree(doc, path = [], depth = 0) {
|
|
|
184
191
|
if (doc.$switch && doc.cases && typeof doc.cases === "object") {
|
|
185
192
|
for (const [caseName, caseDef] of Object.entries(doc.cases)) {
|
|
186
193
|
const casePath = [...path, "cases", caseName];
|
|
187
|
-
if (caseDef && typeof caseDef === "object" &&
|
|
188
|
-
rows.push({
|
|
194
|
+
if (caseDef && typeof caseDef === "object" && (caseDef as JxMutableNode).$ref) {
|
|
195
|
+
rows.push({
|
|
196
|
+
node: caseDef as JxMutableNode,
|
|
197
|
+
path: casePath,
|
|
198
|
+
depth: depth + 1,
|
|
199
|
+
nodeType: "case-ref",
|
|
200
|
+
});
|
|
189
201
|
} else if (caseDef && typeof caseDef === "object") {
|
|
190
|
-
rows.push({
|
|
202
|
+
rows.push({
|
|
203
|
+
node: caseDef as JxMutableNode,
|
|
204
|
+
path: casePath,
|
|
205
|
+
depth: depth + 1,
|
|
206
|
+
nodeType: "case",
|
|
207
|
+
});
|
|
191
208
|
// Recurse into case children (skip the case node itself — already emitted)
|
|
192
|
-
const caseChildren = flattenTree(caseDef, casePath, depth + 2);
|
|
209
|
+
const caseChildren = flattenTree(caseDef as JxMutableNode, casePath, depth + 2);
|
|
193
210
|
rows.push(...caseChildren.slice(1));
|
|
194
211
|
}
|
|
195
212
|
}
|
|
@@ -204,7 +221,7 @@ export function flattenTree(doc, path = [], depth = 0) {
|
|
|
204
221
|
* @param {JxMutableNode | null} node
|
|
205
222
|
* @returns {string}
|
|
206
223
|
*/
|
|
207
|
-
export function nodeLabel(node) {
|
|
224
|
+
export function nodeLabel(node: JxMutableNode | null) {
|
|
208
225
|
if (!node) return "?";
|
|
209
226
|
// $map container (Repeater)
|
|
210
227
|
if (node.$prototype === "Array") {
|
|
@@ -227,7 +244,7 @@ export function nodeLabel(node) {
|
|
|
227
244
|
* @param {JxMutableNode} doc
|
|
228
245
|
* @returns {StudioState}
|
|
229
246
|
*/
|
|
230
|
-
export function createState(doc) {
|
|
247
|
+
export function createState(doc: JxMutableNode): StudioState {
|
|
231
248
|
const initial = { document: doc, selection: null };
|
|
232
249
|
return {
|
|
233
250
|
document: doc,
|
|
@@ -264,6 +281,7 @@ export function createState(doc) {
|
|
|
264
281
|
gitCommitMessage: "", // commit message input
|
|
265
282
|
gitLoading: false, // loading indicator during async ops
|
|
266
283
|
gitError: null, // error message string
|
|
284
|
+
gitDiffState: null,
|
|
267
285
|
pendingInlineEdit: null, // null | { path, mediaName } — deferred inline edit awaiting canvas readiness
|
|
268
286
|
},
|
|
269
287
|
canvas: {
|
|
@@ -283,8 +301,8 @@ export function createState(doc) {
|
|
|
283
301
|
* @param {Partial<StudioState>} session
|
|
284
302
|
* @returns {StudioState}
|
|
285
303
|
*/
|
|
286
|
-
export function toFlat(doc
|
|
287
|
-
return
|
|
304
|
+
export function toFlat(doc: Partial<StudioState>, session: Partial<StudioState>) {
|
|
305
|
+
return { ...doc, ...session } as StudioState;
|
|
288
306
|
}
|
|
289
307
|
|
|
290
308
|
/**
|
|
@@ -293,7 +311,7 @@ export function toFlat(doc, session) {
|
|
|
293
311
|
* @param {StudioState} S
|
|
294
312
|
* @returns {{ doc: Partial<StudioState>; session: Partial<StudioState> }}
|
|
295
313
|
*/
|
|
296
|
-
export function fromFlat(S) {
|
|
314
|
+
export function fromFlat(S: StudioState) {
|
|
297
315
|
const {
|
|
298
316
|
document,
|
|
299
317
|
dirty,
|
|
@@ -334,11 +352,10 @@ export function fromFlat(S) {
|
|
|
334
352
|
// selectedPath: string|null, searchQuery: string }
|
|
335
353
|
// DirEntry: { name, path, type: "file"|"directory", size, modified }
|
|
336
354
|
|
|
337
|
-
|
|
338
|
-
export let projectState = null;
|
|
355
|
+
export let projectState: ProjectState | null = null;
|
|
339
356
|
|
|
340
357
|
/** @param {ProjectState | null} ps */
|
|
341
|
-
export function setProjectState(ps) {
|
|
358
|
+
export function setProjectState(ps: ProjectState | null) {
|
|
342
359
|
projectState = ps;
|
|
343
360
|
}
|
|
344
361
|
|
|
@@ -349,7 +366,7 @@ export function setProjectState(ps) {
|
|
|
349
366
|
* @returns {ProjectState}
|
|
350
367
|
*/
|
|
351
368
|
export function requireProjectState() {
|
|
352
|
-
return
|
|
369
|
+
return projectState as ProjectState;
|
|
353
370
|
}
|
|
354
371
|
|
|
355
372
|
// ─── Frontmatter mutation ───────────────────────────────────────────────────
|
|
@@ -363,7 +380,7 @@ export function requireProjectState() {
|
|
|
363
380
|
* @param {unknown} value
|
|
364
381
|
* @returns {StudioState}
|
|
365
382
|
*/
|
|
366
|
-
export function updateFrontmatter(state, field, value) {
|
|
383
|
+
export function updateFrontmatter(state: StudioState, field: string, value: unknown) {
|
|
367
384
|
const fm = { ...state.content?.frontmatter };
|
|
368
385
|
if (value === undefined || value === null || value === "") delete fm[field];
|
|
369
386
|
else fm[field] = value;
|
|
@@ -381,7 +398,7 @@ export function updateFrontmatter(state, field, value) {
|
|
|
381
398
|
* @param {JxPath | null} path
|
|
382
399
|
* @returns {StudioState}
|
|
383
400
|
*/
|
|
384
|
-
export function selectNode(state, path) {
|
|
401
|
+
export function selectNode(state: StudioState, path: JxPath | null) {
|
|
385
402
|
return { ...state, selection: path };
|
|
386
403
|
}
|
|
387
404
|
|
|
@@ -390,7 +407,7 @@ export function selectNode(state, path) {
|
|
|
390
407
|
* @param {JxPath | null} path
|
|
391
408
|
* @returns {StudioState}
|
|
392
409
|
*/
|
|
393
|
-
export function hoverNode(state, path) {
|
|
410
|
+
export function hoverNode(state: StudioState, path: JxPath | null) {
|
|
394
411
|
return { ...state, hover: path };
|
|
395
412
|
}
|
|
396
413
|
|
|
@@ -404,7 +421,7 @@ export function hoverNode(state, path) {
|
|
|
404
421
|
* @param {string | null} documentPath
|
|
405
422
|
* @returns {StudioState}
|
|
406
423
|
*/
|
|
407
|
-
export function pushDocument(state, doc, documentPath) {
|
|
424
|
+
export function pushDocument(state: StudioState, doc: JxMutableNode, documentPath: string | null) {
|
|
408
425
|
const frame = {
|
|
409
426
|
document: state.document,
|
|
410
427
|
selection: state.selection,
|
|
@@ -428,7 +445,7 @@ export function pushDocument(state, doc, documentPath) {
|
|
|
428
445
|
* @param {StudioState} state
|
|
429
446
|
* @returns {StudioState}
|
|
430
447
|
*/
|
|
431
|
-
export function popDocument(state) {
|
|
448
|
+
export function popDocument(state: StudioState) {
|
|
432
449
|
if (!state.documentStack || state.documentStack.length === 0) return state;
|
|
433
450
|
const stack = [...state.documentStack];
|
|
434
451
|
const frame = stack.pop();
|