@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
|
@@ -20,6 +20,7 @@ import {
|
|
|
20
20
|
resolveLayoutDoc,
|
|
21
21
|
} from "../site-context";
|
|
22
22
|
import { documentBase } from "./canvas-origin";
|
|
23
|
+
import { getPlatform, hasPlatform } from "../platform";
|
|
23
24
|
import { componentRegistry, computeRelativePath } from "../files/components";
|
|
24
25
|
import { prepareForEditMode } from "../utils/edit-display";
|
|
25
26
|
import {
|
|
@@ -28,11 +29,11 @@ import {
|
|
|
28
29
|
substitutePreviewParams,
|
|
29
30
|
} from "../page-params";
|
|
30
31
|
import { isComponentDoc, substitutePreviewProps } from "../component-props";
|
|
31
|
-
import {
|
|
32
|
+
import { assetContextFor } from "./asset-refs";
|
|
32
33
|
|
|
34
|
+
import type { AssetContext } from "./asset-refs";
|
|
33
35
|
import type { JxElement, JxMutableNode, JxPath } from "@jxsuite/schema/types";
|
|
34
36
|
import type { ComponentEntry } from "../files/components.js";
|
|
35
|
-
import type { ContentSectionEntry } from "../types";
|
|
36
37
|
import type { LayoutMarker } from "./path-mapping";
|
|
37
38
|
import type { Tab } from "../tabs/tab";
|
|
38
39
|
import type { WireMapperCtx } from "./iframe-protocol";
|
|
@@ -138,6 +139,8 @@ export async function resolveCanvasDocument(
|
|
|
138
139
|
): Promise<{
|
|
139
140
|
renderDoc: JxMutableNode;
|
|
140
141
|
docBase: string | undefined;
|
|
142
|
+
/** How the canvas must address project media, or null when its origin serves the site URL space. */
|
|
143
|
+
assets: AssetContext | null;
|
|
141
144
|
mapperCtx: WireMapperCtx;
|
|
142
145
|
siteStyle: Record<string, unknown> | null;
|
|
143
146
|
}> {
|
|
@@ -179,9 +182,23 @@ export async function resolveCanvasDocument(
|
|
|
179
182
|
}
|
|
180
183
|
}
|
|
181
184
|
|
|
182
|
-
const
|
|
183
|
-
|
|
184
|
-
|
|
185
|
+
const fileBase = documentBase(projectState?.projectRoot);
|
|
186
|
+
const docBase = S.documentPath ? new URL(S.documentPath, fileBase).href : undefined;
|
|
187
|
+
/*
|
|
188
|
+
* How this render addresses media, as PLAIN DATA — the resolver itself is a function and cannot
|
|
189
|
+
* cross into the canvas realm, so the iframe rebuilds it from this.
|
|
190
|
+
*
|
|
191
|
+
* It replaces a walk over the render document that used to sit further down this function. A walk
|
|
192
|
+
* can only ever see LITERAL values: `applyAttributes` resolves a `{"$ref": …}` or `"${…}"` src
|
|
193
|
+
* inside a reactive effect, so at walk time a bound image src is not a string at all. That is why
|
|
194
|
+
* a collection listing of thirty bound card images broke all at once in preview and nowhere else
|
|
195
|
+
* — design and edit swap every bound src for a transparent pixel, so they never reached the
|
|
196
|
+
* network to fail.
|
|
197
|
+
*/
|
|
198
|
+
const assets = assetContextFor(S.documentPath, {
|
|
199
|
+
fileBaseUrl: fileBase,
|
|
200
|
+
...(hasPlatform() ? { space: getPlatform().assetSpace } : {}),
|
|
201
|
+
});
|
|
185
202
|
|
|
186
203
|
// Substitute chosen dynamic route params ({$ref: "#/$params/x"} → literal), inject state.$page,
|
|
187
204
|
// And bake the substituted class-prototype state entries via the backend resolver — in every
|
|
@@ -210,16 +227,6 @@ export async function resolveCanvasDocument(
|
|
|
210
227
|
}
|
|
211
228
|
}
|
|
212
229
|
|
|
213
|
-
// A content entry references its media relative to ITSELF; the built site serves those files from
|
|
214
|
-
// The content type's asset mount. Studio opens the entry standalone, so the collection loader that
|
|
215
|
-
// Normally performs that mapping never runs — do it here, on the RENDER doc only, so the canvas
|
|
216
|
-
// Previews the URL production serves while the source doc keeps the authored relative ref.
|
|
217
|
-
renderDoc = rewriteContentAssets(
|
|
218
|
-
renderDoc,
|
|
219
|
-
S.documentPath,
|
|
220
|
-
projectState?.projectConfig?.content as Record<string, ContentSectionEntry> | undefined,
|
|
221
|
-
);
|
|
222
|
-
|
|
223
230
|
const arrayPaths = new Set<string>();
|
|
224
231
|
if (canvasMode === "design" || canvasMode === "edit") {
|
|
225
232
|
(function findArrayPaths(node: JxMutableNode, path: (string | number)[]) {
|
|
@@ -321,6 +328,7 @@ export async function resolveCanvasDocument(
|
|
|
321
328
|
}
|
|
322
329
|
|
|
323
330
|
return {
|
|
331
|
+
assets,
|
|
324
332
|
docBase,
|
|
325
333
|
mapperCtx: {
|
|
326
334
|
arrayPaths: [...arrayPaths],
|
|
@@ -37,10 +37,12 @@ import { attachCursorStyles } from "../collab/monaco-cursors";
|
|
|
37
37
|
import type { AwarenessLike } from "../collab/monaco-cursors";
|
|
38
38
|
import type { BindingAwareness } from "../collab/monaco-binding";
|
|
39
39
|
import { monacoTheme, shell } from "../shell";
|
|
40
|
-
import { parseSourceForPath
|
|
40
|
+
import { parseSourceForPath } from "../files/file-ops";
|
|
41
|
+
import { serializeDocument } from "../files/serialize-document";
|
|
41
42
|
import { detachGridPanel, gridPanelMounted, renderGridMode } from "../grid/grid-panel";
|
|
42
43
|
import { detachLibraryPane, libraryPaneMounted, renderLibraryMode } from "../browse/library-pane";
|
|
43
44
|
import { detachEntryPane, entryPaneMounted, renderEntryMode } from "../content/entry-editor";
|
|
45
|
+
import { detachMediaPane, mediaPaneMounted, renderMediaMode } from "../media/media-pane";
|
|
44
46
|
import {
|
|
45
47
|
detachSettingsPane,
|
|
46
48
|
renderSettingsPane,
|
|
@@ -370,6 +372,7 @@ function resetCanvasView(surface: CanvasSurface) {
|
|
|
370
372
|
detachGridPanel(paneId);
|
|
371
373
|
detachLibraryPane(paneId);
|
|
372
374
|
detachEntryPane(paneId);
|
|
375
|
+
detachMediaPane(paneId);
|
|
373
376
|
detachSettingsPane(paneId);
|
|
374
377
|
disposeSourceCollab(surface);
|
|
375
378
|
disposeSourceEditor(surface);
|
|
@@ -883,6 +886,12 @@ function renderCanvasImpl(surface: CanvasSurface) {
|
|
|
883
886
|
return;
|
|
884
887
|
}
|
|
885
888
|
|
|
889
|
+
// Media fast-path, same shape: the viewer owns its own effect, so learning an image's dimensions
|
|
890
|
+
// Repaints that panel and nothing reaches the canvas pipeline.
|
|
891
|
+
if (canvasMode === "media" && mediaPaneMounted(surface.paneId, tab) && !modeChanged) {
|
|
892
|
+
return;
|
|
893
|
+
}
|
|
894
|
+
|
|
886
895
|
// Settings fast-path, same shape: the Project Settings editor subscribes to the section registry
|
|
887
896
|
// And to its own chosen section, so a mounted editor needs nothing from the canvas pipeline.
|
|
888
897
|
if (canvasMode === "settings" && settingsPaneMounted(surface) && !modeChanged) {
|
|
@@ -953,6 +962,9 @@ function renderCanvasImpl(surface: CanvasSurface) {
|
|
|
953
962
|
// Same for the entry form, whose effect scope subscribes to the document's frontmatter
|
|
954
963
|
detachEntryPane(surface.paneId);
|
|
955
964
|
|
|
965
|
+
// And for the media viewer, whose effect scope subscribes to the tab's path
|
|
966
|
+
detachMediaPane(surface.paneId);
|
|
967
|
+
|
|
956
968
|
// Same for the Project Settings editor, which holds a registry subscription
|
|
957
969
|
detachSettingsPane(surface.paneId);
|
|
958
970
|
|
|
@@ -1068,6 +1080,15 @@ function renderCanvasImpl(surface: CanvasSurface) {
|
|
|
1068
1080
|
return;
|
|
1069
1081
|
}
|
|
1070
1082
|
|
|
1083
|
+
/* Media mode: an image, a video, a font specimen — a file the studio can SHOW but has no document
|
|
1084
|
+
model for (`media/media-pane.ts`). Same non-iframe-editor pattern as the four above. */
|
|
1085
|
+
if (canvasMode === "media") {
|
|
1086
|
+
canvasWrap.style.padding = "0";
|
|
1087
|
+
canvasWrap.style.display = "block";
|
|
1088
|
+
renderMediaMode(surface, tab);
|
|
1089
|
+
return;
|
|
1090
|
+
}
|
|
1091
|
+
|
|
1071
1092
|
// Grid mode: spreadsheet editor over the tab's grid source (mirrors the Monaco/stylebook
|
|
1072
1093
|
// Non-iframe-editor pattern; the panel owns its own reactivity from here).
|
|
1073
1094
|
if (canvasMode === "grid") {
|
|
@@ -1508,6 +1529,7 @@ setSurfaceTeardown((surface) => {
|
|
|
1508
1529
|
detachGridPanel(surface.paneId);
|
|
1509
1530
|
detachLibraryPane(surface.paneId);
|
|
1510
1531
|
detachEntryPane(surface.paneId);
|
|
1532
|
+
detachMediaPane(surface.paneId);
|
|
1511
1533
|
detachSettingsPane(surface.paneId);
|
|
1512
1534
|
attachDocumentHeaderHost(surface.paneId, null);
|
|
1513
1535
|
disposeSourceCollab(surface);
|
|
@@ -2775,6 +2775,7 @@ function preparePassRender(
|
|
|
2775
2775
|
const formatElements = formatByName(renderTab?.doc.sourceFormat)?.studio?.elements;
|
|
2776
2776
|
const editableTags = formatElements ? formatEditableVerdicts(formatElements) : undefined;
|
|
2777
2777
|
return {
|
|
2778
|
+
assets: resolved.assets,
|
|
2778
2779
|
doc: cloneableDoc,
|
|
2779
2780
|
docBase: resolved.docBase ?? `${canvasBaseOrigin()}/`,
|
|
2780
2781
|
mapperCtx: resolved.mapperCtx,
|
|
@@ -2898,6 +2899,9 @@ export function mountStylebookCanvas(
|
|
|
2898
2899
|
const message: ParentToIframe = {
|
|
2899
2900
|
// The specimen carries no tab identity, but the STAGE it is mounted into does: a stylebook is
|
|
2900
2901
|
// Opened as a tab like anything else, and its `previewColorScheme` is that tab's.
|
|
2902
|
+
/* A stylebook specimen is generated, not a project document, so it references no project
|
|
2903
|
+
media and needs no resolution. */
|
|
2904
|
+
assets: null,
|
|
2901
2905
|
colorScheme: schemeWireFor(tabOfContainer(canvasEl)),
|
|
2902
2906
|
doc: cloneableDoc,
|
|
2903
2907
|
docBase: `${canvasBaseOrigin()}/`,
|
|
@@ -56,12 +56,38 @@ export function applyIframePatch(
|
|
|
56
56
|
const echoed = new Set(echoPaths.map((p) => serializeJxPath(p)));
|
|
57
57
|
for (const op of forwardOps) {
|
|
58
58
|
if (op.op === "set-key" && echoed.has(serializeJxPath(op.path))) {
|
|
59
|
+
// The content is already right; only the model-derived placeholder class can be stale, and
|
|
60
|
+
// Reconciling a class touches neither the caret nor the text under it.
|
|
61
|
+
syncEchoedPlaceholder(shadowDoc, op.path, container);
|
|
59
62
|
continue;
|
|
60
63
|
}
|
|
61
64
|
applyOpToDom(shadowDoc, op, container, ctx);
|
|
62
65
|
}
|
|
63
66
|
}
|
|
64
67
|
|
|
68
|
+
/**
|
|
69
|
+
* Re-derive the empty-placeholder class of an ECHOED node without writing its content.
|
|
70
|
+
*
|
|
71
|
+
* An echo is the author's own typing coming back as a patch, so the DOM already holds the text —
|
|
72
|
+
* but `empty-text-placeholder` was stamped when the block was empty and nothing else would ever
|
|
73
|
+
* take it off, because the write that would have (patchText) is exactly what echo suppression
|
|
74
|
+
* skips. The CSS asks the DOM whether the block is empty, so nothing is misdrawn in the meantime;
|
|
75
|
+
* this keeps the class itself honest, so the DOM does not carry a claim the document contradicts.
|
|
76
|
+
*
|
|
77
|
+
* Best-effort by design: a missing element is not an error worth escalating a full render (and the
|
|
78
|
+
* caret with it) over a class.
|
|
79
|
+
*/
|
|
80
|
+
function syncEchoedPlaceholder(
|
|
81
|
+
doc: JxMutableNode,
|
|
82
|
+
path: (string | number)[],
|
|
83
|
+
container: HTMLElement,
|
|
84
|
+
): void {
|
|
85
|
+
const el = findElement(container, path);
|
|
86
|
+
if (el) {
|
|
87
|
+
syncPlaceholderClass(el, getNodeAtPath(doc, path));
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
65
91
|
/** The render context, asserted present for ops that re-render a subtree (the caller always has it). */
|
|
66
92
|
function requireCtx(ctx: IframeRenderCtx | undefined): IframeRenderCtx {
|
|
67
93
|
if (!ctx) {
|
|
@@ -134,7 +160,7 @@ function insertChild(
|
|
|
134
160
|
// Remap existing siblings BEFORE attaching the new subtree so it isn't itself remapped.
|
|
135
161
|
remapChildPaths(container, parentPath, index, 1);
|
|
136
162
|
insertAt(parentEl, newEl, domChildReference(parentEl, parentNode, index));
|
|
137
|
-
|
|
163
|
+
syncPlaceholderClass(parentEl, parentNode);
|
|
138
164
|
}
|
|
139
165
|
|
|
140
166
|
/** Re-render the node at `docPath` and swap it into the DOM in place (disposing the old subtree). */
|
|
@@ -165,7 +191,7 @@ function removeChild(
|
|
|
165
191
|
el.remove();
|
|
166
192
|
remapChildPaths(container, parentPath, index + 1, -1);
|
|
167
193
|
if (parentEl) {
|
|
168
|
-
|
|
194
|
+
syncPlaceholderClass(parentEl, getNodeAtPath(doc, parentPath));
|
|
169
195
|
}
|
|
170
196
|
}
|
|
171
197
|
|
|
@@ -202,25 +228,25 @@ function moveChild(
|
|
|
202
228
|
// Emptiness may have changed at either end (e.g. moved the last/only child out, or into an empty
|
|
203
229
|
// Container) — keep both parents' placeholder classes in sync with the folded doc.
|
|
204
230
|
if (fromParentEl) {
|
|
205
|
-
|
|
231
|
+
syncPlaceholderClass(
|
|
206
232
|
fromParentEl,
|
|
207
233
|
getNodeAtPath(doc, elementPath(fromParentEl) ?? fromParentPath),
|
|
208
234
|
);
|
|
209
235
|
}
|
|
210
|
-
|
|
236
|
+
syncPlaceholderClass(toParentEl, toParentNode);
|
|
211
237
|
}
|
|
212
238
|
|
|
213
239
|
/**
|
|
214
|
-
*
|
|
215
|
-
* change.
|
|
240
|
+
* Put `el`'s empty-placeholder class back in step with its (folded) doc node — a container's after
|
|
241
|
+
* a child change, a text block's after its content changed.
|
|
216
242
|
*/
|
|
217
|
-
function
|
|
243
|
+
function syncPlaceholderClass(el: Element, node: JxMutableNode | undefined): void {
|
|
218
244
|
for (const cls of EMPTY_PLACEHOLDER_CLASSES) {
|
|
219
|
-
|
|
245
|
+
el.classList.remove(cls);
|
|
220
246
|
}
|
|
221
|
-
const placeholder =
|
|
247
|
+
const placeholder = node ? computeEmptyPlaceholderClass(node) : null;
|
|
222
248
|
if (placeholder) {
|
|
223
|
-
|
|
249
|
+
el.classList.add(placeholder);
|
|
224
250
|
}
|
|
225
251
|
}
|
|
226
252
|
|
|
@@ -321,24 +347,24 @@ function patchText(doc: JxMutableNode, path: (string | number)[], container: HTM
|
|
|
321
347
|
const el = requireElement(container, path);
|
|
322
348
|
const node = getNodeAtPath(doc, path);
|
|
323
349
|
el.textContent = textDisplayValue(node);
|
|
324
|
-
|
|
325
|
-
el.classList.remove(cls);
|
|
326
|
-
}
|
|
327
|
-
const placeholder = node ? computeEmptyPlaceholderClass(node) : null;
|
|
328
|
-
if (placeholder) {
|
|
329
|
-
el.classList.add(placeholder);
|
|
330
|
-
}
|
|
350
|
+
syncPlaceholderClass(el, node);
|
|
331
351
|
}
|
|
332
352
|
|
|
333
353
|
/** Locate the rendered element for a document path via its stamped `data-jx-path` attribute. */
|
|
334
354
|
function requireElement(container: HTMLElement, path: (string | number)[]): HTMLElement {
|
|
355
|
+
const el = findElement(container, path);
|
|
356
|
+
if (!el) {
|
|
357
|
+
throw new Error(`iframe-patch-element-not-found:${serializeJxPath(path)}`);
|
|
358
|
+
}
|
|
359
|
+
return el;
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
/** The rendered element for a document path, or null when this render has none. */
|
|
363
|
+
function findElement(container: HTMLElement, path: (string | number)[]): HTMLElement | null {
|
|
335
364
|
const serialized = serializeJxPath(path);
|
|
336
365
|
const esc = serialized.replaceAll("\\", String.raw`\\`).replaceAll("'", String.raw`\'`);
|
|
337
366
|
const el = container.querySelector(`[data-jx-path='${esc}']`);
|
|
338
|
-
|
|
339
|
-
throw new Error(`iframe-patch-element-not-found:${serialized}`);
|
|
340
|
-
}
|
|
341
|
-
return el;
|
|
367
|
+
return el instanceof HTMLElement ? el : null;
|
|
342
368
|
}
|
|
343
369
|
|
|
344
370
|
/**
|
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
* the cross-frame analog of the legacy renderer's `renderGeneration` staleness guard.
|
|
10
10
|
*/
|
|
11
11
|
|
|
12
|
+
import type { AssetContext } from "./asset-resolve";
|
|
12
13
|
import type { JxDocOp } from "../tabs/patch-ops";
|
|
13
14
|
import type { JxContentResult, SlashCommand } from "../editor/inline-edit";
|
|
14
15
|
import type { JxExpressionNode, JxMutableNode } from "@jxsuite/schema/types";
|
|
@@ -88,6 +89,14 @@ export type ParentToIframe =
|
|
|
88
89
|
*/
|
|
89
90
|
allowAutoRequests?: boolean;
|
|
90
91
|
docBase: string;
|
|
92
|
+
/**
|
|
93
|
+
* How this host addresses project media, or null/absent when the canvas origin serves the
|
|
94
|
+
* site's own URL space (desktop, `jx dev`) and no reference needs resolving.
|
|
95
|
+
*
|
|
96
|
+
* Plain data because the resolver is a FUNCTION and functions do not cross a realm: the
|
|
97
|
+
* iframe rebuilds the resolver from this and installs it on the runtime.
|
|
98
|
+
*/
|
|
99
|
+
assets?: AssetContext | null;
|
|
91
100
|
mapperCtx: WireMapperCtx;
|
|
92
101
|
siteStyle: Record<string, unknown> | null;
|
|
93
102
|
// Forced color-scheme preview (spec §9.5): "light"/"dark" sets data-color-scheme on the
|
|
@@ -13,19 +13,22 @@
|
|
|
13
13
|
|
|
14
14
|
import {
|
|
15
15
|
buildScope,
|
|
16
|
+
canvasStyleValue,
|
|
16
17
|
defineElement,
|
|
17
18
|
renderNode,
|
|
18
19
|
runScoped,
|
|
20
|
+
setCanvasAssetResolver,
|
|
19
21
|
setCanvasDelinkAnchors,
|
|
20
22
|
setCanvasViewportTranspose,
|
|
21
23
|
setRootMedia,
|
|
22
24
|
setSkipAutoRequests,
|
|
23
25
|
setSkipServerFunctions,
|
|
24
26
|
setStampPropBindings,
|
|
25
|
-
transposeCanvasUnits,
|
|
26
27
|
} from "@jxsuite/runtime";
|
|
28
|
+
import { resolveAssetRef } from "./asset-resolve";
|
|
27
29
|
import { classifyRenderNode, serializeJxPath } from "./path-mapping";
|
|
28
|
-
import {
|
|
30
|
+
import type { AssetContext } from "./asset-resolve";
|
|
31
|
+
import { SITE_STYLE_ID, buildSiteStyleCSS } from "@jxsuite/site/site-style";
|
|
29
32
|
import type { CanvasMode } from "./iframe-protocol";
|
|
30
33
|
import type { JxDocument } from "@jxsuite/schema/types";
|
|
31
34
|
import type { PathMapCtx } from "./path-mapping";
|
|
@@ -102,9 +105,25 @@ export const EDIT_PLACEHOLDER_STYLE_ID = "jx-canvas-edit-css";
|
|
|
102
105
|
* iframe-safe fallbacks — the parent theme variables (--fg-dim/--radius/--accent) don't exist in
|
|
103
106
|
* the iframe document. The placeholder CLASSES are stamped by prepareForEditMode (parent-side
|
|
104
107
|
* resolution + surgical subtree renders), so preview mode never matches these rules — but the sheet
|
|
105
|
-
* is still removed there (belt and braces). The `[
|
|
106
|
-
*
|
|
107
|
-
*
|
|
108
|
+
* is still removed there (belt and braces). The `[data-jx-active-block]` hint marks the caret's
|
|
109
|
+
* empty paragraph (e.g. right after an Enter split) and advertises the slash menu.
|
|
110
|
+
*
|
|
111
|
+
* `.empty-text-placeholder` is gated on emptiness IN THE DOM because a CLASS cannot tell the truth
|
|
112
|
+
* about a block the author is typing in. The class is stamped from the document, and the first
|
|
113
|
+
* thing typed into an empty block reaches the DOM natively — the model learns of it a commit tick
|
|
114
|
+
* later, and that patch comes back as an ECHO, which `applyIframePatch` deliberately does not
|
|
115
|
+
* re-render (it would destroy the caret). So the class outlived its own meaning, and "Click here to
|
|
116
|
+
* add text..." sat beside the text you had just typed until the next full render. The class now
|
|
117
|
+
* selects WHICH affordance; the DOM answers whether the block is empty.
|
|
118
|
+
*
|
|
119
|
+
* "Empty" is `:empty` OR a lone `<br>`, because emptying a block by editing does not leave it
|
|
120
|
+
* `:empty`: the engine drops in a filler `<br>` to keep the line visible (Chrome 151, verified —
|
|
121
|
+
* `execCommand("delete")` over a `<p>`'s whole content leaves `<p><br></p>`). Without that arm the
|
|
122
|
+
* affordance would go dark exactly when the block became empty again.
|
|
123
|
+
*
|
|
124
|
+
* `:not([data-jx-active-block])` keeps the slash hint winning on an empty block that HAS the caret:
|
|
125
|
+
* both rules match the same element, and the emptiness test broke the specificity tie that used to
|
|
126
|
+
* decide it by source order.
|
|
108
127
|
*/
|
|
109
128
|
export const EDIT_PLACEHOLDER_CSS = `
|
|
110
129
|
.empty-media-placeholder {
|
|
@@ -120,7 +139,7 @@ export const EDIT_PLACEHOLDER_CSS = `
|
|
|
120
139
|
font-size: 0;
|
|
121
140
|
overflow: hidden;
|
|
122
141
|
}
|
|
123
|
-
.empty-text-placeholder:not([contenteditable])::after {
|
|
142
|
+
.empty-text-placeholder:is(:empty, :has(> br:only-child)):not([data-jx-active-block]):not([contenteditable])::after {
|
|
124
143
|
content: "Click here to add text...";
|
|
125
144
|
color: color-mix(in srgb, #808080 40%, transparent);
|
|
126
145
|
font-style: italic;
|
|
@@ -146,7 +165,7 @@ export const EDIT_PLACEHOLDER_CSS = `
|
|
|
146
165
|
pointer-events: none;
|
|
147
166
|
white-space: nowrap;
|
|
148
167
|
}
|
|
149
|
-
[data-jx-active-block]:empty::after {
|
|
168
|
+
[data-jx-active-block]:is(:empty, :has(> br:only-child))::after {
|
|
150
169
|
content: "Type / for commands";
|
|
151
170
|
color: color-mix(in srgb, #808080 40%, transparent);
|
|
152
171
|
font-style: italic;
|
|
@@ -409,7 +428,9 @@ export function applySiteStyle(
|
|
|
409
428
|
existing?.remove();
|
|
410
429
|
return;
|
|
411
430
|
}
|
|
412
|
-
|
|
431
|
+
/* `canvasStyleValue`, not `transposeCanvasUnits`: the site style block is CSS the canvas emits
|
|
432
|
+
itself, so a `url()` in it needs the same asset resolution every other declaration gets. */
|
|
433
|
+
const css = buildSiteStyleCSS(siteStyle, mediaQueries, canvasStyleValue);
|
|
413
434
|
if (existing) {
|
|
414
435
|
existing.textContent = css;
|
|
415
436
|
return;
|
|
@@ -434,7 +455,7 @@ export function applyPreviewColorScheme(doc: Document, scheme: "light" | "dark"
|
|
|
434
455
|
}
|
|
435
456
|
|
|
436
457
|
/** Inject the document's `$head` (link/meta/script) into the iframe's <head>, de-duped by href/src. */
|
|
437
|
-
export function injectHead(doc: JxDocument): void {
|
|
458
|
+
export function injectHead(doc: JxDocument, assets: AssetContext | null = null): void {
|
|
438
459
|
const head = (doc as { $head?: HeadEntry[] }).$head;
|
|
439
460
|
if (!Array.isArray(head)) {
|
|
440
461
|
return;
|
|
@@ -451,14 +472,16 @@ export function injectHead(doc: JxDocument): void {
|
|
|
451
472
|
const attrs = { ...entry.attributes } as Record<string, unknown>;
|
|
452
473
|
for (const key of ["href", "src"]) {
|
|
453
474
|
const val = attrs[key];
|
|
454
|
-
if (
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
475
|
+
if (typeof val !== "string" || val === "") {
|
|
476
|
+
continue;
|
|
477
|
+
}
|
|
478
|
+
if (!val.startsWith("/") && !val.startsWith(".") && !val.startsWith("http")) {
|
|
479
|
+
/* The bare-specifier lane owns `/node_modules/<pkg>` — the HOST's URL space, not the
|
|
480
|
+
project's. A project resolver has nothing to resolve it to, so it never sees it. */
|
|
460
481
|
attrs[key] = `/node_modules/${val}`;
|
|
482
|
+
continue;
|
|
461
483
|
}
|
|
484
|
+
attrs[key] = resolveAssetRef(val, assets) ?? val;
|
|
462
485
|
}
|
|
463
486
|
const sel = `${tag}${attrs.href ? `[href="${String(attrs.href)}"]` : ""}${attrs.src ? `[src="${String(attrs.src)}"]` : ""}`;
|
|
464
487
|
if (sel !== tag && document.head.querySelector(sel)) {
|
|
@@ -620,9 +643,20 @@ export async function renderResolvedDocument(opts: {
|
|
|
620
643
|
mode: CanvasMode;
|
|
621
644
|
mapperCtx: PathMapCtx;
|
|
622
645
|
siteStyle?: Record<string, unknown> | null;
|
|
646
|
+
/**
|
|
647
|
+
* How this host addresses project media, or null when the canvas origin serves the site's own URL
|
|
648
|
+
* space (desktop, `jx dev`) and references need no resolution at all.
|
|
649
|
+
*/
|
|
650
|
+
assets?: AssetContext | null;
|
|
623
651
|
/** This render may fetch automatic `Request` entries even outside preview (Data-panel Refresh). */
|
|
624
652
|
allowAutoRequests?: boolean;
|
|
625
653
|
}): Promise<RenderHandle> {
|
|
654
|
+
/* FIRST, before anything emits CSS or an attribute. The resolver is module-global in the runtime,
|
|
655
|
+
so it is set on every render — including to null — or a previous document's context would
|
|
656
|
+
resolve this one's references. It cannot cross the realm as a function, which is why the parent
|
|
657
|
+
posts the plain-data context and the resolver is closed over it here. */
|
|
658
|
+
const assets = opts.assets ?? null;
|
|
659
|
+
setCanvasAssetResolver(assets ? (value) => resolveAssetRef(value, assets) : null);
|
|
626
660
|
setSkipServerFunctions(opts.mode !== "preview");
|
|
627
661
|
// Same gate for automatic `$prototype: "Request"` state entries. `buildScope` re-resolves every
|
|
628
662
|
// State entry on each full render, so without this an escalating authoring action (a signals-panel
|
|
@@ -644,7 +678,7 @@ export async function renderResolvedDocument(opts: {
|
|
|
644
678
|
// Component internals get stamped).
|
|
645
679
|
setStampPropBindings(opts.mode === "design" || opts.mode === "edit");
|
|
646
680
|
applySiteStyle(opts.siteStyle, (opts.doc as { $media?: Record<string, string> }).$media ?? {});
|
|
647
|
-
injectHead(opts.doc);
|
|
681
|
+
injectHead(opts.doc, assets);
|
|
648
682
|
syncEditModeCss(opts.container.ownerDocument, opts.mode);
|
|
649
683
|
syncPreviewShell(opts.container.ownerDocument, opts.mode);
|
|
650
684
|
syncStylebookCss(opts.container.ownerDocument, opts.mode);
|
|
@@ -61,6 +61,7 @@ import { i18nCommands } from "../i18n/i18n-commands";
|
|
|
61
61
|
import { newProjectCommands } from "../new-project/new-project-modal";
|
|
62
62
|
import { canvasCommands } from "../editor/shortcuts";
|
|
63
63
|
import { formatCommands, registerSelectionCommands } from "../panels/block-action-bar";
|
|
64
|
+
import { fileFormatCommands } from "../format/convert-file";
|
|
64
65
|
import { registerTabCommands } from "../workspace/workspace";
|
|
65
66
|
import { derivationCommands, noopDerivationDeps } from "../workspace/pane-derive";
|
|
66
67
|
import type { AnyCommand } from "./registry";
|
|
@@ -154,6 +155,9 @@ export function appCommandSet(): AnyCommand[] {
|
|
|
154
155
|
// That renders them, and projected here so the palette, the level check and the generated
|
|
155
156
|
// Keyboard sheet all see the first `caret`-scoped chords the app has ever had.
|
|
156
157
|
...formatCommands(),
|
|
158
|
+
// The file-format verb: `file.convertFormat`, declared for `context/file` so the Files tree
|
|
159
|
+
// Renders it off the record rather than hand-building a row beside the declared ones.
|
|
160
|
+
...fileFormatCommands(),
|
|
157
161
|
/* The canvas keyboard's own nine, plus the clipboard pair they spread.
|
|
158
162
|
These were the last records the app registered and this projection did not contain, so ⌘C,
|
|
159
163
|
⌘X, ⌘V, Enter, the three structural arrows and all three zoom chords were absent from
|
|
@@ -216,6 +216,35 @@ export function enumProperty(declared: readonly string[], description: string):
|
|
|
216
216
|
return { description, enum: [...declared], type: "string" };
|
|
217
217
|
}
|
|
218
218
|
|
|
219
|
+
/**
|
|
220
|
+
* An enum property whose values are derived WHEN THE SCHEMA IS READ, not when the record is
|
|
221
|
+
* defined.
|
|
222
|
+
*
|
|
223
|
+
* `enumProperty(collections(), …)` looks identical and is wrong for anything a PROJECT supplies,
|
|
224
|
+
* and the difference is invisible in a unit test: a command record is built at module scope —
|
|
225
|
+
* `commands/app-commands.ts` and `studio.ts` both build theirs before a project is open — so the
|
|
226
|
+
* array freezes at `[]`, and `panels/quick-search.ts`'s `paletteArgs` offers an empty choice list
|
|
227
|
+
* forever after. `run` re-derives the list, which is why the programmatic call works and the
|
|
228
|
+
* palette does not: two answers to "what is there", one of them a snapshot of the empty boot
|
|
229
|
+
* state.
|
|
230
|
+
*
|
|
231
|
+
* A getter is the whole fix — the palette reads `property.enum` when it opens the prompt, the AI
|
|
232
|
+
* tool's parameters serialise it when the tool list is built, and `scripts/check-shot-contract.ts`
|
|
233
|
+
* reads it in a bare Bun process where the honest answer really is "none declared".
|
|
234
|
+
*/
|
|
235
|
+
export function derivedEnumProperty(
|
|
236
|
+
declared: () => readonly string[],
|
|
237
|
+
description: string,
|
|
238
|
+
): object {
|
|
239
|
+
return {
|
|
240
|
+
description,
|
|
241
|
+
get enum() {
|
|
242
|
+
return [...declared()];
|
|
243
|
+
},
|
|
244
|
+
type: "string",
|
|
245
|
+
};
|
|
246
|
+
}
|
|
247
|
+
|
|
219
248
|
/** A number property, optionally bounded — the bounds a control already enforces, written down. */
|
|
220
249
|
export function numberProperty(
|
|
221
250
|
description: string,
|
package/src/commands/context.ts
CHANGED
|
@@ -55,6 +55,7 @@ export type EditorKind =
|
|
|
55
55
|
| "library"
|
|
56
56
|
| "config"
|
|
57
57
|
| "entry"
|
|
58
|
+
| "media"
|
|
58
59
|
| "none";
|
|
59
60
|
|
|
60
61
|
/** The Canvas view axis — one control, three values (§4.2). */
|
|
@@ -87,6 +88,10 @@ const EDITOR_KIND_BY_MODE: Readonly<Record<string, EditorKind>> = {
|
|
|
87
88
|
"git-diff": "diff",
|
|
88
89
|
grid: "grid",
|
|
89
90
|
manage: "library",
|
|
91
|
+
/* The Media viewer — `media/media-pane.ts`. An image, a video, a font specimen: files the studio
|
|
92
|
+
can SHOW but has no document model for, which is why clicking one used to end in "No format
|
|
93
|
+
class imported for …", advice that means nothing about a PNG. */
|
|
94
|
+
media: "media",
|
|
90
95
|
preview: "canvas",
|
|
91
96
|
/* Project Settings — `settings/settings-document.ts`'s SETTINGS_MODE. A form over `project.json`,
|
|
92
97
|
which is a configuration object and not a document tree; `stylebook` (Project Styles) is the
|
|
@@ -117,6 +122,7 @@ export const EDITOR_KIND_LABELS: Readonly<Record<EditorKind, string>> = {
|
|
|
117
122
|
diff: "Diff",
|
|
118
123
|
entry: "Entry",
|
|
119
124
|
grid: "Grid",
|
|
125
|
+
media: "Media",
|
|
120
126
|
library: "Library",
|
|
121
127
|
none: "None",
|
|
122
128
|
};
|
|
@@ -232,6 +238,14 @@ export interface CommandContext {
|
|
|
232
238
|
ai: {
|
|
233
239
|
configured: boolean;
|
|
234
240
|
streaming: boolean;
|
|
241
|
+
/**
|
|
242
|
+
* A turn is suspended on an `ask_user` question.
|
|
243
|
+
*
|
|
244
|
+
* Distinct from `streaming` rather than folded into it: no tokens are moving, so a surface that
|
|
245
|
+
* paints a spinner on `streaming` must not paint one here — but the turn is alive and holding a
|
|
246
|
+
* tool open, so Stop is enabled on the union of the two.
|
|
247
|
+
*/
|
|
248
|
+
waiting: boolean;
|
|
235
249
|
};
|
|
236
250
|
capability: Record<Capability, boolean>;
|
|
237
251
|
}
|
|
@@ -287,7 +301,7 @@ export function emptyContext(): CommandContext {
|
|
|
287
301
|
focus: { region: "pane" },
|
|
288
302
|
modal: { open: false },
|
|
289
303
|
collab: { attached: false, readOnly: false, sourceCanonical: false },
|
|
290
|
-
ai: { configured: false, streaming: false },
|
|
304
|
+
ai: { configured: false, streaming: false, waiting: false },
|
|
291
305
|
capability: {
|
|
292
306
|
gitClone: false,
|
|
293
307
|
importSite: false,
|
package/src/commands/defaults.ts
CHANGED
|
@@ -102,6 +102,7 @@ export interface CommandDeps {
|
|
|
102
102
|
undo: () => void;
|
|
103
103
|
redo: () => void;
|
|
104
104
|
openInBrowser: () => void | Promise<void>;
|
|
105
|
+
buildSite: () => void | Promise<void>;
|
|
105
106
|
closeDocument: () => void;
|
|
106
107
|
duplicateSelection: () => void;
|
|
107
108
|
deleteSelection: () => void;
|
|
@@ -147,6 +148,7 @@ export function noopCommandDeps(): CommandDeps {
|
|
|
147
148
|
undo: () => {},
|
|
148
149
|
redo: () => {},
|
|
149
150
|
openInBrowser: () => {},
|
|
151
|
+
buildSite: () => {},
|
|
150
152
|
closeDocument: () => {},
|
|
151
153
|
duplicateSelection: () => {},
|
|
152
154
|
deleteSelection: () => {},
|
|
@@ -243,9 +245,35 @@ export function defaultCommands(deps: CommandDeps): AnyCommand[] {
|
|
|
243
245
|
group: "2_output",
|
|
244
246
|
when: (ctx) => ctx.project.isSite,
|
|
245
247
|
enablement: documentOpen,
|
|
246
|
-
|
|
248
|
+
/* "A page to preview", not "a built page to open". Nothing is built on the way here any
|
|
249
|
+
more: the working tree is rendered at its real route, so what this waits for is a document
|
|
250
|
+
with a route rather than an output file. */
|
|
251
|
+
requires: "a page to preview",
|
|
247
252
|
run: () => deps.openInBrowser(),
|
|
248
253
|
},
|
|
254
|
+
{
|
|
255
|
+
/*
|
|
256
|
+
* The compiler, kept reachable under its own verb.
|
|
257
|
+
*
|
|
258
|
+
* `View: Open in Browser` used to run a full build, which answered "does my site build?" as a
|
|
259
|
+
* side effect of asking "what does this page look like?". Those are different questions and
|
|
260
|
+
* only one of them is worth waiting for a bundler, an image pipeline and every emitter. So
|
|
261
|
+
* the build keeps a command of its own rather than disappearing with the coupling.
|
|
262
|
+
*
|
|
263
|
+
* `commandbar/overflow` rather than `commandbar/primary`: the primary row is budgeted at five
|
|
264
|
+
* and is document-level by frequency (studio-ui-guidelines §12), and a build is neither
|
|
265
|
+
* frequent nor about the document in front of you. No default chord for the same reason.
|
|
266
|
+
*/
|
|
267
|
+
id: "project.buildSite",
|
|
268
|
+
title: "Build Site",
|
|
269
|
+
category: "Project",
|
|
270
|
+
level: "project",
|
|
271
|
+
menus: ["commandbar/overflow", "palette"],
|
|
272
|
+
group: "2_output",
|
|
273
|
+
when: (ctx) => ctx.project.isSite,
|
|
274
|
+
requires: "a site project",
|
|
275
|
+
run: () => deps.buildSite(),
|
|
276
|
+
},
|
|
249
277
|
|
|
250
278
|
// ── Edit ──
|
|
251
279
|
{
|
package/src/commands/levels.ts
CHANGED
|
@@ -77,6 +77,14 @@ export const PLACEMENTS = [
|
|
|
77
77
|
*/
|
|
78
78
|
"blockbar/format",
|
|
79
79
|
"outline/row",
|
|
80
|
+
/**
|
|
81
|
+
* The rail foot's ⚙ Settings menu — the settings FAMILY, and only it.
|
|
82
|
+
*
|
|
83
|
+
* A separate placement rather than a second use of `commandbar/overflow`, for the reason the
|
|
84
|
+
* status bar is three placements: the ⬢ menu is the chrome's residue for retired controls and
|
|
85
|
+
* this is a named family with its own trigger. Same admits, different surface, different budget.
|
|
86
|
+
*/
|
|
87
|
+
"settings/menu",
|
|
80
88
|
"palette",
|
|
81
89
|
"never",
|
|
82
90
|
] as const;
|
|
@@ -119,6 +127,14 @@ export const PLACEMENT_MATRIX: Readonly<Record<Placement, PlacementRule>> = {
|
|
|
119
127
|
note: "the bar's inline-format cluster — a range inside the selection is still the selection",
|
|
120
128
|
},
|
|
121
129
|
"outline/row": { admits: ["selection"], note: "row actions act on the row's node" },
|
|
130
|
+
"settings/menu": {
|
|
131
|
+
admits: ["application", "project"],
|
|
132
|
+
note:
|
|
133
|
+
"the rail foot's gear menu — the settings family. It is a MENU, not a pinned slot, so it " +
|
|
134
|
+
"may host two levels for the reason commandbar/overflow does: a menu prints each row's own " +
|
|
135
|
+
"name, chord and gate, and draws a divider where the level changes. The rail's PINNED " +
|
|
136
|
+
"groups stay single-level, in PANEL_PLACEMENT_MATRIX",
|
|
137
|
+
},
|
|
122
138
|
palette: {
|
|
123
139
|
admits: ["application", "project", "document", "selection"],
|
|
124
140
|
note: "the level-agnostic surface; it groups rows by level",
|