@jxsuite/studio 10.1.0 → 10.3.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/dist/chunks/{2020-mkb0qa6p.js → 2020-906vx2dq.js} +3 -3
- package/dist/chunks/adapter-hnvad61x.js +4 -0
- package/dist/chunks/add-new-flow-3tkn72ey.js +4 -0
- package/dist/chunks/cf-account-picker-xb8vvt2h.js +4 -0
- package/dist/chunks/cf-settings-mkbcz4z2.js +4 -0
- package/dist/chunks/components-w5zqwva1.js +4 -0
- package/dist/chunks/content-source-1vzxq1ba.js +4 -0
- package/dist/chunks/convert-to-repeater-gwzh9gar.js +4 -0
- package/dist/chunks/editor-wvf5e6j9.js +4 -0
- package/dist/chunks/entry-commands-mx2whrjx.js +4 -0
- package/dist/chunks/extension-sections-338ee35t.js +4 -0
- package/dist/chunks/file-ops-9bxvk59j.js +4 -0
- package/dist/chunks/files-t07yqqej.js +4 -0
- package/dist/chunks/format-host-5ja5gxyk.js +4 -0
- package/dist/chunks/fs-events-3424ezp3.js +4 -0
- package/dist/chunks/grid-open-1mpq9mzb.js +4 -0
- package/dist/chunks/{grid-view-r22jsqht.js → grid-view-w9nfy203.js} +15 -15
- package/dist/chunks/icons-gb967qc8.js +4 -0
- package/dist/chunks/iframe-host-wsg0bn73.js +4 -0
- package/dist/chunks/image-bake-4afr4y6a.js +4 -0
- package/dist/chunks/image-editor-x7t8jbjn.js +4 -0
- package/dist/chunks/index-f9qr7kzh.js +4 -0
- package/dist/chunks/{index-pxwwf00y.js → index-yjrfeks8.js} +3 -3
- package/dist/chunks/{javascript-pzqryby6.js → javascript-hy2mws8v.js} +3 -3
- package/dist/chunks/{jsonMode-tcnyxyap.js → jsonMode-vj30te2h.js} +6 -6
- package/dist/chunks/jx-validate-ttr5ewf0.js +4 -0
- package/dist/chunks/layers-m49nmwvx.js +4 -0
- package/dist/chunks/layers-panel-hvy2e549.js +4 -0
- package/dist/chunks/lit-html-k2mb9k0r.js +4 -0
- package/dist/chunks/media-library-ae67q4at.js +4 -0
- package/dist/chunks/media-open-yp9jm834.js +4 -0
- package/dist/chunks/media-picker-s41ngdfr.js +4 -0
- package/dist/chunks/{monaco-binding-bqpge64f.js → monaco-binding-a51py33c.js} +3 -3
- package/dist/chunks/monaco-buffer-pa0g6ax7.js +4 -0
- package/dist/chunks/{monaco-setup-1mkab762.js → monaco-setup-g9kterhx.js} +3 -3
- package/dist/chunks/notify-bwehrp7q.js +4 -0
- package/dist/chunks/parse-hee7ne9b.js +4 -0
- package/dist/chunks/platform-kv0k65cy.js +4 -0
- package/dist/chunks/publish-panel-gzfxcm0t.js +4 -0
- package/dist/chunks/publish-quick-w3nxd7q4.js +4 -0
- package/dist/chunks/publish-workspace-dppd82tc.js +4 -0
- package/dist/chunks/quick-search-tdxaam55.js +4 -0
- package/dist/chunks/references-vqyr5s9x.js +4 -0
- package/dist/chunks/right-panel-0yrw4cgz.js +4 -0
- package/dist/chunks/source-control-6fk72me2.js +4 -0
- package/dist/chunks/{studio-0g2sm01j.js → studio-0939t1q1.js} +4 -4
- package/dist/chunks/studio-0gvvqj3h.js +5 -0
- package/dist/chunks/studio-0khd8xtx.js +4 -0
- package/dist/chunks/{studio-rf8d6p9f.js → studio-0w3541dg.js} +6 -6
- package/dist/chunks/{studio-9b7177jq.js → studio-0x6f3nk9.js} +4 -4
- package/dist/chunks/studio-1410q5dw.js +5 -0
- package/dist/chunks/{studio-8qejv8yf.js → studio-15hwqgd8.js} +4 -4
- package/dist/chunks/studio-1ks5d9y4.js +5 -0
- package/dist/chunks/{studio-881z1xrx.js → studio-2ggfzrrd.js} +4 -4
- package/dist/chunks/{studio-jw0q5j6k.js → studio-321ym1tt.js} +4 -4
- package/dist/chunks/studio-3c10v2q1.js +5 -0
- package/dist/chunks/{studio-9rcbvksg.js → studio-3na1r48j.js} +8 -8
- package/dist/chunks/{studio-ggm7zf70.js → studio-3q7mhbgx.js} +5 -5
- package/dist/chunks/studio-3qr08sxq.js +4 -0
- package/dist/chunks/studio-3xk9zdgb.js +5 -0
- package/dist/chunks/studio-423tfdte.js +5 -0
- package/dist/chunks/{studio-brdp85q7.js → studio-45mj4jqe.js} +4 -4
- package/dist/chunks/studio-4f7thq0p.js +5 -0
- package/dist/chunks/{studio-w67vfgy5.js → studio-4fnb5648.js} +4 -4
- package/dist/chunks/studio-4zd6an2b.js +5 -0
- package/dist/chunks/studio-53wy9ek3.js +5 -0
- package/dist/chunks/studio-5n193zqd.js +5 -0
- package/dist/chunks/{studio-x2metr13.js → studio-5tet77de.js} +3 -3
- package/dist/chunks/studio-7rrdbvex.js +5 -0
- package/dist/chunks/studio-8kx6m0rg.js +5 -0
- package/dist/chunks/{studio-va0e63sk.js → studio-90zkgne5.js} +7 -7
- package/dist/chunks/studio-9c6cbq2c.js +4 -0
- package/dist/chunks/studio-9dq7rwc7.js +5 -0
- package/dist/chunks/studio-9qx1ywbv.js +9 -0
- package/dist/chunks/studio-a65kbs2e.js +9 -0
- package/dist/chunks/studio-aam8zt8z.js +5 -0
- package/dist/chunks/{studio-8jb31dcf.js → studio-abh9kfdh.js} +4 -4
- package/dist/chunks/studio-b4e0ftkt.js +5 -0
- package/dist/chunks/studio-b68k0z00.js +5 -0
- package/dist/chunks/{studio-dy2xtt7a.js → studio-bct64zmj.js} +4 -4
- package/dist/chunks/studio-bz0b5brj.js +6 -0
- package/dist/chunks/studio-cfnn70h1.js +5 -0
- package/dist/chunks/studio-daksw8kh.js +5 -0
- package/dist/chunks/{studio-mwnmzx7g.js → studio-e7vjyp04.js} +7 -7
- package/dist/chunks/studio-ea6r1zyr.js +5 -0
- package/dist/chunks/studio-egnjpyde.js +9 -0
- package/dist/chunks/studio-ejrypvmq.js +5 -0
- package/dist/chunks/studio-eqcddvc2.js +928 -0
- package/dist/chunks/studio-f8tyeyzt.js +5 -0
- package/dist/chunks/studio-fj6c905s.js +5 -0
- package/dist/chunks/{studio-hsr3k06v.js → studio-fkrr7kgg.js} +5 -5
- package/dist/chunks/studio-fspe7m9t.js +5 -0
- package/dist/chunks/{studio-4zqfw5ha.js → studio-g0hxtcga.js} +4 -4
- package/dist/chunks/studio-g9brbewz.js +5 -0
- package/dist/chunks/studio-ggx3j7nd.js +5 -0
- package/dist/chunks/{studio-jh6t5qz4.js → studio-hfxttkyr.js} +4 -4
- package/dist/chunks/studio-hh394phe.js +5 -0
- package/dist/chunks/{studio-07k0geap.js → studio-j0zf5x2x.js} +4 -4
- package/dist/chunks/studio-jeb7hth3.js +5 -0
- package/dist/chunks/studio-k0xzrse2.js +64 -0
- package/dist/chunks/studio-k1xqrgm5.js +5 -0
- package/dist/chunks/studio-k2evngrj.js +7 -0
- package/dist/chunks/studio-k3we3kqb.js +5 -0
- package/dist/chunks/{studio-nf0pqpbj.js → studio-n9qba0gx.js} +4 -4
- package/dist/chunks/studio-nf8esxxc.js +5 -0
- package/dist/chunks/studio-nn4f9m9c.js +5 -0
- package/dist/chunks/studio-nskxe9q1.js +6 -0
- package/dist/chunks/{studio-4nkm4404.js → studio-nvcn2ykc.js} +4 -4
- package/dist/chunks/studio-nwynxqtb.js +5 -0
- package/dist/chunks/{studio-f2j9et3h.js → studio-nzemfbkk.js} +8 -8
- package/dist/chunks/studio-p2a03pgm.js +5 -0
- package/dist/chunks/{studio-skynbac3.js → studio-pm17wdc4.js} +4 -4
- package/dist/chunks/{studio-9cp96v90.js → studio-q80fg1d6.js} +7 -7
- package/dist/chunks/studio-r050sez0.js +6 -0
- package/dist/chunks/{studio-sd07xva5.js → studio-s7t4vp1d.js} +5 -5
- package/dist/chunks/studio-s9fng7m8.js +7 -0
- package/dist/chunks/studio-sce30r7b.js +4 -0
- package/dist/chunks/{studio-zxm98z2m.js → studio-stahswgb.js} +6 -6
- package/dist/chunks/studio-swedpd82.js +5 -0
- package/dist/chunks/studio-thz5t120.js +5 -0
- package/dist/chunks/studio-tpxcryf2.js +5 -0
- package/dist/chunks/{studio-tpy3rdxn.js → studio-v5wktj6f.js} +4 -4
- package/dist/chunks/{studio-7vb997ff.js → studio-vpevkfxw.js} +4 -4
- package/dist/chunks/studio-waekn290.js +5 -0
- package/dist/chunks/studio-wfct4h6n.js +5 -0
- package/dist/chunks/studio-wgc54sxf.js +5 -0
- package/dist/chunks/studio-wqfaqw4m.js +5 -0
- package/dist/chunks/studio-wse1rs8t.js +7 -0
- package/dist/chunks/studio-x0gsfr1d.js +16 -0
- package/dist/chunks/studio-xpx91fby.js +6 -0
- package/dist/chunks/studio-y1b86ast.js +5 -0
- package/dist/chunks/studio-yrcrncev.js +5 -0
- package/dist/chunks/{studio-zacvfm31.js → studio-yzpskb0j.js} +4 -4
- package/dist/chunks/studio-zfdbmhp3.js +5 -0
- package/dist/chunks/{tsMode-6vwhy6ty.js → tsMode-jg1dtphj.js} +7 -7
- package/dist/chunks/workspace-9btkc8tk.js +4 -0
- package/dist/chunks/workspace-home-qcc4z5ag.js +4 -0
- package/dist/chunks/{ws-client-zcxkfgmf.js → ws-client-arz2qsy9.js} +3 -3
- package/dist/iframe-entry.js +13 -13
- package/dist/manifest.json +83 -77
- package/dist/studio.js +74 -75
- package/package.json +5 -5
- package/src/browse/library-model.ts +56 -23
- package/src/browse/library-pane.ts +44 -29
- package/src/browse/library-preview.ts +6 -2
- package/src/canvas/asset-refs.ts +13 -4
- package/src/canvas/asset-resolve.ts +54 -11
- package/src/canvas/canvas-render.ts +25 -0
- package/src/canvas/iframe-entry.ts +5 -1
- package/src/canvas/iframe-host.ts +16 -6
- package/src/canvas/iframe-protocol.ts +9 -1
- package/src/commands/context.ts +6 -0
- package/src/commands/live-context.ts +5 -2
- package/src/editor/shortcuts.ts +6 -0
- package/src/files/asset-versions.ts +108 -2
- package/src/files/file-ops.ts +11 -1
- package/src/files/files.ts +111 -48
- package/src/files/media-paths.ts +12 -4
- package/src/files/media-upload.ts +195 -21
- package/src/files/new-file-name.ts +39 -1
- package/src/media/image-bake.ts +164 -0
- package/src/media/image-editor.ts +846 -0
- package/src/media/image-format.ts +262 -0
- package/src/media/image-recipe.ts +651 -0
- package/src/media/media-commands.ts +65 -1
- package/src/media/media-library-model.ts +1 -1
- package/src/media/media-library.ts +175 -15
- package/src/media/media-open.ts +156 -3
- package/src/media/media-pane.ts +8 -4
- package/src/panels/data-grid.ts +0 -4
- package/src/panels/pane-context.ts +6 -1
- package/src/panels/settings-menu.ts +3 -3
- package/src/panels/settings-pane.ts +42 -56
- package/src/panels/tab-strip.ts +12 -3
- package/src/services/ai-import-tools.ts +6 -3
- package/src/services/ai-models.ts +65 -11
- package/src/services/ai-settings.ts +45 -5
- package/src/services/document-assistant.ts +5 -2
- package/src/services/settings/definitions.ts +19 -0
- package/src/settings/contributed-section.ts +16 -7
- package/src/settings/css-vars-editor.ts +4 -4
- package/src/settings/general-settings.ts +1 -4
- package/src/settings/preferences-accounts.ts +2 -2
- package/src/settings/preferences-dialog.ts +4 -2
- package/src/settings/project-sections.ts +1 -1
- package/src/settings/section-registry.ts +5 -4
- package/src/settings/settings-document.ts +33 -24
- package/src/settings/settings-workspace.ts +205 -0
- package/src/shell.ts +31 -6
- package/src/studio.ts +13 -2
- package/src/surfaces/ai-credentials-form.json +59 -5
- package/src/surfaces/ai-credentials-form.ts +15 -2
- package/src/surfaces/commandbar.ts +7 -3
- package/src/surfaces/image-editor.json +1390 -0
- package/src/surfaces/image-editor.ts +272 -0
- package/src/surfaces/media-library.json +129 -1
- package/src/surfaces/media-library.ts +17 -2
- package/src/surfaces/settings-contributed.json +4 -4
- package/src/surfaces/settings-contributed.ts +2 -2
- package/src/surfaces/settings-pane.json +262 -180
- package/src/surfaces/settings-pane.ts +63 -14
- package/src/types.ts +2 -1
- package/src/ui/ai-credentials-form.ts +128 -11
- package/src/ui/regions.ts +5 -5
- package/src/workspace/tab-workspace.ts +5 -3
- package/src/workspace/workspace-home.ts +18 -4
- package/src/workspace/workspace-ids.ts +40 -7
- package/src/workspace/workspace-switch.ts +3 -1
- package/src/workspace/workspace.ts +22 -1
- package/dist/chunks/adapter-xcpsmecx.js +0 -4
- package/dist/chunks/add-new-flow-ptmhmzr9.js +0 -4
- package/dist/chunks/cf-account-picker-z1sgwpwd.js +0 -4
- package/dist/chunks/cf-settings-xqvhxxe9.js +0 -4
- package/dist/chunks/components-whsc81a7.js +0 -4
- package/dist/chunks/content-source-jwtxg78w.js +0 -4
- package/dist/chunks/convert-to-repeater-f92ey3kp.js +0 -4
- package/dist/chunks/editor-zdqdpq1x.js +0 -4
- package/dist/chunks/entry-commands-w8p2fbpt.js +0 -4
- package/dist/chunks/extension-sections-y2p1te2r.js +0 -4
- package/dist/chunks/file-ops-13ab4swj.js +0 -4
- package/dist/chunks/files-zbc8p6v1.js +0 -4
- package/dist/chunks/format-host-zjsbktbx.js +0 -4
- package/dist/chunks/fs-events-t6x0hch6.js +0 -4
- package/dist/chunks/grid-open-fyzdwpkh.js +0 -4
- package/dist/chunks/icons-853n6qt3.js +0 -4
- package/dist/chunks/iframe-host-kbyqc7pt.js +0 -4
- package/dist/chunks/index-6nvj89vb.js +0 -4
- package/dist/chunks/jx-validate-5sp8a4rm.js +0 -4
- package/dist/chunks/layers-panel-qrxj9vr2.js +0 -4
- package/dist/chunks/layers-tpzayggx.js +0 -4
- package/dist/chunks/lit-html-x6nrxsx3.js +0 -4
- package/dist/chunks/media-library-ywppnjrz.js +0 -4
- package/dist/chunks/media-picker-etxgc8qq.js +0 -4
- package/dist/chunks/monaco-buffer-qrnqhk0b.js +0 -4
- package/dist/chunks/notify-kcv38wwe.js +0 -4
- package/dist/chunks/parse-df2kkmhc.js +0 -4
- package/dist/chunks/platform-dtsysmyd.js +0 -4
- package/dist/chunks/publish-panel-evb1rcxt.js +0 -4
- package/dist/chunks/publish-quick-m9vkhahq.js +0 -4
- package/dist/chunks/publish-workspace-ktk9h536.js +0 -4
- package/dist/chunks/quick-search-2f47fxcw.js +0 -4
- package/dist/chunks/references-5w6ajjv7.js +0 -4
- package/dist/chunks/right-panel-kz0a6byf.js +0 -4
- package/dist/chunks/source-control-1phx90zk.js +0 -4
- package/dist/chunks/studio-0hr000ba.js +0 -5
- package/dist/chunks/studio-1wyp7dt5.js +0 -5
- package/dist/chunks/studio-20ap8dds.js +0 -5
- package/dist/chunks/studio-291gqh98.js +0 -5
- package/dist/chunks/studio-37twdaf7.js +0 -64
- package/dist/chunks/studio-4smcnv33.js +0 -5
- package/dist/chunks/studio-56egy6vf.js +0 -5
- package/dist/chunks/studio-5dmgmyw4.js +0 -5
- package/dist/chunks/studio-5e7p1vgd.js +0 -5
- package/dist/chunks/studio-5m0w815d.js +0 -5
- package/dist/chunks/studio-695k95bw.js +0 -5
- package/dist/chunks/studio-6vwvshw8.js +0 -6
- package/dist/chunks/studio-732mw0g8.js +0 -5
- package/dist/chunks/studio-76t560kn.js +0 -6
- package/dist/chunks/studio-7823f0g8.js +0 -5
- package/dist/chunks/studio-8486jn4r.js +0 -5
- package/dist/chunks/studio-848rk4p1.js +0 -5
- package/dist/chunks/studio-8x8616xd.js +0 -5
- package/dist/chunks/studio-a3s6frde.js +0 -4
- package/dist/chunks/studio-ahf3cspq.js +0 -4
- package/dist/chunks/studio-bkmv1h3e.js +0 -9
- package/dist/chunks/studio-cgb97rwv.js +0 -5
- package/dist/chunks/studio-ch8gt8vt.js +0 -5
- package/dist/chunks/studio-cqf1k4qk.js +0 -5
- package/dist/chunks/studio-cvz0e00f.js +0 -5
- package/dist/chunks/studio-dbgv4fv9.js +0 -16
- package/dist/chunks/studio-eesyjg7h.js +0 -9
- package/dist/chunks/studio-et6t8g4m.js +0 -4
- package/dist/chunks/studio-fep7hr66.js +0 -7
- package/dist/chunks/studio-fyf7j57j.js +0 -5
- package/dist/chunks/studio-g5prntab.js +0 -5
- package/dist/chunks/studio-ga77mf07.js +0 -5
- package/dist/chunks/studio-h7gy12h2.js +0 -5
- package/dist/chunks/studio-j933k8nr.js +0 -5
- package/dist/chunks/studio-jgbvqcgh.js +0 -5
- package/dist/chunks/studio-k4xc5g6h.js +0 -5
- package/dist/chunks/studio-p0nf2e1f.js +0 -6
- package/dist/chunks/studio-pd9dsqpq.js +0 -5
- package/dist/chunks/studio-pmzg17fj.js +0 -5
- package/dist/chunks/studio-pzfa2vmb.js +0 -5
- package/dist/chunks/studio-qyb5bebs.js +0 -9
- package/dist/chunks/studio-rt498t2v.js +0 -5
- package/dist/chunks/studio-s0fg2wav.js +0 -5
- package/dist/chunks/studio-t3j8htp7.js +0 -7
- package/dist/chunks/studio-thj81e98.js +0 -7
- package/dist/chunks/studio-tyw7tk0x.js +0 -5
- package/dist/chunks/studio-vcz63n42.js +0 -5
- package/dist/chunks/studio-vffwe9k7.js +0 -5
- package/dist/chunks/studio-vjhej0w5.js +0 -928
- package/dist/chunks/studio-vxjap704.js +0 -5
- package/dist/chunks/studio-xebavr9s.js +0 -5
- package/dist/chunks/studio-yg5n49za.js +0 -5
- package/dist/chunks/studio-yhz5b313.js +0 -5
- package/dist/chunks/studio-zsjpsw08.js +0 -5
- package/dist/chunks/workspace-d65bz1qk.js +0 -4
- package/dist/chunks/workspace-home-vbg9b3jh.js +0 -4
- package/src/settings/settings-dialog.ts +0 -192
- package/src/surfaces/settings-dialog.json +0 -51
- package/src/surfaces/settings-dialog.ts +0 -169
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jxsuite/studio",
|
|
3
|
-
"version": "10.
|
|
3
|
+
"version": "10.3.0",
|
|
4
4
|
"description": "Jx Studio — visual builder for Jx documents",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -60,16 +60,16 @@
|
|
|
60
60
|
"dependencies": {
|
|
61
61
|
"@atlaskit/pragmatic-drag-and-drop": "^4.0.0",
|
|
62
62
|
"@atlaskit/pragmatic-drag-and-drop-hitbox": "^3.0.0",
|
|
63
|
-
"@jxsuite/ai": "^0.
|
|
63
|
+
"@jxsuite/ai": "^0.42.0",
|
|
64
64
|
"@jxsuite/collab": "^0.11.4",
|
|
65
|
-
"@jxsuite/create": "^1.5.
|
|
65
|
+
"@jxsuite/create": "^1.5.2",
|
|
66
66
|
"@jxsuite/formulas": "^0.0.26",
|
|
67
67
|
"@jxsuite/markup": "^1.0.2",
|
|
68
|
-
"@jxsuite/protocol": "^6.
|
|
68
|
+
"@jxsuite/protocol": "^6.2.0",
|
|
69
69
|
"@jxsuite/runtime": "^6.0.1",
|
|
70
70
|
"@jxsuite/schema": "^5.0.0",
|
|
71
71
|
"@jxsuite/site": "^2.2.2",
|
|
72
|
-
"@jxsuite/ui": "^0.
|
|
72
|
+
"@jxsuite/ui": "^0.9.0",
|
|
73
73
|
"@vue/reactivity": "3.5.44",
|
|
74
74
|
"ajv": "^8.20.0",
|
|
75
75
|
"ajv-formats": "^3.0.1",
|
|
@@ -200,44 +200,64 @@ export function entryCollectionOf(path: string, pageExtensions?: readonly string
|
|
|
200
200
|
*/
|
|
201
201
|
const SKIP_DIRS = new Set(["node_modules", ".git", ".jx", ".cache"]);
|
|
202
202
|
|
|
203
|
+
/** What every branch of one scan shares. */
|
|
204
|
+
interface ScanRun {
|
|
205
|
+
platform: Pick<StudioPlatform, "listDirectory">;
|
|
206
|
+
out: LibraryFile[];
|
|
207
|
+
failures: ScanFailure[];
|
|
208
|
+
i18n: ResolvedI18n | null;
|
|
209
|
+
pageExtensions: readonly string[] | undefined;
|
|
210
|
+
/**
|
|
211
|
+
* Every directory some branch has claimed, the roots included. A collection may live in an
|
|
212
|
+
* ANCESTOR of the project (a site inside the vault it publishes declares `source: "../.."`), and
|
|
213
|
+
* that walk reaches the project's own directories under the same project-relative paths the roots
|
|
214
|
+
* walk: every page was listed twice. A directory is walked by whichever branch claims it first.
|
|
215
|
+
*/
|
|
216
|
+
claimed: Set<string>;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
/**
|
|
220
|
+
* The project root as a listing names it from outside: `""` (relative to itself) or `"."`. A walk
|
|
221
|
+
* from an ancestor stops there; the project's own directories are roots of the scan already, and
|
|
222
|
+
* the rest of the root (`dist/`, `scripts/`) holds no collection's entries.
|
|
223
|
+
*/
|
|
224
|
+
function isProjectRoot(path: string): boolean {
|
|
225
|
+
const normalized = normalizeDir(path);
|
|
226
|
+
return normalized === "" || normalized === ".";
|
|
227
|
+
}
|
|
228
|
+
|
|
203
229
|
/**
|
|
204
230
|
* Walk one directory tree, collecting files and recording — never swallowing — what it could not
|
|
205
231
|
* read. A failure at depth is reported against the directory that failed, not the root, so the
|
|
206
232
|
* Problem names the path the author can act on.
|
|
207
233
|
*/
|
|
208
|
-
async function walk(
|
|
209
|
-
dir: string,
|
|
210
|
-
platform: Pick<StudioPlatform, "listDirectory">,
|
|
211
|
-
out: LibraryFile[],
|
|
212
|
-
failures: ScanFailure[],
|
|
213
|
-
i18n: ResolvedI18n | null,
|
|
214
|
-
optional = false,
|
|
215
|
-
pageExtensions?: readonly string[],
|
|
216
|
-
): Promise<void> {
|
|
234
|
+
async function walk(dir: string, run: ScanRun, optional = false): Promise<void> {
|
|
217
235
|
let entries: DirEntry[];
|
|
218
236
|
try {
|
|
219
237
|
// Noted: the Library walks every media directory, so it is where most versions are learned.
|
|
220
|
-
entries = await notedListing(() => platform.listDirectory(dir));
|
|
238
|
+
entries = await notedListing(() => run.platform.listDirectory(dir));
|
|
221
239
|
} catch (error) {
|
|
222
240
|
// A declared collection's folder that does not exist yet is an empty collection, not a fault.
|
|
223
241
|
if (!optional) {
|
|
224
|
-
failures.push({ dir, error: errorMessage(error) });
|
|
242
|
+
run.failures.push({ dir, error: errorMessage(error) });
|
|
225
243
|
}
|
|
226
244
|
return;
|
|
227
245
|
}
|
|
228
246
|
const subdirectories: string[] = [];
|
|
229
247
|
for (const entry of entries) {
|
|
230
248
|
if (entry.type === "directory") {
|
|
231
|
-
|
|
249
|
+
const key = normalizeDir(entry.path);
|
|
250
|
+
if (!SKIP_DIRS.has(entry.name) && !isProjectRoot(key) && !run.claimed.has(key)) {
|
|
251
|
+
run.claimed.add(key);
|
|
232
252
|
subdirectories.push(entry.path);
|
|
233
253
|
}
|
|
234
254
|
continue;
|
|
235
255
|
}
|
|
236
256
|
const ext = extensionOf(entry.name);
|
|
237
|
-
out.push({
|
|
238
|
-
collection: entryCollectionOf(entry.path, pageExtensions),
|
|
257
|
+
run.out.push({
|
|
258
|
+
collection: entryCollectionOf(entry.path, run.pageExtensions),
|
|
239
259
|
ext,
|
|
240
|
-
locale: servedLocaleOfPath(entry.path, i18n) ?? undefined,
|
|
260
|
+
locale: servedLocaleOfPath(entry.path, run.i18n) ?? undefined,
|
|
241
261
|
media: MEDIA_EXTENSIONS.has(ext),
|
|
242
262
|
modified: entry.modified,
|
|
243
263
|
name: entry.name,
|
|
@@ -248,9 +268,7 @@ async function walk(
|
|
|
248
268
|
}
|
|
249
269
|
// Siblings in parallel, depth serialized per branch: the platform counts every call as in-flight,
|
|
250
270
|
// So `probe.idle()` already knows the scan is running without the Library declaring a source.
|
|
251
|
-
await Promise.all(
|
|
252
|
-
subdirectories.map((sub) => walk(sub, platform, out, failures, i18n, false, pageExtensions)),
|
|
253
|
-
);
|
|
271
|
+
await Promise.all(subdirectories.map((sub) => walk(sub, run)));
|
|
254
272
|
}
|
|
255
273
|
|
|
256
274
|
/**
|
|
@@ -265,16 +283,31 @@ export async function scanLibrary(
|
|
|
265
283
|
optional: ReadonlySet<string> = new Set(),
|
|
266
284
|
pageExtensions?: readonly string[],
|
|
267
285
|
): Promise<LibraryScan> {
|
|
268
|
-
const files: LibraryFile[] = [];
|
|
269
|
-
const failures: ScanFailure[] = [];
|
|
270
286
|
// Resolved ONCE for the whole walk. `resolveI18n` canonicalizes every declared tag through
|
|
271
287
|
// `Intl.Locale`, and a project with 300 pages would otherwise pay for that per file.
|
|
272
288
|
const { i18n } = resolveI18n(projectState?.projectConfig ?? {});
|
|
289
|
+
const run: ScanRun = {
|
|
290
|
+
claimed: new Set(),
|
|
291
|
+
failures: [],
|
|
292
|
+
i18n,
|
|
293
|
+
out: [],
|
|
294
|
+
pageExtensions,
|
|
295
|
+
platform,
|
|
296
|
+
};
|
|
297
|
+
/* Every root is claimed before any walk descends: `map` runs to the end synchronously, and a walk
|
|
298
|
+
claims its subdirectories only after its own listing has been awaited. A root named twice is
|
|
299
|
+
walked once. */
|
|
273
300
|
await Promise.all(
|
|
274
|
-
dirs.map((dir) =>
|
|
275
|
-
|
|
276
|
-
|
|
301
|
+
dirs.map((dir) => {
|
|
302
|
+
const key = normalizeDir(dir);
|
|
303
|
+
if (run.claimed.has(key)) {
|
|
304
|
+
return Promise.resolve();
|
|
305
|
+
}
|
|
306
|
+
run.claimed.add(key);
|
|
307
|
+
return walk(dir, run, optional.has(dir));
|
|
308
|
+
}),
|
|
277
309
|
);
|
|
310
|
+
const { failures, out: files } = run;
|
|
278
311
|
files.sort((a, b) => a.path.localeCompare(b.path));
|
|
279
312
|
return { failures, files };
|
|
280
313
|
}
|
|
@@ -44,7 +44,16 @@ import { getPlatform } from "../platform";
|
|
|
44
44
|
import { projectState, renderOnly } from "../store";
|
|
45
45
|
import { invalidateUsages } from "../services/references";
|
|
46
46
|
import { confirmFileDelete, renamePromptMessage } from "../files/file-ops";
|
|
47
|
-
import {
|
|
47
|
+
import { duplicateAsset } from "../files/media-upload";
|
|
48
|
+
import { renameRefusal } from "../files/new-file-name";
|
|
49
|
+
import {
|
|
50
|
+
commitRename,
|
|
51
|
+
listRenameSiblings,
|
|
52
|
+
notifyMoveOutcome,
|
|
53
|
+
openFileInTab,
|
|
54
|
+
parentDirOf,
|
|
55
|
+
renameStatus,
|
|
56
|
+
} from "../files/files";
|
|
48
57
|
import { runActiveReported } from "../commands/run-reported";
|
|
49
58
|
import {
|
|
50
59
|
PAGES_COLLECTION,
|
|
@@ -680,9 +689,15 @@ function showLibraryContextMenu(path: string, x: number, y: number): void {
|
|
|
680
689
|
},
|
|
681
690
|
{ dividerAbove: true },
|
|
682
691
|
),
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
692
|
+
/* Not offered without raw bytes, as in the Media workspace (studio.md §9.3): a copy made as
|
|
693
|
+
text would be a damaged copy of anything that is not UTF-8. */
|
|
694
|
+
...(getPlatform().readFileBytes !== undefined
|
|
695
|
+
? [
|
|
696
|
+
row("duplicate", "Duplicate", () => {
|
|
697
|
+
void duplicateLibraryFile(file);
|
|
698
|
+
}),
|
|
699
|
+
]
|
|
700
|
+
: []),
|
|
686
701
|
row(
|
|
687
702
|
"delete",
|
|
688
703
|
"Delete",
|
|
@@ -696,17 +711,23 @@ function showLibraryContextMenu(path: string, x: number, y: number): void {
|
|
|
696
711
|
}
|
|
697
712
|
|
|
698
713
|
/**
|
|
699
|
-
* The rename dialog
|
|
714
|
+
* The rename dialog: the same copy, the same refusals and the same commit as the Files panel's.
|
|
700
715
|
*
|
|
701
|
-
* A rename here rewrites references exactly as one in the sidebar does
|
|
702
|
-
*
|
|
716
|
+
* A rename here rewrites references exactly as one in the sidebar does and replaces a destination
|
|
717
|
+
* just as silently, and an open tab has to follow the file. Two flows that disagreed about any of
|
|
718
|
+
* that is the divergence the shared helpers exist to prevent.
|
|
703
719
|
*/
|
|
704
720
|
async function renameLibraryFile(file: LibraryFile) {
|
|
721
|
+
const path = file.path.replaceAll("\\", "/");
|
|
722
|
+
const taken = await listRenameSiblings(path, SOURCE);
|
|
723
|
+
if (taken === null) {
|
|
724
|
+
return;
|
|
725
|
+
}
|
|
705
726
|
const message = await renamePromptMessage(file.path);
|
|
706
727
|
const prompt: PromptDialogOptions = {
|
|
707
728
|
confirmLabel: "Rename",
|
|
708
729
|
select: "stem",
|
|
709
|
-
validate: (value) => (value
|
|
730
|
+
validate: (value) => renameRefusal(path, value, taken),
|
|
710
731
|
value: file.name,
|
|
711
732
|
};
|
|
712
733
|
if (message !== undefined) {
|
|
@@ -716,15 +737,13 @@ async function renameLibraryFile(file: LibraryFile) {
|
|
|
716
737
|
if (!newName || newName === file.name) {
|
|
717
738
|
return;
|
|
718
739
|
}
|
|
719
|
-
const
|
|
720
|
-
const parent = normalized.includes("/") ? normalized.slice(0, normalized.lastIndexOf("/")) : ".";
|
|
740
|
+
const parent = parentDirOf(path);
|
|
721
741
|
const newPath = parent === "." ? newName : `${parent}/${newName}`;
|
|
722
742
|
try {
|
|
723
|
-
await
|
|
724
|
-
invalidateUsages();
|
|
743
|
+
const report = await commitRename(path, newPath);
|
|
725
744
|
invalidateLibrary();
|
|
726
745
|
await loadLibrary();
|
|
727
|
-
|
|
746
|
+
notifyMoveOutcome(renameStatus(newName, report), report, newPath);
|
|
728
747
|
} catch (error) {
|
|
729
748
|
notify.error(`Could not rename ${file.name}.`, {
|
|
730
749
|
detail: errorMessage(error),
|
|
@@ -734,25 +753,21 @@ async function renameLibraryFile(file: LibraryFile) {
|
|
|
734
753
|
}
|
|
735
754
|
}
|
|
736
755
|
|
|
756
|
+
/**
|
|
757
|
+
* Copy the file beside itself, through the one copy Studio has (`duplicateAsset`): byte for byte,
|
|
758
|
+
* under a name nothing else has. A file listed here is usually text, but not provably so. A
|
|
759
|
+
* collection that declares no format lists whatever its folder holds, and a declared Markdown
|
|
760
|
+
* collection can hold a Latin-1 entry, which a read as UTF-8 would have damaged. `document` is what
|
|
761
|
+
* keeps a copy that IS text openable on a backend that files an upload as a binary.
|
|
762
|
+
*/
|
|
737
763
|
async function duplicateLibraryFile(file: LibraryFile) {
|
|
738
|
-
const
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
try {
|
|
744
|
-
const platform = getPlatform();
|
|
745
|
-
const content = await platform.readFile(file.path);
|
|
746
|
-
await platform.writeFile(copyPath, content);
|
|
764
|
+
const copy = await duplicateAsset(file.path.replaceAll("\\", "/"), {
|
|
765
|
+
document: true,
|
|
766
|
+
source: SOURCE,
|
|
767
|
+
});
|
|
768
|
+
if (copy !== null) {
|
|
747
769
|
invalidateLibrary();
|
|
748
770
|
await loadLibrary();
|
|
749
|
-
notify.success(`Duplicated as ${copyName}`);
|
|
750
|
-
} catch (error) {
|
|
751
|
-
notify.error(`Could not duplicate ${file.name}.`, {
|
|
752
|
-
detail: errorMessage(error),
|
|
753
|
-
path: file.path,
|
|
754
|
-
source: SOURCE,
|
|
755
|
-
});
|
|
756
771
|
}
|
|
757
772
|
}
|
|
758
773
|
|
|
@@ -34,7 +34,7 @@ import { assetContextFor, hostAssetDeclarations, resolveAssetRef } from "../canv
|
|
|
34
34
|
import { canvasBaseOrigin, documentBase, hostResolveToken } from "../canvas/canvas-origin";
|
|
35
35
|
import { npmModuleUrl } from "../canvas/npm-modules";
|
|
36
36
|
import { projectState } from "../store";
|
|
37
|
-
import { assetVersionsSnapshot } from "../files/asset-versions";
|
|
37
|
+
import { assetVersionsSnapshot, localRevisionsSnapshot } from "../files/asset-versions";
|
|
38
38
|
import { componentRegistry } from "../files/components";
|
|
39
39
|
import { getPlatform } from "../platform";
|
|
40
40
|
import { loadFormats, formatForPath } from "../format/format-host";
|
|
@@ -184,7 +184,11 @@ async function renderDocument(
|
|
|
184
184
|
`documentDir` would resolve its relative refs against the wrong entry. The version map is
|
|
185
185
|
read as the render starts, after the reads above: the Library is the many-images screen,
|
|
186
186
|
and a versioned src is what lets its cards repaint from the browser cache. */
|
|
187
|
-
const assets = context && {
|
|
187
|
+
const assets = context && {
|
|
188
|
+
...context,
|
|
189
|
+
revisions: localRevisionsSnapshot(),
|
|
190
|
+
versions: assetVersionsSnapshot(),
|
|
191
|
+
};
|
|
188
192
|
setCanvasAssetResolver((value) => resolveAssetRef(value, assets));
|
|
189
193
|
try {
|
|
190
194
|
const element = renderNode(document_, scope);
|
package/src/canvas/asset-refs.ts
CHANGED
|
@@ -18,7 +18,11 @@ import { contentMountFor, resolveAssetRef } from "./asset-resolve";
|
|
|
18
18
|
import { documentBase, loopbackAssetSrc } from "./canvas-origin";
|
|
19
19
|
import { getPlatform, hasPlatform } from "../platform";
|
|
20
20
|
import { projectState } from "../store";
|
|
21
|
-
import {
|
|
21
|
+
import {
|
|
22
|
+
assetVersionsSnapshot,
|
|
23
|
+
hasLocalRevisions,
|
|
24
|
+
localRevisionsSnapshot,
|
|
25
|
+
} from "../files/asset-versions";
|
|
22
26
|
import { activeTab } from "../workspace/workspace";
|
|
23
27
|
import type { AssetContext } from "./asset-resolve";
|
|
24
28
|
import type { ContentSectionEntry } from "../types";
|
|
@@ -59,9 +63,10 @@ export function assetContextFor(
|
|
|
59
63
|
projectState?.projectConfig?.content as Record<string, ContentSectionEntry> | undefined,
|
|
60
64
|
projectLocales(),
|
|
61
65
|
);
|
|
62
|
-
/* In site space the mount is the ONLY thing that needs doing, so no mount means no work
|
|
66
|
+
/* In site space the mount is the ONLY thing that needs doing, so no mount means no work — unless
|
|
67
|
+
Studio has just written a file in place, whose references need its repaint key (§3.4). In repo
|
|
63
68
|
space every reference needs rebasing, mount or not. */
|
|
64
|
-
if (space === "site" && !mount) {
|
|
69
|
+
if (space === "site" && !mount && !hasLocalRevisions()) {
|
|
65
70
|
return null;
|
|
66
71
|
}
|
|
67
72
|
const context: AssetContext = {
|
|
@@ -116,7 +121,11 @@ export function previewAssetSrcFor(value: string, documentPath: string | null |
|
|
|
116
121
|
return value;
|
|
117
122
|
}
|
|
118
123
|
const ctx = assetContextFor(documentPath, hostAssetDeclarations());
|
|
119
|
-
const versioned = ctx && {
|
|
124
|
+
const versioned = ctx && {
|
|
125
|
+
...ctx,
|
|
126
|
+
revisions: localRevisionsSnapshot(),
|
|
127
|
+
versions: assetVersionsSnapshot(),
|
|
128
|
+
};
|
|
120
129
|
return loopbackAssetSrc(resolveAssetRef(value, versioned) ?? value);
|
|
121
130
|
}
|
|
122
131
|
|
|
@@ -99,6 +99,11 @@ export interface AssetContext {
|
|
|
99
99
|
* unversioned, exactly as before versions existed.
|
|
100
100
|
*/
|
|
101
101
|
versions?: Readonly<Record<string, string>> | undefined;
|
|
102
|
+
/**
|
|
103
|
+
* Studio's own repaint keys by normalized project path, appended as `?r=<key>` in BOTH spaces
|
|
104
|
+
* (see {@link withRevision}). A path appears only after Studio itself wrote its bytes.
|
|
105
|
+
*/
|
|
106
|
+
revisions?: Readonly<Record<string, string>> | undefined;
|
|
102
107
|
}
|
|
103
108
|
|
|
104
109
|
/**
|
|
@@ -122,7 +127,27 @@ export interface AssetContext {
|
|
|
122
127
|
* @returns {string} The versioned URL
|
|
123
128
|
*/
|
|
124
129
|
export function withVersion(url: string, version: string | undefined): string {
|
|
125
|
-
|
|
130
|
+
return withParam(url, "v", version);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* `url` with `r=<revision>` in its query: Studio's own repaint key for a path it has just written
|
|
135
|
+
* (specs/studio.md §3.4). Same placement rules as {@link withVersion}, and a different name on
|
|
136
|
+
* purpose: `v` is a version the HOST issued and may answer immutably, and this is a token Studio
|
|
137
|
+
* minted, which never may be. It exists because an unversioned URL is byte-identical before and
|
|
138
|
+
* after an in-place overwrite, so without it a decoded `<img>` keeps the old pixels.
|
|
139
|
+
*
|
|
140
|
+
* @param {string} url - A URL, absolute or relative
|
|
141
|
+
* @param {string | undefined} revision - The path's repaint key, if Studio holds one
|
|
142
|
+
* @returns {string} The URL carrying the key
|
|
143
|
+
*/
|
|
144
|
+
export function withRevision(url: string, revision?: string): string {
|
|
145
|
+
return withParam(url, "r", revision);
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/** `url` with one `name=value` query parameter, replacing every `name` it already had. */
|
|
149
|
+
function withParam(url: string, name: "v" | "r", value: string | undefined): string {
|
|
150
|
+
if (!isString(value) || value === "") {
|
|
126
151
|
return url;
|
|
127
152
|
}
|
|
128
153
|
const hashAt = url.indexOf("#");
|
|
@@ -131,23 +156,23 @@ export function withVersion(url: string, version: string | undefined): string {
|
|
|
131
156
|
const queryAt = beforeHash.indexOf("?");
|
|
132
157
|
const base = queryAt === -1 ? beforeHash : beforeHash.slice(0, queryAt);
|
|
133
158
|
const parts = queryAt === -1 ? [] : beforeHash.slice(queryAt + 1).split("&");
|
|
134
|
-
const kept = parts.filter((part) => part !== "" && !
|
|
135
|
-
kept.push(
|
|
159
|
+
const kept = parts.filter((part) => part !== "" && !namesKey(part, name));
|
|
160
|
+
kept.push(`${name}=${encodeURIComponent(value)}`);
|
|
136
161
|
return `${base}?${kept.join("&")}${fragment}`;
|
|
137
162
|
}
|
|
138
163
|
|
|
139
|
-
/** Whether one `key[=value]` query part's key decodes to `
|
|
140
|
-
function
|
|
164
|
+
/** Whether one `key[=value]` query part's key decodes to `name`, the way `URLSearchParams` reads it. */
|
|
165
|
+
function namesKey(part: string, name: string): boolean {
|
|
141
166
|
const eq = part.indexOf("=");
|
|
142
167
|
const key = eq === -1 ? part : part.slice(0, eq);
|
|
143
|
-
if (key ===
|
|
168
|
+
if (key === name) {
|
|
144
169
|
return true;
|
|
145
170
|
}
|
|
146
171
|
if (!key.includes("%")) {
|
|
147
172
|
return false;
|
|
148
173
|
}
|
|
149
174
|
try {
|
|
150
|
-
return decodeURIComponent(key) ===
|
|
175
|
+
return decodeURIComponent(key) === name;
|
|
151
176
|
} catch {
|
|
152
177
|
// A malformed escape decodes to itself under `URLSearchParams`, which is never a bare `v`.
|
|
153
178
|
return false;
|
|
@@ -292,13 +317,31 @@ export function resolveAssetRef(value: string, ctx: AssetContext | null): string
|
|
|
292
317
|
return null;
|
|
293
318
|
}
|
|
294
319
|
const { suffix } = splitRefSuffix(value);
|
|
295
|
-
return
|
|
296
|
-
|
|
297
|
-
|
|
320
|
+
return withRevision(
|
|
321
|
+
withVersion(
|
|
322
|
+
`${ctx.fileBaseUrl}${encodeProjectPath(path)}${suffix}`,
|
|
323
|
+
ownVersion(ctx.versions, path),
|
|
324
|
+
),
|
|
325
|
+
ownVersion(ctx.revisions, path),
|
|
298
326
|
);
|
|
299
327
|
}
|
|
300
328
|
const [mount] = ctx.mounts;
|
|
301
|
-
|
|
329
|
+
const mounted = mount ? mountedRefFor(value, ctx.documentDir, mount) : null;
|
|
330
|
+
/* In site space a reference is otherwise left as written, which after an in-place overwrite is
|
|
331
|
+
the same string naming new bytes. So a path Studio has just written gets its repaint key here
|
|
332
|
+
too, on the mounted URL or on the reference as written. The lane lookup only runs when Studio
|
|
333
|
+
holds a key at all, which on most sessions is never. */
|
|
334
|
+
const revision = siteRevision(value, ctx);
|
|
335
|
+
return revision === undefined ? mounted : withRevision(mounted ?? value, revision);
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
/** The repaint key for the project file a site-space reference names, or undefined. */
|
|
339
|
+
function siteRevision(value: string, ctx: AssetContext): string | undefined {
|
|
340
|
+
if (!ctx.revisions || Object.keys(ctx.revisions).length === 0) {
|
|
341
|
+
return undefined;
|
|
342
|
+
}
|
|
343
|
+
const path = projectPathForRef(value, ctx.documentDir, ctx.mounts, ctx.lanes);
|
|
344
|
+
return path === null ? undefined : ownVersion(ctx.revisions, path);
|
|
302
345
|
}
|
|
303
346
|
|
|
304
347
|
/**
|
|
@@ -51,6 +51,12 @@ import { parseJsonDocument, serializeJson } from "@jxsuite/schema/json-layout";
|
|
|
51
51
|
import { detachGridPanel, gridPanelMounted, renderGridMode } from "../grid/grid-panel";
|
|
52
52
|
import { detachEntryPane, entryPaneMounted, renderEntryMode } from "../content/entry-editor";
|
|
53
53
|
import { detachMediaPane, mediaPaneMounted, renderMediaMode } from "../media/media-pane";
|
|
54
|
+
import {
|
|
55
|
+
IMAGE_MODE,
|
|
56
|
+
detachImageEditor,
|
|
57
|
+
imageEditorMounted,
|
|
58
|
+
renderImageMode,
|
|
59
|
+
} from "../media/image-editor";
|
|
54
60
|
import {
|
|
55
61
|
detachVariablesView,
|
|
56
62
|
renderVariablesMode,
|
|
@@ -629,6 +635,7 @@ function resetCanvasView(surface: CanvasSurface) {
|
|
|
629
635
|
detachEntryPane(paneId);
|
|
630
636
|
detachWritingBanner(paneId);
|
|
631
637
|
detachMediaPane(paneId);
|
|
638
|
+
detachImageEditor(paneId);
|
|
632
639
|
detachVariablesView(paneId);
|
|
633
640
|
detachWorkspaceHome(paneId);
|
|
634
641
|
disposeSourceCollab(surface);
|
|
@@ -1182,6 +1189,11 @@ function renderCanvasImpl(surface: CanvasSurface) {
|
|
|
1182
1189
|
return;
|
|
1183
1190
|
}
|
|
1184
1191
|
|
|
1192
|
+
// Image-editor fast-path, same shape: a gesture repaints the editor through its own effect.
|
|
1193
|
+
if (canvasMode === IMAGE_MODE && imageEditorMounted(surface.paneId, tab) && !modeChanged) {
|
|
1194
|
+
return;
|
|
1195
|
+
}
|
|
1196
|
+
|
|
1185
1197
|
// Variables fast-path, same shape: the view follows the document through its own effect.
|
|
1186
1198
|
if (
|
|
1187
1199
|
canvasMode === PROJECT_VARIABLES_VIEW &&
|
|
@@ -1267,6 +1279,9 @@ function renderCanvasImpl(surface: CanvasSurface) {
|
|
|
1267
1279
|
// And for the media viewer, whose effect scope subscribes to the tab's path
|
|
1268
1280
|
detachMediaPane(surface.paneId);
|
|
1269
1281
|
|
|
1282
|
+
// And for the Image editor, whose effect scope subscribes to the tab's recipe
|
|
1283
|
+
detachImageEditor(surface.paneId);
|
|
1284
|
+
|
|
1270
1285
|
// And for the Variables view, whose effect scope subscribes to the document's style
|
|
1271
1286
|
detachVariablesView(surface.paneId);
|
|
1272
1287
|
|
|
@@ -1404,6 +1419,15 @@ function renderCanvasImpl(surface: CanvasSurface) {
|
|
|
1404
1419
|
return;
|
|
1405
1420
|
}
|
|
1406
1421
|
|
|
1422
|
+
/* Image mode: the media viewer's editing sibling (`media/image-editor.ts`, §9.5). Same pattern;
|
|
1423
|
+
its recipe lives with the tab, so a teardown here loses no unsaved work. */
|
|
1424
|
+
if (canvasMode === IMAGE_MODE) {
|
|
1425
|
+
canvasWrap.style.padding = "0";
|
|
1426
|
+
canvasWrap.style.display = "block";
|
|
1427
|
+
renderImageMode(surface, tab);
|
|
1428
|
+
return;
|
|
1429
|
+
}
|
|
1430
|
+
|
|
1407
1431
|
/* The Styles workspace's Variables view: `project.json`'s CSS custom properties, edited in a
|
|
1408
1432
|
form (`style/variables-view.ts`). Like a home, it writes nothing onto the stage: its own style
|
|
1409
1433
|
block fills it (`surfaces/styles-variables.json`). */
|
|
@@ -2003,6 +2027,7 @@ setSurfaceTeardown((surface) => {
|
|
|
2003
2027
|
detachGridPanel(surface.paneId);
|
|
2004
2028
|
detachEntryPane(surface.paneId);
|
|
2005
2029
|
detachMediaPane(surface.paneId);
|
|
2030
|
+
detachImageEditor(surface.paneId);
|
|
2006
2031
|
detachVariablesView(surface.paneId);
|
|
2007
2032
|
detachWorkspaceHome(surface.paneId);
|
|
2008
2033
|
detachWritingBanner(surface.paneId);
|
|
@@ -145,6 +145,8 @@ const FILE_DRAG_SRC: DragSrcKind = { type: "block" };
|
|
|
145
145
|
* every reference unversioned — the behaviour from before versions existed.
|
|
146
146
|
*/
|
|
147
147
|
let assetVersions: Readonly<Record<string, string>> = {};
|
|
148
|
+
/** Studio's repaint keys, posted beside the versions in the same message (studio.md §3.4). */
|
|
149
|
+
let assetRevisions: Readonly<Record<string, string>> = {};
|
|
148
150
|
/**
|
|
149
151
|
* The asset context of the newest render accepted, WITHOUT versions — kept so a later
|
|
150
152
|
* `assetVersions` can rebuild the resolver around the new map. Null when that render resolves
|
|
@@ -154,7 +156,7 @@ let liveAssets: AssetContext | null = null;
|
|
|
154
156
|
|
|
155
157
|
/** A render's asset context with the current versions folded in, or null for no resolution. */
|
|
156
158
|
function versionedAssets(assets: AssetContext | null): AssetContext | null {
|
|
157
|
-
return assets ? { ...assets, versions: assetVersions } : null;
|
|
159
|
+
return assets ? { ...assets, revisions: assetRevisions, versions: assetVersions } : null;
|
|
158
160
|
}
|
|
159
161
|
|
|
160
162
|
/**
|
|
@@ -307,6 +309,7 @@ export function startCanvasIframe(opts: {
|
|
|
307
309
|
const { channel, container } = opts;
|
|
308
310
|
// A frame starts knowing no versions; the host posts the map before the first render.
|
|
309
311
|
assetVersions = {};
|
|
312
|
+
assetRevisions = {};
|
|
310
313
|
liveAssets = null;
|
|
311
314
|
let handle: RenderHandle | null = null;
|
|
312
315
|
// Disposer for the live render's dataScope re-post effect (see the render handler); stopped
|
|
@@ -923,6 +926,7 @@ export function startCanvasIframe(opts: {
|
|
|
923
926
|
old map, and a patch draws through that resolver — so it is rebuilt around the new one
|
|
924
927
|
here, not left for the next render. */
|
|
925
928
|
assetVersions = msg.versions;
|
|
929
|
+
assetRevisions = msg.revisions ?? {};
|
|
926
930
|
const assets = versionedAssets(liveAssets);
|
|
927
931
|
if (assets) {
|
|
928
932
|
setCanvasAssetResolver((value) => resolveAssetRef(value, assets));
|
|
@@ -50,7 +50,11 @@ import { isNodeObject } from "@jxsuite/schema/guards";
|
|
|
50
50
|
import { isPopover } from "@jxsuite/schema/overlays";
|
|
51
51
|
import { localeDirection } from "@jxsuite/schema/locale";
|
|
52
52
|
import { getPlatform, hasPlatform } from "../platform";
|
|
53
|
-
import {
|
|
53
|
+
import {
|
|
54
|
+
assetVersionsEpoch,
|
|
55
|
+
assetVersionsSnapshot,
|
|
56
|
+
localRevisionsSnapshot,
|
|
57
|
+
} from "../files/asset-versions";
|
|
54
58
|
import type {
|
|
55
59
|
ApplyFormatIntent,
|
|
56
60
|
CanvasMode,
|
|
@@ -150,6 +154,8 @@ export interface HostState {
|
|
|
150
154
|
* changes identity only when a media version moves.
|
|
151
155
|
*/
|
|
152
156
|
postedVersions: Readonly<Record<string, string>> | null;
|
|
157
|
+
/** The repaint-key snapshot posted beside {@link HostState.postedVersions}, compared the same way. */
|
|
158
|
+
postedRevisions: Readonly<Record<string, string>> | null;
|
|
153
159
|
overlay: OverlayLayer;
|
|
154
160
|
/** Primary selected path (mirrors `session.selection`'s last entry), for hover de-dupe. */
|
|
155
161
|
selectionPath: (string | number)[] | null;
|
|
@@ -1606,15 +1612,17 @@ function syncAssetVersions(host: HostState): void {
|
|
|
1606
1612
|
return;
|
|
1607
1613
|
}
|
|
1608
1614
|
const versions = assetVersionsSnapshot();
|
|
1609
|
-
|
|
1615
|
+
const revisions = localRevisionsSnapshot();
|
|
1616
|
+
if (versions === host.postedVersions && revisions === host.postedRevisions) {
|
|
1610
1617
|
return;
|
|
1611
1618
|
}
|
|
1612
|
-
|
|
1613
|
-
|
|
1619
|
+
const first = host.postedVersions === null;
|
|
1620
|
+
host.postedVersions = versions;
|
|
1621
|
+
host.postedRevisions = revisions;
|
|
1622
|
+
if (first && Object.keys(versions).length === 0 && Object.keys(revisions).length === 0) {
|
|
1614
1623
|
return;
|
|
1615
1624
|
}
|
|
1616
|
-
host.
|
|
1617
|
-
host.channel.post({ kind: "assetVersions", versions });
|
|
1625
|
+
host.channel.post({ kind: "assetVersions", revisions, versions });
|
|
1618
1626
|
}
|
|
1619
1627
|
|
|
1620
1628
|
let assetVersionsWatchStarted = false;
|
|
@@ -1990,6 +1998,7 @@ function ensureHost(canvasEl: HTMLElement): HostState {
|
|
|
1990
1998
|
panReqId: -1,
|
|
1991
1999
|
pending: null,
|
|
1992
2000
|
postedVersions: null,
|
|
2001
|
+
postedRevisions: null,
|
|
1993
2002
|
idle: null,
|
|
1994
2003
|
pendingEnterEdit: null,
|
|
1995
2004
|
pendingMeasures: new Map(),
|
|
@@ -2143,6 +2152,7 @@ function handleMessage(state: HostState, msg: IframeToParent): void {
|
|
|
2143
2152
|
renders, and a FIFO channel makes "posted first" mean "known first". A frame that said
|
|
2144
2153
|
`ready` again has rebooted and lost what it was sent before. */
|
|
2145
2154
|
state.postedVersions = null;
|
|
2155
|
+
state.postedRevisions = null;
|
|
2146
2156
|
syncAssetVersions(state);
|
|
2147
2157
|
if (state.pending) {
|
|
2148
2158
|
state.channel.post(state.pending);
|
|
@@ -164,7 +164,15 @@ export type ParentToIframe =
|
|
|
164
164
|
* latest map and re-installs its asset resolver around it, so the next node a patch draws already
|
|
165
165
|
* uses it. A frame built before this message existed ignores it and keeps resolving unversioned.
|
|
166
166
|
*/
|
|
167
|
-
| {
|
|
167
|
+
| {
|
|
168
|
+
kind: "assetVersions";
|
|
169
|
+
versions: Readonly<Record<string, string>>;
|
|
170
|
+
/**
|
|
171
|
+
* Studio's own repaint keys (studio.md §3.4), for paths Studio itself just wrote in place.
|
|
172
|
+
* Optional so a host that predates them still speaks the message; absent reads as none.
|
|
173
|
+
*/
|
|
174
|
+
revisions?: Readonly<Record<string, string>>;
|
|
175
|
+
}
|
|
168
176
|
// Flip the forced color-scheme preview on the iframe root without re-rendering — a document-level
|
|
169
177
|
// Idempotent attribute write, deliberately gen-less (like endEdit).
|
|
170
178
|
| { kind: "setColorScheme"; scheme: "light" | "dark" | null }
|
package/src/commands/context.ts
CHANGED
|
@@ -69,6 +69,7 @@ export type EditorKind =
|
|
|
69
69
|
| "tokens"
|
|
70
70
|
| "entry"
|
|
71
71
|
| "media"
|
|
72
|
+
| "image"
|
|
72
73
|
| "home"
|
|
73
74
|
| "none";
|
|
74
75
|
|
|
@@ -107,10 +108,14 @@ const EDITOR_KIND_BY_MODE = new Map<string, EditorKind>([
|
|
|
107
108
|
["content-home", "home"],
|
|
108
109
|
["media-home", "home"],
|
|
109
110
|
["publish-home", "home"],
|
|
111
|
+
["settings-home", "home"],
|
|
110
112
|
/* The Media viewer — `media/media-pane.ts`. An image, a video, a font specimen: files the studio
|
|
111
113
|
can SHOW but has no document model for, which is why clicking one used to end in "No format
|
|
112
114
|
class imported for …", advice that means nothing about a PNG. */
|
|
113
115
|
["media", "media"],
|
|
116
|
+
/* The Image editor — `media/image-editor.ts`'s IMAGE_MODE, the media viewer's editing sibling
|
|
117
|
+
(§9.5). A recipe over one image: nothing to paste into, so not the Canvas key scope. */
|
|
118
|
+
["image", "image"],
|
|
114
119
|
["preview", "canvas"],
|
|
115
120
|
["source", "code"],
|
|
116
121
|
["stylebook", "config"],
|
|
@@ -142,6 +147,7 @@ export const EDITOR_KIND_LABELS: Readonly<Record<EditorKind, string>> = {
|
|
|
142
147
|
entry: "Entry",
|
|
143
148
|
grid: "Grid",
|
|
144
149
|
home: "Home",
|
|
150
|
+
image: "Image",
|
|
145
151
|
media: "Media",
|
|
146
152
|
none: "None",
|
|
147
153
|
tokens: "Variables",
|