@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
|
@@ -58,6 +58,7 @@ import { getExtensions, loadExtensions } from "../format/format-host";
|
|
|
58
58
|
import { optionalStringArg, stringProperty } from "../commands/command-args";
|
|
59
59
|
import type { TemplateResult } from "lit-html";
|
|
60
60
|
import type { AnyCommand, CommandRegistry } from "../commands/registry";
|
|
61
|
+
import { isMediaFormat } from "../utils/studio-utils";
|
|
61
62
|
|
|
62
63
|
interface SignalsPanelState {
|
|
63
64
|
document: JxMutableNode;
|
|
@@ -915,7 +916,7 @@ function renderSignalEditorTemplate(
|
|
|
915
916
|
: nothing
|
|
916
917
|
}
|
|
917
918
|
${
|
|
918
|
-
def.format
|
|
919
|
+
isMediaFormat(def.format)
|
|
919
920
|
? renderFieldRow({
|
|
920
921
|
hasValue: false,
|
|
921
922
|
label: "Default",
|
package/src/panels/toolbar.ts
CHANGED
|
@@ -41,12 +41,13 @@ import { openQuickSearch } from "./quick-search";
|
|
|
41
41
|
import { showPromptDialog } from "../ui/layers";
|
|
42
42
|
import { getPlatform, hasPlatform } from "../platform";
|
|
43
43
|
import { getPreviewNavigateHandler } from "../canvas/preview-navigate";
|
|
44
|
+
import { armPreviewOverlay, flushPreviewOverlay } from "../preview/preview-overlay";
|
|
44
45
|
import { documentUrlPattern, dynamicRouteParams } from "../page-params";
|
|
45
46
|
import { getNodeAtPath, nodeLabel, projectState } from "../store";
|
|
46
47
|
import { activeRegistry } from "../commands/active-registry";
|
|
47
48
|
import { notify } from "../services/notify";
|
|
48
49
|
import type { Tab } from "../tabs/tab";
|
|
49
|
-
import type { SiteBuildResult } from "../types";
|
|
50
|
+
import type { SiteBuildResult, SitePreviewResult, StudioPlatform } from "../types";
|
|
50
51
|
import type { CommandRegistry } from "../commands/registry";
|
|
51
52
|
import type { EffectScope } from "@vue/reactivity";
|
|
52
53
|
import type { TemplateResult } from "lit-html";
|
|
@@ -253,27 +254,96 @@ export async function runOpenInBrowser() {
|
|
|
253
254
|
notify.warn(target.reason, { key: OPEN_IN_BROWSER, source: "Preview" });
|
|
254
255
|
return;
|
|
255
256
|
}
|
|
256
|
-
/* Build first, because the reader is about to see the OUTPUT and the author is looking at the
|
|
257
|
-
DOCUMENT. Without this they differ by however long it has been since anyone ran a build —
|
|
258
|
-
which for most projects is "always", since nothing in Studio had ever written the output at
|
|
259
|
-
all. A preview that quietly shows last week's page is worse than one that says it cannot open.
|
|
260
|
-
|
|
261
|
-
The build is also what NAMES the origin. The built site is served on a port of its own, because
|
|
262
|
-
the editor's own paths mean the project's sources while a built page means its output by the
|
|
263
|
-
same paths — so a backend that cannot build cannot preview either, and says so instead of
|
|
264
|
-
sending the reader to an address where half the site is the wrong file. */
|
|
265
257
|
const platform = hasPlatform() ? getPlatform() : null;
|
|
266
|
-
if (!platform?.buildSite) {
|
|
267
|
-
notify.warn("This backend cannot
|
|
258
|
+
if (!platform?.previewSite && !platform?.buildSite) {
|
|
259
|
+
notify.warn("This backend cannot preview the site.", {
|
|
260
|
+
key: OPEN_IN_BROWSER,
|
|
261
|
+
source: "Preview",
|
|
262
|
+
});
|
|
263
|
+
return;
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
/* A LIVE preview is what this action is for, and a build is the fallback for a backend that
|
|
267
|
+
cannot do one. The two answer different questions: "what does this page look like at its real
|
|
268
|
+
URL?" wants the tree as it stands, and "does my site build?" is `Build Site`. */
|
|
269
|
+
if (platform.previewSite) {
|
|
270
|
+
await runLivePreview(platform.previewSite, target.path);
|
|
271
|
+
return;
|
|
272
|
+
}
|
|
273
|
+
await runBuiltPreview(platform.buildSite!, target.path);
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
/**
|
|
277
|
+
* Preview the working tree, and open a tab only if one is not already showing this project.
|
|
278
|
+
*
|
|
279
|
+
* The overlay is flushed FIRST, and that ordering is the whole point: the page the author is about
|
|
280
|
+
* to look at should carry the keystroke they just typed, not the one before the debounce.
|
|
281
|
+
*/
|
|
282
|
+
async function runLivePreview(
|
|
283
|
+
previewSite: NonNullable<StudioPlatform["previewSite"]>,
|
|
284
|
+
path: string,
|
|
285
|
+
) {
|
|
286
|
+
armPreviewOverlay();
|
|
287
|
+
await flushPreviewOverlay();
|
|
288
|
+
notify.info("Opening a live preview…", { key: OPEN_IN_BROWSER, source: "Preview" });
|
|
289
|
+
let result: SitePreviewResult;
|
|
290
|
+
try {
|
|
291
|
+
result = await previewSite({ route: path });
|
|
292
|
+
} catch (error) {
|
|
293
|
+
notify.error(`The site could not be previewed: ${errorText(error)}`, {
|
|
268
294
|
key: OPEN_IN_BROWSER,
|
|
269
295
|
source: "Preview",
|
|
270
296
|
});
|
|
271
297
|
return;
|
|
272
298
|
}
|
|
299
|
+
if (!result.url) {
|
|
300
|
+
notify.warn("This backend serves no preview of the site.", {
|
|
301
|
+
key: OPEN_IN_BROWSER,
|
|
302
|
+
source: "Preview",
|
|
303
|
+
});
|
|
304
|
+
return;
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
/* `reused` is honoured, and honouring it is the feature. A tab already holding this project's
|
|
308
|
+
preview has been pointed at the route; opening another would leave the author with two tabs on
|
|
309
|
+
one project, which is exactly what retargeting the first one exists to prevent. What it costs
|
|
310
|
+
is that the browser does not come forward — no page can raise a background tab, and handing the
|
|
311
|
+
URL to the OS again would open a duplicate rather than switch to it — so the notification says
|
|
312
|
+
where to look. A reader who CLOSED their tab is not stuck here: the stream closes with it, so
|
|
313
|
+
`reused` comes back false and a fresh tab opens without anyone having to ask. */
|
|
314
|
+
if (result.reused) {
|
|
315
|
+
notify.info(`Updated your preview tab — switch to your browser to see ${path}`, {
|
|
316
|
+
key: OPEN_IN_BROWSER,
|
|
317
|
+
source: "Preview",
|
|
318
|
+
});
|
|
319
|
+
return;
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
if (result.errors.length > 0) {
|
|
323
|
+
notify.warn(`Previewed with ${result.errors.length} problem(s): ${result.errors[0]}`, {
|
|
324
|
+
key: OPEN_IN_BROWSER,
|
|
325
|
+
source: "Preview",
|
|
326
|
+
});
|
|
327
|
+
} else {
|
|
328
|
+
notify.success(
|
|
329
|
+
`Opened a live preview of ${result.routes} page(s) — rendered from your working tree, not a build.`,
|
|
330
|
+
{ key: OPEN_IN_BROWSER, source: "Preview" },
|
|
331
|
+
);
|
|
332
|
+
}
|
|
333
|
+
openUrlExternally(`${result.url}${path}`);
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
/**
|
|
337
|
+
* The compiler-backed path, for a backend that declares no live preview.
|
|
338
|
+
*
|
|
339
|
+
* Kept so nothing regresses on a backend that predates `previewSite`: the cloud adapter answers
|
|
340
|
+
* `buildSite` with `mode: "live"` of its own, and `jx dev` answers it with a real build.
|
|
341
|
+
*/
|
|
342
|
+
async function runBuiltPreview(buildSite: NonNullable<StudioPlatform["buildSite"]>, path: string) {
|
|
273
343
|
notify.info("Building the site…", { key: OPEN_IN_BROWSER, source: "Preview" });
|
|
274
344
|
let result: SiteBuildResult;
|
|
275
345
|
try {
|
|
276
|
-
result = await
|
|
346
|
+
result = await buildSite();
|
|
277
347
|
} catch (error) {
|
|
278
348
|
notify.error(`The site could not be built: ${errorText(error)}`, {
|
|
279
349
|
key: OPEN_IN_BROWSER,
|
|
@@ -288,20 +358,70 @@ export async function runOpenInBrowser() {
|
|
|
288
358
|
});
|
|
289
359
|
return;
|
|
290
360
|
}
|
|
361
|
+
/* A live preview is not a build and the report must not say it was: what it renders is the
|
|
362
|
+
working tree, unsaved edits and all, assembled in the reader's browser rather than compiled. */
|
|
363
|
+
const live = result.mode === "live";
|
|
291
364
|
if (result.errors.length > 0) {
|
|
292
365
|
/* Named, and the page still opens. A partial build produced pages, and the author can see the
|
|
293
366
|
one they asked for while reading what failed — which is the whole difference between a
|
|
294
367
|
preview and a build log. */
|
|
295
|
-
|
|
368
|
+
const verb = live ? "previewed" : "built";
|
|
369
|
+
notify.warn(`The site ${verb} with ${result.errors.length} error(s): ${result.errors[0]}`, {
|
|
296
370
|
key: OPEN_IN_BROWSER,
|
|
297
371
|
source: "Preview",
|
|
298
372
|
});
|
|
373
|
+
} else if (live) {
|
|
374
|
+
notify.success(
|
|
375
|
+
`Opened a live preview of ${result.routes} page(s) — rendered from your working tree, not a build.`,
|
|
376
|
+
{ key: OPEN_IN_BROWSER, source: "Preview" },
|
|
377
|
+
);
|
|
299
378
|
} else {
|
|
300
379
|
notify.success(`Built ${result.routes} page(s).`, { key: OPEN_IN_BROWSER, source: "Preview" });
|
|
301
380
|
}
|
|
302
|
-
openUrlExternally(`${result.url}${
|
|
381
|
+
openUrlExternally(`${result.url}${path}`);
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
/**
|
|
385
|
+
* Run `Build Site`, the compiler-backed answer to "does my site build?".
|
|
386
|
+
*
|
|
387
|
+
* `View: Open in Browser` stopped meaning this when it started previewing the working tree, and the
|
|
388
|
+
* question did not stop being worth asking: a build runs the bundler, the image pipeline and every
|
|
389
|
+
* emitter, and a live preview runs none of them. So it keeps its own verb, its own report, and the
|
|
390
|
+
* output origin it always opened.
|
|
391
|
+
*/
|
|
392
|
+
export async function runBuildSite() {
|
|
393
|
+
const platform = hasPlatform() ? getPlatform() : null;
|
|
394
|
+
if (!platform?.buildSite) {
|
|
395
|
+
notify.warn("This backend cannot build the site.", { key: BUILD_SITE, source: "Build" });
|
|
396
|
+
return;
|
|
397
|
+
}
|
|
398
|
+
notify.info("Building the site…", { key: BUILD_SITE, source: "Build" });
|
|
399
|
+
let result: SiteBuildResult;
|
|
400
|
+
try {
|
|
401
|
+
result = await platform.buildSite();
|
|
402
|
+
} catch (error) {
|
|
403
|
+
notify.error(`The site could not be built: ${errorText(error)}`, {
|
|
404
|
+
key: BUILD_SITE,
|
|
405
|
+
source: "Build",
|
|
406
|
+
});
|
|
407
|
+
return;
|
|
408
|
+
}
|
|
409
|
+
if (result.errors.length > 0) {
|
|
410
|
+
notify.warn(`Built with ${result.errors.length} error(s): ${result.errors[0]}`, {
|
|
411
|
+
key: BUILD_SITE,
|
|
412
|
+
source: "Build",
|
|
413
|
+
});
|
|
414
|
+
return;
|
|
415
|
+
}
|
|
416
|
+
notify.success(`Built ${result.routes} page(s), ${result.files} file(s).`, {
|
|
417
|
+
key: BUILD_SITE,
|
|
418
|
+
source: "Build",
|
|
419
|
+
});
|
|
303
420
|
}
|
|
304
421
|
|
|
422
|
+
/** One notification key for the build, kept apart so a build report never replaces a preview's. */
|
|
423
|
+
const BUILD_SITE = "project.buildSite";
|
|
424
|
+
|
|
305
425
|
/** One notification key for the whole flow, so building → built → failed replaces rather than piles. */
|
|
306
426
|
const OPEN_IN_BROWSER = "view.openInBrowser";
|
|
307
427
|
|
package/src/platforms/cloud.ts
CHANGED
|
@@ -28,6 +28,7 @@ import type {
|
|
|
28
28
|
GitStatusResult,
|
|
29
29
|
PackageInfo,
|
|
30
30
|
ProjectListEntry,
|
|
31
|
+
SiteBuildResult,
|
|
31
32
|
ProjectSchemasResponse,
|
|
32
33
|
ReferencesResult,
|
|
33
34
|
RenameResult,
|
|
@@ -36,6 +37,7 @@ import type {
|
|
|
36
37
|
StudioPlatform,
|
|
37
38
|
} from "../types";
|
|
38
39
|
import { problemDetail, problemSlug } from "@jxsuite/protocol";
|
|
40
|
+
import type { UploadResult } from "@jxsuite/protocol";
|
|
39
41
|
|
|
40
42
|
export interface CloudProject {
|
|
41
43
|
owner: string;
|
|
@@ -232,8 +234,15 @@ export function createCloudPlatform(project: CloudProject | null): StudioPlatfor
|
|
|
232
234
|
The session serves the project tree at /raw.
|
|
233
235
|
|
|
234
236
|
Conditional because `base` is "" until a project is bound, and "/raw/" would be a confident
|
|
235
|
-
wrong answer — worse than the default, which at least fails visibly.
|
|
236
|
-
|
|
237
|
+
wrong answer — worse than the default, which at least fails visibly.
|
|
238
|
+
|
|
239
|
+
`assetSpace` rides in the SAME spread, and that is the whole of its correctness: studio.jx-
|
|
240
|
+
suite.com is a multi-tenant SPA origin, so `/hero.jpg` misses Workers Static Assets and the
|
|
241
|
+
single-page-app fallback answers index.html at HTTP 200 — the <img> gets HTML, renders
|
|
242
|
+
broken, and logs nothing. Declaring "repo" tells Studio to resolve each reference to the
|
|
243
|
+
project file it names and address it under /raw. Until a project is bound there is no /raw to
|
|
244
|
+
address, so the project-less hub declares neither and stays exactly as it was. */
|
|
245
|
+
...(base ? { assetSpace: "repo" as const, documentBaseUrl: `${base}/raw/` } : {}),
|
|
237
246
|
/* Open Project routes through Studio's repo picker (write-access repos via listRepos +
|
|
238
247
|
importProject) — sessions are URL-bound, so openProject() below never opens a dialog. */
|
|
239
248
|
openProjectPicker: "repo-list",
|
|
@@ -314,7 +323,10 @@ export function createCloudPlatform(project: CloudProject | null): StudioPlatfor
|
|
|
314
323
|
method: "POST",
|
|
315
324
|
body: data,
|
|
316
325
|
});
|
|
317
|
-
|
|
326
|
+
const body = await okJson<Partial<UploadResult>>(res, `Upload failed: ${path}`);
|
|
327
|
+
// The session echoes the path today; falling back to the request keeps a backend that says
|
|
328
|
+
// Nothing working rather than writing `undefined` into a document.
|
|
329
|
+
return { path: body.path ?? path, ...(body.size === undefined ? {} : { size: body.size }) };
|
|
318
330
|
},
|
|
319
331
|
|
|
320
332
|
async deleteFile(path: string) {
|
|
@@ -754,6 +766,25 @@ export function createCloudPlatform(project: CloudProject | null): StudioPlatfor
|
|
|
754
766
|
// The GitHub repo IS the remote; nothing to add.
|
|
755
767
|
},
|
|
756
768
|
|
|
769
|
+
/**
|
|
770
|
+
* Open in Browser, cloud edition — a LIVE preview rather than a build.
|
|
771
|
+
*
|
|
772
|
+
* The backend cannot run `jx build`: it never executes project JS, and the compiler needs
|
|
773
|
+
* `sharp`, a bundler and a filesystem, none of which exist in a Worker. What it can do is serve
|
|
774
|
+
* the working tree as a real site on an origin of its own, rendering each page with the same
|
|
775
|
+
* runtime the canvas uses — so the reply names `mode: "live"` and Studio says so, instead of
|
|
776
|
+
* letting a reader assume they are looking at build output.
|
|
777
|
+
*
|
|
778
|
+
* That it is not a build is also the best thing about it: the preview is the tree as it stands,
|
|
779
|
+
* including edits nobody has saved and edits another collaborator is making right now.
|
|
780
|
+
*/
|
|
781
|
+
async buildSite() {
|
|
782
|
+
return okJson<SiteBuildResult>(
|
|
783
|
+
await api("/build", { method: "POST" }),
|
|
784
|
+
"The site could not be previewed.",
|
|
785
|
+
);
|
|
786
|
+
},
|
|
787
|
+
|
|
757
788
|
// ─── Project catalogue & navigation (Studio welcome / New Project UI) ──
|
|
758
789
|
|
|
759
790
|
async listProjects(): Promise<ProjectListEntry[]> {
|
|
@@ -28,6 +28,7 @@ import type {
|
|
|
28
28
|
ExtensionsInfo,
|
|
29
29
|
FsEvent,
|
|
30
30
|
ImportProgressEvent,
|
|
31
|
+
ImportReadyEvent,
|
|
31
32
|
ImportSiteOptions,
|
|
32
33
|
ProjectSchemasResponse,
|
|
33
34
|
ReferencesResult,
|
|
@@ -35,9 +36,11 @@ import type {
|
|
|
35
36
|
SecretsSetRequest,
|
|
36
37
|
SecretsSetResponse,
|
|
37
38
|
SiteBuildResult,
|
|
39
|
+
SitePreviewResult,
|
|
38
40
|
StarterInfo,
|
|
39
41
|
} from "../types";
|
|
40
42
|
import { problemDetail, problemMessage } from "@jxsuite/protocol";
|
|
43
|
+
import type { UploadResult } from "@jxsuite/protocol";
|
|
41
44
|
|
|
42
45
|
/** A directory entry from the server, tolerating extra wire fields. */
|
|
43
46
|
type WireDirEntry = DirEntry & Record<string, unknown>;
|
|
@@ -61,11 +64,26 @@ interface SiteEntry {
|
|
|
61
64
|
*
|
|
62
65
|
* The adapter is stateless apart from `_projectRoot`, which tracks the server-relative project
|
|
63
66
|
* directory (e.g. "examples/site-demo"). All paths passed INTO PAL methods are project-relative;
|
|
64
|
-
* the adapter prefixes them with `_projectRoot` before hitting the server
|
|
65
|
-
*
|
|
67
|
+
* the adapter prefixes them with `_projectRoot` before hitting the server.
|
|
68
|
+
*
|
|
69
|
+
* **Coming back, the space depends on the route, and the routes do not all agree** (the contract is
|
|
70
|
+
* `specs/server.md` §4.1). `/__studio/files` echoes what it was asked — server-root-relative — so
|
|
71
|
+
* its replies are stripped. The refactor routes (`file/rename`, `references`) answer in the ACTIVE
|
|
72
|
+
* PROJECT's space, because that is the space their sweep runs in, so their replies are returned
|
|
73
|
+
* untouched. Stripping one of those is not a harmless no-op: it would eat the first segment of any
|
|
74
|
+
* path that begins with the project root's own name.
|
|
66
75
|
*/
|
|
67
76
|
export function createDevServerPlatform() {
|
|
68
77
|
let _projectRoot = "";
|
|
78
|
+
/**
|
|
79
|
+
* The in-flight `activate()` for the current root, so a query whose answer depends on the
|
|
80
|
+
* server's project binding can await it.
|
|
81
|
+
*
|
|
82
|
+
* `set projectRoot` fires activation fire-and-forget, and `/__studio/references` is the one route
|
|
83
|
+
* with no `dir` parameter — its sweep root comes solely from the server's activated project. A
|
|
84
|
+
* query landing inside that window would sweep the server's own root instead (issue 239).
|
|
85
|
+
*/
|
|
86
|
+
let _activation: Promise<unknown> | null = null;
|
|
69
87
|
/**
|
|
70
88
|
* Lazy /__studio/collab capability probe (null = not asked yet). Its result is the subprotocol
|
|
71
89
|
* negotiation, not a boolean: the probe is the only place the client can learn what envelope the
|
|
@@ -125,7 +143,7 @@ export function createDevServerPlatform() {
|
|
|
125
143
|
// Fire-and-forget: callers that need the binding in place before reading await activate()
|
|
126
144
|
// Themselves. The rejection is reported rather than dropped — an unactivated root leaves
|
|
127
145
|
// Every rootless endpoint (git especially) operating on the server's OWN root.
|
|
128
|
-
|
|
146
|
+
_activation = this.activate(_projectRoot).catch((error: unknown) => {
|
|
129
147
|
console.error("Project activation failed:", error);
|
|
130
148
|
});
|
|
131
149
|
}
|
|
@@ -334,8 +352,9 @@ export function createDevServerPlatform() {
|
|
|
334
352
|
opts: ImportSiteOptions,
|
|
335
353
|
onProgress: (evt: ImportProgressEvent) => void,
|
|
336
354
|
signal?: AbortSignal,
|
|
355
|
+
onReady?: (evt: ImportReadyEvent) => void,
|
|
337
356
|
) {
|
|
338
|
-
return await streamImport("/__studio/import-site", opts, onProgress, signal);
|
|
357
|
+
return await streamImport("/__studio/import-site", opts, onProgress, signal, onReady);
|
|
339
358
|
},
|
|
340
359
|
|
|
341
360
|
// ─── File operations ──────────────────────────────────────────────────
|
|
@@ -391,7 +410,10 @@ export function createDevServerPlatform() {
|
|
|
391
410
|
if (!res.ok) {
|
|
392
411
|
throw new Error(`Upload failed: ${path}`);
|
|
393
412
|
}
|
|
394
|
-
|
|
413
|
+
const body = (await res.json()) as Partial<UploadResult>;
|
|
414
|
+
// The dev server echoes the path today; falling back to the request keeps a backend that says
|
|
415
|
+
// Nothing working rather than writing `undefined` into a document.
|
|
416
|
+
return { path: body.path ?? path, ...(body.size === undefined ? {} : { size: body.size }) };
|
|
395
417
|
},
|
|
396
418
|
|
|
397
419
|
/** @param {string} path */
|
|
@@ -417,21 +439,11 @@ export function createDevServerPlatform() {
|
|
|
417
439
|
if (!res.ok) {
|
|
418
440
|
throw new Error(`Failed to rename: ${from} → ${to}`);
|
|
419
441
|
}
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
if (typeof report.to === "string") {
|
|
426
|
-
report.to = stripRoot(report.to);
|
|
427
|
-
}
|
|
428
|
-
for (const f of report.references?.files ?? []) {
|
|
429
|
-
f.path = stripRoot(f.path);
|
|
430
|
-
}
|
|
431
|
-
for (const e of report.errors ?? []) {
|
|
432
|
-
e.path = stripRoot(e.path);
|
|
433
|
-
}
|
|
434
|
-
return report;
|
|
442
|
+
/* No strip on the way back: the route takes its paths in server space and answers in the
|
|
443
|
+
project's (specs/server.md §4.1). Stripping an already-project-relative path is not merely
|
|
444
|
+
redundant — it corrupts any path whose first segment happens to match the project root's
|
|
445
|
+
own name, which a server-root-relative root makes reachable. */
|
|
446
|
+
return readJson<RenameResult>(res);
|
|
435
447
|
},
|
|
436
448
|
|
|
437
449
|
/**
|
|
@@ -442,6 +454,7 @@ export function createDevServerPlatform() {
|
|
|
442
454
|
* @param {{ path?: string; tagName?: string }} target
|
|
443
455
|
*/
|
|
444
456
|
async findReferences(target: { path?: string; tagName?: string }): Promise<ReferencesResult> {
|
|
457
|
+
await _activation;
|
|
445
458
|
const params = new URLSearchParams();
|
|
446
459
|
if (target.path) {
|
|
447
460
|
params.set("path", serverPath(target.path));
|
|
@@ -453,18 +466,8 @@ export function createDevServerPlatform() {
|
|
|
453
466
|
if (!res.ok) {
|
|
454
467
|
throw new Error(`Failed to find references: ${target.path ?? target.tagName ?? ""}`);
|
|
455
468
|
}
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
if (typeof result.path === "string") {
|
|
459
|
-
result.path = stripRoot(result.path);
|
|
460
|
-
}
|
|
461
|
-
for (const f of result.files ?? []) {
|
|
462
|
-
f.path = stripRoot(f.path);
|
|
463
|
-
}
|
|
464
|
-
for (const e of result.errors ?? []) {
|
|
465
|
-
e.path = stripRoot(e.path);
|
|
466
|
-
}
|
|
467
|
-
return result;
|
|
469
|
+
// Project-relative on the way out, exactly as `renameFile` above receives it.
|
|
470
|
+
return readJson<ReferencesResult>(res);
|
|
468
471
|
},
|
|
469
472
|
|
|
470
473
|
/**
|
|
@@ -623,8 +626,8 @@ export function createDevServerPlatform() {
|
|
|
623
626
|
return Boolean(data.needsInstall);
|
|
624
627
|
},
|
|
625
628
|
|
|
626
|
-
async
|
|
627
|
-
const res = await fetch("/__studio/packages/
|
|
629
|
+
async packageVersions() {
|
|
630
|
+
const res = await fetch("/__studio/packages/versions");
|
|
628
631
|
if (!res.ok) {
|
|
629
632
|
return [];
|
|
630
633
|
}
|
|
@@ -1145,6 +1148,41 @@ export function createDevServerPlatform() {
|
|
|
1145
1148
|
return (await res.json()) as SiteBuildResult;
|
|
1146
1149
|
},
|
|
1147
1150
|
|
|
1151
|
+
/**
|
|
1152
|
+
* Preview the working tree live, and point this project's open tab at a route.
|
|
1153
|
+
*
|
|
1154
|
+
* The dev server holds the same live origin the desktop launchers do, so the browser Studio
|
|
1155
|
+
* gets the same preview: composed on demand, unsaved edits included.
|
|
1156
|
+
*/
|
|
1157
|
+
async previewSite(opts: { route: string }) {
|
|
1158
|
+
const res = await fetch("/__studio/preview", {
|
|
1159
|
+
body: JSON.stringify(opts),
|
|
1160
|
+
headers: { "Content-Type": "application/json" },
|
|
1161
|
+
method: "POST",
|
|
1162
|
+
});
|
|
1163
|
+
if (!res.ok) {
|
|
1164
|
+
const body = await readJson<ErrorBody>(res);
|
|
1165
|
+
throw new Error(problemDetail(body) ?? "The site could not be previewed.");
|
|
1166
|
+
}
|
|
1167
|
+
return (await res.json()) as SitePreviewResult;
|
|
1168
|
+
},
|
|
1169
|
+
|
|
1170
|
+
async setPreviewOverlay(path: string, contents: string) {
|
|
1171
|
+
await fetch("/__studio/preview/overlay", {
|
|
1172
|
+
body: JSON.stringify({ contents, path }),
|
|
1173
|
+
headers: { "Content-Type": "application/json" },
|
|
1174
|
+
method: "POST",
|
|
1175
|
+
});
|
|
1176
|
+
},
|
|
1177
|
+
|
|
1178
|
+
async clearPreviewOverlay(path?: string) {
|
|
1179
|
+
await fetch("/__studio/preview/overlay", {
|
|
1180
|
+
body: JSON.stringify(path === undefined ? {} : { path }),
|
|
1181
|
+
headers: { "Content-Type": "application/json" },
|
|
1182
|
+
method: "DELETE",
|
|
1183
|
+
});
|
|
1184
|
+
},
|
|
1185
|
+
|
|
1148
1186
|
/**
|
|
1149
1187
|
* @param {string} name
|
|
1150
1188
|
* @param {string} url
|