@jxsuite/studio 2.4.1 → 2.4.3
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/chunks/{content-source-cn9te96r.js → content-source-k9724vcv.js} +8 -8
- package/dist/chunks/{convert-to-repeater-65gjgdst.js → convert-to-repeater-k10dvcc7.js} +7 -7
- package/dist/chunks/{extension-sections-mdgmqrry.js → extension-sections-0ada0xcw.js} +11 -11
- package/dist/chunks/{files-dsh5g8j4.js → files-yew27sn9.js} +12 -12
- package/dist/chunks/{format-host-m9h45a79.js → format-host-wewba4nq.js} +2 -2
- package/dist/chunks/{git-panel-rfs8pqjj.js → git-panel-2dh1aw50.js} +7 -7
- package/dist/chunks/{layers-panel-6cj5nf6a.js → layers-panel-qnsghwwd.js} +10 -10
- package/dist/chunks/{publish-panel-b0xh7b4y.js → publish-panel-a0pdft5w.js} +5 -5
- package/dist/chunks/{quick-search-a83fhnsh.js → quick-search-9avyc4pc.js} +13 -13
- package/dist/chunks/{right-panel-hrw5kdh5.js → right-panel-kcnhtc5j.js} +9 -9
- package/dist/chunks/{studio-4m39t01b.js → studio-0b6fs05q.js} +5 -5
- package/dist/chunks/{studio-gqpbrrj5.js → studio-26sv44br.js} +8 -8
- package/dist/chunks/{studio-d1f2rqxh.js → studio-28xc8ws4.js} +4 -4
- package/dist/chunks/{studio-675v08qj.js → studio-aweyr4v1.js} +2 -2
- package/dist/chunks/{studio-0svvdm0a.js → studio-cz2j1c0g.js} +4 -4
- package/dist/chunks/{studio-qymkxbcq.js → studio-dgj7gq44.js} +3 -3
- package/dist/chunks/{studio-rkr6pm84.js → studio-ef8t53vc.js} +3 -3
- package/dist/chunks/{studio-1d6bm7eg.js → studio-f5yyvqxv.js} +7 -7
- package/dist/chunks/{studio-rz861p2b.js → studio-hb7c46jy.js} +5 -5
- package/dist/chunks/{studio-gfzyxa0m.js → studio-hyn3vhwr.js} +5 -5
- package/dist/chunks/{studio-9sxn0eaa.js → studio-jnakhg87.js} +4 -4
- package/dist/chunks/{studio-tdt7b6qb.js → studio-ke244pam.js} +6 -6
- package/dist/chunks/{studio-h6553a8h.js → studio-pyrw8h0w.js} +9 -7
- package/dist/chunks/{studio-kfq9ed73.js → studio-ra7ar50f.js} +3 -3
- package/dist/chunks/{studio-40fm3hyr.js → studio-rqemfhz6.js} +2 -2
- package/dist/chunks/{studio-91sdrn9x.js → studio-sccge2wf.js} +2 -2
- package/dist/chunks/{studio-n9k15qdg.js → studio-v1va0vhd.js} +8 -8
- package/dist/chunks/{studio-jand3kjg.js → studio-v2c5pmjj.js} +10 -10
- package/dist/chunks/{studio-qb6vy1z1.js → studio-ws6ef6js.js} +8 -8
- package/dist/iframe-entry.js +35 -3
- package/dist/studio.js +131 -32
- package/package.json +9 -9
- package/src/canvas/canvas-origin.ts +14 -2
- package/src/canvas/iframe-inline-edit.ts +79 -25
- package/src/platforms/cloud.ts +61 -2
- package/src/types.ts +3 -1
package/dist/studio.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import {
|
|
2
2
|
seedPublishConnected
|
|
3
|
-
} from "./chunks/studio-
|
|
3
|
+
} from "./chunks/studio-dgj7gq44.js";
|
|
4
4
|
import"./chunks/studio-wdt9y92a.js";
|
|
5
5
|
import {
|
|
6
6
|
ElementResolutionController,
|
|
@@ -67,7 +67,7 @@ import {
|
|
|
67
67
|
registerStyleCommands,
|
|
68
68
|
render as render3,
|
|
69
69
|
setInspectorTab
|
|
70
|
-
} from "./chunks/studio-
|
|
70
|
+
} from "./chunks/studio-f5yyvqxv.js";
|
|
71
71
|
import {
|
|
72
72
|
CommandUnavailableError,
|
|
73
73
|
chordFromEvent,
|
|
@@ -99,7 +99,7 @@ import {
|
|
|
99
99
|
showSlashMenu,
|
|
100
100
|
showSlashMenuAtRect,
|
|
101
101
|
suppressBlockActionBar
|
|
102
|
-
} from "./chunks/studio-
|
|
102
|
+
} from "./chunks/studio-ws6ef6js.js";
|
|
103
103
|
import {
|
|
104
104
|
addableContexts,
|
|
105
105
|
defaultCommands,
|
|
@@ -111,7 +111,7 @@ import {
|
|
|
111
111
|
tokenLabel,
|
|
112
112
|
transposeStylebookStyle,
|
|
113
113
|
writeTokenOverride
|
|
114
|
-
} from "./chunks/studio-
|
|
114
|
+
} from "./chunks/studio-hyn3vhwr.js";
|
|
115
115
|
import {
|
|
116
116
|
DERIVE_PRESETS,
|
|
117
117
|
JxColorPopover,
|
|
@@ -144,7 +144,7 @@ import {
|
|
|
144
144
|
spNumberField,
|
|
145
145
|
spTextArea,
|
|
146
146
|
spTextField
|
|
147
|
-
} from "./chunks/studio-
|
|
147
|
+
} from "./chunks/studio-v1va0vhd.js";
|
|
148
148
|
import {
|
|
149
149
|
LitElement,
|
|
150
150
|
css
|
|
@@ -160,7 +160,7 @@ import {
|
|
|
160
160
|
import {
|
|
161
161
|
initQuickSearch,
|
|
162
162
|
openQuickSearch
|
|
163
|
-
} from "./chunks/studio-
|
|
163
|
+
} from "./chunks/studio-jnakhg87.js";
|
|
164
164
|
import {
|
|
165
165
|
addRecentProject,
|
|
166
166
|
clearRecentProjects,
|
|
@@ -187,10 +187,10 @@ import {
|
|
|
187
187
|
removeRecentProject,
|
|
188
188
|
renderFilesTemplate,
|
|
189
189
|
seedEntry
|
|
190
|
-
} from "./chunks/studio-
|
|
190
|
+
} from "./chunks/studio-v2c5pmjj.js";
|
|
191
191
|
import {
|
|
192
192
|
computeWindow
|
|
193
|
-
} from "./chunks/studio-
|
|
193
|
+
} from "./chunks/studio-sccge2wf.js";
|
|
194
194
|
import {
|
|
195
195
|
autoSyncProjectOnOpen,
|
|
196
196
|
cleanupGitPanel,
|
|
@@ -211,13 +211,13 @@ import {
|
|
|
211
211
|
registerSourceControlCommands,
|
|
212
212
|
renderGitPanel,
|
|
213
213
|
stripRange
|
|
214
|
-
} from "./chunks/studio-
|
|
214
|
+
} from "./chunks/studio-ke244pam.js";
|
|
215
215
|
import {
|
|
216
216
|
createPanelScheduler
|
|
217
217
|
} from "./chunks/studio-8jpzqk5s.js";
|
|
218
218
|
import {
|
|
219
219
|
selectContributedEntry
|
|
220
|
-
} from "./chunks/studio-
|
|
220
|
+
} from "./chunks/studio-hb7c46jy.js";
|
|
221
221
|
import {
|
|
222
222
|
notifySettingsDocument,
|
|
223
223
|
onSettingsDocumentChanged,
|
|
@@ -238,7 +238,7 @@ import {
|
|
|
238
238
|
renderForm,
|
|
239
239
|
renderProvenanceChip,
|
|
240
240
|
resolveContextPointer
|
|
241
|
-
} from "./chunks/studio-
|
|
241
|
+
} from "./chunks/studio-ef8t53vc.js";
|
|
242
242
|
import {
|
|
243
243
|
validateDoc,
|
|
244
244
|
validateProjectConfig
|
|
@@ -263,7 +263,7 @@ import {
|
|
|
263
263
|
registerGridCommands,
|
|
264
264
|
scanLibrary,
|
|
265
265
|
uploadDirForCategory
|
|
266
|
-
} from "./chunks/studio-
|
|
266
|
+
} from "./chunks/studio-0b6fs05q.js";
|
|
267
267
|
import {
|
|
268
268
|
live
|
|
269
269
|
} from "./chunks/studio-c31yb4t6.js";
|
|
@@ -307,7 +307,7 @@ import {
|
|
|
307
307
|
setIncludeDrafts,
|
|
308
308
|
startFsSync,
|
|
309
309
|
writesForTurn
|
|
310
|
-
} from "./chunks/studio-
|
|
310
|
+
} from "./chunks/studio-26sv44br.js";
|
|
311
311
|
import"./chunks/studio-2w11grav.js";
|
|
312
312
|
import"./chunks/studio-bw5dz6km.js";
|
|
313
313
|
import {
|
|
@@ -316,18 +316,18 @@ import {
|
|
|
316
316
|
isImage,
|
|
317
317
|
setMediaChangedHandler,
|
|
318
318
|
uploadAssets
|
|
319
|
-
} from "./chunks/studio-
|
|
319
|
+
} from "./chunks/studio-cz2j1c0g.js";
|
|
320
320
|
import {
|
|
321
321
|
beginActivity,
|
|
322
322
|
registerActivityPanel,
|
|
323
323
|
showProgressModal
|
|
324
|
-
} from "./chunks/studio-
|
|
324
|
+
} from "./chunks/studio-28xc8ws4.js";
|
|
325
325
|
import {
|
|
326
326
|
currentDeploy,
|
|
327
327
|
latestDeployment,
|
|
328
328
|
noteDeployment,
|
|
329
329
|
platformSupportsPublish
|
|
330
|
-
} from "./chunks/studio-
|
|
330
|
+
} from "./chunks/studio-aweyr4v1.js";
|
|
331
331
|
import {
|
|
332
332
|
CATEGORIES,
|
|
333
333
|
SPAN_FULL_RENDER,
|
|
@@ -439,7 +439,7 @@ import {
|
|
|
439
439
|
timeSpan,
|
|
440
440
|
timeSpanAsync,
|
|
441
441
|
updateActivePanelHeaders
|
|
442
|
-
} from "./chunks/studio-
|
|
442
|
+
} from "./chunks/studio-pyrw8h0w.js";
|
|
443
443
|
import {
|
|
444
444
|
BOTTOM_TAB_IDS,
|
|
445
445
|
BUFFER_COMMIT,
|
|
@@ -606,7 +606,7 @@ import {
|
|
|
606
606
|
updateSiteConfig,
|
|
607
607
|
view,
|
|
608
608
|
workspace
|
|
609
|
-
} from "./chunks/studio-
|
|
609
|
+
} from "./chunks/studio-ra7ar50f.js";
|
|
610
610
|
import {
|
|
611
611
|
classMap,
|
|
612
612
|
ifDefined,
|
|
@@ -669,7 +669,7 @@ import {
|
|
|
669
669
|
mountStillWanted,
|
|
670
670
|
refreshExtensionUi,
|
|
671
671
|
refreshFormats
|
|
672
|
-
} from "./chunks/studio-
|
|
672
|
+
} from "./chunks/studio-rqemfhz6.js";
|
|
673
673
|
import {
|
|
674
674
|
getPlatform,
|
|
675
675
|
hasPlatform,
|
|
@@ -16807,7 +16807,7 @@ function entryIdsFor(collection) {
|
|
|
16807
16807
|
if (known) {
|
|
16808
16808
|
return known;
|
|
16809
16809
|
}
|
|
16810
|
-
const pending = import("./chunks/content-source-
|
|
16810
|
+
const pending = import("./chunks/content-source-k9724vcv.js").then((m) => m.listCollectionEntryIds(collection));
|
|
16811
16811
|
entryIdCache.set(collection, pending);
|
|
16812
16812
|
return pending;
|
|
16813
16813
|
}
|
|
@@ -17068,7 +17068,7 @@ function renderEntryMode(surface, tab) {
|
|
|
17068
17068
|
});
|
|
17069
17069
|
}
|
|
17070
17070
|
async function openEntryEditor(path) {
|
|
17071
|
-
const { openFileInTab: openFileInTab2 } = await import("./chunks/files-
|
|
17071
|
+
const { openFileInTab: openFileInTab2 } = await import("./chunks/files-yew27sn9.js");
|
|
17072
17072
|
await openFileInTab2(path);
|
|
17073
17073
|
for (const [id, tab] of workspace.tabs.entries()) {
|
|
17074
17074
|
if (tab.documentPath !== path) {
|
|
@@ -17129,7 +17129,7 @@ async function createEntry(collectionName) {
|
|
|
17129
17129
|
});
|
|
17130
17130
|
return null;
|
|
17131
17131
|
}
|
|
17132
|
-
const { createFileIn: createFileIn2 } = await import("./chunks/files-
|
|
17132
|
+
const { createFileIn: createFileIn2 } = await import("./chunks/files-yew27sn9.js");
|
|
17133
17133
|
const created = await createFileIn2({
|
|
17134
17134
|
content,
|
|
17135
17135
|
dir: collection.dir,
|
|
@@ -22349,7 +22349,7 @@ function groupProblems(records = problems) {
|
|
|
22349
22349
|
return groups;
|
|
22350
22350
|
}
|
|
22351
22351
|
function openProblemPath(path) {
|
|
22352
|
-
import("./chunks/files-
|
|
22352
|
+
import("./chunks/files-yew27sn9.js").then((m) => m.openFileInTab(path));
|
|
22353
22353
|
}
|
|
22354
22354
|
function actionTpl(record) {
|
|
22355
22355
|
const id = record.action;
|
|
@@ -28687,6 +28687,71 @@ function renderAiPanelTemplate() {
|
|
|
28687
28687
|
`;
|
|
28688
28688
|
}
|
|
28689
28689
|
|
|
28690
|
+
// ../schema/src/component-meta.ts
|
|
28691
|
+
function collectSlotDefs(node, out = []) {
|
|
28692
|
+
if (!node || typeof node !== "object" || Array.isArray(node)) {
|
|
28693
|
+
return out;
|
|
28694
|
+
}
|
|
28695
|
+
const el = node;
|
|
28696
|
+
if (el.tagName === "slot") {
|
|
28697
|
+
const attrs = el.attributes;
|
|
28698
|
+
const rawName = attrs?.name;
|
|
28699
|
+
const name = typeof rawName === "string" ? rawName.trim() : "";
|
|
28700
|
+
const { children } = el;
|
|
28701
|
+
out.push({
|
|
28702
|
+
name,
|
|
28703
|
+
...Array.isArray(children) && children.length > 0 ? { fallback: children } : {}
|
|
28704
|
+
});
|
|
28705
|
+
}
|
|
28706
|
+
if (Array.isArray(el.children)) {
|
|
28707
|
+
for (const c of el.children) {
|
|
28708
|
+
collectSlotDefs(c, out);
|
|
28709
|
+
}
|
|
28710
|
+
}
|
|
28711
|
+
return out;
|
|
28712
|
+
}
|
|
28713
|
+
function isPropEntry(def) {
|
|
28714
|
+
if (def == null) {
|
|
28715
|
+
return false;
|
|
28716
|
+
}
|
|
28717
|
+
if (typeof def !== "object") {
|
|
28718
|
+
return true;
|
|
28719
|
+
}
|
|
28720
|
+
const obj = def;
|
|
28721
|
+
return !obj.$prototype && !obj.$handler && !obj.$compute;
|
|
28722
|
+
}
|
|
28723
|
+
function componentMetaFrom(doc, path) {
|
|
28724
|
+
if (!doc || typeof doc !== "object" || Array.isArray(doc)) {
|
|
28725
|
+
return null;
|
|
28726
|
+
}
|
|
28727
|
+
const content = doc;
|
|
28728
|
+
const { tagName } = content;
|
|
28729
|
+
if (typeof tagName !== "string" || !tagName.includes("-")) {
|
|
28730
|
+
return null;
|
|
28731
|
+
}
|
|
28732
|
+
const slots = collectSlotDefs(content);
|
|
28733
|
+
const state2 = content.state ?? {};
|
|
28734
|
+
return {
|
|
28735
|
+
$id: typeof content.$id === "string" && content.$id ? content.$id : null,
|
|
28736
|
+
hasElements: Array.isArray(content.$elements) && content.$elements.length > 0,
|
|
28737
|
+
path,
|
|
28738
|
+
props: Object.entries(state2).filter(([, def]) => isPropEntry(def)).map(([name, def]) => {
|
|
28739
|
+
if (typeof def !== "object" || def === null) {
|
|
28740
|
+
return { default: def, name, type: typeof def };
|
|
28741
|
+
}
|
|
28742
|
+
const obj = def;
|
|
28743
|
+
return {
|
|
28744
|
+
default: obj.default,
|
|
28745
|
+
format: obj.format,
|
|
28746
|
+
name,
|
|
28747
|
+
type: obj.type
|
|
28748
|
+
};
|
|
28749
|
+
}),
|
|
28750
|
+
...slots.length > 0 ? { slots } : {},
|
|
28751
|
+
tagName
|
|
28752
|
+
};
|
|
28753
|
+
}
|
|
28754
|
+
|
|
28690
28755
|
// ../protocol/src/problem.ts
|
|
28691
28756
|
var PROBLEM_TYPE_BASE = "https://jxsuite.com/problems/";
|
|
28692
28757
|
function problemSlug(type) {
|
|
@@ -29098,7 +29163,41 @@ function createCloudPlatform(project) {
|
|
|
29098
29163
|
return data.result;
|
|
29099
29164
|
},
|
|
29100
29165
|
async discoverComponents() {
|
|
29101
|
-
|
|
29166
|
+
const SKIP = new Set(["node_modules", "dist", ".git", ".claude", ".obsidian", "build"]);
|
|
29167
|
+
const MAX_FILES = 400;
|
|
29168
|
+
const MAX_DEPTH = 6;
|
|
29169
|
+
const jsonPaths = [];
|
|
29170
|
+
const walk = async (dir, depth) => {
|
|
29171
|
+
if (depth > MAX_DEPTH || jsonPaths.length >= MAX_FILES) {
|
|
29172
|
+
return;
|
|
29173
|
+
}
|
|
29174
|
+
let entries;
|
|
29175
|
+
try {
|
|
29176
|
+
entries = await platform.listDirectory(dir);
|
|
29177
|
+
} catch {
|
|
29178
|
+
return;
|
|
29179
|
+
}
|
|
29180
|
+
const dirs = [];
|
|
29181
|
+
for (const entry of entries) {
|
|
29182
|
+
if (entry.type === "directory") {
|
|
29183
|
+
if (!SKIP.has(entry.name) && !entry.name.startsWith(".")) {
|
|
29184
|
+
dirs.push(entry.path);
|
|
29185
|
+
}
|
|
29186
|
+
} else if (entry.name.endsWith(".json") && jsonPaths.length < MAX_FILES) {
|
|
29187
|
+
jsonPaths.push(entry.path);
|
|
29188
|
+
}
|
|
29189
|
+
}
|
|
29190
|
+
await Promise.all(dirs.map(async (d) => walk(d, depth + 1)));
|
|
29191
|
+
};
|
|
29192
|
+
await walk("", 0);
|
|
29193
|
+
const metas = await Promise.all(jsonPaths.map(async (path) => {
|
|
29194
|
+
try {
|
|
29195
|
+
return componentMetaFrom(JSON.parse(await platform.readFile(path)), path);
|
|
29196
|
+
} catch {
|
|
29197
|
+
return null;
|
|
29198
|
+
}
|
|
29199
|
+
}));
|
|
29200
|
+
return metas.filter((m) => m !== null);
|
|
29102
29201
|
},
|
|
29103
29202
|
async codeService() {
|
|
29104
29203
|
return null;
|
|
@@ -58591,9 +58690,9 @@ function renderDefsEditor(container) {
|
|
|
58591
58690
|
|
|
58592
58691
|
// src/version.ts
|
|
58593
58692
|
var APP_NAME = "Jx Studio";
|
|
58594
|
-
var VERSION = "2.4.
|
|
58595
|
-
var BUILD_DATE = "2026-08-
|
|
58596
|
-
var GIT_COMMIT = "
|
|
58693
|
+
var VERSION = "2.4.3";
|
|
58694
|
+
var BUILD_DATE = "2026-08-25T03:20:06.206Z";
|
|
58695
|
+
var GIT_COMMIT = "624ef53";
|
|
58597
58696
|
var LINKS = {
|
|
58598
58697
|
github: "https://github.com/jxsuite/jx",
|
|
58599
58698
|
docs: "https://github.com/jxsuite/jx#readme",
|
|
@@ -59670,7 +59769,7 @@ async function openProjectSettings(section) {
|
|
|
59670
59769
|
showSettingsDocument();
|
|
59671
59770
|
notifySettingsDocument();
|
|
59672
59771
|
try {
|
|
59673
|
-
const { syncExtensionSettingsSections } = await import("./chunks/extension-sections-
|
|
59772
|
+
const { syncExtensionSettingsSections } = await import("./chunks/extension-sections-0ada0xcw.js");
|
|
59674
59773
|
await syncExtensionSettingsSections();
|
|
59675
59774
|
} catch (error) {
|
|
59676
59775
|
notify.warn(`Could not load extension settings sections — ${errorMessage(error)}`, {
|
|
@@ -60294,7 +60393,7 @@ function publishCommands() {
|
|
|
60294
60393
|
name: "set_up_publishing"
|
|
60295
60394
|
},
|
|
60296
60395
|
run: async () => {
|
|
60297
|
-
const { openPublishPanel } = await import("./chunks/publish-panel-
|
|
60396
|
+
const { openPublishPanel } = await import("./chunks/publish-panel-a0pdft5w.js");
|
|
60298
60397
|
openPublishPanel();
|
|
60299
60398
|
},
|
|
60300
60399
|
title: "Set Up Publishing"
|
|
@@ -61016,7 +61115,7 @@ function i18nCommands() {
|
|
|
61016
61115
|
});
|
|
61017
61116
|
return;
|
|
61018
61117
|
}
|
|
61019
|
-
const { openFileInTab: openFileInTab2 } = await import("./chunks/files-
|
|
61118
|
+
const { openFileInTab: openFileInTab2 } = await import("./chunks/files-yew27sn9.js");
|
|
61020
61119
|
await openFileInTab2(path);
|
|
61021
61120
|
},
|
|
61022
61121
|
title: "Open Translation"
|
|
@@ -61063,7 +61162,7 @@ function i18nCommands() {
|
|
|
61063
61162
|
return;
|
|
61064
61163
|
}
|
|
61065
61164
|
const cut = path.lastIndexOf("/");
|
|
61066
|
-
const { createFileIn: createFileIn2, openFileInTab: openFileInTab2 } = await import("./chunks/files-
|
|
61165
|
+
const { createFileIn: createFileIn2, openFileInTab: openFileInTab2 } = await import("./chunks/files-yew27sn9.js");
|
|
61067
61166
|
const created = await createFileIn2({
|
|
61068
61167
|
content,
|
|
61069
61168
|
dir: cut === -1 ? "." : path.slice(0, cut),
|
|
@@ -61876,5 +61975,5 @@ export {
|
|
|
61876
61975
|
hasUnsavedTabs
|
|
61877
61976
|
};
|
|
61878
61977
|
|
|
61879
|
-
//# debugId=
|
|
61978
|
+
//# debugId=980E02AF5DBA79D264756E2164756E21
|
|
61880
61979
|
//# sourceMappingURL=studio.js.map
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jxsuite/studio",
|
|
3
|
-
"version": "2.4.
|
|
3
|
+
"version": "2.4.3",
|
|
4
4
|
"description": "Jx Studio — visual builder for Jx documents",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -57,14 +57,14 @@
|
|
|
57
57
|
"dependencies": {
|
|
58
58
|
"@atlaskit/pragmatic-drag-and-drop": "^3.0.0",
|
|
59
59
|
"@atlaskit/pragmatic-drag-and-drop-hitbox": "^2.1.0",
|
|
60
|
-
"@jxsuite/ai": "^0.36.
|
|
61
|
-
"@jxsuite/collab": "^0.8.
|
|
62
|
-
"@jxsuite/create": "^1.3.
|
|
63
|
-
"@jxsuite/formulas": "^0.0.
|
|
64
|
-
"@jxsuite/markup": "^0.4.
|
|
65
|
-
"@jxsuite/protocol": "^1.2.
|
|
66
|
-
"@jxsuite/runtime": "^2.0.
|
|
67
|
-
"@jxsuite/schema": "^1.8.
|
|
60
|
+
"@jxsuite/ai": "^0.36.3",
|
|
61
|
+
"@jxsuite/collab": "^0.8.2",
|
|
62
|
+
"@jxsuite/create": "^1.3.6",
|
|
63
|
+
"@jxsuite/formulas": "^0.0.13",
|
|
64
|
+
"@jxsuite/markup": "^0.4.6",
|
|
65
|
+
"@jxsuite/protocol": "^1.2.1",
|
|
66
|
+
"@jxsuite/runtime": "^2.0.5",
|
|
67
|
+
"@jxsuite/schema": "^1.8.1",
|
|
68
68
|
"@spectrum-web-components/accordion": "^1.12.2",
|
|
69
69
|
"@spectrum-web-components/action-bar": "1.12.2",
|
|
70
70
|
"@spectrum-web-components/action-button": "^1.12.2",
|
|
@@ -42,12 +42,24 @@ export function canvasBaseOrigin(): string {
|
|
|
42
42
|
* @returns {string} A base URL ending in `/`, safe to pass to `new URL(relativePath, base)`.
|
|
43
43
|
*/
|
|
44
44
|
export function documentBase(projectRoot?: string): string {
|
|
45
|
+
const origin = canvasBaseOrigin();
|
|
45
46
|
const declared = hasPlatform() ? getPlatform().documentBaseUrl : undefined;
|
|
46
47
|
if (declared) {
|
|
47
|
-
|
|
48
|
+
const withSlash = declared.endsWith("/") ? declared : `${declared}/`;
|
|
49
|
+
/*
|
|
50
|
+
* ABSOLUTE, ALWAYS. The caller uses this as `new URL(documentPath, base)`, and a `base` that is
|
|
51
|
+
* itself relative throws `Failed to construct 'URL': Invalid base URL` — which is not a failed
|
|
52
|
+
* fetch but a failed MOUNT: the canvas renders nothing at all.
|
|
53
|
+
*
|
|
54
|
+
* Jx Cloud declares a root-relative base (`/api/v1/p/:owner/:repo/:branch/studio/raw/`), which
|
|
55
|
+
* is the natural thing to declare and must keep working. Resolving it against the canvas origin
|
|
56
|
+
* here means a platform may declare either shape. An already-absolute URL passes through
|
|
57
|
+
* unchanged, because that is what `new URL` does with one.
|
|
58
|
+
*/
|
|
59
|
+
return new URL(withSlash, `${origin}/`).href;
|
|
48
60
|
}
|
|
49
61
|
const root = projectRoot || "";
|
|
50
|
-
return `${
|
|
62
|
+
return `${origin}/${root ? `${root}/` : ""}`;
|
|
51
63
|
}
|
|
52
64
|
|
|
53
65
|
/**
|
|
@@ -38,6 +38,7 @@ import { adjacentBlock, blocksInOrder } from "./iframe-position";
|
|
|
38
38
|
import { isTagActiveInSelection, toggleInlineFormat } from "../editor/inline-format";
|
|
39
39
|
import { applyLink, insertTemplateToken, linkStateForSelection } from "../editor/inline-link";
|
|
40
40
|
import { restoreTemplateExpressions } from "../utils/edit-display";
|
|
41
|
+
import { RESERVED_KEYS, camelToKebab } from "@jxsuite/runtime";
|
|
41
42
|
import { rectOfRange } from "../utils/geometry";
|
|
42
43
|
import { getNodeAtPath } from "../state";
|
|
43
44
|
import { parseJxPath, serializeJxPath } from "./path-mapping";
|
|
@@ -86,6 +87,77 @@ function ownerInstanceOf(el: HTMLElement): HTMLElement | null {
|
|
|
86
87
|
return null;
|
|
87
88
|
}
|
|
88
89
|
|
|
90
|
+
/**
|
|
91
|
+
* Whether the instance node delivers `prop` by some route OTHER than `$props` — in which case
|
|
92
|
+
* "absent from `$props`" does not mean "unset", and a `$props` write would add a SECOND source for
|
|
93
|
+
* one rendered value.
|
|
94
|
+
*
|
|
95
|
+
* The routes are the property bridge's own, in the order `connectedCallback` reads them
|
|
96
|
+
* (`compiler.md` §4.4) — a `data-jx-props` payload, literal `props.*` attributes, then JS
|
|
97
|
+
* properties set before connection — plus the one that reaches that third source without going
|
|
98
|
+
* through `$props`. Each was reproduced against the real runtime:
|
|
99
|
+
*
|
|
100
|
+
* 1. `attributes: { "data-jx-props": "{…}" }` — the compiler's pre-rendered payload, parsed into state
|
|
101
|
+
* before anything else. Studio edits source documents rather than build output, so this is the
|
|
102
|
+
* rare one; it is here because the bridge reads it.
|
|
103
|
+
* 2. The JSON shorthand `attributes: { "props.text": "…" }`. `connectedCallback` collects `props.*`
|
|
104
|
+
* attributes into state, so the marker renders that text while `$props` is empty. This repo's
|
|
105
|
+
* own site carries 95 of them.
|
|
106
|
+
* 3. A prop whose name collides with a REFLECTED HTMLElement property — `title`, `role`, `id`, `lang`,
|
|
107
|
+
* `dir`, `slot`, `hidden`. `setAttribute("title", …)` writes the JS property as a side effect,
|
|
108
|
+
* and the merge `key in this && this[key] !== undefined` picks it up. 41 shipped starter
|
|
109
|
+
* components declare `title` or `role` as state. Matched the way `instanceSupplies` matches:
|
|
110
|
+
* attribute names are case-insensitive, and an ARIA-style reflection carries a kebab attribute
|
|
111
|
+
* for a camelCase property (`ariaLabel` → `aria-label`).
|
|
112
|
+
* 4. A TOP-LEVEL key on the instance node — `{ "tagName": "x-card", "heading": "Local" }`. This is the
|
|
113
|
+
* property-first interface written straight onto the node: `applyProperties` sets every
|
|
114
|
+
* non-reserved key as a JS property, which is exactly what the merge then reads back off `this`.
|
|
115
|
+
* Reserved keys (`name`, `items`, `children`, …) are skipped there and so are skipped here —
|
|
116
|
+
* refusing on one would block a prop that the key never delivered.
|
|
117
|
+
*
|
|
118
|
+
* Editing any of them writes `$props[prop]` and leaves the other value standing. For case 3 the
|
|
119
|
+
* attribute WINS (applyAttributes runs after the $props assignment) so the edit is simply
|
|
120
|
+
* invisible; for the rest `$props` wins and the stale value waits — clearing the prop later
|
|
121
|
+
* resurrects it. Refusing keeps the properties panel, which edits the real source, as the one way
|
|
122
|
+
* in.
|
|
123
|
+
*/
|
|
124
|
+
function deliveredOutsideProps(node: JxMutableNode | undefined, prop: string): boolean {
|
|
125
|
+
if (!node) {
|
|
126
|
+
return false;
|
|
127
|
+
}
|
|
128
|
+
const attrs = node.attributes as Record<string, unknown> | undefined;
|
|
129
|
+
if (attrs) {
|
|
130
|
+
if (attrs[`props.${prop}`] !== undefined) {
|
|
131
|
+
return true;
|
|
132
|
+
}
|
|
133
|
+
const reflected = new Set([prop.toLowerCase(), camelToKebab(prop).toLowerCase()]);
|
|
134
|
+
for (const [name, val] of Object.entries(attrs)) {
|
|
135
|
+
if (val !== undefined && reflected.has(name.toLowerCase())) {
|
|
136
|
+
return true;
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
const payload = attrs["data-jx-props"];
|
|
140
|
+
if (typeof payload === "string" && payloadNames(payload, prop)) {
|
|
141
|
+
return true;
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
// `$`-prefixed keys are scope bindings, never DOM properties — `applyProperties` skips them too.
|
|
145
|
+
if (RESERVED_KEYS.has(prop) || prop.startsWith("$")) {
|
|
146
|
+
return false;
|
|
147
|
+
}
|
|
148
|
+
return (node as Record<string, unknown>)[prop] !== undefined;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/** Whether a `data-jx-props` JSON payload carries `prop`. Unparseable payloads deliver nothing. */
|
|
152
|
+
function payloadNames(payload: string, prop: string): boolean {
|
|
153
|
+
try {
|
|
154
|
+
const parsed: unknown = JSON.parse(payload);
|
|
155
|
+
return typeof parsed === "object" && parsed !== null && prop in parsed;
|
|
156
|
+
} catch {
|
|
157
|
+
return false;
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
|
|
89
161
|
/** Locate the rendered element for a document path via its stamped `data-jx-path`. */
|
|
90
162
|
function elementForPath(container: HTMLElement, path: JxPath): HTMLElement | null {
|
|
91
163
|
const serialized = serializeJxPath(path);
|
|
@@ -267,10 +339,12 @@ export function startIframeInlineEdit(
|
|
|
267
339
|
};
|
|
268
340
|
|
|
269
341
|
/**
|
|
270
|
-
* Whether the instance's
|
|
271
|
-
*
|
|
272
|
-
*
|
|
273
|
-
*
|
|
342
|
+
* Whether the instance's prop (read from the pre-edit-display shadow doc) is plain-text editable
|
|
343
|
+
* AND `$props` is the place it lives — a value the instance delivers by another route belongs to
|
|
344
|
+
* that route, and `deliveredOutsideProps` refuses it. Template-valued (`"${...}"`) and
|
|
345
|
+
* `$ref`-valued props render display sugar, not the value — committing typed text would silently
|
|
346
|
+
* destroy the binding, so they're blocked. An absent getShadowDoc is permissive (tests, same
|
|
347
|
+
* contract as getMode).
|
|
274
348
|
*/
|
|
275
349
|
const propEditableAt = (hostPath: JxPath, prop: string): boolean => {
|
|
276
350
|
const shadowDoc = opts?.getShadowDoc?.();
|
|
@@ -278,27 +352,7 @@ export function startIframeInlineEdit(
|
|
|
278
352
|
return true;
|
|
279
353
|
}
|
|
280
354
|
const node = getNodeAtPath(shadowDoc, hostPath) as JxMutableNode | undefined;
|
|
281
|
-
|
|
282
|
-
* `$props` IS NOT THE ONLY PLACE A PROP VALUE LIVES, and "absent from $props" is therefore not
|
|
283
|
-
* the same question as "unset". Two shapes deliver one through `attributes` instead, and both
|
|
284
|
-
* were reproduced against the real runtime:
|
|
285
|
-
*
|
|
286
|
-
* 1. The JSON shorthand `attributes: { "props.text": "…" }`. `connectedCallback` collects
|
|
287
|
-
* `props.*` attributes into state, so the marker renders that text while `$props` is
|
|
288
|
-
* empty. This repo's own site carries 95 of them.
|
|
289
|
-
* 2. A prop whose name collides with a REFLECTED HTMLElement property — `title`, `role`,
|
|
290
|
-
* `id`, `lang`, `dir`, `slot`, `hidden`. `setAttribute("title", …)` writes the JS property
|
|
291
|
-
* as a side effect, and the merge `key in this && this[key] !== undefined` picks it up.
|
|
292
|
-
* 41 shipped starter components declare `title` or `role` as state.
|
|
293
|
-
*
|
|
294
|
-
* Editing either would write `$props[prop]` and leave the attribute standing — two sources for
|
|
295
|
-
* one rendered value. In case 2 the attribute WINS (applyAttributes runs after the $props
|
|
296
|
-
* assignment), so the edit is simply invisible; in case 1 clearing the prop later resurrects
|
|
297
|
-
* the old text. Refusing keeps the properties panel, which edits the attribute itself, as the
|
|
298
|
-
* one way in.
|
|
299
|
-
*/
|
|
300
|
-
const attrs = node?.attributes as Record<string, unknown> | undefined;
|
|
301
|
-
if (attrs?.[`props.${prop}`] !== undefined || attrs?.[prop] !== undefined) {
|
|
355
|
+
if (deliveredOutsideProps(node, prop)) {
|
|
302
356
|
return false;
|
|
303
357
|
}
|
|
304
358
|
const raw = (node?.$props as Record<string, unknown> | undefined)?.[prop];
|
package/src/platforms/cloud.ts
CHANGED
|
@@ -15,8 +15,10 @@ import { negotiateCollab } from "@jxsuite/collab/negotiate";
|
|
|
15
15
|
import type { CollabNegotiation } from "@jxsuite/collab/negotiate";
|
|
16
16
|
import type { WsCollabConnection } from "@jxsuite/collab/client";
|
|
17
17
|
import type { ProjectConfig } from "@jxsuite/schema/types";
|
|
18
|
+
import { componentMetaFrom } from "@jxsuite/schema/component-meta";
|
|
18
19
|
import type {
|
|
19
20
|
AccountStatus,
|
|
21
|
+
ComponentMeta,
|
|
20
22
|
CreateProjectDestination,
|
|
21
23
|
DirEntry,
|
|
22
24
|
ExtensionsInfo,
|
|
@@ -518,9 +520,66 @@ export function createCloudPlatform(project: CloudProject | null): StudioPlatfor
|
|
|
518
520
|
|
|
519
521
|
// ─── Components / code services (cloud: static-only posture) ──────────
|
|
520
522
|
|
|
523
|
+
/**
|
|
524
|
+
* Component discovery, by READING rather than executing.
|
|
525
|
+
*
|
|
526
|
+
* This returned `[]` under "no execution of project JS in the cloud", and the cost was not the
|
|
527
|
+
* loss of discovery — it was the canvas. `canvas-live-render` injects the `$elements` a
|
|
528
|
+
* document's tags need only when the registry is non-empty, so an empty registry meant nothing
|
|
529
|
+
* was ever registered, no component was ever fetched, and every component instance rendered as
|
|
530
|
+
* an unregistered custom element: an empty inline box. A page built from components came up as
|
|
531
|
+
* blank space between the layout's chrome.
|
|
532
|
+
*
|
|
533
|
+
* Discovery does not need to execute anything. For a JSON component it is a file read and a
|
|
534
|
+
* property lookup — `componentMetaFrom` is that function, shared with the two Bun backends so
|
|
535
|
+
* "which `state` entries are props" has one answer. Non-JSON component formats DO need a
|
|
536
|
+
* project-supplied parser, and those stay out: the posture is kept where it actually applies
|
|
537
|
+
* rather than across the whole feature.
|
|
538
|
+
*
|
|
539
|
+
* Bounded rather than unbounded: the walk skips the directories a scan has no business in and
|
|
540
|
+
* stops at a depth and a file count, because this runs against a remote session over HTTP and a
|
|
541
|
+
* pathological tree must not turn opening a project into thousands of requests.
|
|
542
|
+
*/
|
|
521
543
|
async discoverComponents() {
|
|
522
|
-
|
|
523
|
-
|
|
544
|
+
const SKIP = new Set(["node_modules", "dist", ".git", ".claude", ".obsidian", "build"]);
|
|
545
|
+
const MAX_FILES = 400;
|
|
546
|
+
const MAX_DEPTH = 6;
|
|
547
|
+
const jsonPaths: string[] = [];
|
|
548
|
+
|
|
549
|
+
const walk = async (dir: string, depth: number): Promise<void> => {
|
|
550
|
+
if (depth > MAX_DEPTH || jsonPaths.length >= MAX_FILES) {
|
|
551
|
+
return;
|
|
552
|
+
}
|
|
553
|
+
let entries: DirEntry[];
|
|
554
|
+
try {
|
|
555
|
+
entries = await platform.listDirectory(dir);
|
|
556
|
+
} catch {
|
|
557
|
+
return; // A directory that vanished mid-walk is not a discovery failure.
|
|
558
|
+
}
|
|
559
|
+
const dirs: string[] = [];
|
|
560
|
+
for (const entry of entries) {
|
|
561
|
+
if (entry.type === "directory") {
|
|
562
|
+
if (!SKIP.has(entry.name) && !entry.name.startsWith(".")) {
|
|
563
|
+
dirs.push(entry.path);
|
|
564
|
+
}
|
|
565
|
+
} else if (entry.name.endsWith(".json") && jsonPaths.length < MAX_FILES) {
|
|
566
|
+
jsonPaths.push(entry.path);
|
|
567
|
+
}
|
|
568
|
+
}
|
|
569
|
+
await Promise.all(dirs.map(async (d) => walk(d, depth + 1)));
|
|
570
|
+
};
|
|
571
|
+
await walk("", 0);
|
|
572
|
+
|
|
573
|
+
const metas = await Promise.all(
|
|
574
|
+
jsonPaths.map(async (path) => {
|
|
575
|
+
try {
|
|
576
|
+
return componentMetaFrom(JSON.parse(await platform.readFile(path)), path);
|
|
577
|
+
} catch {
|
|
578
|
+
return null; // Not JSON, not readable, or not a component — all the same answer here.
|
|
579
|
+
}
|
|
580
|
+
}),
|
|
581
|
+
);
|
|
582
|
+
return metas.filter((m): m is NonNullable<typeof m> => m !== null) as ComponentMeta[];
|
|
524
583
|
},
|
|
525
584
|
|
|
526
585
|
async codeService() {
|
package/src/types.ts
CHANGED
|
@@ -195,7 +195,9 @@ export interface StudioPlatform {
|
|
|
195
195
|
* every `$ref` fetch landed on the SPA fallback — which answers HTML at **200**, so `res.ok`
|
|
196
196
|
* passed and the parse died on `Unexpected token '<'`. Images failed the same way in silence.
|
|
197
197
|
*
|
|
198
|
-
*
|
|
198
|
+
* May be absolute or root-relative — a root-relative value is resolved against the canvas origin,
|
|
199
|
+
* because the canvas uses this as `new URL(path, base)` and a relative BASE throws. A missing
|
|
200
|
+
* trailing `/` is added: without it `new URL` drops the last segment.
|
|
199
201
|
*/
|
|
200
202
|
documentBaseUrl?: string;
|
|
201
203
|
activate: (root?: string) => Promise<void>;
|