@jxsuite/studio 0.24.0 → 0.25.1
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 +84 -7
- package/dist/studio.js.map +4 -4
- package/package.json +4 -4
- package/src/panels/shared.ts +1 -1
- package/src/utils/edit-display.ts +98 -6
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jxsuite/studio",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.25.1",
|
|
4
4
|
"description": "Jx Studio — visual builder for Jx documents",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -32,9 +32,9 @@
|
|
|
32
32
|
"dependencies": {
|
|
33
33
|
"@atlaskit/pragmatic-drag-and-drop": "^1.8.1",
|
|
34
34
|
"@atlaskit/pragmatic-drag-and-drop-hitbox": "^1.1.0",
|
|
35
|
-
"@jxsuite/parser": "^0.
|
|
36
|
-
"@jxsuite/runtime": "^0.
|
|
37
|
-
"@jxsuite/schema": "^0.
|
|
35
|
+
"@jxsuite/parser": "^0.25.1",
|
|
36
|
+
"@jxsuite/runtime": "^0.25.1",
|
|
37
|
+
"@jxsuite/schema": "^0.25.1",
|
|
38
38
|
"@spectrum-web-components/accordion": "^1.12.1",
|
|
39
39
|
"@spectrum-web-components/action-bar": "1.12.1",
|
|
40
40
|
"@spectrum-web-components/action-button": "^1.12.1",
|
package/src/panels/shared.ts
CHANGED
|
@@ -66,7 +66,7 @@ export function defaultDef(tag: string) {
|
|
|
66
66
|
else if (tag === "blockquote") def.textContent = "Quote";
|
|
67
67
|
else if (tag === "pre") def.textContent = "Preformatted text";
|
|
68
68
|
else if (tag === "input") def.attributes = { type: "text", placeholder: "Enter text..." };
|
|
69
|
-
else if (tag === "img") def.attributes = {
|
|
69
|
+
else if (tag === "img") def.attributes = { alt: "Image" };
|
|
70
70
|
else if (tag === "iframe") def.attributes = { src: "" };
|
|
71
71
|
else if (tag === "select") def.children = [{ tagName: "option", textContent: "Option 1" }];
|
|
72
72
|
else if (tag === "ul" || tag === "ol") def.children = [{ tagName: "li", textContent: "Item" }];
|
|
@@ -7,6 +7,10 @@
|
|
|
7
7
|
|
|
8
8
|
import type { JxMutableNode } from "@jxsuite/schema/types";
|
|
9
9
|
|
|
10
|
+
const mediaTags = new Set(["img", "video", "source", "iframe", "audio"]);
|
|
11
|
+
const TRANSPARENT_PX =
|
|
12
|
+
"data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7";
|
|
13
|
+
|
|
10
14
|
/**
|
|
11
15
|
* Convert a template string to a displayable expression for edit mode. Replaces ${expr} with ❮ expr
|
|
12
16
|
* ❯ so the runtime renders it as literal text.
|
|
@@ -48,9 +52,42 @@ export function prepareForEditMode(node: JxMutableNode): JxMutableNode {
|
|
|
48
52
|
const /** @type {Record<string, unknown>} */ obj = node as Record<string, unknown>;
|
|
49
53
|
|
|
50
54
|
const out: Record<string, unknown> = {};
|
|
55
|
+
let needsMediaPlaceholder = false;
|
|
56
|
+
const isMediaElement = mediaTags.has((obj.tagName as string) || "");
|
|
57
|
+
|
|
58
|
+
// Check if this media element lacks a resolvable src (top-level or in attributes)
|
|
59
|
+
if (isMediaElement) {
|
|
60
|
+
const attrs = obj.attributes as Record<string, unknown> | undefined;
|
|
61
|
+
const topSrc = obj.src;
|
|
62
|
+
const attrSrc = attrs?.src;
|
|
63
|
+
const topPoster = obj.poster;
|
|
64
|
+
const attrPoster = attrs?.poster;
|
|
65
|
+
const hasSrc = (topSrc && topSrc !== "") || (attrSrc && attrSrc !== "");
|
|
66
|
+
const hasPoster = (topPoster && topPoster !== "") || (attrPoster && attrPoster !== "");
|
|
67
|
+
if (!hasSrc && !hasPoster) {
|
|
68
|
+
needsMediaPlaceholder = true;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
51
72
|
for (const [k, v] of Object.entries(obj)) {
|
|
52
|
-
if (k === "state" || k === "$media" || k === "$
|
|
73
|
+
if (k === "state" || k === "$media" || k === "$elements") {
|
|
53
74
|
out[k] = v; // preserve as-is for runtime resolution
|
|
75
|
+
} else if (k === "$props" && v && typeof v === "object") {
|
|
76
|
+
// Process $props values: convert template strings to display format
|
|
77
|
+
const propsOut: Record<string, unknown> = {};
|
|
78
|
+
for (const [pk, pv] of Object.entries(v)) {
|
|
79
|
+
if (typeof pv === "string" && pv.includes("${")) {
|
|
80
|
+
const isUrlAttr = pk === "src" || pk === "href" || pk === "poster" || pk === "action";
|
|
81
|
+
propsOut[pk] = isUrlAttr ? "" : templateToEditDisplay(pv);
|
|
82
|
+
} else if (pv && typeof pv === "object" && (pv as Record<string, unknown>).$ref) {
|
|
83
|
+
const ref = (pv as Record<string, unknown>).$ref as string;
|
|
84
|
+
const label = ref.startsWith("#/state/") ? ref.slice(8) : ref;
|
|
85
|
+
propsOut[pk] = `{${label}}`;
|
|
86
|
+
} else {
|
|
87
|
+
propsOut[pk] = pv;
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
out[k] = propsOut;
|
|
54
91
|
} else if (k === "children") {
|
|
55
92
|
if (Array.isArray(v)) {
|
|
56
93
|
out.children = v.map(prepareForEditMode);
|
|
@@ -106,6 +143,30 @@ export function prepareForEditMode(node: JxMutableNode): JxMutableNode {
|
|
|
106
143
|
];
|
|
107
144
|
}
|
|
108
145
|
}
|
|
146
|
+
} else if (k === "attributes" && isMediaElement && v && typeof v === "object") {
|
|
147
|
+
// Process attributes for media elements: replace src/poster with transparent pixel
|
|
148
|
+
const attrs = v as Record<string, unknown>;
|
|
149
|
+
const processed: Record<string, unknown> = {};
|
|
150
|
+
for (const [ak, av] of Object.entries(attrs)) {
|
|
151
|
+
if (ak === "src" || ak === "poster") {
|
|
152
|
+
if (typeof av === "string" && av !== "" && !av.includes("${")) {
|
|
153
|
+
processed[ak] = av;
|
|
154
|
+
} else {
|
|
155
|
+
needsMediaPlaceholder = true;
|
|
156
|
+
processed[ak] = TRANSPARENT_PX;
|
|
157
|
+
}
|
|
158
|
+
} else if (typeof av === "string" && av.includes("${")) {
|
|
159
|
+
const isUrlAttr = ak === "href" || ak === "action";
|
|
160
|
+
processed[ak] = isUrlAttr ? "" : templateToEditDisplay(av);
|
|
161
|
+
} else if (av && typeof av === "object" && (av as Record<string, unknown>).$ref) {
|
|
162
|
+
const ref = (av as Record<string, unknown>).$ref as string;
|
|
163
|
+
const label = ref.startsWith("#/state/") ? ref.slice(8) : ref;
|
|
164
|
+
processed[ak] = `{${label}}`;
|
|
165
|
+
} else {
|
|
166
|
+
processed[ak] = av;
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
out.attributes = processed;
|
|
109
170
|
} else if (k === "style") {
|
|
110
171
|
// Replace template strings in style values with empty strings
|
|
111
172
|
if (v && typeof v === "object") {
|
|
@@ -119,16 +180,39 @@ export function prepareForEditMode(node: JxMutableNode): JxMutableNode {
|
|
|
119
180
|
}
|
|
120
181
|
} else if (typeof v === "string" && v.includes("${")) {
|
|
121
182
|
// Template string in a display property → show raw expression
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
183
|
+
const isMediaSrc =
|
|
184
|
+
(k === "src" || k === "poster") && mediaTags.has((obj.tagName as string) || "");
|
|
185
|
+
if (isMediaSrc) {
|
|
186
|
+
needsMediaPlaceholder = true;
|
|
187
|
+
out[k] = TRANSPARENT_PX;
|
|
188
|
+
} else {
|
|
189
|
+
const isUrlAttr = k === "src" || k === "href" || k === "poster" || k === "action";
|
|
190
|
+
out[k] = isUrlAttr ? "" : templateToEditDisplay(v);
|
|
191
|
+
}
|
|
125
192
|
} else if (v && typeof v === "object" && (v as Record<string, unknown>).$ref) {
|
|
126
193
|
// $ref binding → show ref path as literal text
|
|
127
194
|
const ref = (v as Record<string, unknown>).$ref as string;
|
|
128
195
|
const label = ref.startsWith("#/state/") ? ref.slice(8) : ref;
|
|
129
|
-
|
|
196
|
+
const isMediaSrc =
|
|
197
|
+
(k === "src" || k === "poster") && mediaTags.has((obj.tagName as string) || "");
|
|
198
|
+
if (isMediaSrc) {
|
|
199
|
+
needsMediaPlaceholder = true;
|
|
200
|
+
out[k] = TRANSPARENT_PX;
|
|
201
|
+
} else {
|
|
202
|
+
out[k] = `{${label}}`;
|
|
203
|
+
}
|
|
130
204
|
} else {
|
|
131
|
-
|
|
205
|
+
// Empty src/poster on media elements → use transparent pixel placeholder
|
|
206
|
+
if (
|
|
207
|
+
(k === "src" || k === "poster") &&
|
|
208
|
+
v === "" &&
|
|
209
|
+
mediaTags.has((obj.tagName as string) || "")
|
|
210
|
+
) {
|
|
211
|
+
needsMediaPlaceholder = true;
|
|
212
|
+
out[k] = TRANSPARENT_PX;
|
|
213
|
+
} else {
|
|
214
|
+
out[k] = prepareForEditMode(v as JxMutableNode);
|
|
215
|
+
}
|
|
132
216
|
}
|
|
133
217
|
}
|
|
134
218
|
|
|
@@ -202,5 +286,13 @@ export function prepareForEditMode(node: JxMutableNode): JxMutableNode {
|
|
|
202
286
|
}
|
|
203
287
|
}
|
|
204
288
|
|
|
289
|
+
// Media elements with missing/dynamic src get a placeholder class
|
|
290
|
+
if (needsMediaPlaceholder) {
|
|
291
|
+
const cls = (out.className as string) || "";
|
|
292
|
+
if (!cls.includes("empty-media-placeholder")) {
|
|
293
|
+
out.className = cls ? cls + " empty-media-placeholder" : "empty-media-placeholder";
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
|
|
205
297
|
return out;
|
|
206
298
|
}
|