@jxsuite/studio 2.4.3 → 4.0.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/README.md +5 -5
- package/dist/chunks/Overlay.dev-723bv0df.js +23 -0
- package/dist/chunks/cf-settings-45fgyx8y.js +21 -0
- package/dist/chunks/{components-z6cnkyy1.js → components-zmx10t62.js} +6 -5
- package/dist/chunks/content-source-t88vb2dr.js +49 -0
- package/dist/chunks/{convert-to-repeater-k10dvcc7.js → convert-to-repeater-dhw1nrz9.js} +27 -18
- package/dist/chunks/entry-commands-6z6bhxg0.js +46 -0
- package/dist/chunks/{extension-sections-0ada0xcw.js → extension-sections-3ydgjbdy.js} +32 -23
- package/dist/chunks/file-ops-rskmhxyq.js +48 -0
- package/dist/chunks/files-0cratsr0.js +92 -0
- package/dist/chunks/{format-host-wewba4nq.js → format-host-tgcmfs4p.js} +3 -3
- package/dist/chunks/fs-events-hrrg328g.js +21 -0
- package/dist/chunks/{git-panel-2dh1aw50.js → git-panel-0yywyk76.js} +21 -15
- package/dist/chunks/{jx-validate-jj9rnt4n.js → jx-validate-8q2g05x1.js} +6 -4
- package/dist/chunks/layers-panel-z3tqe5cd.js +67 -0
- package/dist/chunks/layers-q75tj2mf.js +45 -0
- package/dist/chunks/lit-html-j055mkh8.js +22 -0
- package/dist/chunks/monaco-buffer-dqw0xxpx.js +25 -0
- package/dist/chunks/{monaco-setup-a5wkmbgh.js → monaco-setup-rws7k4br.js} +4 -4
- package/dist/chunks/notify-yjm79n4s.js +30 -0
- package/dist/chunks/parse-24d0gay3.js +19 -0
- package/dist/chunks/platform-ba1j1gbh.js +16 -0
- package/dist/chunks/publish-panel-54jvhskq.js +28 -0
- package/dist/chunks/quick-search-cgfmjwq5.js +65 -0
- package/dist/chunks/references-3qx8fkyp.js +29 -0
- package/dist/chunks/right-panel-cqp8bc4v.js +46 -0
- package/dist/chunks/sp-overlay.dev-yztmtrs1.js +18 -0
- package/dist/chunks/sp-popover.dev-n2etmqdp.js +9 -0
- package/dist/chunks/sp-progress-circle.dev-hryhj0es.js +15 -0
- package/dist/chunks/sp-tooltip.dev-xcvpnqr0.js +15 -0
- package/dist/chunks/{sp-tray.dev-64v4pxw8.js → sp-tray.dev-tkbscm06.js} +5 -5
- package/dist/chunks/{studio-f5yyvqxv.js → studio-1hbqnt19.js} +37 -31
- package/dist/chunks/studio-2zkz8evn.js +3163 -0
- package/dist/chunks/{studio-0b6fs05q.js → studio-4pqqxbrt.js} +23 -15
- package/dist/chunks/{studio-kvfx9z2c.js → studio-4rzyrtjz.js} +70 -31
- package/dist/chunks/{studio-ef8t53vc.js → studio-5q69gfag.js} +60 -56
- package/dist/chunks/{studio-v2c5pmjj.js → studio-5rywrkxz.js} +565 -133
- package/dist/chunks/{studio-6kv3apa9.js → studio-68n67094.js} +25 -9
- package/dist/chunks/{studio-0rqqhrdy.js → studio-7nte2hj4.js} +2 -2
- package/dist/chunks/{studio-hyn3vhwr.js → studio-8swrjbny.js} +31 -18
- package/dist/chunks/studio-92ab2jqe.js +282 -0
- package/dist/chunks/{studio-sas7mt3k.js → studio-9ab8gqsm.js} +2 -2
- package/dist/chunks/{studio-28xc8ws4.js → studio-9c4k4vhv.js} +14 -10
- package/dist/chunks/studio-9n4mrmnb.js +184 -0
- package/dist/chunks/{studio-tr20m8g8.js → studio-9r6j5ev1.js} +37 -2
- package/dist/chunks/{studio-aweyr4v1.js → studio-ahjfdy6e.js} +9 -7
- package/dist/chunks/{studio-s519zc99.js → studio-bgxt93ga.js} +3 -3
- package/dist/chunks/{studio-26sv44br.js → studio-cb12yz9w.js} +8543 -8784
- package/dist/chunks/studio-cft9r4r7.js +119 -0
- package/dist/chunks/{studio-rqemfhz6.js → studio-eenfkt4e.js} +6 -6
- package/dist/chunks/studio-eesa1myv.js +498 -0
- package/dist/chunks/{studio-ay8wcr2b.js → studio-eqq1w638.js} +7 -9
- package/dist/chunks/{studio-ke244pam.js → studio-ex8zyp98.js} +51 -25
- package/dist/chunks/studio-gv8vvshc.js +118 -0
- package/dist/chunks/studio-hbtp0eh1.js +1315 -0
- package/dist/chunks/{studio-cz2j1c0g.js → studio-htsf0w6m.js} +75 -123
- package/dist/chunks/{studio-pyrw8h0w.js → studio-kjjsjak4.js} +528 -212
- package/dist/chunks/{studio-5x5774ak.js → studio-kryvsw6j.js} +3 -3
- package/dist/chunks/studio-kyh9avvv.js +28 -0
- package/dist/chunks/{studio-jnakhg87.js → studio-kyk2v92a.js} +14 -14
- package/dist/chunks/{studio-twsafr1e.js → studio-m3hwn7y1.js} +3 -3
- package/dist/chunks/{studio-tzx3xmvx.js → studio-mmv72a27.js} +8 -8
- package/dist/chunks/{studio-vhk5fmfv.js → studio-n8b9wqwy.js} +5 -5
- package/dist/chunks/{studio-cjm7fdjf.js → studio-p4ngc046.js} +8 -104
- package/dist/chunks/{studio-sccge2wf.js → studio-p8j86wag.js} +2 -2
- package/dist/chunks/{studio-v1va0vhd.js → studio-pdfjntbk.js} +75 -139
- package/dist/chunks/studio-q43a5y9h.js +394 -0
- package/dist/chunks/studio-q7v10cg2.js +106 -0
- package/dist/chunks/{studio-2w11grav.js → studio-qq6jym04.js} +3 -3
- package/dist/chunks/studio-r1r7afxx.js +1066 -0
- package/dist/chunks/{studio-3fk1ac9z.js → studio-rdx8jcxy.js} +4 -4
- package/dist/chunks/{studio-4mqb9a0j.js → studio-rn9ta2mz.js} +4 -4
- package/dist/chunks/studio-sf7zjgv1.js +91 -0
- package/dist/chunks/studio-t7qb9db8.js +665 -0
- package/dist/chunks/studio-tmc5emrw.js +8 -0
- package/dist/chunks/{studio-wdt9y92a.js → studio-txwkm6p9.js} +2 -2
- package/dist/chunks/{studio-dgj7gq44.js → studio-v38hmvx1.js} +7 -7
- package/dist/chunks/{studio-7ejemhej.js → studio-vadzgkrz.js} +6 -6
- package/dist/chunks/studio-wcy8trfx.js +263 -0
- package/dist/chunks/{studio-hb7c46jy.js → studio-wx5609xd.js} +21 -15
- package/dist/chunks/studio-xrapxwwc.js +1539 -0
- package/dist/chunks/{studio-ws6ef6js.js → studio-yxrkyv8a.js} +41 -29
- package/dist/chunks/{studio-xm3dbxvn.js → studio-zczps70q.js} +2 -2
- package/dist/chunks/workspace-xpres40v.js +89 -0
- package/dist/iframe-entry.js +450 -48
- package/dist/studio.js +2455 -1761
- package/package.json +16 -15
- package/src/browse/library-layouts.ts +3 -3
- package/src/browse/library-pane.ts +59 -5
- package/src/browse/library-preview.ts +19 -3
- package/src/canvas/asset-refs.ts +119 -0
- package/src/canvas/asset-resolve.ts +256 -0
- package/src/canvas/canvas-live-render.ts +23 -15
- package/src/canvas/canvas-render.ts +23 -1
- package/src/canvas/iframe-entry.ts +1 -0
- package/src/canvas/iframe-host.ts +4 -0
- package/src/canvas/iframe-patch.ts +47 -21
- package/src/canvas/iframe-protocol.ts +9 -0
- package/src/canvas/iframe-render.ts +50 -16
- package/src/commands/app-commands.ts +4 -0
- package/src/commands/command-args.ts +29 -0
- package/src/commands/context.ts +15 -1
- package/src/commands/defaults.ts +29 -1
- package/src/commands/levels.ts +16 -0
- package/src/commands/live-context.ts +3 -0
- package/src/content/collection-match.ts +267 -0
- package/src/content/entry-commands.ts +38 -29
- package/src/content/entry-fields.ts +6 -0
- package/src/editor/file-drop-action.ts +2 -1
- package/src/editor/shortcuts.ts +5 -0
- package/src/files/file-ops.ts +10 -42
- package/src/files/files.ts +487 -61
- package/src/files/fs-events.ts +9 -0
- package/src/files/gitignore.ts +432 -0
- package/src/files/media-paths.ts +37 -14
- package/src/files/media-upload.ts +47 -6
- package/src/files/serialize-document.ts +60 -0
- package/src/format/convert-file.ts +511 -0
- package/src/format/format-choices.ts +199 -0
- package/src/grid/grid-idle.ts +45 -0
- package/src/grid/grid-view.ts +35 -0
- package/src/grid/schema-columns.ts +2 -1
- package/src/grid/sources/content-source.ts +24 -3
- package/src/media/media-open.ts +58 -0
- package/src/media/media-pane.ts +310 -0
- package/src/new-project/import-tab.ts +333 -160
- package/src/new-project/new-project-modal.ts +27 -34
- package/src/packages/ensure-deps.ts +14 -7
- package/src/packages/jxsuite-update.ts +11 -11
- package/src/page-params.ts +10 -46
- package/src/panels/activity-bar.ts +45 -16
- package/src/panels/ai-chat/attached-context.ts +6 -1
- package/src/panels/ai-chat/chat-view.ts +255 -13
- package/src/panels/ai-chat/composer.ts +21 -80
- package/src/panels/ai-panel.ts +116 -2
- package/src/panels/frontmatter-fields.ts +6 -2
- package/src/panels/navigator-panels.ts +7 -6
- package/src/panels/properties-panel.ts +8 -2
- package/src/panels/seo-modal.ts +5 -6
- package/src/panels/settings-menu.ts +799 -0
- package/src/panels/signals-panel.ts +2 -1
- package/src/panels/toolbar.ts +135 -15
- package/src/platforms/cloud.ts +34 -3
- package/src/platforms/devserver.ts +72 -34
- package/src/preview/preview-overlay.ts +226 -0
- package/src/services/ai-ask.ts +247 -0
- package/src/services/ai-import-tools.ts +494 -0
- package/src/services/ai-models.ts +101 -11
- package/src/services/ai-project-tools.ts +20 -26
- package/src/services/ai-settings.ts +69 -71
- package/src/services/ai-system-prompt.ts +88 -2
- package/src/services/ai-turn-signal.ts +70 -0
- package/src/services/cf-settings.ts +19 -37
- package/src/services/connection.ts +74 -0
- package/src/services/context-manager.ts +89 -0
- package/src/services/document-assistant.ts +49 -7
- package/src/services/idle.ts +6 -1
- package/src/services/import-client.ts +33 -6
- package/src/services/import-run.ts +158 -0
- package/src/services/import-seed.ts +120 -0
- package/src/services/jx-validate.ts +58 -6
- package/src/services/project-adoption.ts +71 -0
- package/src/services/references.ts +0 -0
- package/src/services/settings/definitions.ts +181 -0
- package/src/services/settings/kernel.ts +335 -0
- package/src/services/settings/write-queue.ts +116 -0
- package/src/services/tool-executor.ts +34 -2
- package/src/settings/contexts-section.ts +45 -3
- package/src/settings/dependencies-editor.ts +43 -23
- package/src/settings/general-settings.ts +30 -12
- package/src/settings/preferences-accounts.ts +6 -19
- package/src/settings/preferences-dialog.ts +18 -41
- package/src/settings/preferences-keymap.ts +8 -18
- package/src/settings/preferences-sections.ts +57 -0
- package/src/settings/schema-field-ui.ts +2 -2
- package/src/settings/settings-document.ts +65 -9
- package/src/shell.ts +6 -14
- package/src/studio.ts +65 -7
- package/src/tabs/transact.ts +42 -19
- package/src/types.ts +164 -8
- package/src/ui/ai-credentials-form.ts +52 -27
- package/src/ui/ai-model-picker.ts +162 -0
- package/src/ui/choice-field.ts +72 -0
- package/src/ui/layers.ts +83 -5
- package/src/ui/media-picker.ts +6 -11
- package/src/utils/studio-utils.ts +43 -31
- package/styles/forced-colors.css +7 -0
- package/styles/overlays.css +256 -0
- package/styles/panels.css +13 -1
- package/styles/shell.css +111 -0
- package/dist/chunks/Overlay.dev-d2ajy6sj.js +0 -23
- package/dist/chunks/cf-settings-g5fbf2zp.js +0 -17
- package/dist/chunks/content-source-k9724vcv.js +0 -39
- package/dist/chunks/files-yew27sn9.js +0 -69
- package/dist/chunks/layers-panel-qnsghwwd.js +0 -59
- package/dist/chunks/publish-panel-a0pdft5w.js +0 -21
- package/dist/chunks/quick-search-9avyc4pc.js +0 -54
- package/dist/chunks/right-panel-kcnhtc5j.js +0 -38
- package/dist/chunks/sp-overlay.dev-s18hymn9.js +0 -18
- package/dist/chunks/sp-popover.dev-dj9x7e7a.js +0 -9
- package/dist/chunks/sp-progress-circle.dev-0zxcyz3r.js +0 -15
- package/dist/chunks/sp-tooltip.dev-5c6d1kjz.js +0 -15
- package/dist/chunks/studio-a7pzveq1.js +0 -110
- package/dist/chunks/studio-c31yb4t6.js +0 -55
- package/dist/chunks/studio-ra7ar50f.js +0 -6682
- package/src/canvas/content-assets.ts +0 -263
- package/src/canvas/site-style-css.ts +0 -121
- package/src/services/settings-store.ts +0 -99
package/src/studio.ts
CHANGED
|
@@ -94,7 +94,8 @@ import { mountJumpBar } from "./panels/jump-bar";
|
|
|
94
94
|
import { cellForPane } from "./panels/pane-grid";
|
|
95
95
|
import { notify } from "./services/notify";
|
|
96
96
|
import { beginActivity } from "./panels/activity-panel";
|
|
97
|
-
import { exportFile, parseSourceForPath, saveFile
|
|
97
|
+
import { exportFile, parseSourceForPath, saveFile } from "./files/file-ops";
|
|
98
|
+
import { serializeDocument } from "./files/serialize-document";
|
|
98
99
|
import {
|
|
99
100
|
formatForPath,
|
|
100
101
|
loadFormats,
|
|
@@ -113,6 +114,7 @@ import {
|
|
|
113
114
|
registerFileTreeDnD,
|
|
114
115
|
reloadCleanTab,
|
|
115
116
|
} from "./files/files";
|
|
117
|
+
import { resetIgnoreCache } from "./files/gitignore";
|
|
116
118
|
import { startFsSync } from "./files/fs-events";
|
|
117
119
|
import { invalidateParamValues } from "./page-params";
|
|
118
120
|
import {
|
|
@@ -128,7 +130,13 @@ import { invalidateLibrary } from "./browse/library-pane";
|
|
|
128
130
|
import { invalidateMediaCache } from "./ui/media-picker";
|
|
129
131
|
import { setMediaChangedHandler } from "./files/media-upload";
|
|
130
132
|
import { applyFileDrop } from "./editor/file-drop-action";
|
|
131
|
-
import {
|
|
133
|
+
import {
|
|
134
|
+
assistantCommands,
|
|
135
|
+
isAssistantStreaming,
|
|
136
|
+
isAssistantWaiting,
|
|
137
|
+
revealImportHandoff,
|
|
138
|
+
seedAssistantMessages,
|
|
139
|
+
} from "./panels/ai-panel";
|
|
132
140
|
import { seedPublishConnected } from "./publish/publish-panel";
|
|
133
141
|
|
|
134
142
|
import { getPlatform, hasPlatform, registerPlatform } from "./platform";
|
|
@@ -180,6 +188,8 @@ import * as frontmatterPanelMod from "./panels/frontmatter-panel";
|
|
|
180
188
|
import * as rightPanelMod from "./panels/right-panel";
|
|
181
189
|
import * as chatPanelMod from "./panels/chat-panel";
|
|
182
190
|
import { setProjectAdopter } from "./services/project-adoption";
|
|
191
|
+
import { setImportHandoff } from "./services/import-seed";
|
|
192
|
+
import { watchConnection } from "./services/connection";
|
|
183
193
|
import { tabBufferUnsaved } from "./services/monaco-buffer";
|
|
184
194
|
import * as leftPanelMod from "./panels/left-panel";
|
|
185
195
|
import * as tabStrip from "./panels/tab-strip";
|
|
@@ -204,7 +214,12 @@ import { initQuickSearch } from "./panels/quick-search";
|
|
|
204
214
|
import { hydrateAccountStatus } from "./account-status";
|
|
205
215
|
import { hydrateProjectList } from "./project-list";
|
|
206
216
|
import { addRecentProject, hydrateRecentProjects, removeRecentProject } from "./recent-projects";
|
|
207
|
-
import {
|
|
217
|
+
import {
|
|
218
|
+
hydrateSettings,
|
|
219
|
+
onSettingsChanged,
|
|
220
|
+
settingsSettled,
|
|
221
|
+
watchRemoteSettings,
|
|
222
|
+
} from "./services/settings/kernel";
|
|
208
223
|
import { initWelcome } from "./panels/welcome-screen";
|
|
209
224
|
import {
|
|
210
225
|
openAddRepoModal,
|
|
@@ -223,6 +238,7 @@ import { registerPreferencesCommands } from "./settings/preferences-dialog";
|
|
|
223
238
|
import { registerAboutCommands } from "./about/about-modal";
|
|
224
239
|
import { registerCollabCommands } from "./collab/collab-commands";
|
|
225
240
|
import { registerLibraryCommands } from "./browse/library-commands";
|
|
241
|
+
import { registerFileFormatCommands } from "./format/convert-file";
|
|
226
242
|
import { registerPublishCommands } from "./publish/publish-commands";
|
|
227
243
|
import { registerGridViewCommands } from "./grid/grid-panel";
|
|
228
244
|
import { registerRedirectsCommands } from "./grid/redirects-grid";
|
|
@@ -794,6 +810,11 @@ frontmatterPanelMod.mount();
|
|
|
794
810
|
// The assistant's create_project tool adopts freshly scaffolded projects through the same
|
|
795
811
|
// Flow as the recent-projects list.
|
|
796
812
|
setProjectAdopter(openRecentProject);
|
|
813
|
+
setImportHandoff(revealImportHandoff);
|
|
814
|
+
|
|
815
|
+
/* A launcher whose backend can go away says so. Installs nothing on a platform that cannot lose
|
|
816
|
+
one — see services/connection.ts for why only the chromium shell can. */
|
|
817
|
+
watchConnection();
|
|
797
818
|
|
|
798
819
|
leftPanelMod.mount({
|
|
799
820
|
cloneRepository: () => cloneRepository({ openRecentProject }),
|
|
@@ -966,6 +987,7 @@ if (_projectParam) {
|
|
|
966
987
|
}
|
|
967
988
|
}
|
|
968
989
|
|
|
990
|
+
resetIgnoreCache();
|
|
969
991
|
setProjectState({
|
|
970
992
|
dirs: new Map(),
|
|
971
993
|
expanded: new Set(),
|
|
@@ -995,15 +1017,18 @@ if (_projectParam) {
|
|
|
995
1017
|
"public",
|
|
996
1018
|
"styles",
|
|
997
1019
|
]);
|
|
998
|
-
|
|
999
|
-
|
|
1020
|
+
/* Through `loadDirectory`, not `platform.listDirectory`: this is the second copy of the
|
|
1021
|
+
conventional-dir walk (the first is `openProject` in files/files.ts), and the loader is
|
|
1022
|
+
what reads the `.gitignore` chain governing each directory. Listing around it left the
|
|
1023
|
+
`?project=` boot as the one door into a project whose tree knew no ignore rules. */
|
|
1024
|
+
await loadDirectory(".");
|
|
1025
|
+
const dirEntries = requireProjectState().dirs.get(".") ?? [];
|
|
1000
1026
|
const foundDirs = [];
|
|
1001
1027
|
for (const e of dirEntries) {
|
|
1002
1028
|
if (e.type === "directory" && conventionalDirs.has(e.name)) {
|
|
1003
1029
|
foundDirs.push(e.name);
|
|
1004
1030
|
requireProjectState().expanded.add(e.path || e.name);
|
|
1005
|
-
|
|
1006
|
-
requireProjectState().dirs.set(e.path || e.name, sub);
|
|
1031
|
+
await loadDirectory(e.path || e.name);
|
|
1007
1032
|
}
|
|
1008
1033
|
}
|
|
1009
1034
|
requireProjectState().projectDirs = foundDirs;
|
|
@@ -1136,6 +1161,32 @@ void hydrateSettings().then(() => {
|
|
|
1136
1161
|
render();
|
|
1137
1162
|
});
|
|
1138
1163
|
|
|
1164
|
+
/*
|
|
1165
|
+
* A setting changing repaints, wherever the change came from — this window's Preferences, or
|
|
1166
|
+
* hydration landing after boot. Key-gated surfaces (the assistant's setup notice, the New Project
|
|
1167
|
+
* gates, the Accounts list) read the store on the render path rather than caching a copy, so this
|
|
1168
|
+
* one subscription is what keeps every one of them honest.
|
|
1169
|
+
*/
|
|
1170
|
+
onSettingsChanged(() => {
|
|
1171
|
+
render();
|
|
1172
|
+
});
|
|
1173
|
+
|
|
1174
|
+
/*
|
|
1175
|
+
* Another window changing a setting is news this one wants. Nothing is lost without it — a patch
|
|
1176
|
+
* cannot clobber a key it does not name — but a second window would otherwise show a provider that
|
|
1177
|
+
* had been reconfigured somewhere else until it was restarted.
|
|
1178
|
+
*/
|
|
1179
|
+
watchRemoteSettings();
|
|
1180
|
+
|
|
1181
|
+
/*
|
|
1182
|
+
* Settings are written on a queue that coalesces a burst into one send, so a change made in the
|
|
1183
|
+
* last moments before the window closes may still be in flight. Draining it here is the difference
|
|
1184
|
+
* between "your provider was saved" and a settings file that never heard about it.
|
|
1185
|
+
*/
|
|
1186
|
+
globalThis.addEventListener("beforeunload", () => {
|
|
1187
|
+
void settingsSettled();
|
|
1188
|
+
});
|
|
1189
|
+
|
|
1139
1190
|
// ─── Left panel: delegated to panels/left-panel.js ───────────────────────────
|
|
1140
1191
|
|
|
1141
1192
|
function renderLeftPanel() {
|
|
@@ -1266,6 +1317,7 @@ async function openRecentProject(root: string) {
|
|
|
1266
1317
|
const config = JSON.parse(content) as ProjectConfig;
|
|
1267
1318
|
|
|
1268
1319
|
closeAllTabs();
|
|
1320
|
+
resetIgnoreCache();
|
|
1269
1321
|
|
|
1270
1322
|
setProjectState({
|
|
1271
1323
|
...projectState,
|
|
@@ -1371,6 +1423,7 @@ const commandRegistry = createCommandRegistry({
|
|
|
1371
1423
|
// The probe `live-context.ts` declared optional and nobody ever passed, so `ctx.ai.streaming`
|
|
1372
1424
|
// Read false forever. `assistant.stop` is gated on it.
|
|
1373
1425
|
aiStreaming: isAssistantStreaming,
|
|
1426
|
+
aiWaiting: isAssistantWaiting,
|
|
1374
1427
|
canvasMode: getCanvasMode,
|
|
1375
1428
|
isCaretActive,
|
|
1376
1429
|
isModalOpen,
|
|
@@ -1385,6 +1438,10 @@ registerStudioCommands(
|
|
|
1385
1438
|
// Hands the URL to the launcher's preview-navigate handler — the OS browser — where a bare
|
|
1386
1439
|
// `window.open` would open a webview with no address bar. The browser build falls back to a
|
|
1387
1440
|
// New tab either way.
|
|
1441
|
+
// `View: Open in Browser` renders the working tree; `Build Site` runs the compiler. Two
|
|
1442
|
+
// Verbs, because "what does this page look like?" and "does my site build?" are two questions
|
|
1443
|
+
// And only one of them is worth waiting for a bundler and an image pipeline.
|
|
1444
|
+
buildSite: () => toolbarPanel.runBuildSite(),
|
|
1388
1445
|
openInBrowser: () => toolbarPanel.runOpenInBrowser(),
|
|
1389
1446
|
openProject,
|
|
1390
1447
|
// Wrapped rather than passed by reference: `saveFile` takes an optional tab and reports
|
|
@@ -1452,6 +1509,7 @@ registerGridCommands(commandRegistry);
|
|
|
1452
1509
|
registerSettingsCommands(commandRegistry);
|
|
1453
1510
|
registerPreferencesCommands(commandRegistry);
|
|
1454
1511
|
registerLibraryCommands(commandRegistry);
|
|
1512
|
+
registerFileFormatCommands(commandRegistry);
|
|
1455
1513
|
registerContentCommands(commandRegistry);
|
|
1456
1514
|
/* The four translation verbs: open a sibling translation, create the missing one, show the
|
|
1457
1515
|
Languages panel, declare a language. `i18n.switchLocale` is deliberately not among them — it sets
|
package/src/tabs/transact.ts
CHANGED
|
@@ -1135,6 +1135,46 @@ export function mutateUpdateMediaNestedStyle(
|
|
|
1135
1135
|
recordPatch({ op: "set-style", path });
|
|
1136
1136
|
}
|
|
1137
1137
|
|
|
1138
|
+
/**
|
|
1139
|
+
* Drop every level of `stylePath` a deletion has just left empty, DEEPEST FIRST.
|
|
1140
|
+
*
|
|
1141
|
+
* Emptying the leaf usually empties its parent with it — `["table", "th"]` losing its last
|
|
1142
|
+
* declaration leaves `table` holding nothing — and an empty object still serialises, so it reaches
|
|
1143
|
+
* the file, the diff, and every equality check the collab layer makes. Every other style writer in
|
|
1144
|
+
* this module is careful about that; these two were not.
|
|
1145
|
+
*
|
|
1146
|
+
* The walk this replaces went top-down and stopped at the first empty level it found, which is the
|
|
1147
|
+
* one order that cannot work: on the way down the shallow levels are still non-empty _because_ the
|
|
1148
|
+
* leaf it is looking for is inside them, so it deleted the leaf and returned with every ancestor
|
|
1149
|
+
* left behind. Writing then deleting `["table", "th"]` on a node with no style at all produced `{
|
|
1150
|
+
* table: {} }` — a style the author never wrote, on a node that should have had none, because
|
|
1151
|
+
* `ensureNestedStyle` creates the intermediates on the way in.
|
|
1152
|
+
*
|
|
1153
|
+
* @param {JxStyle} root - The object `stylePath[0]` is a key of: a node's `style`, or a media
|
|
1154
|
+
* block.
|
|
1155
|
+
* @param {string[]} stylePath
|
|
1156
|
+
*/
|
|
1157
|
+
function pruneEmptyStylePath(root: JxStyle, stylePath: string[]) {
|
|
1158
|
+
// Each entry holds the segment at its own index; an absent (or scalar) one ends the descent.
|
|
1159
|
+
const owners: JxStyle[] = [root];
|
|
1160
|
+
for (const seg of stylePath) {
|
|
1161
|
+
const next = getNestedStyle(owners.at(-1), seg);
|
|
1162
|
+
if (!next) {
|
|
1163
|
+
break;
|
|
1164
|
+
}
|
|
1165
|
+
owners.push(next);
|
|
1166
|
+
}
|
|
1167
|
+
for (let i = owners.length - 2; i >= 0; i--) {
|
|
1168
|
+
const owner = owners[i]!;
|
|
1169
|
+
const seg = stylePath[i]!;
|
|
1170
|
+
const block = getNestedStyle(owner, seg);
|
|
1171
|
+
// Re-read rather than reuse `owners[i + 1]`: the level below may have gone this same loop.
|
|
1172
|
+
if (block && Object.keys(block).length === 0) {
|
|
1173
|
+
delete owner[seg];
|
|
1174
|
+
}
|
|
1175
|
+
}
|
|
1176
|
+
}
|
|
1177
|
+
|
|
1138
1178
|
/**
|
|
1139
1179
|
* Update a style property at a nested style path (e.g., ["table", "th"]). Creates intermediate
|
|
1140
1180
|
* objects as needed.
|
|
@@ -1163,16 +1203,7 @@ export function mutateUpdateNestedStylePath(
|
|
|
1163
1203
|
}
|
|
1164
1204
|
if (value === undefined || value === "") {
|
|
1165
1205
|
delete obj[prop];
|
|
1166
|
-
|
|
1167
|
-
let cur: JxStyle | undefined = node.style;
|
|
1168
|
-
for (let i = 0; i < stylePath.length && cur; i++) {
|
|
1169
|
-
const child = getNestedStyle(cur, stylePath[i]!);
|
|
1170
|
-
if (child && Object.keys(child).length === 0) {
|
|
1171
|
-
delete cur[stylePath[i]!];
|
|
1172
|
-
break;
|
|
1173
|
-
}
|
|
1174
|
-
cur = child;
|
|
1175
|
-
}
|
|
1206
|
+
pruneEmptyStylePath(node.style, stylePath);
|
|
1176
1207
|
} else {
|
|
1177
1208
|
obj[prop] = value;
|
|
1178
1209
|
}
|
|
@@ -1214,15 +1245,7 @@ export function mutateUpdateMediaNestedStylePath(
|
|
|
1214
1245
|
}
|
|
1215
1246
|
if (value === undefined || value === "") {
|
|
1216
1247
|
delete obj[prop];
|
|
1217
|
-
|
|
1218
|
-
for (let i = 0; i < stylePath.length && cur; i++) {
|
|
1219
|
-
const child = getNestedStyle(cur, stylePath[i]!);
|
|
1220
|
-
if (child && Object.keys(child).length === 0) {
|
|
1221
|
-
delete cur[stylePath[i]!];
|
|
1222
|
-
break;
|
|
1223
|
-
}
|
|
1224
|
-
cur = child;
|
|
1225
|
-
}
|
|
1248
|
+
pruneEmptyStylePath(media, stylePath);
|
|
1226
1249
|
if (Object.keys(media).length === 0) {
|
|
1227
1250
|
delete node.style[key];
|
|
1228
1251
|
}
|
package/src/types.ts
CHANGED
|
@@ -13,6 +13,7 @@ import type {
|
|
|
13
13
|
|
|
14
14
|
import type {
|
|
15
15
|
AppInfo,
|
|
16
|
+
AssetCapabilities,
|
|
16
17
|
CfConnection,
|
|
17
18
|
CodeServiceResult,
|
|
18
19
|
ComponentMeta,
|
|
@@ -31,10 +32,12 @@ import type {
|
|
|
31
32
|
GitLogEntry,
|
|
32
33
|
GitStatusResult,
|
|
33
34
|
ImportProgressEvent,
|
|
35
|
+
ImportReadyEvent,
|
|
34
36
|
ImportSiteOptions,
|
|
35
|
-
|
|
37
|
+
ImportSiteSummary,
|
|
36
38
|
PackageInfo,
|
|
37
39
|
PackageOpResult,
|
|
40
|
+
PackageVersionInfo,
|
|
38
41
|
ProjectListEntry,
|
|
39
42
|
ProjectSchemasResponse,
|
|
40
43
|
RecentProjectEntry,
|
|
@@ -42,7 +45,9 @@ import type {
|
|
|
42
45
|
RenameResult,
|
|
43
46
|
SecretsSetRequest,
|
|
44
47
|
SecretsSetResponse,
|
|
48
|
+
SettingsPatch,
|
|
45
49
|
StarterInfo,
|
|
50
|
+
UploadResult,
|
|
46
51
|
} from "@jxsuite/protocol";
|
|
47
52
|
|
|
48
53
|
export type {
|
|
@@ -76,12 +81,15 @@ export type {
|
|
|
76
81
|
GitFileStatus,
|
|
77
82
|
GitLogEntry,
|
|
78
83
|
GitStatusResult,
|
|
84
|
+
ImportBreakpointPolicy,
|
|
79
85
|
ImportProgressEvent,
|
|
86
|
+
ImportReadyEvent,
|
|
80
87
|
ImportSiteOptions,
|
|
88
|
+
ImportSiteSummary,
|
|
81
89
|
JsonValue,
|
|
82
|
-
OutdatedInfo,
|
|
83
90
|
PackageInfo,
|
|
84
91
|
PackageOpResult,
|
|
92
|
+
PackageVersionInfo,
|
|
85
93
|
ProjectListEntry,
|
|
86
94
|
ProjectSchemasResponse,
|
|
87
95
|
PullRequestInfo,
|
|
@@ -93,6 +101,7 @@ export type {
|
|
|
93
101
|
SecretsListResponse,
|
|
94
102
|
SecretsSetRequest,
|
|
95
103
|
SecretsSetResponse,
|
|
104
|
+
SettingsPatch,
|
|
96
105
|
StarterInfo,
|
|
97
106
|
} from "@jxsuite/protocol";
|
|
98
107
|
|
|
@@ -145,6 +154,20 @@ export interface SiteBuildResult {
|
|
|
145
154
|
routes: number;
|
|
146
155
|
files: number;
|
|
147
156
|
errors: string[];
|
|
157
|
+
/**
|
|
158
|
+
* How the backend produced what {@link url} serves.
|
|
159
|
+
*
|
|
160
|
+
* `"built"` is compiler output — what the published site will literally be. `"live"` is the
|
|
161
|
+
* working tree rendered as a site: the same pages at the same routes, assembled by the runtime in
|
|
162
|
+
* the reader's browser rather than prerendered, because the backend cannot run a build at all.
|
|
163
|
+
*
|
|
164
|
+
* The distinction is reported rather than hidden because the two differ in ways a reader would
|
|
165
|
+
* otherwise be misled by — a live preview has no prerendered HTML, no optimized images, no
|
|
166
|
+
* islands and no server-timing results — and because a live preview has something a build does
|
|
167
|
+
* not: it shows the tree as it stands, unsaved edits included. Absent means `"built"`, so a
|
|
168
|
+
* backend that predates this keeps its meaning.
|
|
169
|
+
*/
|
|
170
|
+
mode?: "built" | "live";
|
|
148
171
|
/**
|
|
149
172
|
* Origin the built site is browsable at, e.g. `http://127.0.0.1:41234`.
|
|
150
173
|
*
|
|
@@ -156,6 +179,24 @@ export interface SiteBuildResult {
|
|
|
156
179
|
url?: string;
|
|
157
180
|
}
|
|
158
181
|
|
|
182
|
+
/**
|
|
183
|
+
* What a live site preview reports back.
|
|
184
|
+
*
|
|
185
|
+
* Everything {@link SiteBuildResult} carries, plus the one answer only a live preview can give:
|
|
186
|
+
* whether the reader already had a tab on this project.
|
|
187
|
+
*/
|
|
188
|
+
export interface SitePreviewResult extends SiteBuildResult {
|
|
189
|
+
/**
|
|
190
|
+
* A tab already holding this project's preview took the route, so open nothing.
|
|
191
|
+
*
|
|
192
|
+
* The caller MUST honour it. One tab per project is the contract, and a caller that opens a
|
|
193
|
+
* second one anyway gives the author two tabs on the same project — which is precisely what
|
|
194
|
+
* retargeting the first one exists to prevent. `false` means the backend has no live client, or
|
|
195
|
+
* had one that did not answer, and the caller should open a tab itself.
|
|
196
|
+
*/
|
|
197
|
+
reused?: boolean;
|
|
198
|
+
}
|
|
199
|
+
|
|
159
200
|
export interface StudioPlatform {
|
|
160
201
|
id: string;
|
|
161
202
|
projectRoot: string;
|
|
@@ -200,6 +241,33 @@ export interface StudioPlatform {
|
|
|
200
241
|
* trailing `/` is added: without it `new URL` drops the last segment.
|
|
201
242
|
*/
|
|
202
243
|
documentBaseUrl?: string;
|
|
244
|
+
/**
|
|
245
|
+
* What the canvas ORIGIN answers for a SITE URL — and therefore how Studio must address media.
|
|
246
|
+
*
|
|
247
|
+
* `"site"` (the default when absent): the canvas origin already serves the published site URL
|
|
248
|
+
* space, so `/hero.jpg` and `/styles/main.css` resolve on their own and Studio touches neither.
|
|
249
|
+
* The dev server and the desktop loopback both do — `serveProjectFile` in `@jxsuite/server` IS
|
|
250
|
+
* that URL space — so neither declares anything here and both stay byte-identical.
|
|
251
|
+
*
|
|
252
|
+
* `"repo"`: the host serves PROJECT-RELATIVE paths under {@link documentBaseUrl}, and nothing
|
|
253
|
+
* answers the site URL space. Studio resolves each authored reference to the project file it
|
|
254
|
+
* names and rebases that path onto `documentBaseUrl`. Inert without one — a host that says its
|
|
255
|
+
* site URLs are wrong without saying what is right has told Studio nothing it can act on.
|
|
256
|
+
*
|
|
257
|
+
* This is about the ORIGIN, not the backend: a host whose files are perfectly reachable can still
|
|
258
|
+
* be `"repo"` space, because what decides it is what answers `GET /hero.jpg` on the document the
|
|
259
|
+
* canvas is running in.
|
|
260
|
+
*/
|
|
261
|
+
assetSpace?: "site" | "repo";
|
|
262
|
+
/**
|
|
263
|
+
* What this backend will accept as an upload, when it says.
|
|
264
|
+
*
|
|
265
|
+
* Absent, or any field absent, means "no declared limit", and Studio must not invent one — a
|
|
266
|
+
* limit it made up is a file the user cannot upload for no reason anyone can name. A DECLARED
|
|
267
|
+
* limit is different: Studio refuses before spending the round trip and names the number in the
|
|
268
|
+
* refusal, and narrows the file picker's `accept` to match.
|
|
269
|
+
*/
|
|
270
|
+
assetCapabilities?: AssetCapabilities;
|
|
203
271
|
activate: (root?: string) => Promise<void>;
|
|
204
272
|
openProject: () => Promise<{
|
|
205
273
|
config: ProjectConfig;
|
|
@@ -224,8 +292,26 @@ export interface StudioPlatform {
|
|
|
224
292
|
listDirectory: (dir: string) => Promise<DirEntry[]>;
|
|
225
293
|
readFile: (path: string) => Promise<string>;
|
|
226
294
|
writeFile: (path: string, content: string) => Promise<void>;
|
|
227
|
-
|
|
295
|
+
/**
|
|
296
|
+
* Store bytes at a project path, and report where they really landed.
|
|
297
|
+
*
|
|
298
|
+
* The result's `path` is the ANSWER, not an echo: a backend may de-duplicate by content hash,
|
|
299
|
+
* append a collision suffix, or normalize a name, and the reference Studio writes into the
|
|
300
|
+
* document has to name what was actually written. This was `Promise<unknown>` and every caller
|
|
301
|
+
* used the REQUESTED path, so any such backend produced documents pointing at files that are not
|
|
302
|
+
* there.
|
|
303
|
+
*/
|
|
304
|
+
uploadFile: (path: string, data: string | File | Blob | ArrayBuffer) => Promise<UploadResult>;
|
|
228
305
|
deleteFile: (path: string) => Promise<void>;
|
|
306
|
+
/**
|
|
307
|
+
* Move a file and rewrite the project's references to it.
|
|
308
|
+
*
|
|
309
|
+
* `from` and `to` are project-relative, and so is every path in the report — including
|
|
310
|
+
* `references.files[].path` and the `errors[].path` naming a document the refactor could not
|
|
311
|
+
* write. An adapter over a backend with a different path space translates the REQUEST or the
|
|
312
|
+
* REPLY, never both: which one needs translating is a property of that backend's route, and doing
|
|
313
|
+
* both silently assumes it echoes its own input space (specs/desktop.md §3.1).
|
|
314
|
+
*/
|
|
229
315
|
renameFile: (from: string, to: string) => Promise<RenameResult>;
|
|
230
316
|
/**
|
|
231
317
|
* Where a file or a component tag is used across the project — the read side of the same walker
|
|
@@ -239,6 +325,9 @@ export interface StudioPlatform {
|
|
|
239
325
|
*
|
|
240
326
|
* At least one of `path` / `tagName` must be given. A `path` naming a component document
|
|
241
327
|
* contributes its own root tag, so one call answers "as a file AND as an element" together.
|
|
328
|
+
*
|
|
329
|
+
* `path` is project-relative going in, and every path coming back is too. A backend that cannot
|
|
330
|
+
* place the target inside the active project MUST fail rather than answer zero.
|
|
242
331
|
*/
|
|
243
332
|
findReferences?: (target: { path?: string; tagName?: string }) => Promise<ReferencesResult>;
|
|
244
333
|
createDirectory: (path: string) => Promise<void>;
|
|
@@ -258,8 +347,12 @@ export interface StudioPlatform {
|
|
|
258
347
|
installDependencies?: () => Promise<PackageOpResult>;
|
|
259
348
|
/** Whether the project has uninstalled dependencies (node_modules missing). */
|
|
260
349
|
dependenciesNeedInstall?: () => Promise<boolean>;
|
|
261
|
-
/**
|
|
262
|
-
|
|
350
|
+
/**
|
|
351
|
+
* The newest version published on the registry for each dependency, whether or not it is behind.
|
|
352
|
+
* Optional: a platform that cannot reach a registry omits it, and Studio shows no Latest column
|
|
353
|
+
* values rather than inventing them.
|
|
354
|
+
*/
|
|
355
|
+
packageVersions?: () => Promise<PackageVersionInfo[]>;
|
|
263
356
|
/**
|
|
264
357
|
* Rewrite the version range of each named package (preserving its dependencies/devDependencies
|
|
265
358
|
* placement) and run `bun install`. Used by the @jxsuite bump and per-dependency updates.
|
|
@@ -349,6 +442,31 @@ export interface StudioPlatform {
|
|
|
349
442
|
* build happened to leave — which for most projects is nothing at all.
|
|
350
443
|
*/
|
|
351
444
|
buildSite?: () => Promise<SiteBuildResult>;
|
|
445
|
+
/**
|
|
446
|
+
* Preview the site LIVE at `route`, with no build on the path.
|
|
447
|
+
*
|
|
448
|
+
* The sibling of {@link buildSite} and what `View: Open in Browser` reaches first. Nothing is
|
|
449
|
+
* compiled: the backend composes the working tree on demand and the runtime assembles each page
|
|
450
|
+
* in the reader's browser, so what opens carries edits nobody has saved. `buildSite` keeps its
|
|
451
|
+
* own meaning and its own command, because "what does this page look like at its real URL?" and
|
|
452
|
+
* "does my site build?" are different questions and only one of them wants to wait.
|
|
453
|
+
*
|
|
454
|
+
* Optional, and the fallback is `buildSite`: a backend that predates this keeps working, and a
|
|
455
|
+
* backend that can do neither says so rather than opening an origin where half the site is the
|
|
456
|
+
* wrong file.
|
|
457
|
+
*/
|
|
458
|
+
previewSite?: (opts: { route: string }) => Promise<SitePreviewResult>;
|
|
459
|
+
/**
|
|
460
|
+
* Publish the bytes a save WOULD write for one document, so a live preview shows the canvas
|
|
461
|
+
* rather than the disk.
|
|
462
|
+
*
|
|
463
|
+
* Bytes rather than a document, and the same bytes `writeFile` would receive: that is what makes
|
|
464
|
+
* "what the reader sees" and "what saving would produce" one answer instead of two that drift. A
|
|
465
|
+
* backend holds them in memory and writes them nowhere.
|
|
466
|
+
*/
|
|
467
|
+
setPreviewOverlay?: (path: string, contents: string) => Promise<void>;
|
|
468
|
+
/** Drop one document's unsaved bytes, or every one of this project's when no path is given. */
|
|
469
|
+
clearPreviewOverlay?: (path?: string) => Promise<void>;
|
|
352
470
|
gitAddRemote: (name: string, url: string) => Promise<void>;
|
|
353
471
|
/**
|
|
354
472
|
* How the New Project modal collects a destination, and which `CreateProjectDestination` variant
|
|
@@ -389,12 +507,19 @@ export interface StudioPlatform {
|
|
|
389
507
|
* AI-guided import of an existing site into a new project. Runs in the backend (headless Chrome +
|
|
390
508
|
* fs), streaming progress until the project is written. Absent on platforms without a backend
|
|
391
509
|
* import pipeline — the New Project modal hides its Import tab then.
|
|
510
|
+
*
|
|
511
|
+
* `onReady` fires the moment the destination holds an openable `project.json` — seconds in, where
|
|
512
|
+
* the resolved promise is minutes away on a real crawl. It is what lets the caller OPEN the
|
|
513
|
+
* project and let its file tree fill up while the run continues, instead of showing a welcome
|
|
514
|
+
* screen throughout. Optional on both sides: a backend that never sends the line simply never
|
|
515
|
+
* calls it, and the caller opens the project when the run ends, as it always did.
|
|
392
516
|
*/
|
|
393
517
|
importSite?: (
|
|
394
518
|
opts: ImportSiteOptions,
|
|
395
519
|
onProgress: (evt: ImportProgressEvent) => void,
|
|
396
520
|
signal?: AbortSignal,
|
|
397
|
-
|
|
521
|
+
onReady?: (evt: ImportReadyEvent) => void,
|
|
522
|
+
) => Promise<{ root: string; config: ProjectConfig; result?: ImportSiteSummary }>;
|
|
398
523
|
/**
|
|
399
524
|
* Open a native directory picker and return the chosen absolute path (null when cancelled). Backs
|
|
400
525
|
* the New Project modal's **Browse…** button on `createDestination: "path"` platforms. Desktop
|
|
@@ -508,8 +633,39 @@ export interface StudioPlatform {
|
|
|
508
633
|
* settings live in localStorage only.
|
|
509
634
|
*/
|
|
510
635
|
getSettings?: () => Promise<Record<string, string>>;
|
|
511
|
-
/**
|
|
512
|
-
|
|
636
|
+
/**
|
|
637
|
+
* Apply a set of changes to the backend store, and answer with the store as it then stands.
|
|
638
|
+
*
|
|
639
|
+
* A PATCH rather than the whole map, which is a correctness property and not an optimisation: a
|
|
640
|
+
* key named by neither `set` nor `remove` must be left exactly as it was found. Writing the whole
|
|
641
|
+
* map let any window overwrite the shared file with its own view of it, and on the chromium
|
|
642
|
+
* launcher every window is a separate process with a separate browser profile — so a window that
|
|
643
|
+
* held no settings could, and did, clear the credentials another window had just stored. It also
|
|
644
|
+
* preserves keys this build does not know: one written by a newer version, or by hand.
|
|
645
|
+
*/
|
|
646
|
+
patchSettings?: (patch: SettingsPatch) => Promise<Record<string, string>>;
|
|
647
|
+
/**
|
|
648
|
+
* Be told when the backend store changes — by another window, or by hand. Returns the
|
|
649
|
+
* unsubscribe.
|
|
650
|
+
*
|
|
651
|
+
* Optional, and a display concern rather than a correctness one: `patchSettings` already stops a
|
|
652
|
+
* window from clobbering another's keys, so a platform without this leaves a second window
|
|
653
|
+
* showing a stale value until its next boot rather than losing anything.
|
|
654
|
+
*/
|
|
655
|
+
subscribeSettings?: (handler: (settings: Record<string, string>) => void) => () => void;
|
|
656
|
+
/**
|
|
657
|
+
* Watch the backend connection, on platforms whose transport can lose one.
|
|
658
|
+
*
|
|
659
|
+
* Only a socket-based launcher implements this: the dev server talks over `fetch` and has nothing
|
|
660
|
+
* to lose, and the electrobun launcher's RPC times a request out rather than dropping it. The
|
|
661
|
+
* chromium launcher holds one long-lived WebSocket, and when it died every call after it hung
|
|
662
|
+
* with no error anywhere — so the transport reports the state, and the studio says it out loud.
|
|
663
|
+
*
|
|
664
|
+
* The handler is called on each transition, never repeatedly for one state.
|
|
665
|
+
*/
|
|
666
|
+
subscribeConnection?: (
|
|
667
|
+
handler: (state: { online: boolean; reason?: string }) => void,
|
|
668
|
+
) => () => void;
|
|
513
669
|
}
|
|
514
670
|
|
|
515
671
|
// ─── Studio Types ───────────────────────────────────────────────────────────
|