@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
|
* Md-convert.js — Bidirectional mdast ↔ Jx conversion
|
|
3
4
|
*
|
|
@@ -14,9 +15,12 @@ import { unified } from "unified";
|
|
|
14
15
|
import remarkStringify from "remark-stringify";
|
|
15
16
|
import remarkDirective from "remark-directive";
|
|
16
17
|
import remarkGfm from "remark-gfm";
|
|
17
|
-
import { MD_ALL } from "./md-allowlist
|
|
18
|
+
import { MD_ALL } from "./md-allowlist";
|
|
18
19
|
import { htmlToJx } from "@jxsuite/parser/transpile";
|
|
19
20
|
|
|
21
|
+
import type { JxElement, JxMutableNode } from "@jxsuite/schema/types";
|
|
22
|
+
import type { MdastNode } from "@jxsuite/parser/types";
|
|
23
|
+
|
|
20
24
|
// ─── mdast → Jx ──────────────────────────────────────────────────────────
|
|
21
25
|
|
|
22
26
|
/**
|
|
@@ -24,8 +28,8 @@ import { htmlToJx } from "@jxsuite/parser/transpile";
|
|
|
24
28
|
*
|
|
25
29
|
* @type {Record<string, (n: MdastNode) => string>}
|
|
26
30
|
*/
|
|
27
|
-
const MDAST_TAG_MAP = {
|
|
28
|
-
heading: (
|
|
31
|
+
const MDAST_TAG_MAP: Record<string, (n: MdastNode) => string> = {
|
|
32
|
+
heading: (n: MdastNode) => `h${n.depth}`,
|
|
29
33
|
paragraph: () => "p",
|
|
30
34
|
text: () => "span",
|
|
31
35
|
emphasis: () => "em",
|
|
@@ -35,13 +39,13 @@ const MDAST_TAG_MAP = {
|
|
|
35
39
|
link: () => "a",
|
|
36
40
|
image: () => "img",
|
|
37
41
|
blockquote: () => "blockquote",
|
|
38
|
-
list: (
|
|
42
|
+
list: (n: MdastNode) => (n.ordered ? "ol" : "ul"),
|
|
39
43
|
listItem: () => "li",
|
|
40
44
|
code: () => "pre",
|
|
41
45
|
thematicBreak: () => "hr",
|
|
42
46
|
table: () => "table",
|
|
43
47
|
tableRow: () => "tr",
|
|
44
|
-
tableCell: (
|
|
48
|
+
tableCell: (n: MdastNode) => (n.isHeader ? "th" : "td"),
|
|
45
49
|
break: () => "br",
|
|
46
50
|
};
|
|
47
51
|
|
|
@@ -51,25 +55,23 @@ const MDAST_TAG_MAP = {
|
|
|
51
55
|
* @param {MdastNode} mdast - Root mdast node (type: 'root')
|
|
52
56
|
* @returns {JxElement} Jx element tree
|
|
53
57
|
*/
|
|
54
|
-
export function mdToJx(mdast) {
|
|
58
|
+
export function mdToJx(mdast: MdastNode) {
|
|
55
59
|
if (mdast.type === "root") {
|
|
56
60
|
return {
|
|
57
|
-
children:
|
|
58
|
-
(
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
.filter(Boolean)
|
|
62
|
-
),
|
|
61
|
+
children: (mdast.children ?? [])
|
|
62
|
+
.filter((n: MdastNode) => n.type !== "yaml" && n.type !== "toml")
|
|
63
|
+
.flatMap(convertMdastNode)
|
|
64
|
+
.filter(Boolean) as (JxElement | string)[],
|
|
63
65
|
};
|
|
64
66
|
}
|
|
65
|
-
return
|
|
67
|
+
return convertMdastNode(mdast) as JxElement;
|
|
66
68
|
}
|
|
67
69
|
|
|
68
70
|
/**
|
|
69
71
|
* @param {MdastNode} node
|
|
70
72
|
* @returns {JxElement | null}
|
|
71
73
|
*/
|
|
72
|
-
function convertMdastNode(node) {
|
|
74
|
+
function convertMdastNode(node: MdastNode) {
|
|
73
75
|
if (!node) return null;
|
|
74
76
|
|
|
75
77
|
// Directive nodes → custom elements
|
|
@@ -84,62 +86,62 @@ function convertMdastNode(node) {
|
|
|
84
86
|
if (node.type === "html") {
|
|
85
87
|
if (!node.value) return null;
|
|
86
88
|
const nodes = htmlToJx(node.value);
|
|
87
|
-
return nodes.length === 1
|
|
88
|
-
? /** @type {JxElement} */ (nodes[0])
|
|
89
|
-
: { tagName: "div", children: nodes };
|
|
89
|
+
return nodes.length === 1 ? (nodes[0] as JxElement) : { tagName: "div", children: nodes };
|
|
90
90
|
}
|
|
91
91
|
|
|
92
92
|
const tagFn = MDAST_TAG_MAP[node.type];
|
|
93
93
|
if (!tagFn) return null;
|
|
94
94
|
|
|
95
95
|
const tag = tagFn(node);
|
|
96
|
-
|
|
97
|
-
const el = { tagName: tag };
|
|
96
|
+
const el: JxElement = { tagName: tag };
|
|
98
97
|
|
|
99
98
|
switch (node.type) {
|
|
100
99
|
case "heading":
|
|
101
100
|
case "paragraph": {
|
|
102
101
|
// If contains only a single text child, flatten to textContent
|
|
103
102
|
if (node.children?.length === 1 && node.children[0].type === "text") {
|
|
104
|
-
el.textContent = node.children[0].value;
|
|
103
|
+
el.textContent = node.children[0].value ?? null;
|
|
105
104
|
} else if (node.children?.length) {
|
|
106
|
-
el.children =
|
|
107
|
-
|
|
108
|
-
|
|
105
|
+
el.children = node.children.flatMap(convertMdastNode).filter(Boolean) as (
|
|
106
|
+
| JxElement
|
|
107
|
+
| string
|
|
108
|
+
)[];
|
|
109
109
|
}
|
|
110
110
|
break;
|
|
111
111
|
}
|
|
112
112
|
|
|
113
113
|
case "text":
|
|
114
|
-
el.textContent = node.value;
|
|
114
|
+
el.textContent = node.value ?? null;
|
|
115
115
|
break;
|
|
116
116
|
|
|
117
117
|
case "emphasis":
|
|
118
118
|
case "strong":
|
|
119
119
|
case "delete": {
|
|
120
120
|
if (node.children?.length === 1 && node.children[0].type === "text") {
|
|
121
|
-
el.textContent = node.children[0].value;
|
|
121
|
+
el.textContent = node.children[0].value ?? null;
|
|
122
122
|
} else if (node.children?.length) {
|
|
123
|
-
el.children =
|
|
124
|
-
|
|
125
|
-
|
|
123
|
+
el.children = node.children.flatMap(convertMdastNode).filter(Boolean) as (
|
|
124
|
+
| JxElement
|
|
125
|
+
| string
|
|
126
|
+
)[];
|
|
126
127
|
}
|
|
127
128
|
break;
|
|
128
129
|
}
|
|
129
130
|
|
|
130
131
|
case "inlineCode":
|
|
131
|
-
el.textContent = node.value;
|
|
132
|
+
el.textContent = node.value ?? null;
|
|
132
133
|
break;
|
|
133
134
|
|
|
134
135
|
case "link":
|
|
135
136
|
el.attributes = { href: node.url ?? "" };
|
|
136
137
|
if (node.title) el.attributes.title = node.title;
|
|
137
138
|
if (node.children?.length === 1 && node.children[0].type === "text") {
|
|
138
|
-
el.textContent = node.children[0].value;
|
|
139
|
+
el.textContent = node.children[0].value ?? null;
|
|
139
140
|
} else if (node.children?.length) {
|
|
140
|
-
el.children =
|
|
141
|
-
|
|
142
|
-
|
|
141
|
+
el.children = node.children.flatMap(convertMdastNode).filter(Boolean) as (
|
|
142
|
+
| JxElement
|
|
143
|
+
| string
|
|
144
|
+
)[];
|
|
143
145
|
}
|
|
144
146
|
break;
|
|
145
147
|
|
|
@@ -151,17 +153,19 @@ function convertMdastNode(node) {
|
|
|
151
153
|
case "blockquote":
|
|
152
154
|
case "listItem":
|
|
153
155
|
if (node.children?.length) {
|
|
154
|
-
el.children =
|
|
155
|
-
|
|
156
|
-
|
|
156
|
+
el.children = node.children.flatMap(convertMdastNode).filter(Boolean) as (
|
|
157
|
+
| JxElement
|
|
158
|
+
| string
|
|
159
|
+
)[];
|
|
157
160
|
}
|
|
158
161
|
break;
|
|
159
162
|
|
|
160
163
|
case "list":
|
|
161
164
|
if (node.children?.length) {
|
|
162
|
-
el.children =
|
|
163
|
-
|
|
164
|
-
|
|
165
|
+
el.children = node.children.flatMap(convertMdastNode).filter(Boolean) as (
|
|
166
|
+
| JxElement
|
|
167
|
+
| string
|
|
168
|
+
)[];
|
|
165
169
|
}
|
|
166
170
|
if (node.start != null && node.start !== 1) {
|
|
167
171
|
el.attributes = { start: String(node.start) };
|
|
@@ -173,7 +177,7 @@ function convertMdastNode(node) {
|
|
|
173
177
|
el.children = [
|
|
174
178
|
{
|
|
175
179
|
tagName: "code",
|
|
176
|
-
textContent: node.value,
|
|
180
|
+
textContent: node.value ?? null,
|
|
177
181
|
...(node.lang ? { attributes: { class: `language-${node.lang}` } } : {}),
|
|
178
182
|
},
|
|
179
183
|
];
|
|
@@ -186,36 +190,36 @@ function convertMdastNode(node) {
|
|
|
186
190
|
|
|
187
191
|
case "table": {
|
|
188
192
|
// Mdast tables have rows directly; split into thead/tbody
|
|
189
|
-
const rows =
|
|
190
|
-
(node.children ?? []).flatMap(convertMdastNode).filter(Boolean)
|
|
191
|
-
);
|
|
193
|
+
const rows = (node.children ?? []).flatMap(convertMdastNode).filter(Boolean) as JxElement[];
|
|
192
194
|
const thead =
|
|
193
195
|
rows.length > 0
|
|
194
|
-
? { tagName: "thead", children:
|
|
196
|
+
? { tagName: "thead", children: [rows[0]] as (JxElement | string)[] }
|
|
195
197
|
: null;
|
|
196
198
|
const tbody =
|
|
197
199
|
rows.length > 1
|
|
198
|
-
? { tagName: "tbody", children:
|
|
200
|
+
? { tagName: "tbody", children: rows.slice(1) as (JxElement | string)[] }
|
|
199
201
|
: null;
|
|
200
|
-
el.children =
|
|
202
|
+
el.children = [thead, tbody].filter(Boolean) as (JxElement | string)[];
|
|
201
203
|
break;
|
|
202
204
|
}
|
|
203
205
|
|
|
204
206
|
case "tableRow":
|
|
205
207
|
if (node.children?.length) {
|
|
206
|
-
el.children =
|
|
207
|
-
|
|
208
|
-
|
|
208
|
+
el.children = node.children.flatMap(convertMdastNode).filter(Boolean) as (
|
|
209
|
+
| JxElement
|
|
210
|
+
| string
|
|
211
|
+
)[];
|
|
209
212
|
}
|
|
210
213
|
break;
|
|
211
214
|
|
|
212
215
|
case "tableCell":
|
|
213
216
|
if (node.children?.length === 1 && node.children[0].type === "text") {
|
|
214
|
-
el.textContent = node.children[0].value;
|
|
217
|
+
el.textContent = node.children[0].value ?? null;
|
|
215
218
|
} else if (node.children?.length) {
|
|
216
|
-
el.children =
|
|
217
|
-
|
|
218
|
-
|
|
219
|
+
el.children = node.children.flatMap(convertMdastNode).filter(Boolean) as (
|
|
220
|
+
| JxElement
|
|
221
|
+
| string
|
|
222
|
+
)[];
|
|
219
223
|
}
|
|
220
224
|
break;
|
|
221
225
|
}
|
|
@@ -227,25 +231,23 @@ function convertMdastNode(node) {
|
|
|
227
231
|
* @param {MdastNode} node
|
|
228
232
|
* @returns {JxElement | null}
|
|
229
233
|
*/
|
|
230
|
-
function convertDirective(node) {
|
|
231
|
-
|
|
232
|
-
const el = { tagName: node.name };
|
|
234
|
+
function convertDirective(node: MdastNode) {
|
|
235
|
+
const el: JxElement = { tagName: node.name ?? "div" };
|
|
233
236
|
if (node.attributes && Object.keys(node.attributes).length > 0) {
|
|
234
237
|
el.attributes = { ...node.attributes };
|
|
235
238
|
}
|
|
236
239
|
if (node.type === "textDirective") {
|
|
237
240
|
// Text directives place label as textContent
|
|
238
241
|
if (node.children?.length === 1 && node.children[0].type === "text") {
|
|
239
|
-
el.textContent = node.children[0].value;
|
|
242
|
+
el.textContent = node.children[0].value ?? null;
|
|
240
243
|
} else if (node.children?.length) {
|
|
241
|
-
el.children =
|
|
242
|
-
|
|
243
|
-
|
|
244
|
+
el.children = node.children.flatMap(convertMdastNode).filter(Boolean) as (
|
|
245
|
+
| JxElement
|
|
246
|
+
| string
|
|
247
|
+
)[];
|
|
244
248
|
}
|
|
245
249
|
} else if (node.type === "containerDirective" && node.children?.length) {
|
|
246
|
-
el.children =
|
|
247
|
-
node.children.flatMap(convertMdastNode).filter(Boolean)
|
|
248
|
-
);
|
|
250
|
+
el.children = node.children.flatMap(convertMdastNode).filter(Boolean) as (JxElement | string)[];
|
|
249
251
|
}
|
|
250
252
|
return el;
|
|
251
253
|
}
|
|
@@ -308,15 +310,11 @@ const TAG_MDAST_MAP = {
|
|
|
308
310
|
* @param {JxElement} jx - Jx element tree (root content div)
|
|
309
311
|
* @returns {MdastNode} Mdast root node
|
|
310
312
|
*/
|
|
311
|
-
export function jxToMd(jx) {
|
|
312
|
-
const childArray =
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
childArray
|
|
317
|
-
.map((/** @type {JxElement | string} */ child) => convertJxNode(child, true))
|
|
318
|
-
.filter(Boolean)
|
|
319
|
-
);
|
|
313
|
+
export function jxToMd(jx: JxElement) {
|
|
314
|
+
const childArray = Array.isArray(jx.children) ? jx.children : ([] as (JxElement | string)[]);
|
|
315
|
+
const children = childArray
|
|
316
|
+
.map((child: JxElement | string) => convertJxNode(child, true))
|
|
317
|
+
.filter(Boolean) as MdastNode[];
|
|
320
318
|
|
|
321
319
|
return { type: "root", children };
|
|
322
320
|
}
|
|
@@ -328,7 +326,7 @@ export function jxToMd(jx) {
|
|
|
328
326
|
* @param {JxElement} el
|
|
329
327
|
* @returns {boolean}
|
|
330
328
|
*/
|
|
331
|
-
function hasJxProps(el) {
|
|
329
|
+
function hasJxProps(el: JxElement) {
|
|
332
330
|
for (const key of Object.keys(el)) {
|
|
333
331
|
if (
|
|
334
332
|
key === "tagName" ||
|
|
@@ -350,7 +348,7 @@ function hasJxProps(el) {
|
|
|
350
348
|
* @param {boolean} isBlock - Whether this element is in a block context
|
|
351
349
|
* @returns {MdastNode | null} Mdast node
|
|
352
350
|
*/
|
|
353
|
-
function convertJxNode(el, isBlock) {
|
|
351
|
+
function convertJxNode(el: JxElement | string | number, isBlock: boolean): MdastNode | null {
|
|
354
352
|
// Bare string/number text nodes → mdast text nodes
|
|
355
353
|
if (typeof el === "string" || typeof el === "number") {
|
|
356
354
|
return { type: "text", value: String(el) };
|
|
@@ -364,7 +362,7 @@ function convertJxNode(el, isBlock) {
|
|
|
364
362
|
return convertToDirective(el, isBlock);
|
|
365
363
|
}
|
|
366
364
|
|
|
367
|
-
const mdastType =
|
|
365
|
+
const mdastType = (TAG_MDAST_MAP as Record<string, string>)[tag];
|
|
368
366
|
if (!mdastType) return null;
|
|
369
367
|
|
|
370
368
|
switch (mdastType) {
|
|
@@ -398,17 +396,17 @@ function convertJxNode(el, isBlock) {
|
|
|
398
396
|
case "link":
|
|
399
397
|
return {
|
|
400
398
|
type: "link",
|
|
401
|
-
url:
|
|
402
|
-
title:
|
|
399
|
+
url: (el.attributes?.href as string) ?? "",
|
|
400
|
+
title: (el.attributes?.title as string | null) ?? null,
|
|
403
401
|
children: inlineChildren(el),
|
|
404
402
|
};
|
|
405
403
|
|
|
406
404
|
case "image":
|
|
407
405
|
return {
|
|
408
406
|
type: "image",
|
|
409
|
-
url:
|
|
410
|
-
alt:
|
|
411
|
-
title:
|
|
407
|
+
url: (el.attributes?.src as string) ?? "",
|
|
408
|
+
alt: (el.attributes?.alt as string) ?? "",
|
|
409
|
+
title: (el.attributes?.title as string | null) ?? null,
|
|
412
410
|
};
|
|
413
411
|
|
|
414
412
|
case "blockquote":
|
|
@@ -421,14 +419,11 @@ function convertJxNode(el, isBlock) {
|
|
|
421
419
|
return {
|
|
422
420
|
type: "list",
|
|
423
421
|
ordered: tag === "ol",
|
|
424
|
-
start:
|
|
425
|
-
tag === "ol" ? parseInt(/** @type {string} */ (el.attributes?.start), 10) || 1 : null,
|
|
422
|
+
start: tag === "ol" ? parseInt(el.attributes?.start as string, 10) || 1 : null,
|
|
426
423
|
spread: false,
|
|
427
|
-
children:
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
.filter(Boolean)
|
|
431
|
-
),
|
|
424
|
+
children: ((el.children ?? []) as (JxElement | string)[])
|
|
425
|
+
.map((c: JxElement | string) => convertJxNode(c, true))
|
|
426
|
+
.filter(Boolean) as MdastNode[],
|
|
432
427
|
};
|
|
433
428
|
|
|
434
429
|
case "listItem":
|
|
@@ -440,10 +435,10 @@ function convertJxNode(el, isBlock) {
|
|
|
440
435
|
|
|
441
436
|
case "code": {
|
|
442
437
|
// pre > code → fenced code block
|
|
443
|
-
const codeChild =
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
const langClass =
|
|
438
|
+
const codeChild = Array.isArray(el.children)
|
|
439
|
+
? (el.children[0] as JxElement | undefined)
|
|
440
|
+
: undefined;
|
|
441
|
+
const langClass = (codeChild?.attributes?.class as string) ?? "";
|
|
447
442
|
const lang = langClass.replace("language-", "") || null;
|
|
448
443
|
return {
|
|
449
444
|
type: "code",
|
|
@@ -460,12 +455,11 @@ function convertJxNode(el, isBlock) {
|
|
|
460
455
|
|
|
461
456
|
case "table": {
|
|
462
457
|
// Flatten thead/tbody back to rows
|
|
463
|
-
|
|
464
|
-
const
|
|
465
|
-
for (const section of /** @type {(JxElement | string)[]} */ (el.children ?? [])) {
|
|
458
|
+
const rows: MdastNode[] = [];
|
|
459
|
+
for (const section of (el.children ?? []) as (JxElement | string)[]) {
|
|
466
460
|
if (typeof section === "string") continue;
|
|
467
461
|
if (section.tagName === "thead" || section.tagName === "tbody") {
|
|
468
|
-
for (const row of
|
|
462
|
+
for (const row of (section.children ?? []) as (JxElement | string)[]) {
|
|
469
463
|
const mdRow = convertJxNode(row, true);
|
|
470
464
|
if (mdRow) {
|
|
471
465
|
// Mark header cells
|
|
@@ -488,11 +482,9 @@ function convertJxNode(el, isBlock) {
|
|
|
488
482
|
case "tableRow":
|
|
489
483
|
return {
|
|
490
484
|
type: "tableRow",
|
|
491
|
-
children:
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
.filter(Boolean)
|
|
495
|
-
),
|
|
485
|
+
children: ((el.children ?? []) as (JxElement | string)[])
|
|
486
|
+
.map((c: JxElement | string) => convertJxNode(c, false))
|
|
487
|
+
.filter(Boolean) as MdastNode[],
|
|
496
488
|
};
|
|
497
489
|
|
|
498
490
|
case "tableCell":
|
|
@@ -512,15 +504,13 @@ function convertJxNode(el, isBlock) {
|
|
|
512
504
|
* @param {JxElement} el
|
|
513
505
|
* @returns {MdastNode[]}
|
|
514
506
|
*/
|
|
515
|
-
function inlineChildren(el) {
|
|
507
|
+
function inlineChildren(el: JxElement): MdastNode[] {
|
|
516
508
|
if (el.textContent != null) {
|
|
517
509
|
return [{ type: "text", value: String(el.textContent) }];
|
|
518
510
|
}
|
|
519
|
-
return
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
.filter(Boolean)
|
|
523
|
-
);
|
|
511
|
+
return ((el.children ?? []) as (JxElement | string)[])
|
|
512
|
+
.map((c: JxElement | string) => convertJxNode(c, false))
|
|
513
|
+
.filter(Boolean) as MdastNode[];
|
|
524
514
|
}
|
|
525
515
|
|
|
526
516
|
/**
|
|
@@ -529,16 +519,14 @@ function inlineChildren(el) {
|
|
|
529
519
|
* @param {JxElement} el
|
|
530
520
|
* @returns {MdastNode[]}
|
|
531
521
|
*/
|
|
532
|
-
function blockChildren(el) {
|
|
522
|
+
function blockChildren(el: JxElement): MdastNode[] {
|
|
533
523
|
if (el.textContent != null) {
|
|
534
524
|
// Wrap bare text in a paragraph
|
|
535
525
|
return [{ type: "paragraph", children: [{ type: "text", value: String(el.textContent) }] }];
|
|
536
526
|
}
|
|
537
|
-
return
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
.filter(Boolean)
|
|
541
|
-
);
|
|
527
|
+
return ((el.children ?? []) as (JxElement | string)[])
|
|
528
|
+
.map((c: JxElement | string) => convertJxNode(c, true))
|
|
529
|
+
.filter(Boolean) as MdastNode[];
|
|
542
530
|
}
|
|
543
531
|
|
|
544
532
|
/**
|
|
@@ -549,27 +537,24 @@ function blockChildren(el) {
|
|
|
549
537
|
* @param {JxElement} el
|
|
550
538
|
* @returns {Record<string, string>}
|
|
551
539
|
*/
|
|
552
|
-
function collectDirectiveAttrs(el) {
|
|
553
|
-
|
|
554
|
-
const propsObj = {};
|
|
540
|
+
function collectDirectiveAttrs(el: JxElement) {
|
|
541
|
+
const propsObj: Record<string, unknown> = {};
|
|
555
542
|
|
|
556
543
|
for (const [key, value] of Object.entries(el)) {
|
|
557
|
-
if (
|
|
558
|
-
key === "tagName" ||
|
|
559
|
-
key === "children" ||
|
|
560
|
-
key === "textContent" ||
|
|
561
|
-
key === "innerHTML" ||
|
|
562
|
-
key === "attributes"
|
|
563
|
-
)
|
|
544
|
+
if (key === "tagName" || key === "textContent" || key === "innerHTML" || key === "attributes")
|
|
564
545
|
continue;
|
|
546
|
+
if (key === "children") {
|
|
547
|
+
if (value && typeof value === "object" && !Array.isArray(value)) {
|
|
548
|
+
propsObj.children = value;
|
|
549
|
+
}
|
|
550
|
+
continue;
|
|
551
|
+
}
|
|
565
552
|
propsObj[key] = value;
|
|
566
553
|
}
|
|
567
554
|
|
|
568
555
|
// Merge HTML attributes
|
|
569
556
|
if (el.attributes) {
|
|
570
|
-
for (const [key, value] of Object.entries(
|
|
571
|
-
/** @type {Record<string, unknown>} */ (el.attributes),
|
|
572
|
-
)) {
|
|
557
|
+
for (const [key, value] of Object.entries(el.attributes as Record<string, unknown>)) {
|
|
573
558
|
propsObj[key] = value;
|
|
574
559
|
}
|
|
575
560
|
}
|
|
@@ -585,8 +570,8 @@ function collectDirectiveAttrs(el) {
|
|
|
585
570
|
* @param {boolean} isBlock
|
|
586
571
|
* @returns {MdastNode}
|
|
587
572
|
*/
|
|
588
|
-
function convertToDirective(el, isBlock) {
|
|
589
|
-
const tag =
|
|
573
|
+
function convertToDirective(el: JxElement, isBlock: boolean): MdastNode {
|
|
574
|
+
const tag = (el.tagName as string) ?? "div";
|
|
590
575
|
const attrs = collectDirectiveAttrs(el);
|
|
591
576
|
|
|
592
577
|
if (!isBlock) {
|
|
@@ -598,16 +583,19 @@ function convertToDirective(el, isBlock) {
|
|
|
598
583
|
children:
|
|
599
584
|
el.textContent != null
|
|
600
585
|
? [{ type: "text", value: String(el.textContent) }]
|
|
601
|
-
:
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
.filter(Boolean)
|
|
605
|
-
),
|
|
586
|
+
: (((el.children ?? []) as (JxElement | string)[])
|
|
587
|
+
.map((c: JxElement | string) => convertJxNode(c, false))
|
|
588
|
+
.filter(Boolean) as MdastNode[]),
|
|
606
589
|
};
|
|
607
590
|
}
|
|
608
591
|
|
|
609
592
|
// Block without children → leafDirective
|
|
610
|
-
const
|
|
593
|
+
const rawChildren = el.children;
|
|
594
|
+
const childrenIsObject =
|
|
595
|
+
rawChildren && typeof rawChildren === "object" && !Array.isArray(rawChildren);
|
|
596
|
+
const childArray = childrenIsObject
|
|
597
|
+
? undefined
|
|
598
|
+
: (rawChildren as (JxElement | string)[] | undefined);
|
|
611
599
|
if (!childArray?.length && el.textContent == null) {
|
|
612
600
|
return {
|
|
613
601
|
type: "leafDirective",
|
|
@@ -627,19 +615,15 @@ function convertToDirective(el, isBlock) {
|
|
|
627
615
|
} else if (INLINE_CONTENT_TAGS.has(tag)) {
|
|
628
616
|
// Tags with inline content model: wrap all children in a single paragraph
|
|
629
617
|
// so remark serializes them as one continuous inline flow
|
|
630
|
-
const inlineNodes =
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
.filter(Boolean)
|
|
634
|
-
);
|
|
618
|
+
const inlineNodes = ((el.children ?? []) as (JxElement | string)[])
|
|
619
|
+
.map((c: JxElement | string) => convertJxNode(c, false))
|
|
620
|
+
.filter(Boolean) as MdastNode[];
|
|
635
621
|
directiveChildren =
|
|
636
622
|
inlineNodes.length > 0 ? [{ type: "paragraph", children: inlineNodes }] : [];
|
|
637
623
|
} else {
|
|
638
|
-
directiveChildren =
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
.filter(Boolean)
|
|
642
|
-
);
|
|
624
|
+
directiveChildren = ((el.children ?? []) as (JxElement | string)[])
|
|
625
|
+
.map((c: JxElement | string) => convertJxNode(c, true))
|
|
626
|
+
.filter(Boolean) as MdastNode[];
|
|
643
627
|
}
|
|
644
628
|
|
|
645
629
|
return {
|
|
@@ -696,15 +680,13 @@ const JX_ANNOTATION_KEYS = new Set(["$title", "$description"]);
|
|
|
696
680
|
* @param {JxMutableNode} doc - Jx JSON document
|
|
697
681
|
* @returns {string} Jx Markdown source
|
|
698
682
|
*/
|
|
699
|
-
export function jxDocToMd(doc) {
|
|
683
|
+
export function jxDocToMd(doc: JxMutableNode) {
|
|
700
684
|
const { stringify: stringifyYaml } = yamlImport();
|
|
701
685
|
|
|
702
|
-
|
|
703
|
-
const lines = [];
|
|
686
|
+
const lines: string[] = [];
|
|
704
687
|
|
|
705
688
|
// Emit YAML frontmatter
|
|
706
|
-
|
|
707
|
-
const frontmatter = {};
|
|
689
|
+
const frontmatter: Record<string, unknown> = {};
|
|
708
690
|
for (const [key, value] of Object.entries(doc)) {
|
|
709
691
|
if (key === "children") continue;
|
|
710
692
|
frontmatter[key] = value;
|
|
@@ -719,20 +701,16 @@ export function jxDocToMd(doc) {
|
|
|
719
701
|
|
|
720
702
|
// Convert children to mdast and stringify with remark
|
|
721
703
|
if (Array.isArray(doc.children) && doc.children.length > 0) {
|
|
722
|
-
const mdastChildren =
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
convertJxNode(/** @type {JxElement | string} */ (child), true),
|
|
726
|
-
)
|
|
727
|
-
.filter(Boolean)
|
|
728
|
-
);
|
|
704
|
+
const mdastChildren = doc.children
|
|
705
|
+
.map((child: JxMutableNode | string) => convertJxNode(child as JxElement | string, true))
|
|
706
|
+
.filter(Boolean) as MdastNode[];
|
|
729
707
|
|
|
730
708
|
const mdast = { type: "root", children: mdastChildren };
|
|
731
709
|
const md = unified()
|
|
732
710
|
.use(remarkGfm)
|
|
733
711
|
.use(remarkDirective)
|
|
734
712
|
.use(remarkStringify, { bullet: "-", emphasis: "*", strong: "*" })
|
|
735
|
-
.stringify(
|
|
713
|
+
.stringify(mdast as unknown as import("mdast").Root);
|
|
736
714
|
|
|
737
715
|
lines.push(/** @type {string} */ (md));
|
|
738
716
|
}
|
|
@@ -750,7 +728,7 @@ export function jxDocToMd(doc) {
|
|
|
750
728
|
*
|
|
751
729
|
* @returns {{ stringify: (v: unknown) => string }}
|
|
752
730
|
*/
|
|
753
|
-
let _yaml
|
|
731
|
+
let _yaml: { stringify: (v: unknown) => string } | null = null;
|
|
754
732
|
function yamlImport() {
|
|
755
733
|
if (!_yaml) {
|
|
756
734
|
// Dynamic require avoided; use the yaml package already available in studio
|
|
@@ -767,7 +745,7 @@ function yamlImport() {
|
|
|
767
745
|
* @param {number} indent
|
|
768
746
|
* @returns {string}
|
|
769
747
|
*/
|
|
770
|
-
function yamlStringifySimple(value, indent = 0) {
|
|
748
|
+
function yamlStringifySimple(value: unknown, indent: number = 0): string {
|
|
771
749
|
if (value === null || value === undefined) return "null";
|
|
772
750
|
if (typeof value === "boolean") return String(value);
|
|
773
751
|
if (typeof value === "number") return String(value);
|
|
@@ -785,13 +763,13 @@ function yamlStringifySimple(value, indent = 0) {
|
|
|
785
763
|
if (value.length === 0) return "[]";
|
|
786
764
|
return value
|
|
787
765
|
.map((item) => {
|
|
788
|
-
const itemStr = yamlStringifySimple(item, indent + 1);
|
|
766
|
+
const itemStr: string = yamlStringifySimple(item, indent + 1);
|
|
789
767
|
if (typeof item === "object" && item !== null && !Array.isArray(item)) {
|
|
790
768
|
// Object items: first key on same line as -, rest indented
|
|
791
|
-
const objLines = itemStr.split("\n");
|
|
769
|
+
const objLines: string[] = itemStr.split("\n");
|
|
792
770
|
return `${prefix}- ${objLines[0]}\n${objLines
|
|
793
771
|
.slice(1)
|
|
794
|
-
.map((l) => `${prefix} ${l}`)
|
|
772
|
+
.map((l: string) => `${prefix} ${l}`)
|
|
795
773
|
.join("\n")}`;
|
|
796
774
|
}
|
|
797
775
|
return `${prefix}- ${itemStr}`;
|
|
@@ -800,11 +778,11 @@ function yamlStringifySimple(value, indent = 0) {
|
|
|
800
778
|
}
|
|
801
779
|
|
|
802
780
|
if (typeof value === "object") {
|
|
803
|
-
const entries = Object.entries(
|
|
781
|
+
const entries = Object.entries(value as Record<string, unknown>);
|
|
804
782
|
if (entries.length === 0) return "{}";
|
|
805
783
|
return entries
|
|
806
784
|
.map(([k, v]) => {
|
|
807
|
-
const valStr = yamlStringifySimple(v, indent + 1);
|
|
785
|
+
const valStr: string = yamlStringifySimple(v, indent + 1);
|
|
808
786
|
if (typeof v === "object" && v !== null) {
|
|
809
787
|
return `${prefix}${k}:\n${valStr}`;
|
|
810
788
|
}
|
|
@@ -823,11 +801,10 @@ function yamlStringifySimple(value, indent = 0) {
|
|
|
823
801
|
* @param {Record<string, unknown>} propsObj
|
|
824
802
|
* @returns {Record<string, string>}
|
|
825
803
|
*/
|
|
826
|
-
function collapsePropsToAttrMap(propsObj) {
|
|
827
|
-
|
|
828
|
-
const result = {};
|
|
804
|
+
function collapsePropsToAttrMap(propsObj: Record<string, unknown>) {
|
|
805
|
+
const result: Record<string, string> = {};
|
|
829
806
|
|
|
830
|
-
function walk(
|
|
807
|
+
function walk(obj: Record<string, unknown>, prefix: string) {
|
|
831
808
|
for (const [key, value] of Object.entries(obj)) {
|
|
832
809
|
let mdAttrKey = key;
|
|
833
810
|
// Strip $ prefix for Jx keywords
|
|
@@ -846,7 +823,7 @@ function collapsePropsToAttrMap(propsObj) {
|
|
|
846
823
|
if (key === "@") {
|
|
847
824
|
// Bare "@" (no media name) — treat contents as base-level style props
|
|
848
825
|
if (value && typeof value === "object" && !Array.isArray(value)) {
|
|
849
|
-
walk(
|
|
826
|
+
walk(value as Record<string, unknown>, prefix);
|
|
850
827
|
}
|
|
851
828
|
continue;
|
|
852
829
|
}
|
|
@@ -857,7 +834,7 @@ function collapsePropsToAttrMap(propsObj) {
|
|
|
857
834
|
const fullKey = prefix ? `${prefix}.${mdAttrKey}` : mdAttrKey;
|
|
858
835
|
|
|
859
836
|
if (value && typeof value === "object" && !Array.isArray(value)) {
|
|
860
|
-
walk(
|
|
837
|
+
walk(value as Record<string, unknown>, fullKey);
|
|
861
838
|
} else {
|
|
862
839
|
result[fullKey] = String(value);
|
|
863
840
|
}
|