@jxsuite/studio 0.19.0 → 0.20.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.css +98 -98
- package/dist/studio.js +12577 -12183
- package/dist/studio.js.map +146 -144
- package/dist/workers/editor.worker.js +79 -79
- package/dist/workers/json.worker.js +109 -109
- package/dist/workers/ts.worker.js +82 -82
- package/package.json +17 -6
- package/src/browse/browse.js +25 -19
- package/src/canvas/canvas-diff.js +24 -16
- package/src/canvas/canvas-helpers.js +24 -19
- package/src/canvas/canvas-live-render.js +94 -39
- package/src/canvas/canvas-render.js +119 -73
- package/src/canvas/canvas-utils.js +38 -15
- package/src/canvas/nested-site-style.js +50 -0
- package/src/editor/component-inline-edit.js +14 -13
- package/src/editor/content-inline-edit.js +22 -10
- package/src/editor/context-menu.js +1 -1
- package/src/editor/convert-to-component.js +12 -10
- package/src/editor/inline-edit.js +66 -28
- package/src/editor/insertion-helper.js +16 -12
- package/src/editor/shortcuts.js +4 -2
- package/src/editor/slash-menu.js +6 -4
- package/src/files/components.js +1 -1
- package/src/files/file-ops.js +37 -16
- package/src/files/files.js +78 -52
- package/src/github/github-auth.js +122 -0
- package/src/github/github-publish.js +115 -0
- package/src/markdown/md-convert.js +172 -107
- package/src/new-project/new-project-modal.js +204 -0
- package/src/panels/activity-bar.js +22 -20
- package/src/panels/ai-panel.js +399 -0
- package/src/panels/block-action-bar.js +5 -5
- package/src/panels/canvas-dnd.js +8 -2
- package/src/panels/data-explorer.js +19 -13
- package/src/panels/dnd.js +144 -92
- package/src/panels/editors.js +55 -35
- package/src/panels/elements-panel.js +33 -19
- package/src/panels/events-panel.js +15 -10
- package/src/panels/git-panel.js +469 -98
- package/src/panels/head-panel.js +121 -81
- package/src/panels/imports-panel.js +88 -64
- package/src/panels/layers-panel.js +64 -43
- package/src/panels/left-panel.js +85 -47
- package/src/panels/overlays.js +1 -0
- package/src/panels/panel-events.js +45 -30
- package/src/panels/preview-render.js +23 -18
- package/src/panels/properties-panel.js +365 -249
- package/src/panels/pseudo-preview.js +2 -2
- package/src/panels/quick-search.js +7 -5
- package/src/panels/right-panel.js +26 -2
- package/src/panels/shared.js +4 -4
- package/src/panels/signals-panel.js +295 -166
- package/src/panels/statusbar.js +4 -4
- package/src/panels/style-inputs.js +67 -35
- package/src/panels/style-panel.js +233 -132
- package/src/panels/style-utils.js +34 -16
- package/src/panels/stylebook-layers-panel.js +12 -10
- package/src/panels/stylebook-panel.js +134 -66
- package/src/panels/toolbar.js +333 -109
- package/src/panels/welcome-screen.js +121 -0
- package/src/platform.js +2 -4
- package/src/platforms/devserver.js +113 -7
- package/src/resize-edges.js +98 -0
- package/src/services/cem-export.js +12 -7
- package/src/services/code-services.js +30 -12
- package/src/settings/content-types-editor.js +7 -7
- package/src/settings/css-vars-editor.js +30 -24
- package/src/settings/defs-editor.js +12 -7
- package/src/settings/general-settings.js +85 -3
- package/src/settings/head-editor.js +30 -24
- package/src/settings/schema-field-ui.js +58 -39
- package/src/site-context.js +41 -31
- package/src/state.js +70 -29
- package/src/store.js +21 -26
- package/src/studio.js +103 -81
- package/src/tabs/tab.js +67 -43
- package/src/tabs/transact.js +23 -13
- package/src/ui/button-group.js +24 -24
- package/src/ui/color-selector.js +28 -23
- package/src/ui/field-row.js +3 -3
- package/src/ui/icons.js +2 -2
- package/src/ui/media-picker.js +7 -7
- package/src/ui/spectrum.js +5 -1
- package/src/ui/unit-selector.js +18 -16
- package/src/ui/value-selector.js +21 -15
- package/src/ui/widgets.js +22 -19
- package/src/utils/canvas-media.js +5 -4
- package/src/utils/edit-display.js +31 -20
- package/src/utils/google-fonts.js +11 -11
- package/src/utils/inherited-style.js +9 -8
- package/src/utils/studio-utils.js +7 -7
- package/src/view.js +58 -1
- package/src/workspace/workspace.js +10 -7
|
@@ -13,6 +13,7 @@
|
|
|
13
13
|
import { unified } from "unified";
|
|
14
14
|
import remarkStringify from "remark-stringify";
|
|
15
15
|
import remarkDirective from "remark-directive";
|
|
16
|
+
import remarkGfm from "remark-gfm";
|
|
16
17
|
import { MD_ALL } from "./md-allowlist.js";
|
|
17
18
|
import { htmlToJx } from "@jxsuite/parser/transpile";
|
|
18
19
|
|
|
@@ -21,10 +22,10 @@ import { htmlToJx } from "@jxsuite/parser/transpile";
|
|
|
21
22
|
/**
|
|
22
23
|
* Mdast node-type → Jx tagName mapping
|
|
23
24
|
*
|
|
24
|
-
* @type {Record<string, (n:
|
|
25
|
+
* @type {Record<string, (n: MdastNode) => string>}
|
|
25
26
|
*/
|
|
26
27
|
const MDAST_TAG_MAP = {
|
|
27
|
-
heading: (/** @type {
|
|
28
|
+
heading: (/** @type {MdastNode} */ n) => `h${n.depth}`,
|
|
28
29
|
paragraph: () => "p",
|
|
29
30
|
text: () => "span",
|
|
30
31
|
emphasis: () => "em",
|
|
@@ -34,37 +35,39 @@ const MDAST_TAG_MAP = {
|
|
|
34
35
|
link: () => "a",
|
|
35
36
|
image: () => "img",
|
|
36
37
|
blockquote: () => "blockquote",
|
|
37
|
-
list: (/** @type {
|
|
38
|
+
list: (/** @type {MdastNode} */ n) => (n.ordered ? "ol" : "ul"),
|
|
38
39
|
listItem: () => "li",
|
|
39
40
|
code: () => "pre",
|
|
40
41
|
thematicBreak: () => "hr",
|
|
41
42
|
table: () => "table",
|
|
42
43
|
tableRow: () => "tr",
|
|
43
|
-
tableCell: (/** @type {
|
|
44
|
+
tableCell: (/** @type {MdastNode} */ n) => (n.isHeader ? "th" : "td"),
|
|
44
45
|
break: () => "br",
|
|
45
46
|
};
|
|
46
47
|
|
|
47
48
|
/**
|
|
48
49
|
* Convert an mdast tree to a Jx element tree.
|
|
49
50
|
*
|
|
50
|
-
* @param {
|
|
51
|
-
* @returns {
|
|
51
|
+
* @param {MdastNode} mdast - Root mdast node (type: 'root')
|
|
52
|
+
* @returns {JxElement} Jx element tree
|
|
52
53
|
*/
|
|
53
54
|
export function mdToJx(mdast) {
|
|
54
55
|
if (mdast.type === "root") {
|
|
55
56
|
return {
|
|
56
|
-
children: (
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
57
|
+
children: /** @type {(JxElement | string)[]} */ (
|
|
58
|
+
(mdast.children ?? [])
|
|
59
|
+
.filter((/** @type {MdastNode} */ n) => n.type !== "yaml" && n.type !== "toml")
|
|
60
|
+
.flatMap(convertMdastNode)
|
|
61
|
+
.filter(Boolean)
|
|
62
|
+
),
|
|
60
63
|
};
|
|
61
64
|
}
|
|
62
|
-
return convertMdastNode(mdast);
|
|
65
|
+
return /** @type {JxElement} */ (convertMdastNode(mdast));
|
|
63
66
|
}
|
|
64
67
|
|
|
65
68
|
/**
|
|
66
|
-
* @param {
|
|
67
|
-
* @returns {
|
|
69
|
+
* @param {MdastNode} node
|
|
70
|
+
* @returns {JxElement | null}
|
|
68
71
|
*/
|
|
69
72
|
function convertMdastNode(node) {
|
|
70
73
|
if (!node) return null;
|
|
@@ -81,14 +84,16 @@ function convertMdastNode(node) {
|
|
|
81
84
|
if (node.type === "html") {
|
|
82
85
|
if (!node.value) return null;
|
|
83
86
|
const nodes = htmlToJx(node.value);
|
|
84
|
-
return nodes.length === 1
|
|
87
|
+
return nodes.length === 1
|
|
88
|
+
? /** @type {JxElement} */ (nodes[0])
|
|
89
|
+
: { tagName: "div", children: nodes };
|
|
85
90
|
}
|
|
86
91
|
|
|
87
92
|
const tagFn = MDAST_TAG_MAP[node.type];
|
|
88
93
|
if (!tagFn) return null;
|
|
89
94
|
|
|
90
95
|
const tag = tagFn(node);
|
|
91
|
-
/** @type {
|
|
96
|
+
/** @type {JxElement} */
|
|
92
97
|
const el = { tagName: tag };
|
|
93
98
|
|
|
94
99
|
switch (node.type) {
|
|
@@ -97,8 +102,10 @@ function convertMdastNode(node) {
|
|
|
97
102
|
// If contains only a single text child, flatten to textContent
|
|
98
103
|
if (node.children?.length === 1 && node.children[0].type === "text") {
|
|
99
104
|
el.textContent = node.children[0].value;
|
|
100
|
-
} else if (node.children?.length
|
|
101
|
-
el.children =
|
|
105
|
+
} else if (node.children?.length) {
|
|
106
|
+
el.children = /** @type {(JxElement | string)[]} */ (
|
|
107
|
+
node.children.flatMap(convertMdastNode).filter(Boolean)
|
|
108
|
+
);
|
|
102
109
|
}
|
|
103
110
|
break;
|
|
104
111
|
}
|
|
@@ -112,8 +119,10 @@ function convertMdastNode(node) {
|
|
|
112
119
|
case "delete": {
|
|
113
120
|
if (node.children?.length === 1 && node.children[0].type === "text") {
|
|
114
121
|
el.textContent = node.children[0].value;
|
|
115
|
-
} else if (node.children?.length
|
|
116
|
-
el.children =
|
|
122
|
+
} else if (node.children?.length) {
|
|
123
|
+
el.children = /** @type {(JxElement | string)[]} */ (
|
|
124
|
+
node.children.flatMap(convertMdastNode).filter(Boolean)
|
|
125
|
+
);
|
|
117
126
|
}
|
|
118
127
|
break;
|
|
119
128
|
}
|
|
@@ -123,30 +132,36 @@ function convertMdastNode(node) {
|
|
|
123
132
|
break;
|
|
124
133
|
|
|
125
134
|
case "link":
|
|
126
|
-
el.attributes = { href: node.url };
|
|
135
|
+
el.attributes = { href: node.url ?? "" };
|
|
127
136
|
if (node.title) el.attributes.title = node.title;
|
|
128
137
|
if (node.children?.length === 1 && node.children[0].type === "text") {
|
|
129
138
|
el.textContent = node.children[0].value;
|
|
130
|
-
} else if (node.children?.length
|
|
131
|
-
el.children =
|
|
139
|
+
} else if (node.children?.length) {
|
|
140
|
+
el.children = /** @type {(JxElement | string)[]} */ (
|
|
141
|
+
node.children.flatMap(convertMdastNode).filter(Boolean)
|
|
142
|
+
);
|
|
132
143
|
}
|
|
133
144
|
break;
|
|
134
145
|
|
|
135
146
|
case "image":
|
|
136
|
-
el.attributes = { src: node.url, alt: node.alt ?? "" };
|
|
147
|
+
el.attributes = { src: node.url ?? "", alt: node.alt ?? "" };
|
|
137
148
|
if (node.title) el.attributes.title = node.title;
|
|
138
149
|
break;
|
|
139
150
|
|
|
140
151
|
case "blockquote":
|
|
141
152
|
case "listItem":
|
|
142
|
-
if (node.children?.length
|
|
143
|
-
el.children =
|
|
153
|
+
if (node.children?.length) {
|
|
154
|
+
el.children = /** @type {(JxElement | string)[]} */ (
|
|
155
|
+
node.children.flatMap(convertMdastNode).filter(Boolean)
|
|
156
|
+
);
|
|
144
157
|
}
|
|
145
158
|
break;
|
|
146
159
|
|
|
147
160
|
case "list":
|
|
148
|
-
if (node.children?.length
|
|
149
|
-
el.children =
|
|
161
|
+
if (node.children?.length) {
|
|
162
|
+
el.children = /** @type {(JxElement | string)[]} */ (
|
|
163
|
+
node.children.flatMap(convertMdastNode).filter(Boolean)
|
|
164
|
+
);
|
|
150
165
|
}
|
|
151
166
|
if (node.start != null && node.start !== 1) {
|
|
152
167
|
el.attributes = { start: String(node.start) };
|
|
@@ -171,24 +186,36 @@ function convertMdastNode(node) {
|
|
|
171
186
|
|
|
172
187
|
case "table": {
|
|
173
188
|
// Mdast tables have rows directly; split into thead/tbody
|
|
174
|
-
const rows =
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
189
|
+
const rows = /** @type {JxElement[]} */ (
|
|
190
|
+
(node.children ?? []).flatMap(convertMdastNode).filter(Boolean)
|
|
191
|
+
);
|
|
192
|
+
const thead =
|
|
193
|
+
rows.length > 0
|
|
194
|
+
? { tagName: "thead", children: /** @type {(JxElement | string)[]} */ ([rows[0]]) }
|
|
195
|
+
: null;
|
|
196
|
+
const tbody =
|
|
197
|
+
rows.length > 1
|
|
198
|
+
? { tagName: "tbody", children: /** @type {(JxElement | string)[]} */ (rows.slice(1)) }
|
|
199
|
+
: null;
|
|
200
|
+
el.children = /** @type {(JxElement | string)[]} */ ([thead, tbody].filter(Boolean));
|
|
178
201
|
break;
|
|
179
202
|
}
|
|
180
203
|
|
|
181
204
|
case "tableRow":
|
|
182
|
-
if (node.children?.length
|
|
183
|
-
el.children =
|
|
205
|
+
if (node.children?.length) {
|
|
206
|
+
el.children = /** @type {(JxElement | string)[]} */ (
|
|
207
|
+
node.children.flatMap(convertMdastNode).filter(Boolean)
|
|
208
|
+
);
|
|
184
209
|
}
|
|
185
210
|
break;
|
|
186
211
|
|
|
187
212
|
case "tableCell":
|
|
188
213
|
if (node.children?.length === 1 && node.children[0].type === "text") {
|
|
189
214
|
el.textContent = node.children[0].value;
|
|
190
|
-
} else if (node.children?.length
|
|
191
|
-
el.children =
|
|
215
|
+
} else if (node.children?.length) {
|
|
216
|
+
el.children = /** @type {(JxElement | string)[]} */ (
|
|
217
|
+
node.children.flatMap(convertMdastNode).filter(Boolean)
|
|
218
|
+
);
|
|
192
219
|
}
|
|
193
220
|
break;
|
|
194
221
|
}
|
|
@@ -197,11 +224,11 @@ function convertMdastNode(node) {
|
|
|
197
224
|
}
|
|
198
225
|
|
|
199
226
|
/**
|
|
200
|
-
* @param {
|
|
201
|
-
* @returns {
|
|
227
|
+
* @param {MdastNode} node
|
|
228
|
+
* @returns {JxElement | null}
|
|
202
229
|
*/
|
|
203
230
|
function convertDirective(node) {
|
|
204
|
-
/** @type {
|
|
231
|
+
/** @type {JxElement} */
|
|
205
232
|
const el = { tagName: node.name };
|
|
206
233
|
if (node.attributes && Object.keys(node.attributes).length > 0) {
|
|
207
234
|
el.attributes = { ...node.attributes };
|
|
@@ -210,11 +237,15 @@ function convertDirective(node) {
|
|
|
210
237
|
// Text directives place label as textContent
|
|
211
238
|
if (node.children?.length === 1 && node.children[0].type === "text") {
|
|
212
239
|
el.textContent = node.children[0].value;
|
|
213
|
-
} else if (node.children?.length
|
|
214
|
-
el.children =
|
|
240
|
+
} else if (node.children?.length) {
|
|
241
|
+
el.children = /** @type {(JxElement | string)[]} */ (
|
|
242
|
+
node.children.flatMap(convertMdastNode).filter(Boolean)
|
|
243
|
+
);
|
|
215
244
|
}
|
|
216
|
-
} else if (node.type === "containerDirective" && node.children?.length
|
|
217
|
-
el.children =
|
|
245
|
+
} else if (node.type === "containerDirective" && node.children?.length) {
|
|
246
|
+
el.children = /** @type {(JxElement | string)[]} */ (
|
|
247
|
+
node.children.flatMap(convertMdastNode).filter(Boolean)
|
|
248
|
+
);
|
|
218
249
|
}
|
|
219
250
|
return el;
|
|
220
251
|
}
|
|
@@ -274,13 +305,18 @@ const TAG_MDAST_MAP = {
|
|
|
274
305
|
/**
|
|
275
306
|
* Convert a Jx element tree to an mdast tree.
|
|
276
307
|
*
|
|
277
|
-
* @param {
|
|
278
|
-
* @returns {
|
|
308
|
+
* @param {JxElement} jx - Jx element tree (root content div)
|
|
309
|
+
* @returns {MdastNode} Mdast root node
|
|
279
310
|
*/
|
|
280
311
|
export function jxToMd(jx) {
|
|
281
|
-
const
|
|
282
|
-
.
|
|
283
|
-
|
|
312
|
+
const childArray = /** @type {(JxElement | string)[]} */ (
|
|
313
|
+
Array.isArray(jx.children) ? jx.children : []
|
|
314
|
+
);
|
|
315
|
+
const children = /** @type {MdastNode[]} */ (
|
|
316
|
+
childArray
|
|
317
|
+
.map((/** @type {JxElement | string} */ child) => convertJxNode(child, true))
|
|
318
|
+
.filter(Boolean)
|
|
319
|
+
);
|
|
284
320
|
|
|
285
321
|
return { type: "root", children };
|
|
286
322
|
}
|
|
@@ -289,7 +325,7 @@ export function jxToMd(jx) {
|
|
|
289
325
|
* Check if a Jx element has extra properties beyond the standard mdast-compatible ones. Elements
|
|
290
326
|
* with style, event handlers, state bindings, etc. need directive syntax.
|
|
291
327
|
*
|
|
292
|
-
* @param {
|
|
328
|
+
* @param {JxElement} el
|
|
293
329
|
* @returns {boolean}
|
|
294
330
|
*/
|
|
295
331
|
function hasJxProps(el) {
|
|
@@ -310,9 +346,9 @@ function hasJxProps(el) {
|
|
|
310
346
|
/**
|
|
311
347
|
* Convert a single Jx element to an mdast node.
|
|
312
348
|
*
|
|
313
|
-
* @param {
|
|
349
|
+
* @param {JxElement | string | number} el - Jx element
|
|
314
350
|
* @param {boolean} isBlock - Whether this element is in a block context
|
|
315
|
-
* @returns {
|
|
351
|
+
* @returns {MdastNode | null} Mdast node
|
|
316
352
|
*/
|
|
317
353
|
function convertJxNode(el, isBlock) {
|
|
318
354
|
// Bare string/number text nodes → mdast text nodes
|
|
@@ -362,17 +398,17 @@ function convertJxNode(el, isBlock) {
|
|
|
362
398
|
case "link":
|
|
363
399
|
return {
|
|
364
400
|
type: "link",
|
|
365
|
-
url: el.attributes?.href ?? "",
|
|
366
|
-
title: el.attributes?.title ?? null,
|
|
401
|
+
url: /** @type {string} */ (el.attributes?.href) ?? "",
|
|
402
|
+
title: /** @type {string | null} */ (el.attributes?.title) ?? null,
|
|
367
403
|
children: inlineChildren(el),
|
|
368
404
|
};
|
|
369
405
|
|
|
370
406
|
case "image":
|
|
371
407
|
return {
|
|
372
408
|
type: "image",
|
|
373
|
-
url: el.attributes?.src ?? "",
|
|
374
|
-
alt: el.attributes?.alt ?? "",
|
|
375
|
-
title: el.attributes?.title ?? null,
|
|
409
|
+
url: /** @type {string} */ (el.attributes?.src) ?? "",
|
|
410
|
+
alt: /** @type {string} */ (el.attributes?.alt) ?? "",
|
|
411
|
+
title: /** @type {string | null} */ (el.attributes?.title) ?? null,
|
|
376
412
|
};
|
|
377
413
|
|
|
378
414
|
case "blockquote":
|
|
@@ -385,11 +421,14 @@ function convertJxNode(el, isBlock) {
|
|
|
385
421
|
return {
|
|
386
422
|
type: "list",
|
|
387
423
|
ordered: tag === "ol",
|
|
388
|
-
start:
|
|
424
|
+
start:
|
|
425
|
+
tag === "ol" ? parseInt(/** @type {string} */ (el.attributes?.start), 10) || 1 : null,
|
|
389
426
|
spread: false,
|
|
390
|
-
children:
|
|
391
|
-
|
|
392
|
-
|
|
427
|
+
children: /** @type {MdastNode[]} */ (
|
|
428
|
+
/** @type {(JxElement | string)[]} */ (el.children ?? [])
|
|
429
|
+
.map((/** @type {JxElement | string} */ c) => convertJxNode(c, true))
|
|
430
|
+
.filter(Boolean)
|
|
431
|
+
),
|
|
393
432
|
};
|
|
394
433
|
|
|
395
434
|
case "listItem":
|
|
@@ -401,8 +440,10 @@ function convertJxNode(el, isBlock) {
|
|
|
401
440
|
|
|
402
441
|
case "code": {
|
|
403
442
|
// pre > code → fenced code block
|
|
404
|
-
const codeChild =
|
|
405
|
-
|
|
443
|
+
const codeChild = /** @type {JxElement | undefined} */ (
|
|
444
|
+
Array.isArray(el.children) ? el.children[0] : undefined
|
|
445
|
+
);
|
|
446
|
+
const langClass = /** @type {string} */ (codeChild?.attributes?.class) ?? "";
|
|
406
447
|
const lang = langClass.replace("language-", "") || null;
|
|
407
448
|
return {
|
|
408
449
|
type: "code",
|
|
@@ -419,11 +460,12 @@ function convertJxNode(el, isBlock) {
|
|
|
419
460
|
|
|
420
461
|
case "table": {
|
|
421
462
|
// Flatten thead/tbody back to rows
|
|
422
|
-
/** @type {
|
|
463
|
+
/** @type {MdastNode[]} */
|
|
423
464
|
const rows = [];
|
|
424
|
-
for (const section of el.children ?? []) {
|
|
465
|
+
for (const section of /** @type {(JxElement | string)[]} */ (el.children ?? [])) {
|
|
466
|
+
if (typeof section === "string") continue;
|
|
425
467
|
if (section.tagName === "thead" || section.tagName === "tbody") {
|
|
426
|
-
for (const row of section.children ?? []) {
|
|
468
|
+
for (const row of /** @type {(JxElement | string)[]} */ (section.children ?? [])) {
|
|
427
469
|
const mdRow = convertJxNode(row, true);
|
|
428
470
|
if (mdRow) {
|
|
429
471
|
// Mark header cells
|
|
@@ -439,7 +481,6 @@ function convertJxNode(el, isBlock) {
|
|
|
439
481
|
}
|
|
440
482
|
return {
|
|
441
483
|
type: "table",
|
|
442
|
-
align: null,
|
|
443
484
|
children: rows,
|
|
444
485
|
};
|
|
445
486
|
}
|
|
@@ -447,9 +488,11 @@ function convertJxNode(el, isBlock) {
|
|
|
447
488
|
case "tableRow":
|
|
448
489
|
return {
|
|
449
490
|
type: "tableRow",
|
|
450
|
-
children:
|
|
451
|
-
|
|
452
|
-
|
|
491
|
+
children: /** @type {MdastNode[]} */ (
|
|
492
|
+
/** @type {(JxElement | string)[]} */ (el.children ?? [])
|
|
493
|
+
.map((/** @type {JxElement | string} */ c) => convertJxNode(c, false))
|
|
494
|
+
.filter(Boolean)
|
|
495
|
+
),
|
|
453
496
|
};
|
|
454
497
|
|
|
455
498
|
case "tableCell":
|
|
@@ -466,28 +509,36 @@ function convertJxNode(el, isBlock) {
|
|
|
466
509
|
* Get inline children from a Jx element as mdast nodes. Handles both textContent shorthand and
|
|
467
510
|
* explicit children array.
|
|
468
511
|
*
|
|
469
|
-
* @param {
|
|
470
|
-
* @returns {
|
|
512
|
+
* @param {JxElement} el
|
|
513
|
+
* @returns {MdastNode[]}
|
|
471
514
|
*/
|
|
472
515
|
function inlineChildren(el) {
|
|
473
516
|
if (el.textContent != null) {
|
|
474
517
|
return [{ type: "text", value: String(el.textContent) }];
|
|
475
518
|
}
|
|
476
|
-
return
|
|
519
|
+
return /** @type {MdastNode[]} */ (
|
|
520
|
+
/** @type {(JxElement | string)[]} */ (el.children ?? [])
|
|
521
|
+
.map((/** @type {JxElement | string} */ c) => convertJxNode(c, false))
|
|
522
|
+
.filter(Boolean)
|
|
523
|
+
);
|
|
477
524
|
}
|
|
478
525
|
|
|
479
526
|
/**
|
|
480
527
|
* Get block children from a Jx element as mdast nodes.
|
|
481
528
|
*
|
|
482
|
-
* @param {
|
|
483
|
-
* @returns {
|
|
529
|
+
* @param {JxElement} el
|
|
530
|
+
* @returns {MdastNode[]}
|
|
484
531
|
*/
|
|
485
532
|
function blockChildren(el) {
|
|
486
533
|
if (el.textContent != null) {
|
|
487
534
|
// Wrap bare text in a paragraph
|
|
488
535
|
return [{ type: "paragraph", children: [{ type: "text", value: String(el.textContent) }] }];
|
|
489
536
|
}
|
|
490
|
-
return
|
|
537
|
+
return /** @type {MdastNode[]} */ (
|
|
538
|
+
/** @type {(JxElement | string)[]} */ (el.children ?? [])
|
|
539
|
+
.map((/** @type {JxElement | string} */ c) => convertJxNode(c, true))
|
|
540
|
+
.filter(Boolean)
|
|
541
|
+
);
|
|
491
542
|
}
|
|
492
543
|
|
|
493
544
|
/**
|
|
@@ -495,11 +546,11 @@ function blockChildren(el) {
|
|
|
495
546
|
* handlers, etc.) and HTML attributes into a flat dot-path attribute map suitable for
|
|
496
547
|
* remark-directive.
|
|
497
548
|
*
|
|
498
|
-
* @param {
|
|
549
|
+
* @param {JxElement} el
|
|
499
550
|
* @returns {Record<string, string>}
|
|
500
551
|
*/
|
|
501
552
|
function collectDirectiveAttrs(el) {
|
|
502
|
-
/** @type {Record<string,
|
|
553
|
+
/** @type {Record<string, unknown>} */
|
|
503
554
|
const propsObj = {};
|
|
504
555
|
|
|
505
556
|
for (const [key, value] of Object.entries(el)) {
|
|
@@ -516,7 +567,9 @@ function collectDirectiveAttrs(el) {
|
|
|
516
567
|
|
|
517
568
|
// Merge HTML attributes
|
|
518
569
|
if (el.attributes) {
|
|
519
|
-
for (const [key, value] of Object.entries(
|
|
570
|
+
for (const [key, value] of Object.entries(
|
|
571
|
+
/** @type {Record<string, unknown>} */ (el.attributes),
|
|
572
|
+
)) {
|
|
520
573
|
propsObj[key] = value;
|
|
521
574
|
}
|
|
522
575
|
}
|
|
@@ -528,12 +581,12 @@ function collectDirectiveAttrs(el) {
|
|
|
528
581
|
* Convert a Jx element to a directive node, preserving all Jx-specific properties as collapsed
|
|
529
582
|
* dot-path directive attributes.
|
|
530
583
|
*
|
|
531
|
-
* @param {
|
|
584
|
+
* @param {JxElement} el
|
|
532
585
|
* @param {boolean} isBlock
|
|
533
|
-
* @returns {
|
|
586
|
+
* @returns {MdastNode}
|
|
534
587
|
*/
|
|
535
588
|
function convertToDirective(el, isBlock) {
|
|
536
|
-
const tag = el.tagName ?? "div";
|
|
589
|
+
const tag = /** @type {string} */ (el.tagName) ?? "div";
|
|
537
590
|
const attrs = collectDirectiveAttrs(el);
|
|
538
591
|
|
|
539
592
|
if (!isBlock) {
|
|
@@ -545,14 +598,17 @@ function convertToDirective(el, isBlock) {
|
|
|
545
598
|
children:
|
|
546
599
|
el.textContent != null
|
|
547
600
|
? [{ type: "text", value: String(el.textContent) }]
|
|
548
|
-
:
|
|
549
|
-
|
|
550
|
-
|
|
601
|
+
: /** @type {MdastNode[]} */ (
|
|
602
|
+
/** @type {(JxElement | string)[]} */ (el.children ?? [])
|
|
603
|
+
.map((/** @type {JxElement | string} */ c) => convertJxNode(c, false))
|
|
604
|
+
.filter(Boolean)
|
|
605
|
+
),
|
|
551
606
|
};
|
|
552
607
|
}
|
|
553
608
|
|
|
554
609
|
// Block without children → leafDirective
|
|
555
|
-
|
|
610
|
+
const childArray = /** @type {(JxElement | string)[] | undefined} */ (el.children);
|
|
611
|
+
if (!childArray?.length && el.textContent == null) {
|
|
556
612
|
return {
|
|
557
613
|
type: "leafDirective",
|
|
558
614
|
name: tag,
|
|
@@ -562,7 +618,7 @@ function convertToDirective(el, isBlock) {
|
|
|
562
618
|
}
|
|
563
619
|
|
|
564
620
|
// Block with children → containerDirective
|
|
565
|
-
/** @type {
|
|
621
|
+
/** @type {MdastNode[]} */
|
|
566
622
|
let directiveChildren;
|
|
567
623
|
if (el.textContent != null) {
|
|
568
624
|
directiveChildren = [
|
|
@@ -571,15 +627,19 @@ function convertToDirective(el, isBlock) {
|
|
|
571
627
|
} else if (INLINE_CONTENT_TAGS.has(tag)) {
|
|
572
628
|
// Tags with inline content model: wrap all children in a single paragraph
|
|
573
629
|
// so remark serializes them as one continuous inline flow
|
|
574
|
-
const inlineNodes =
|
|
575
|
-
|
|
576
|
-
|
|
630
|
+
const inlineNodes = /** @type {MdastNode[]} */ (
|
|
631
|
+
/** @type {(JxElement | string)[]} */ (el.children ?? [])
|
|
632
|
+
.map((/** @type {JxElement | string} */ c) => convertJxNode(c, false))
|
|
633
|
+
.filter(Boolean)
|
|
634
|
+
);
|
|
577
635
|
directiveChildren =
|
|
578
636
|
inlineNodes.length > 0 ? [{ type: "paragraph", children: inlineNodes }] : [];
|
|
579
637
|
} else {
|
|
580
|
-
directiveChildren =
|
|
581
|
-
|
|
582
|
-
|
|
638
|
+
directiveChildren = /** @type {MdastNode[]} */ (
|
|
639
|
+
/** @type {(JxElement | string)[]} */ (el.children ?? [])
|
|
640
|
+
.map((/** @type {JxElement | string} */ c) => convertJxNode(c, true))
|
|
641
|
+
.filter(Boolean)
|
|
642
|
+
);
|
|
583
643
|
}
|
|
584
644
|
|
|
585
645
|
return {
|
|
@@ -633,7 +693,7 @@ const JX_ANNOTATION_KEYS = new Set(["$title", "$description"]);
|
|
|
633
693
|
* top-level props and uses remark-stringify with remark-directive for the body — standard markdown
|
|
634
694
|
* elements emit as native syntax, Jx-decorated elements emit as directives.
|
|
635
695
|
*
|
|
636
|
-
* @param {
|
|
696
|
+
* @param {JxMutableNode} doc - Jx JSON document
|
|
637
697
|
* @returns {string} Jx Markdown source
|
|
638
698
|
*/
|
|
639
699
|
export function jxDocToMd(doc) {
|
|
@@ -643,7 +703,7 @@ export function jxDocToMd(doc) {
|
|
|
643
703
|
const lines = [];
|
|
644
704
|
|
|
645
705
|
// Emit YAML frontmatter
|
|
646
|
-
/** @type {Record<string,
|
|
706
|
+
/** @type {Record<string, unknown>} */
|
|
647
707
|
const frontmatter = {};
|
|
648
708
|
for (const [key, value] of Object.entries(doc)) {
|
|
649
709
|
if (key === "children") continue;
|
|
@@ -659,17 +719,22 @@ export function jxDocToMd(doc) {
|
|
|
659
719
|
|
|
660
720
|
// Convert children to mdast and stringify with remark
|
|
661
721
|
if (Array.isArray(doc.children) && doc.children.length > 0) {
|
|
662
|
-
const mdastChildren =
|
|
663
|
-
.
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
722
|
+
const mdastChildren = /** @type {MdastNode[]} */ (
|
|
723
|
+
doc.children
|
|
724
|
+
.map((/** @type {JxMutableNode | string} */ child) =>
|
|
725
|
+
convertJxNode(/** @type {JxElement | string} */ (child), true),
|
|
726
|
+
)
|
|
727
|
+
.filter(Boolean)
|
|
728
|
+
);
|
|
729
|
+
|
|
730
|
+
const mdast = { type: "root", children: mdastChildren };
|
|
667
731
|
const md = unified()
|
|
732
|
+
.use(remarkGfm)
|
|
668
733
|
.use(remarkDirective)
|
|
669
734
|
.use(remarkStringify, { bullet: "-", emphasis: "*", strong: "*" })
|
|
670
|
-
.stringify(mdast);
|
|
735
|
+
.stringify(/** @type {import("mdast").Root} */ (/** @type {unknown} */ (mdast)));
|
|
671
736
|
|
|
672
|
-
lines.push(md);
|
|
737
|
+
lines.push(/** @type {string} */ (md));
|
|
673
738
|
}
|
|
674
739
|
|
|
675
740
|
return (
|
|
@@ -683,9 +748,9 @@ export function jxDocToMd(doc) {
|
|
|
683
748
|
/**
|
|
684
749
|
* Lazy import of yaml stringify — avoids importing at module load.
|
|
685
750
|
*
|
|
686
|
-
* @returns {{ stringify: (v:
|
|
751
|
+
* @returns {{ stringify: (v: unknown) => string }}
|
|
687
752
|
*/
|
|
688
|
-
let _yaml = /** @type {
|
|
753
|
+
let _yaml = /** @type {{ stringify: (v: unknown) => string } | null} */ (null);
|
|
689
754
|
function yamlImport() {
|
|
690
755
|
if (!_yaml) {
|
|
691
756
|
// Dynamic require avoided; use the yaml package already available in studio
|
|
@@ -698,7 +763,7 @@ function yamlImport() {
|
|
|
698
763
|
* Simple YAML stringifier for frontmatter. Handles the subset of YAML needed for Jx frontmatter
|
|
699
764
|
* (scalars, arrays, nested objects).
|
|
700
765
|
*
|
|
701
|
-
* @param {
|
|
766
|
+
* @param {unknown} value
|
|
702
767
|
* @param {number} indent
|
|
703
768
|
* @returns {string}
|
|
704
769
|
*/
|
|
@@ -735,7 +800,7 @@ function yamlStringifySimple(value, indent = 0) {
|
|
|
735
800
|
}
|
|
736
801
|
|
|
737
802
|
if (typeof value === "object") {
|
|
738
|
-
const entries = Object.entries(value);
|
|
803
|
+
const entries = Object.entries(/** @type {Record<string, unknown>} */ (value));
|
|
739
804
|
if (entries.length === 0) return "{}";
|
|
740
805
|
return entries
|
|
741
806
|
.map(([k, v]) => {
|
|
@@ -755,14 +820,14 @@ function yamlStringifySimple(value, indent = 0) {
|
|
|
755
820
|
* Collapse a Jx props object to a flat directive attribute map. Applies key mapping: strips `$`
|
|
756
821
|
* from Jx keywords, `:` from pseudo-classes, `@` from media queries.
|
|
757
822
|
*
|
|
758
|
-
* @param {Record<string,
|
|
823
|
+
* @param {Record<string, unknown>} propsObj
|
|
759
824
|
* @returns {Record<string, string>}
|
|
760
825
|
*/
|
|
761
826
|
function collapsePropsToAttrMap(propsObj) {
|
|
762
827
|
/** @type {Record<string, string>} */
|
|
763
828
|
const result = {};
|
|
764
829
|
|
|
765
|
-
function walk(/** @type {Record<string,
|
|
830
|
+
function walk(/** @type {Record<string, unknown>} */ obj, /** @type {string} */ prefix) {
|
|
766
831
|
for (const [key, value] of Object.entries(obj)) {
|
|
767
832
|
let mdAttrKey = key;
|
|
768
833
|
// Strip $ prefix for Jx keywords
|
|
@@ -785,7 +850,7 @@ function collapsePropsToAttrMap(propsObj) {
|
|
|
785
850
|
const fullKey = prefix ? `${prefix}.${mdAttrKey}` : mdAttrKey;
|
|
786
851
|
|
|
787
852
|
if (value && typeof value === "object" && !Array.isArray(value)) {
|
|
788
|
-
walk(value, fullKey);
|
|
853
|
+
walk(/** @type {Record<string, unknown>} */ (value), fullKey);
|
|
789
854
|
} else {
|
|
790
855
|
result[fullKey] = String(value);
|
|
791
856
|
}
|