@jxsuite/studio 6.1.2 → 8.0.1
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/2020-5wtzce5g.js +4 -0
- package/dist/chunks/adapter-5tphg7xe.js +4 -0
- package/dist/chunks/add-new-flow-psym14sn.js +4 -0
- package/dist/chunks/cf-account-picker-haqazq5r.js +4 -0
- package/dist/chunks/cf-settings-h2wrh6hq.js +4 -0
- package/dist/chunks/{color-area-0fj8a4ga.js → color-area-6rbra5dy.js} +2 -2
- package/dist/chunks/{color-field-xch8kx2g.js → color-field-gy56c94g.js} +3 -3
- package/dist/chunks/color-m7r1vk54.js +4 -0
- package/dist/chunks/{color-slider-jxd8v350.js → color-slider-sqqdyf6c.js} +2 -2
- package/dist/chunks/{combobox-p0efwjke.js → combobox-72h32ejk.js} +3 -3
- package/dist/chunks/components-r22xca09.js +4 -0
- package/dist/chunks/content-source-mmppmcyg.js +4 -0
- package/dist/chunks/convert-to-repeater-he70fc2b.js +4 -0
- package/dist/chunks/editor-ccd9jwsa.js +4 -0
- package/dist/chunks/entry-commands-6qxz9e8p.js +4 -0
- package/dist/chunks/extension-sections-0egb88dm.js +4 -0
- package/dist/chunks/file-ops-qnab36ek.js +4 -0
- package/dist/chunks/files-rcn56qvm.js +4 -0
- package/dist/chunks/format-host-yxz0kjjs.js +4 -0
- package/dist/chunks/fs-events-d1qxq4z4.js +4 -0
- package/dist/chunks/grid-open-4z1g062t.js +4 -0
- package/dist/chunks/{grid-view-d2wq9ezg.js → grid-view-qg1b5eqx.js} +25 -25
- package/dist/chunks/{icons-pnfz478q.js → icons-63bjtbs7.js} +2 -2
- package/dist/chunks/{iframe-entry-jdcktez8.js → iframe-entry-2zggzajv.js} +4 -4
- package/dist/chunks/{iframe-entry-zap785cw.js → iframe-entry-76j8vwwx.js} +4 -4
- package/dist/chunks/iframe-entry-99nr5zfb.js +5 -0
- package/dist/chunks/{iframe-entry-x0ygf2yr.js → iframe-entry-pkgmcgp0.js} +4 -4
- package/dist/chunks/iframe-host-ssv7kyj0.js +4 -0
- package/dist/chunks/{index-zpjmzqfk.js → index-32eqzv1k.js} +3 -3
- package/dist/chunks/index-f0jn7nnp.js +4 -0
- package/dist/chunks/{javascript-m54mnwp6.js → javascript-pjp734q4.js} +3 -3
- package/dist/chunks/jsonMode-njkvsa02.js +13 -0
- package/dist/chunks/jx-validate-ttasrbh7.js +4 -0
- package/dist/chunks/layers-bgyr11tr.js +4 -0
- package/dist/chunks/layers-panel-7ez66zeq.js +4 -0
- package/dist/chunks/{listbox-e74qjyq6.js → listbox-c421cf8c.js} +2 -2
- package/dist/chunks/lit-html-v9gh687x.js +4 -0
- package/dist/chunks/media-library-sq468wts.js +4 -0
- package/dist/chunks/media-picker-shfe59pt.js +4 -0
- package/dist/chunks/{menu-n6yzswmy.js → menu-bdpzbfvs.js} +2 -2
- package/dist/chunks/{monaco-binding-7gx83x3r.js → monaco-binding-vq1hgnn0.js} +3 -3
- package/dist/chunks/monaco-buffer-3afgwvbp.js +4 -0
- package/dist/chunks/monaco-setup-xrd2cbkp.js +4 -0
- package/dist/chunks/notify-kzrjr3fv.js +4 -0
- package/dist/chunks/parse-v77xtwv7.js +4 -0
- package/dist/chunks/platform-7v4rtd7m.js +4 -0
- package/dist/chunks/popover-b89sfej7.js +4 -0
- package/dist/chunks/publish-panel-2z9gw8ee.js +4 -0
- package/dist/chunks/publish-quick-qx2455cj.js +4 -0
- package/dist/chunks/publish-workspace-wrc1dgwx.js +4 -0
- package/dist/chunks/quick-search-n0t0eqxb.js +4 -0
- package/dist/chunks/references-9547rg5s.js +4 -0
- package/dist/chunks/right-panel-wsgnr9pn.js +4 -0
- package/dist/chunks/source-control-kwktr9y6.js +4 -0
- package/dist/chunks/{split-n2gfpm5m.js → split-c9zfdgxf.js} +2 -2
- package/dist/chunks/studio-03ct6yfv.js +9 -0
- package/dist/chunks/studio-07rcfje3.js +5 -0
- package/dist/chunks/studio-3e54qh39.js +7 -0
- package/dist/chunks/studio-3exxhz5g.js +5 -0
- package/dist/chunks/studio-3q40e3rj.js +5 -0
- package/dist/chunks/studio-3qeh9t0h.js +6 -0
- package/dist/chunks/studio-3qqjeep5.js +6 -0
- package/dist/chunks/studio-43sd2mam.js +4 -0
- package/dist/chunks/studio-4hp8pkyt.js +5 -0
- package/dist/chunks/studio-58xg6j2q.js +9 -0
- package/dist/chunks/studio-5g0rprdt.js +7 -0
- package/dist/chunks/{studio-2j1cb95x.js → studio-6m380wp7.js} +4 -4
- package/dist/chunks/studio-80jyh1xh.js +4 -0
- package/dist/chunks/studio-83mk4bgg.js +9 -0
- package/dist/chunks/{studio-v0jzqdjm.js → studio-881z1xrx.js} +4 -4
- package/dist/chunks/studio-8m0y2qe7.js +5 -0
- package/dist/chunks/{studio-d81xfh1g.js → studio-8tr6cfrc.js} +4 -4
- package/dist/chunks/studio-8vcvfs7r.js +6 -0
- package/dist/chunks/studio-9tmebf0s.js +5 -0
- package/dist/chunks/studio-a2bvaptz.js +5 -0
- package/dist/chunks/studio-b7y5mr30.js +5 -0
- package/dist/chunks/studio-bavb31r0.js +5 -0
- package/dist/chunks/{studio-dgz8d0c1.js → studio-bbe4c90a.js} +4 -4
- package/dist/chunks/studio-c05wdnt3.js +5 -0
- package/dist/chunks/studio-ccv2svc6.js +5 -0
- package/dist/chunks/studio-d5grtb9e.js +16 -0
- package/dist/chunks/studio-df0baea9.js +5 -0
- package/dist/chunks/studio-dgk4dqtx.js +7 -0
- package/dist/chunks/studio-e1g64q43.js +5 -0
- package/dist/chunks/studio-ed1hp6zf.js +6 -0
- package/dist/chunks/studio-ed3xx40h.js +7 -0
- package/dist/chunks/studio-f14e1zdj.js +5 -0
- package/dist/chunks/studio-fbhxxc5f.js +5 -0
- package/dist/chunks/studio-fyh0jc96.js +5 -0
- package/dist/chunks/studio-gcxk20n7.js +5 -0
- package/dist/chunks/studio-jaaz8ce9.js +5 -0
- package/dist/chunks/{studio-90pq9km1.js → studio-jcnsdaep.js} +4 -4
- package/dist/chunks/studio-jsgf2q5k.js +7 -0
- package/dist/chunks/{studio-0q988f9w.js → studio-k3xxbagr.js} +3 -3
- package/dist/chunks/{studio-5ve6bbep.js → studio-k7evxqh4.js} +4 -4
- package/dist/chunks/studio-kt3pjkwd.js +5 -0
- package/dist/chunks/studio-m8g16bgz.js +5 -0
- package/dist/chunks/studio-mn0476ag.js +5 -0
- package/dist/chunks/studio-mx1smyq0.js +5 -0
- package/dist/chunks/studio-n0c5h6mf.js +5 -0
- package/dist/chunks/{studio-485046te.js → studio-nh6d342n.js} +7 -7
- package/dist/chunks/studio-nqxqf8p2.js +4 -0
- package/dist/chunks/studio-p4nrkvqr.js +5 -0
- package/dist/chunks/{studio-1fxkaaea.js → studio-pbxa5fpc.js} +4 -4
- package/dist/chunks/studio-pgmvdv7z.js +7 -0
- package/dist/chunks/studio-ph84avkf.js +5 -0
- package/dist/chunks/{studio-tq0rtfwj.js → studio-q0mvbaq6.js} +4 -4
- package/dist/chunks/studio-q0tccd43.js +5 -0
- package/dist/chunks/studio-q7rtx6zq.js +14 -0
- package/dist/chunks/{studio-a3g98wjr.js → studio-qgh13jtz.js} +4 -4
- package/dist/chunks/studio-qmn1xc24.js +5 -0
- package/dist/chunks/studio-qwdqwkj0.js +5 -0
- package/dist/chunks/studio-r5qy30vx.js +5 -0
- package/dist/chunks/studio-rj9eamf8.js +5 -0
- package/dist/chunks/studio-rptgsk96.js +5 -0
- package/dist/chunks/studio-rthyg42z.js +928 -0
- package/dist/chunks/studio-s19tp1xr.js +5 -0
- package/dist/chunks/studio-s32578pn.js +5 -0
- package/dist/chunks/studio-svb7yyve.js +5 -0
- package/dist/chunks/studio-syreb644.js +5 -0
- package/dist/chunks/studio-tf9jryb1.js +10 -0
- package/dist/chunks/studio-tfkznv39.js +5 -0
- package/dist/chunks/studio-tft6r105.js +5 -0
- package/dist/chunks/studio-txf2kysx.js +5 -0
- package/dist/chunks/studio-v8867ddv.js +12 -0
- package/dist/chunks/studio-vxq7fcvv.js +5 -0
- package/dist/chunks/studio-w4rz9hdk.js +5 -0
- package/dist/chunks/studio-wbcjbhy1.js +9 -0
- package/dist/chunks/studio-wc649wss.js +5 -0
- package/dist/chunks/studio-wys23mxz.js +5 -0
- package/dist/chunks/studio-x1x167jh.js +5 -0
- package/dist/chunks/studio-x9hsze61.js +5 -0
- package/dist/chunks/studio-y0n7yxea.js +9 -0
- package/dist/chunks/studio-y3ahhdpx.js +5 -0
- package/dist/chunks/studio-z97c48fk.js +5 -0
- package/dist/chunks/studio-zat62m0c.js +5 -0
- package/dist/chunks/studio-zcd2e7yz.js +5 -0
- package/dist/chunks/studio-zz9qe3rw.js +64 -0
- package/dist/chunks/toast-1gv0h53h.js +4 -0
- package/dist/chunks/{toast-host-ke5z4ng7.js → toast-host-1yg5ar8d.js} +3 -3
- package/dist/chunks/{tooltip-5v8ngwj2.js → tooltip-yypz28gq.js} +3 -3
- package/dist/chunks/{tree-mdq3v5cw.js → tree-9xa4hm7n.js} +2 -2
- package/dist/chunks/tsMode-epyfz1hv.js +14 -0
- package/dist/chunks/workspace-dz7nxfhv.js +4 -0
- package/dist/chunks/workspace-home-xsd7hkjw.js +4 -0
- package/dist/chunks/{ws-client-nze0k6mv.js → ws-client-bv552sfx.js} +3 -3
- package/dist/iframe-entry.js +20 -16
- package/dist/manifest.json +77 -63
- package/dist/studio.js +103 -104
- package/dist/workers/ts.worker.js +3 -3
- package/package.json +12 -11
- package/src/browse/component-examples.ts +272 -0
- package/src/browse/library-commands.ts +64 -104
- package/src/browse/library-layouts.ts +263 -49
- package/src/browse/library-model.ts +181 -133
- package/src/browse/library-pane.ts +738 -446
- package/src/browse/library-preview.ts +149 -19
- package/src/browse/library-source.ts +308 -41
- package/src/browse/preview-root.ts +78 -0
- package/src/browse/preview-style.ts +155 -0
- package/src/canvas/asset-refs.ts +16 -1
- package/src/canvas/canvas-live-render.ts +222 -30
- package/src/canvas/canvas-origin.ts +37 -12
- package/src/canvas/canvas-page-context.ts +112 -0
- package/src/canvas/canvas-render.ts +113 -107
- package/src/canvas/canvas-utils.ts +57 -12
- package/src/canvas/iframe-drop.ts +107 -0
- package/src/canvas/iframe-entry.ts +24 -12
- package/src/canvas/iframe-host.ts +134 -62
- package/src/canvas/iframe-keys.ts +11 -0
- package/src/canvas/iframe-patch.ts +8 -7
- package/src/canvas/iframe-protocol.ts +24 -5
- package/src/canvas/iframe-render.ts +48 -1
- package/src/canvas/iframe-subtree.ts +5 -2
- package/src/canvas/npm-modules.ts +22 -0
- package/src/canvas/preview-navigate.ts +16 -0
- package/src/canvas/repeater-sources.ts +200 -0
- package/src/canvas/stage-scrollbar.ts +5 -1
- package/src/canvas/surface-registry.ts +6 -0
- package/src/commands/app-commands.ts +16 -6
- package/src/commands/budget.ts +10 -8
- package/src/commands/context.ts +35 -10
- package/src/commands/defaults.ts +69 -45
- package/src/commands/keymap.ts +9 -0
- package/src/commands/levels.ts +11 -5
- package/src/commands/live-context.ts +2 -0
- package/src/commands/registry.ts +10 -1
- package/src/content/collection-match.ts +6 -5
- package/src/content/collection-pages.ts +412 -0
- package/src/content/content-collections.ts +270 -0
- package/src/content/entry-commands.ts +51 -60
- package/src/content/entry-editor.ts +111 -18
- package/src/content/entry-fields.ts +13 -1
- package/src/content/entry-inspector.ts +225 -0
- package/src/content/entry-model.ts +17 -3
- package/src/content/entry-open.ts +80 -0
- package/src/content/entry-summaries.ts +440 -0
- package/src/content/file-record-ids.ts +80 -0
- package/src/content/file-records.ts +125 -0
- package/src/content/page-summaries.ts +129 -0
- package/src/content/record-entry.ts +201 -0
- package/src/content/writing-banner.ts +177 -0
- package/src/create/add-new-commands.ts +185 -0
- package/src/create/add-new-flow.ts +1116 -0
- package/src/create/add-new.ts +1014 -0
- package/src/create/element-name.ts +121 -0
- package/src/editor/convert-to-component.ts +67 -43
- package/src/editor/file-drop-action.ts +9 -1
- package/src/editor/inline-edit-apply.ts +44 -0
- package/src/editor/inline-edit.ts +10 -0
- package/src/editor/insert-component.ts +91 -0
- package/src/editor/insert-zone-action.ts +13 -4
- package/src/editor/shortcuts.ts +44 -22
- package/src/editor/slash-menu.ts +51 -8
- package/src/files/components.ts +74 -10
- package/src/files/file-ops.ts +15 -5
- package/src/files/files.ts +164 -150
- package/src/files/fs-events.ts +18 -7
- package/src/files/media-meta.ts +95 -1
- package/src/files/media-types.ts +55 -0
- package/src/files/media-upload.ts +209 -52
- package/src/files/new-file-name.ts +156 -0
- package/src/files/serialize-document.ts +13 -3
- package/src/format/format-host.ts +15 -0
- package/src/github/github-auth.ts +2 -2
- package/src/github/github-publish.ts +4 -3
- package/src/grid/grid-controller.ts +5 -1
- package/src/grid/grid-open.ts +0 -27
- package/src/grid/grid-panel.ts +54 -2
- package/src/grid/grid-reveal.ts +34 -0
- package/src/grid/grid-source.ts +3 -25
- package/src/grid/grid-view.ts +31 -6
- package/src/grid/sources/content-source.ts +75 -7
- package/src/grid/tabulator-tables.d.ts +6 -0
- package/src/hosting/document.ts +2 -2
- package/src/i18n/i18n-commands.ts +2 -1
- package/src/media/media-commands.ts +181 -0
- package/src/media/media-library-model.ts +232 -0
- package/src/media/media-library.ts +1283 -0
- package/src/media/media-pane.ts +47 -19
- package/src/packages/pull-package-sync.ts +1 -1
- package/src/panels/activity-panel.ts +21 -5
- package/src/panels/ai-chat/chat-markdown.ts +8 -8
- package/src/panels/block-action-bar.ts +11 -11
- package/src/panels/canvas-dnd-bridge.ts +5 -2
- package/src/panels/chat-panel.ts +6 -6
- package/src/panels/collections-panel.ts +306 -0
- package/src/panels/component-preview.ts +35 -13
- package/src/panels/data-explorer.ts +1 -1
- package/src/panels/data-grid.ts +4 -0
- package/src/panels/dnd.ts +17 -30
- package/src/panels/elements-panel.ts +9 -2
- package/src/panels/empty-state.ts +2 -2
- package/src/panels/formula-workspace.ts +2 -3
- package/src/panels/frontmatter-fields.ts +9 -9
- package/src/panels/git-diff-open.ts +9 -5
- package/src/panels/head-panel.ts +369 -103
- package/src/panels/i18n-panel.ts +11 -10
- package/src/panels/imports-panel.ts +2 -0
- package/src/panels/jump-bar.ts +27 -2
- package/src/panels/layers-panel.ts +36 -30
- package/src/panels/left-panel.ts +22 -8
- package/src/panels/navigator-panels.ts +23 -11
- package/src/panels/pane-context.ts +111 -29
- package/src/panels/pane-grid.ts +6 -6
- package/src/panels/panel-registry.ts +14 -9
- package/src/panels/properties-panel.ts +7 -3
- package/src/panels/right-panel.ts +199 -43
- package/src/panels/seo-modal.ts +41 -108
- package/src/panels/settings-menu.ts +52 -50
- package/src/panels/settings-pane.ts +268 -162
- package/src/panels/signals-panel.ts +3 -4
- package/src/panels/site-model.ts +367 -0
- package/src/panels/site-panel.ts +638 -0
- package/src/panels/style-panel.ts +37 -3
- package/src/panels/stylebook-doc.ts +142 -10
- package/src/panels/stylebook-layers-panel.ts +39 -80
- package/src/panels/stylebook-panel.ts +47 -10
- package/src/panels/styles-panel.ts +327 -0
- package/src/panels/tab-strip.ts +109 -25
- package/src/platform.ts +1 -1
- package/src/platforms/cloud.ts +28 -1
- package/src/platforms/default-platform.ts +4 -4
- package/src/platforms/devserver.ts +51 -25
- package/src/publish/deploy-checklist.ts +12 -7
- package/src/publish/pages-service.ts +38 -14
- package/src/publish/publish-changes.ts +491 -0
- package/src/publish/publish-commands.ts +83 -18
- package/src/publish/publish-ids.ts +49 -0
- package/src/publish/publish-quick.ts +148 -0
- package/src/publish/publish-workspace.ts +926 -0
- package/src/publish/source-control.ts +907 -0
- package/src/services/automation.ts +16 -20
- package/src/services/cf-settings.ts +4 -4
- package/src/services/connection.ts +2 -2
- package/src/services/directory-picker.ts +2 -2
- package/src/services/references.ts +0 -0
- package/src/services/render-critic.ts +21 -2
- package/src/services/settings/definitions.ts +25 -14
- package/src/settings/contributed-section.ts +49 -10
- package/src/settings/css-vars-editor.ts +93 -27
- package/src/settings/developer-panels.ts +43 -0
- package/src/settings/extension-sections.ts +20 -2
- package/src/settings/general-settings.ts +91 -21
- package/src/settings/preferences-dialog.ts +8 -1
- package/src/settings/project-sections.ts +27 -10
- package/src/settings/schema-field-ui.ts +3 -1
- package/src/settings/section-registry.ts +116 -11
- package/src/settings/settings-dialog.ts +192 -0
- package/src/settings/settings-document.ts +182 -162
- package/src/shell.ts +467 -431
- package/src/site-context.ts +64 -10
- package/src/state.ts +28 -9
- package/src/studio.ts +194 -41
- package/src/style/project-styles.ts +34 -0
- package/src/style/stylebook-specimens.ts +89 -0
- package/src/style/stylebook-target.ts +26 -0
- package/src/style/tag-inheritance.ts +105 -0
- package/src/style/variables-focus.ts +61 -0
- package/src/style/variables-view.ts +235 -0
- package/src/surfaces/add-new.json +1482 -0
- package/src/surfaces/add-new.ts +325 -0
- package/src/surfaces/ai-chat.ts +2 -2
- package/src/surfaces/boot-failure.json +4 -3
- package/src/surfaces/bottom-dock.json +1 -1
- package/src/surfaces/canvas-stage.json +30 -19
- package/src/surfaces/canvas-stage.ts +5 -5
- package/src/surfaces/collections-panel.json +615 -0
- package/src/surfaces/collections-panel.ts +168 -0
- package/src/surfaces/commandbar.json +241 -96
- package/src/surfaces/commandbar.ts +134 -99
- package/src/surfaces/empty-state.json +1 -1
- package/src/surfaces/entry-editor.json +136 -0
- package/src/surfaces/entry-editor.ts +21 -0
- package/src/surfaces/entry-inspector.json +371 -0
- package/src/surfaces/entry-inspector.ts +143 -0
- package/src/surfaces/inspector-dock.json +162 -14
- package/src/surfaces/inspector-dock.ts +46 -19
- package/src/surfaces/library-pane.json +670 -437
- package/src/surfaces/library-pane.ts +94 -59
- package/src/surfaces/media-browser.json +62 -6
- package/src/surfaces/media-browser.ts +15 -1
- package/src/surfaces/media-field.json +1 -1
- package/src/surfaces/media-library.json +1586 -0
- package/src/surfaces/media-library.ts +282 -0
- package/src/surfaces/media-pick-dialog.json +71 -0
- package/src/surfaces/media-pick-dialog.ts +132 -0
- package/src/surfaces/menu.ts +18 -0
- package/src/surfaces/pane-context.json +144 -77
- package/src/surfaces/pane-context.ts +24 -0
- package/src/surfaces/panel-deploy-checklist.json +12 -6
- package/src/surfaces/panel-deploy-checklist.ts +3 -1
- package/src/surfaces/panel-page.json +72 -2
- package/src/surfaces/panel-page.ts +38 -7
- package/src/surfaces/panel-signals.json +1 -1
- package/src/surfaces/panel-signals.ts +5 -5
- package/src/surfaces/panel-stylebook-layers.json +71 -204
- package/src/surfaces/panel-stylebook-layers.ts +0 -21
- package/src/surfaces/preferences.json +120 -48
- package/src/surfaces/preferences.ts +8 -0
- package/src/surfaces/publish-quick.json +428 -0
- package/src/surfaces/publish-quick.ts +165 -0
- package/src/surfaces/publish-workspace.json +2892 -0
- package/src/surfaces/publish-workspace.ts +391 -0
- package/src/surfaces/rail.json +1 -159
- package/src/surfaces/rail.ts +18 -75
- package/src/surfaces/schema-form.json +10 -2
- package/src/surfaces/schema-form.ts +2 -0
- package/src/surfaces/seo.json +1 -1
- package/src/surfaces/settings-contributed.json +5 -5
- package/src/surfaces/settings-contributed.ts +18 -2
- package/src/surfaces/settings-css-vars.json +44 -36
- package/src/surfaces/settings-css-vars.ts +77 -4
- package/src/surfaces/settings-deploy.json +2 -15
- package/src/surfaces/settings-deploy.ts +1 -1
- package/src/surfaces/settings-dialog.json +51 -0
- package/src/surfaces/settings-dialog.ts +169 -0
- package/src/surfaces/settings-overview.json +3 -3
- package/src/surfaces/settings-overview.ts +9 -2
- package/src/surfaces/settings-pane.json +269 -46
- package/src/surfaces/settings-pane.ts +85 -24
- package/src/surfaces/shell.json +2 -1
- package/src/surfaces/site-panel.json +1029 -0
- package/src/surfaces/site-panel.ts +233 -0
- package/src/surfaces/slash-menu.json +63 -21
- package/src/surfaces/slash-menu.ts +23 -0
- package/src/surfaces/statusbar.ts +39 -15
- package/src/surfaces/styles-panel.json +735 -0
- package/src/surfaces/styles-panel.ts +195 -0
- package/src/surfaces/styles-variables.json +149 -0
- package/src/surfaces/styles-variables.ts +145 -0
- package/src/surfaces/tab-strip.json +4 -4
- package/src/surfaces/welcome.ts +1 -1
- package/src/surfaces/writing-banner.json +94 -0
- package/src/surfaces/writing-banner.ts +80 -0
- package/src/tabs/bare-repeaters.ts +101 -0
- package/src/tabs/tab.ts +47 -7
- package/src/types.ts +21 -26
- package/src/ui/form-controls.ts +47 -8
- package/src/ui/layers.ts +13 -0
- package/src/ui/media-picker.ts +284 -40
- package/src/ui/regions.ts +7 -6
- package/src/ui/schema-form.ts +259 -27
- package/src/ui/virtual-window.ts +26 -1
- package/src/utils/base64.ts +3 -3
- package/src/utils/edit-display.ts +322 -67
- package/src/utils/local-day.ts +18 -0
- package/src/workspace/pane-derive.ts +4 -4
- package/src/workspace/session.ts +28 -7
- package/src/workspace/tab-workspace.ts +137 -0
- package/src/workspace/workspace-back.ts +189 -0
- package/src/workspace/workspace-home.ts +133 -0
- package/src/workspace/workspace-ids.ts +129 -0
- package/src/workspace/workspace-switch.ts +372 -0
- package/src/workspace/workspace.ts +125 -11
- package/styles/shell-frame.css +30 -7
- package/styles/shell-frame.json +15 -3
- package/dist/chunks/2020-f2gkpy8d.js +0 -4
- package/dist/chunks/adapter-4912g339.js +0 -4
- package/dist/chunks/cf-account-picker-wccaenh6.js +0 -4
- package/dist/chunks/cf-settings-br6105tr.js +0 -4
- package/dist/chunks/color-28ksk2qm.js +0 -4
- package/dist/chunks/components-db68vsn7.js +0 -4
- package/dist/chunks/content-source-1449qesq.js +0 -4
- package/dist/chunks/convert-to-repeater-5h457egj.js +0 -4
- package/dist/chunks/editor-x9jn86a7.js +0 -4
- package/dist/chunks/entry-commands-may24cpt.js +0 -4
- package/dist/chunks/extension-sections-pvr14vxr.js +0 -4
- package/dist/chunks/file-ops-x9v54pcj.js +0 -4
- package/dist/chunks/files-j3knqf0s.js +0 -4
- package/dist/chunks/format-host-tyre2trb.js +0 -4
- package/dist/chunks/fs-events-64ma9htc.js +0 -4
- package/dist/chunks/git-panel-84ps5dex.js +0 -4
- package/dist/chunks/iframe-entry-fvbk89gm.js +0 -5
- package/dist/chunks/iframe-host-ck6vejt5.js +0 -4
- package/dist/chunks/index-b8xw3vy9.js +0 -4
- package/dist/chunks/jsonMode-w15cmrcm.js +0 -13
- package/dist/chunks/jx-validate-at3zn8wa.js +0 -4
- package/dist/chunks/layers-h4xp1pss.js +0 -4
- package/dist/chunks/layers-panel-m5hw1d7g.js +0 -4
- package/dist/chunks/lit-html-8t8t3dnr.js +0 -4
- package/dist/chunks/media-picker-2cweexc5.js +0 -4
- package/dist/chunks/menu-ynw13y0s.js +0 -4
- package/dist/chunks/monaco-buffer-qzxn567r.js +0 -4
- package/dist/chunks/monaco-setup-gxf6656g.js +0 -4
- package/dist/chunks/notify-cj5zbrs2.js +0 -4
- package/dist/chunks/parse-b7ddhr8m.js +0 -4
- package/dist/chunks/platform-3dfws9js.js +0 -4
- package/dist/chunks/popover-jt2gxmzs.js +0 -4
- package/dist/chunks/publish-panel-1gvxbj21.js +0 -4
- package/dist/chunks/quick-search-0dajrwqg.js +0 -4
- package/dist/chunks/references-49qgt404.js +0 -4
- package/dist/chunks/right-panel-jm1bwe53.js +0 -4
- package/dist/chunks/section-registry-6kwc4fky.js +0 -4
- package/dist/chunks/studio-0rt52vpq.js +0 -5
- package/dist/chunks/studio-18vgbhs5.js +0 -5
- package/dist/chunks/studio-24mccdrj.js +0 -5
- package/dist/chunks/studio-2eh507zk.js +0 -9
- package/dist/chunks/studio-2hcbccfv.js +0 -5
- package/dist/chunks/studio-2jbs3rxv.js +0 -5
- package/dist/chunks/studio-4h8k4dw9.js +0 -5
- package/dist/chunks/studio-5f6n18kx.js +0 -5
- package/dist/chunks/studio-5gthpky6.js +0 -5
- package/dist/chunks/studio-6ep3js6b.js +0 -9
- package/dist/chunks/studio-6wkmf3wz.js +0 -9
- package/dist/chunks/studio-841rf3cg.js +0 -5
- package/dist/chunks/studio-8gmwgheg.js +0 -5
- package/dist/chunks/studio-8qm0ep50.js +0 -5
- package/dist/chunks/studio-924p1tm1.js +0 -6
- package/dist/chunks/studio-a3shpz9y.js +0 -5
- package/dist/chunks/studio-amsbaaar.js +0 -6
- package/dist/chunks/studio-at5spjbc.js +0 -7
- package/dist/chunks/studio-b2xv945e.js +0 -5
- package/dist/chunks/studio-bn5nkafw.js +0 -5
- package/dist/chunks/studio-bw6q5xjf.js +0 -9
- package/dist/chunks/studio-c7q0t9x1.js +0 -5
- package/dist/chunks/studio-camnhc96.js +0 -6
- package/dist/chunks/studio-cffqvnzd.js +0 -5
- package/dist/chunks/studio-efvfrsf8.js +0 -5
- package/dist/chunks/studio-er30bpcx.js +0 -5
- package/dist/chunks/studio-ezscgr64.js +0 -5
- package/dist/chunks/studio-ffrrncvz.js +0 -5
- package/dist/chunks/studio-fpphgn8y.js +0 -10
- package/dist/chunks/studio-j1kvhdjk.js +0 -4
- package/dist/chunks/studio-j430n16g.js +0 -7
- package/dist/chunks/studio-jjkg84nj.js +0 -5
- package/dist/chunks/studio-m0q32ars.js +0 -4
- package/dist/chunks/studio-m6bdst11.js +0 -5
- package/dist/chunks/studio-nv2cptd4.js +0 -64
- package/dist/chunks/studio-nwfrc98n.js +0 -5
- package/dist/chunks/studio-p1wgvp7r.js +0 -5
- package/dist/chunks/studio-pag2wncm.js +0 -5
- package/dist/chunks/studio-qvanvzfd.js +0 -5
- package/dist/chunks/studio-qwa64kt7.js +0 -5
- package/dist/chunks/studio-rk1zc4x3.js +0 -928
- package/dist/chunks/studio-se2r3ptc.js +0 -5
- package/dist/chunks/studio-sg2a0dh0.js +0 -5
- package/dist/chunks/studio-tjzjhe68.js +0 -5
- package/dist/chunks/studio-v2w7q8r1.js +0 -7
- package/dist/chunks/studio-v5vjq1h0.js +0 -4
- package/dist/chunks/studio-v8aga3vp.js +0 -14
- package/dist/chunks/studio-vfcdrfmv.js +0 -5
- package/dist/chunks/studio-vmfkgdjf.js +0 -5
- package/dist/chunks/studio-wt1816jj.js +0 -5
- package/dist/chunks/studio-wz73q5kq.js +0 -4
- package/dist/chunks/studio-y04qtxvp.js +0 -7
- package/dist/chunks/studio-y21g8zzg.js +0 -5
- package/dist/chunks/studio-y64hrt8p.js +0 -12
- package/dist/chunks/studio-ynn90f58.js +0 -16
- package/dist/chunks/studio-yxz682sx.js +0 -5
- package/dist/chunks/studio-zp7snvkq.js +0 -5
- package/dist/chunks/studio-zq4z6tm6.js +0 -5
- package/dist/chunks/toast-c9fn075p.js +0 -4
- package/dist/chunks/tsMode-sf49ee9x.js +0 -14
- package/dist/chunks/workspace-jtfahpee.js +0 -4
- package/src/panels/frontmatter-panel.ts +0 -711
- package/src/panels/git-panel.ts +0 -1108
- package/src/surfaces/doc-header.json +0 -776
- package/src/surfaces/doc-header.ts +0 -222
- package/src/surfaces/git-panel.json +0 -1880
- package/src/surfaces/git-panel.ts +0 -264
|
@@ -0,0 +1,638 @@
|
|
|
1
|
+
/// <reference lib="dom" />
|
|
2
|
+
/**
|
|
3
|
+
* The Site panel — the Design workspace's default Navigator panel (`specs/studio.md` §5.7).
|
|
4
|
+
*
|
|
5
|
+
* The project as an author thinks of it: pages by address, then the templates, components and media
|
|
6
|
+
* pages are made of. This module is the FLOW. It gathers the facts (the files under `pages/` and
|
|
7
|
+
* `layouts/`, the collection each dynamic page expands over and how many entries it has, how many
|
|
8
|
+
* pages use each template, the component registry, the media cache), hands them to
|
|
9
|
+
* `panels/site-model.ts` for ordering and labels, and owns what every control does. The markup is
|
|
10
|
+
* `surfaces/site-panel.json`, mounted by `surfaces/site-panel.ts`.
|
|
11
|
+
*
|
|
12
|
+
* **A drag from this panel is an insert the canvas already understands.** A component card drags `{
|
|
13
|
+
* type: "block", fragment: buildComponentInstance(component) }` and a media thumbnail drags `{
|
|
14
|
+
* type: "block", fragment: elementForAsset(file) }`; the canvas drop handler (`panels/dnd.ts`'s
|
|
15
|
+
* `applyDropInstruction`) inserts both and imports the component on drop exactly as the Insert
|
|
16
|
+
* panel's drag does. The sources are registered on nodes the document announced through
|
|
17
|
+
* `onNodeCreated`, and released when their node leaves the document or the panel is remounted.
|
|
18
|
+
*
|
|
19
|
+
* **It stays current by being told, not by polling.** The project index is one cache, dropped by
|
|
20
|
+
* {@link invalidateSiteIndex} — wired into the filesystem subscription's derived-cache list and the
|
|
21
|
+
* media-changed handler in `studio.ts` — and re-read the next time the panel paints. The component
|
|
22
|
+
* registry and the media cache are their owners' caches, read as they stand; the media cache
|
|
23
|
+
* repaints the Navigator itself when its listing lands.
|
|
24
|
+
*
|
|
25
|
+
* @docs studio/interface
|
|
26
|
+
*/
|
|
27
|
+
|
|
28
|
+
import { nothing } from "lit-html";
|
|
29
|
+
import { draggable } from "@atlaskit/pragmatic-drag-and-drop/element/adapter";
|
|
30
|
+
import { disableNativeDragPreview } from "@atlaskit/pragmatic-drag-and-drop/element/disable-native-drag-preview";
|
|
31
|
+
import { isString } from "@jxsuite/schema/guards";
|
|
32
|
+
import { projectState, renderOnly } from "../store";
|
|
33
|
+
import { getPlatform, hasPlatform } from "../platform";
|
|
34
|
+
import { activeTab } from "../workspace/workspace";
|
|
35
|
+
import { scanLibrary } from "../browse/library-model";
|
|
36
|
+
import { routedPageExtensions } from "../format/format-host";
|
|
37
|
+
import { resolveI18n } from "@jxsuite/schema/locale";
|
|
38
|
+
import { createPreviewCache, createPreviewObserver, previewFor } from "../browse/library-preview";
|
|
39
|
+
import { mountPreview, shownPreview } from "../browse/preview-root";
|
|
40
|
+
import {
|
|
41
|
+
_componentRegistryLoaded,
|
|
42
|
+
buildComponentInstance,
|
|
43
|
+
componentRegistry,
|
|
44
|
+
loadComponentRegistry,
|
|
45
|
+
} from "../files/components";
|
|
46
|
+
import { mediaKind } from "../files/media-upload";
|
|
47
|
+
import { previewFileSrc } from "../files/media-paths";
|
|
48
|
+
import { elementForAsset } from "../editor/file-drop-action";
|
|
49
|
+
import { readPageFacts } from "../content/collection-pages";
|
|
50
|
+
import { listCollectionEntryIds } from "../grid/sources/content-source";
|
|
51
|
+
import { loadUsages, onUsagesInvalidated, peekUsages, splitUsages } from "../services/references";
|
|
52
|
+
import { projectMediaFiles } from "../ui/media-picker";
|
|
53
|
+
import { runActiveReported } from "../commands/run-reported";
|
|
54
|
+
import { emptySitePanelValues, mountSitePanelSurface } from "../surfaces/site-panel";
|
|
55
|
+
import { registerPanel } from "./panel-registry";
|
|
56
|
+
import {
|
|
57
|
+
filterNeedle,
|
|
58
|
+
humanize,
|
|
59
|
+
filterPageRows,
|
|
60
|
+
matchesFilter,
|
|
61
|
+
pageRows,
|
|
62
|
+
selectedFlag,
|
|
63
|
+
templateRows,
|
|
64
|
+
} from "./site-model";
|
|
65
|
+
|
|
66
|
+
import type { JxMutableNode } from "@jxsuite/schema/types";
|
|
67
|
+
import type { ComponentEntry } from "../files/components";
|
|
68
|
+
import type { ProjectMediaFile } from "../ui/media-picker";
|
|
69
|
+
import type {
|
|
70
|
+
SitePanelSurfaceHandle,
|
|
71
|
+
SitePanelValues,
|
|
72
|
+
SiteSectionState,
|
|
73
|
+
} from "../surfaces/site-panel";
|
|
74
|
+
import type { SiteComponentRow, SiteMediaRow, SitePageFact } from "./site-model";
|
|
75
|
+
|
|
76
|
+
// ─── The project index ───────────────────────────────────────────────────────
|
|
77
|
+
|
|
78
|
+
/** What the panel knows about the project's files, read once per invalidation. */
|
|
79
|
+
interface SiteIndex {
|
|
80
|
+
pages: SitePageFact[];
|
|
81
|
+
/** The extensions the build routes under `pages/`, read with the formats. */
|
|
82
|
+
pageExtensions: string[];
|
|
83
|
+
/** Every file under `layouts/`. The model keeps the JSON ones. */
|
|
84
|
+
layouts: string[];
|
|
85
|
+
/** Declared collections → entry counts; a count is `null` until its listing lands. */
|
|
86
|
+
collections: Map<string, number | null>;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
let _index: SiteIndex | null = null;
|
|
90
|
+
/**
|
|
91
|
+
* Whether `_index` predates the last invalidation. A stale index stays on screen while its re-read
|
|
92
|
+
* runs (stale-while-revalidate), so a save does not flash every section back to "Reading…" and tear
|
|
93
|
+
* its rows down. Only a project with no index yet shows the loading state.
|
|
94
|
+
*/
|
|
95
|
+
let _indexStale = false;
|
|
96
|
+
let _loading: Promise<void> | null = null;
|
|
97
|
+
/** Bumped by every invalidation, so a read that started before one cannot land after it. */
|
|
98
|
+
let _epoch = 0;
|
|
99
|
+
|
|
100
|
+
/** The collections `project.json` declares, in declaration order. */
|
|
101
|
+
function declaredCollections(): string[] {
|
|
102
|
+
return Object.keys(projectState?.projectConfig?.content ?? {});
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/** The collection a dynamic page's `$paths` expands over, read from the file itself. */
|
|
106
|
+
async function contentTypeOf(path: string): Promise<string | null> {
|
|
107
|
+
const facts = await readPageFacts(path);
|
|
108
|
+
return facts.contentType;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/** Read the index: one walk of `pages/` and `layouts/`, then each dynamic page's `$paths`. */
|
|
112
|
+
async function readIndex(epoch: number): Promise<void> {
|
|
113
|
+
const [scan, pageExtensions] = await Promise.all([
|
|
114
|
+
scanLibrary(["pages", "layouts"], getPlatform()),
|
|
115
|
+
routedPageExtensions(),
|
|
116
|
+
]);
|
|
117
|
+
const pageFiles = scan.files.filter((file) => file.path.startsWith("pages/"));
|
|
118
|
+
const pages = await Promise.all(
|
|
119
|
+
pageFiles.map(async (file): Promise<SitePageFact> => ({
|
|
120
|
+
contentType: file.path.includes("[") ? await contentTypeOf(file.path) : null,
|
|
121
|
+
path: file.path,
|
|
122
|
+
})),
|
|
123
|
+
);
|
|
124
|
+
if (epoch !== _epoch) {
|
|
125
|
+
return;
|
|
126
|
+
}
|
|
127
|
+
// A re-read keeps the counts it already had until their own re-listing lands.
|
|
128
|
+
const previous = _index?.collections;
|
|
129
|
+
const collections = new Map<string, number | null>(
|
|
130
|
+
declaredCollections().map((name) => [name, previous?.get(name) ?? null]),
|
|
131
|
+
);
|
|
132
|
+
_indexStale = false;
|
|
133
|
+
_index = {
|
|
134
|
+
collections,
|
|
135
|
+
layouts: scan.files.flatMap((file) => (file.path.startsWith("layouts/") ? [file.path] : [])),
|
|
136
|
+
pageExtensions,
|
|
137
|
+
pages,
|
|
138
|
+
};
|
|
139
|
+
repaint();
|
|
140
|
+
// The counts are a second pass, only for the collections a page actually nests under.
|
|
141
|
+
const nested = new Set(
|
|
142
|
+
pages.flatMap((page) =>
|
|
143
|
+
page.contentType && collections.has(page.contentType) ? [page.contentType] : [],
|
|
144
|
+
),
|
|
145
|
+
);
|
|
146
|
+
await Promise.all(
|
|
147
|
+
[...nested].map(async (name) => {
|
|
148
|
+
const ids = await listCollectionEntryIds(name).catch(() => null);
|
|
149
|
+
if (epoch === _epoch && ids) {
|
|
150
|
+
collections.set(name, ids.length);
|
|
151
|
+
}
|
|
152
|
+
}),
|
|
153
|
+
);
|
|
154
|
+
if (epoch === _epoch) {
|
|
155
|
+
repaint();
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
/**
|
|
160
|
+
* The index as it stands, starting the read when there is none.
|
|
161
|
+
*
|
|
162
|
+
* The read never rejects, so there is no failure branch here: `scanLibrary` records an unreadable
|
|
163
|
+
* directory rather than throwing (an unreadable project lists as an empty one, and the Files tree
|
|
164
|
+
* reports why), each page's `$paths` read answers "no collection" on any failure, and a count that
|
|
165
|
+
* cannot be listed stays unknown.
|
|
166
|
+
*/
|
|
167
|
+
function siteIndex(): SiteIndex | null {
|
|
168
|
+
if ((!_index || _indexStale) && !_loading && hasPlatform() && projectState) {
|
|
169
|
+
const epoch = _epoch;
|
|
170
|
+
_loading = readIndex(epoch).finally(() => {
|
|
171
|
+
if (epoch === _epoch) {
|
|
172
|
+
_loading = null;
|
|
173
|
+
}
|
|
174
|
+
});
|
|
175
|
+
}
|
|
176
|
+
return _index;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
/**
|
|
180
|
+
* Forget what the panel knows about the project's files. The next paint re-reads them.
|
|
181
|
+
*
|
|
182
|
+
* Wired where the other file-listing caches are dropped (`studio.ts`'s `invalidateDerivedCaches`
|
|
183
|
+
* and the media-changed handler), because a page, layout or component appearing or disappearing is
|
|
184
|
+
* the same event for all of them. The index it has stays on screen until the re-read lands.
|
|
185
|
+
*
|
|
186
|
+
* The component registry is re-scanned too, once, because a component file created outside Studio
|
|
187
|
+
* is otherwise invisible until the project is reopened. A scan reads every JSON file in the
|
|
188
|
+
* project, so it runs only for a batch that could hold a component definition: Studio's own save of
|
|
189
|
+
* a component already updates the registry (`noteComponentSaved`), and a page or an entry is not
|
|
190
|
+
* one.
|
|
191
|
+
*
|
|
192
|
+
* @param paths - The project-relative paths the event batch touched; `undefined` (a gap in the
|
|
193
|
+
* stream, or an upload) means anything may have changed.
|
|
194
|
+
*/
|
|
195
|
+
export function invalidateSiteIndex(paths?: readonly string[]): void {
|
|
196
|
+
_epoch += 1;
|
|
197
|
+
_indexStale = true;
|
|
198
|
+
_loading = null;
|
|
199
|
+
if (paths === undefined || paths.some((path) => mayDefineComponent(path))) {
|
|
200
|
+
_registryStale = true;
|
|
201
|
+
}
|
|
202
|
+
if (_standing) {
|
|
203
|
+
repaint();
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
/** The trees whose JSON files are documents of their own kind, never component definitions. */
|
|
208
|
+
const NON_COMPONENT_DIRS = ["pages/", "layouts/", "content/", "public/"];
|
|
209
|
+
|
|
210
|
+
/** Whether a changed path could add, change or remove a component definition. */
|
|
211
|
+
export function mayDefineComponent(path: string): boolean {
|
|
212
|
+
if (path.startsWith("components/")) {
|
|
213
|
+
return true;
|
|
214
|
+
}
|
|
215
|
+
return (
|
|
216
|
+
path.endsWith(".json") &&
|
|
217
|
+
path !== "project.json" &&
|
|
218
|
+
!NON_COMPONENT_DIRS.some((dir) => path.startsWith(dir))
|
|
219
|
+
);
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
// ─── Components and media ────────────────────────────────────────────────────
|
|
223
|
+
|
|
224
|
+
let _registryStale = false;
|
|
225
|
+
let _registryLoading = false;
|
|
226
|
+
|
|
227
|
+
/** Make sure the registry has been read (or re-read after an invalidation) once. */
|
|
228
|
+
function ensureRegistry(): void {
|
|
229
|
+
if (_registryLoading || (_componentRegistryLoaded && !_registryStale) || !hasPlatform()) {
|
|
230
|
+
return;
|
|
231
|
+
}
|
|
232
|
+
_registryLoading = true;
|
|
233
|
+
_registryStale = false;
|
|
234
|
+
void loadComponentRegistry().finally(() => {
|
|
235
|
+
_registryLoading = false;
|
|
236
|
+
repaint();
|
|
237
|
+
});
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
/**
|
|
241
|
+
* The component cards the filter admits: the project's own components, then the ones its installed
|
|
242
|
+
* packages provide, by tag (§5.7). A package card has no definition to open and no live preview; it
|
|
243
|
+
* drags an instance like any other card, and the drop imports its package (`enableElement`).
|
|
244
|
+
*/
|
|
245
|
+
function componentRows(needle: string, activePath: string | null): SiteComponentRow[] {
|
|
246
|
+
return componentRegistry.flatMap((comp) => {
|
|
247
|
+
// Named in words, as the board names them ("Post card"); the tag is in the hint and the filter.
|
|
248
|
+
const label = humanize(comp.tagName);
|
|
249
|
+
const tag = `<${comp.tagName}>`;
|
|
250
|
+
const path = definitionPath(comp);
|
|
251
|
+
if (!matchesFilter(needle, comp.tagName, path ?? "", comp.package ?? "")) {
|
|
252
|
+
return [];
|
|
253
|
+
}
|
|
254
|
+
const hint = path
|
|
255
|
+
? `Open ${tag}, or drag it onto the canvas`
|
|
256
|
+
: `${tag} from ${comp.package ?? "a package"}: open Packages, or drag it onto the canvas`;
|
|
257
|
+
return [
|
|
258
|
+
{
|
|
259
|
+
hint,
|
|
260
|
+
key: comp.tagName,
|
|
261
|
+
label,
|
|
262
|
+
path: path ?? "",
|
|
263
|
+
selected: selectedFlag(path !== null && path === activePath),
|
|
264
|
+
tagName: comp.tagName,
|
|
265
|
+
},
|
|
266
|
+
];
|
|
267
|
+
});
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
/** A project component's definition file; `null` for a package component, which has none here. */
|
|
271
|
+
function definitionPath(comp: ComponentEntry | undefined): string | null {
|
|
272
|
+
return comp?.path !== undefined && comp.source !== "npm" ? comp.path : null;
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
/**
|
|
276
|
+
* How many thumbnails the Media section draws. It is a strip, not the library: a project with
|
|
277
|
+
* hundreds of images made the panel thousands of pixels tall and gave every reader as many extra
|
|
278
|
+
* buttons, and the Media workspace is where all of them are browsed (§5.7).
|
|
279
|
+
*/
|
|
280
|
+
export const SITE_MEDIA_LIMIT = 8;
|
|
281
|
+
|
|
282
|
+
/** "Show all 766 in Media" under a strip that holds fewer than the filter admits, else `""`. */
|
|
283
|
+
export function mediaMoreLabel(admitted: number, shown: number): string {
|
|
284
|
+
return admitted > shown ? `Show all ${admitted} in Media` : "";
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
/** The media thumbnails the filter admits. */
|
|
288
|
+
function mediaRows(
|
|
289
|
+
files: readonly ProjectMediaFile[],
|
|
290
|
+
needle: string,
|
|
291
|
+
activePath: string | null,
|
|
292
|
+
): SiteMediaRow[] {
|
|
293
|
+
return files.flatMap((file) =>
|
|
294
|
+
matchesFilter(needle, file.name, file.file)
|
|
295
|
+
? [
|
|
296
|
+
{
|
|
297
|
+
key: file.file,
|
|
298
|
+
name: file.name,
|
|
299
|
+
path: file.file,
|
|
300
|
+
selected: selectedFlag(file.file === activePath),
|
|
301
|
+
src: file.isImage ? previewFileSrc(file.file) : "",
|
|
302
|
+
thumb: file.isImage ? ("image" as const) : ("file" as const),
|
|
303
|
+
},
|
|
304
|
+
]
|
|
305
|
+
: [],
|
|
306
|
+
);
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
/** The project's default layout as a project path (`./layouts/base.json` → `layouts/base.json`). */
|
|
310
|
+
function defaultLayoutPath(): string | null {
|
|
311
|
+
const layout = projectState?.projectConfig?.defaults?.layout;
|
|
312
|
+
return isString(layout) ? layout.replace(/^\.\//, "") : null;
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
/** How many pages use a template, from the reference index — `null` where it cannot say yet. */
|
|
316
|
+
function templateUsage(path: string): number | null {
|
|
317
|
+
const state = peekUsages({ path });
|
|
318
|
+
if (state === null) {
|
|
319
|
+
void loadUsages({ path }).then(repaint);
|
|
320
|
+
return null;
|
|
321
|
+
}
|
|
322
|
+
return state.status === "ready" ? splitUsages(state.result).pages : null;
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
// ─── The view ────────────────────────────────────────────────────────────────
|
|
326
|
+
|
|
327
|
+
/** The filter, as typed. One per window: the panel is project-level and there is one project. */
|
|
328
|
+
let _filter = "";
|
|
329
|
+
|
|
330
|
+
/** A section's state from what it has and whether it is still being read. */
|
|
331
|
+
function sectionState(loading: boolean, all: number, shown: number): SiteSectionState {
|
|
332
|
+
if (loading) {
|
|
333
|
+
return "loading";
|
|
334
|
+
}
|
|
335
|
+
if (all === 0) {
|
|
336
|
+
return "empty";
|
|
337
|
+
}
|
|
338
|
+
return shown === 0 ? "nomatch" : "listed";
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
/** A count for the section heading: what is shown, out of what exists while a filter narrows. */
|
|
342
|
+
function countLabel(all: number, shown: number, loading: boolean): string {
|
|
343
|
+
if (loading || all === 0) {
|
|
344
|
+
return "";
|
|
345
|
+
}
|
|
346
|
+
return shown === all ? String(all) : `${shown} of ${all}`;
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
/** Everything the document draws, as one value. */
|
|
350
|
+
export function sitePanelValues(): SitePanelValues {
|
|
351
|
+
if (!projectState) {
|
|
352
|
+
// A project-level panel with no project: one sentence, and nothing read on its behalf.
|
|
353
|
+
return { ...emptySitePanelValues(), filter: _filter };
|
|
354
|
+
}
|
|
355
|
+
const needle = filterNeedle(_filter);
|
|
356
|
+
const activePath = activeTab.value?.documentPath ?? null;
|
|
357
|
+
const index = siteIndex();
|
|
358
|
+
ensureRegistry();
|
|
359
|
+
const media = projectMediaFiles();
|
|
360
|
+
|
|
361
|
+
const allPages = index
|
|
362
|
+
? pageRows(index.pages, index.collections, activePath, {
|
|
363
|
+
extensions: index.pageExtensions,
|
|
364
|
+
i18n: resolveI18n(projectState.projectConfig ?? {}).i18n,
|
|
365
|
+
})
|
|
366
|
+
: [];
|
|
367
|
+
const pages = filterPageRows(allPages, needle);
|
|
368
|
+
const defaultLayout = defaultLayoutPath();
|
|
369
|
+
const allTemplates = index
|
|
370
|
+
? templateRows(index.layouts, templateUsage, activePath, "", defaultLayout)
|
|
371
|
+
: [];
|
|
372
|
+
const templates = index
|
|
373
|
+
? templateRows(index.layouts, templateUsage, activePath, needle, defaultLayout)
|
|
374
|
+
: [];
|
|
375
|
+
const registryLoading = !_componentRegistryLoaded;
|
|
376
|
+
const allComponents = componentRows("", activePath);
|
|
377
|
+
const components = componentRows(needle, activePath);
|
|
378
|
+
const allMedia = media ? mediaRows(media, "", activePath) : [];
|
|
379
|
+
const shownMedia = media ? mediaRows(media, needle, activePath) : [];
|
|
380
|
+
|
|
381
|
+
const countable = (rows: readonly { kind: string }[]) =>
|
|
382
|
+
rows.filter((row) => row.kind !== "collection").length;
|
|
383
|
+
return {
|
|
384
|
+
body: "project",
|
|
385
|
+
components,
|
|
386
|
+
componentsCount: countLabel(allComponents.length, components.length, registryLoading),
|
|
387
|
+
componentsState: sectionState(registryLoading, allComponents.length, components.length),
|
|
388
|
+
filter: _filter,
|
|
389
|
+
media: shownMedia.slice(0, SITE_MEDIA_LIMIT),
|
|
390
|
+
mediaCount: countLabel(allMedia.length, shownMedia.length, media === null),
|
|
391
|
+
mediaMore: mediaMoreLabel(shownMedia.length, Math.min(shownMedia.length, SITE_MEDIA_LIMIT)),
|
|
392
|
+
mediaState: sectionState(media === null, allMedia.length, shownMedia.length),
|
|
393
|
+
pages,
|
|
394
|
+
pagesCount: countLabel(countable(allPages), countable(pages), index === null),
|
|
395
|
+
pagesState: sectionState(index === null, allPages.length, pages.length),
|
|
396
|
+
templates,
|
|
397
|
+
templatesCount: countLabel(allTemplates.length, templates.length, index === null),
|
|
398
|
+
templatesState: sectionState(index === null, allTemplates.length, templates.length),
|
|
399
|
+
};
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
// ─── The actions ─────────────────────────────────────────────────────────────
|
|
403
|
+
|
|
404
|
+
/** Open a project file in its editor. Lazily, because `files.ts` sits above this module. */
|
|
405
|
+
function openPath(path: string): void {
|
|
406
|
+
void import("../files/files").then(({ openFileInTab }) => openFileInTab(path));
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
const ACTIONS = {
|
|
410
|
+
/* Add new (§9.1.1), through its command, so the `+`, the palette and ⌘N are one door. */
|
|
411
|
+
add: () => {
|
|
412
|
+
void runActiveReported("file.new", {}, "Site panel");
|
|
413
|
+
},
|
|
414
|
+
open: openPath,
|
|
415
|
+
openComponent: (tagName: string) => {
|
|
416
|
+
const comp = componentRegistry.find((entry) => entry.tagName === tagName);
|
|
417
|
+
if (!comp) {
|
|
418
|
+
return;
|
|
419
|
+
}
|
|
420
|
+
const path = definitionPath(comp);
|
|
421
|
+
if (path) {
|
|
422
|
+
openPath(path);
|
|
423
|
+
return;
|
|
424
|
+
}
|
|
425
|
+
// A package component has no definition in the project: its package is what there is to see.
|
|
426
|
+
void runActiveReported("view.setActivity", { tab: "packages" }, "Site panel");
|
|
427
|
+
},
|
|
428
|
+
openMedia: () => {
|
|
429
|
+
void runActiveReported("view.setWorkspace", { workspace: "media" }, "Site panel");
|
|
430
|
+
},
|
|
431
|
+
setFilter: (value: string) => {
|
|
432
|
+
_filter = value;
|
|
433
|
+
repaint();
|
|
434
|
+
},
|
|
435
|
+
};
|
|
436
|
+
|
|
437
|
+
// ─── Previews and drags ──────────────────────────────────────────────────────
|
|
438
|
+
|
|
439
|
+
/** What a drag source's preview hook is handed — the one field this panel uses. */
|
|
440
|
+
interface DragPreviewArgs {
|
|
441
|
+
nativeSetDragImage: ((image: Element, x: number, y: number) => void) | null;
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
/**
|
|
445
|
+
* The payload every drag from this panel carries: the `"block"` kind the canvas drop handler and
|
|
446
|
+
* the drag bridge already accept, and the node it inserts. A type alias rather than an interface,
|
|
447
|
+
* because the drag adapter's data is an index-signature record and only an object type literal is
|
|
448
|
+
* assignable to one.
|
|
449
|
+
*/
|
|
450
|
+
// oxlint-disable-next-line typescript/consistent-type-definitions -- see above
|
|
451
|
+
export type BlockDragData = { type: "block"; fragment: JxMutableNode };
|
|
452
|
+
|
|
453
|
+
/** The mounted surface, the box it is in, and everything registered against its nodes. */
|
|
454
|
+
interface StandingPanel {
|
|
455
|
+
/** `null` only inside {@link mountInto}, between building the record and mounting into it. */
|
|
456
|
+
handle: SitePanelSurfaceHandle | null;
|
|
457
|
+
host: HTMLElement;
|
|
458
|
+
/** Drag registrations, by the node they were made on — released when the node is gone. */
|
|
459
|
+
sources: Map<HTMLElement, () => void>;
|
|
460
|
+
cache: ReturnType<typeof createPreviewCache>;
|
|
461
|
+
observer: ReturnType<typeof createPreviewObserver>;
|
|
462
|
+
/** Preview boxes waiting to scroll into view, and the component path each one shows. */
|
|
463
|
+
pending: Map<HTMLElement, string>;
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
let _standing: StandingPanel | null = null;
|
|
467
|
+
|
|
468
|
+
/** Fill one preview box from the cache, now or when it has rendered. */
|
|
469
|
+
async function fillPreview(panel: StandingPanel, slot: HTMLElement, path: string): Promise<void> {
|
|
470
|
+
const rendered = await previewFor(path, panel.cache);
|
|
471
|
+
if (rendered && _standing === panel && slot.isConnected && !shownPreview(slot)) {
|
|
472
|
+
/* The island: the document drew this box EMPTY so the live render can be moved into it
|
|
473
|
+
(studio-ui-guidelines.md §9.4), exactly as the Library's cards are filled, inside a shadow
|
|
474
|
+
root of its own so the component's ids and Studio's never meet. */
|
|
475
|
+
mountPreview(slot, rendered);
|
|
476
|
+
}
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
/** Register one drag source. A node is announced once, when it is created, so once per node. */
|
|
480
|
+
function adoptSource(
|
|
481
|
+
panel: StandingPanel,
|
|
482
|
+
element: HTMLElement,
|
|
483
|
+
data: () => BlockDragData | null,
|
|
484
|
+
): void {
|
|
485
|
+
const cleanup = draggable({
|
|
486
|
+
element,
|
|
487
|
+
getInitialData: () => data() ?? {},
|
|
488
|
+
onGenerateDragPreview: ({ nativeSetDragImage }: DragPreviewArgs) => {
|
|
489
|
+
disableNativeDragPreview({ nativeSetDragImage });
|
|
490
|
+
},
|
|
491
|
+
});
|
|
492
|
+
panel.sources.set(element, cleanup);
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
/** Release every drag source whose node the document has dropped. */
|
|
496
|
+
function releaseDetached(panel: StandingPanel): void {
|
|
497
|
+
for (const [element, cleanup] of panel.sources) {
|
|
498
|
+
if (!element.isConnected) {
|
|
499
|
+
cleanup();
|
|
500
|
+
panel.sources.delete(element);
|
|
501
|
+
}
|
|
502
|
+
}
|
|
503
|
+
panel.observer.releaseDetached();
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
/** Take a standing panel down, with everything registered against it. */
|
|
507
|
+
function dispose(panel: StandingPanel): void {
|
|
508
|
+
panel.handle?.dispose();
|
|
509
|
+
for (const cleanup of panel.sources.values()) {
|
|
510
|
+
cleanup();
|
|
511
|
+
}
|
|
512
|
+
panel.sources.clear();
|
|
513
|
+
panel.observer.destroy();
|
|
514
|
+
panel.cache.clear();
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
/** The `{ type: "block" }` payload a component card drags, or null for a tag no longer registered. */
|
|
518
|
+
export function componentDragData(tagName: string): BlockDragData | null {
|
|
519
|
+
const comp = componentRegistry.find((entry) => entry.tagName === tagName);
|
|
520
|
+
return comp ? { fragment: buildComponentInstance(comp), type: "block" } : null;
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
/** The `{ type: "block" }` payload a media thumbnail drags: the element that references the file. */
|
|
524
|
+
export function mediaDragData(path: string): BlockDragData | null {
|
|
525
|
+
const file = projectMediaFiles()?.find((entry) => entry.file === path);
|
|
526
|
+
if (!file) {
|
|
527
|
+
return null;
|
|
528
|
+
}
|
|
529
|
+
const fragment = elementForAsset({
|
|
530
|
+
kind: mediaKind({ name: file.name }),
|
|
531
|
+
name: file.name,
|
|
532
|
+
path: file.file,
|
|
533
|
+
ref: file.ref,
|
|
534
|
+
});
|
|
535
|
+
return { fragment, type: "block" };
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
/** Mount the surface into `host`, or bring the one already there up to date. */
|
|
539
|
+
function mountInto(host: HTMLElement): void {
|
|
540
|
+
/* A page created, saved or deleted changes how many pages use a template, and the usage cache is
|
|
541
|
+
dropped for it: the Templates rows ask again on the repaint this asks for. */
|
|
542
|
+
_usagesUnlisten ??= onUsagesInvalidated(repaint);
|
|
543
|
+
if (_standing && (_standing.host !== host || _standing.handle?.connected() === false)) {
|
|
544
|
+
dispose(_standing);
|
|
545
|
+
_standing = null;
|
|
546
|
+
}
|
|
547
|
+
if (_standing) {
|
|
548
|
+
_standing.handle?.update(sitePanelValues());
|
|
549
|
+
releaseDetached(_standing);
|
|
550
|
+
return;
|
|
551
|
+
}
|
|
552
|
+
/* The record first, then the mount: the islands are announced while the document is built, and
|
|
553
|
+
each one is handed straight to the record it belongs to. */
|
|
554
|
+
const pending = new Map<HTMLElement, string>();
|
|
555
|
+
const panel: StandingPanel = {
|
|
556
|
+
cache: createPreviewCache(),
|
|
557
|
+
handle: null,
|
|
558
|
+
host,
|
|
559
|
+
observer: createPreviewObserver((element) => {
|
|
560
|
+
const path = element instanceof HTMLElement ? pending.get(element) : undefined;
|
|
561
|
+
if (path !== undefined && element instanceof HTMLElement) {
|
|
562
|
+
pending.delete(element);
|
|
563
|
+
void fillPreview(panel, element, path);
|
|
564
|
+
}
|
|
565
|
+
}),
|
|
566
|
+
pending,
|
|
567
|
+
sources: new Map(),
|
|
568
|
+
};
|
|
569
|
+
_standing = panel;
|
|
570
|
+
panel.handle = mountSitePanelSurface(host, sitePanelValues(), ACTIONS, {
|
|
571
|
+
componentSource: (element, tagName) => {
|
|
572
|
+
adoptSource(panel, element, () => componentDragData(tagName));
|
|
573
|
+
},
|
|
574
|
+
mediaSource: (element, path) => {
|
|
575
|
+
adoptSource(panel, element, () => mediaDragData(path));
|
|
576
|
+
},
|
|
577
|
+
preview: (element, tagName) => {
|
|
578
|
+
// Only a project component has a definition to render; a package card keeps its box empty.
|
|
579
|
+
const path = definitionPath(componentRegistry.find((comp) => comp.tagName === tagName));
|
|
580
|
+
if (path) {
|
|
581
|
+
pending.set(element, path);
|
|
582
|
+
panel.observer.observe(element);
|
|
583
|
+
}
|
|
584
|
+
},
|
|
585
|
+
});
|
|
586
|
+
}
|
|
587
|
+
|
|
588
|
+
/** The usage-invalidation subscription, taken once the panel first mounts. */
|
|
589
|
+
let _usagesUnlisten: (() => void) | null = null;
|
|
590
|
+
|
|
591
|
+
/** Repaint the Navigator, which repaints this panel when it is the one showing. */
|
|
592
|
+
function repaint(): void {
|
|
593
|
+
renderOnly("leftPanel");
|
|
594
|
+
}
|
|
595
|
+
|
|
596
|
+
/**
|
|
597
|
+
* The Navigator's content area inside a panel body, where a mounted document goes (the reason is
|
|
598
|
+
* `panels/elements-panel.ts`'s: the body is lit's part, and its child is the box a panel owns).
|
|
599
|
+
*/
|
|
600
|
+
function contentArea(body: HTMLElement): HTMLElement {
|
|
601
|
+
return body.querySelector<HTMLElement>(".panel-content") ?? body;
|
|
602
|
+
}
|
|
603
|
+
|
|
604
|
+
/**
|
|
605
|
+
* Contribute the Site panel: project level (it lists and opens the project's files), the Project
|
|
606
|
+
* rail group's one button, and the Navigator's default (`shell.ts`'s `DEFAULT_PANEL_ID`).
|
|
607
|
+
*/
|
|
608
|
+
export function registerSitePanel(): void {
|
|
609
|
+
registerPanel({
|
|
610
|
+
id: "site",
|
|
611
|
+
title: "Site",
|
|
612
|
+
level: "project",
|
|
613
|
+
dock: "navigator",
|
|
614
|
+
icon: "browser",
|
|
615
|
+
render: () => nothing,
|
|
616
|
+
afterRender: (_ctx, host) => {
|
|
617
|
+
mountInto(contentArea(host));
|
|
618
|
+
},
|
|
619
|
+
});
|
|
620
|
+
}
|
|
621
|
+
|
|
622
|
+
/** Forget the mounted panel and the index — tests, and a project switch. */
|
|
623
|
+
export function resetSitePanel(): void {
|
|
624
|
+
if (_standing) {
|
|
625
|
+
dispose(_standing);
|
|
626
|
+
}
|
|
627
|
+
_standing = null;
|
|
628
|
+
_usagesUnlisten?.();
|
|
629
|
+
_usagesUnlisten = null;
|
|
630
|
+
_filter = "";
|
|
631
|
+
_epoch += 1;
|
|
632
|
+
_index = null;
|
|
633
|
+
_indexStale = false;
|
|
634
|
+
_loading = null;
|
|
635
|
+
// A new project's components are not the last one's, whatever the registry says it has loaded.
|
|
636
|
+
_registryStale = true;
|
|
637
|
+
_registryLoading = false;
|
|
638
|
+
}
|