@jxsuite/studio 6.1.2 → 8.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +5 -5
- package/dist/chunks/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,55 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Which files are project media, by extension — the classification alone, with nothing imported.
|
|
3
|
+
*
|
|
4
|
+
* `files/media-upload.ts` owns uploading and re-exports these; they live here so a module that only
|
|
5
|
+
* needs to ask "is this file media" (the collection matcher behind the Content workspace, which the
|
|
6
|
+
* canvas resolver reads too) does not pull the upload core, the platform and the reference index in
|
|
7
|
+
* behind the question.
|
|
8
|
+
*
|
|
9
|
+
* @docs studio/projects/media
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
/** Extensions rendered as a thumbnail (media picker, cards, file-tree icons). */
|
|
13
|
+
export const IMAGE_EXTENSIONS = new Set([
|
|
14
|
+
".jpg",
|
|
15
|
+
".jpeg",
|
|
16
|
+
".png",
|
|
17
|
+
".gif",
|
|
18
|
+
".svg",
|
|
19
|
+
".webp",
|
|
20
|
+
".avif",
|
|
21
|
+
".ico",
|
|
22
|
+
]);
|
|
23
|
+
|
|
24
|
+
/** Extensions an upload places as a `<video>`. */
|
|
25
|
+
export const VIDEO_EXTENSIONS = new Set([".mp4", ".webm", ".mov", ".m4v"]);
|
|
26
|
+
|
|
27
|
+
/** Extensions an upload places as an `<audio>`. */
|
|
28
|
+
export const AUDIO_EXTENSIONS = new Set([".mp3", ".wav", ".ogg", ".m4a", ".flac"]);
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Every extension Studio treats as project media: a superset of {@link IMAGE_EXTENSIONS} and of
|
|
32
|
+
* every extension classified as video or audio, so a file it classifies is one the media listing
|
|
33
|
+
* shows.
|
|
34
|
+
*/
|
|
35
|
+
export const MEDIA_EXTENSIONS = new Set([
|
|
36
|
+
...IMAGE_EXTENSIONS,
|
|
37
|
+
...VIDEO_EXTENSIONS,
|
|
38
|
+
...AUDIO_EXTENSIONS,
|
|
39
|
+
".pdf",
|
|
40
|
+
".woff",
|
|
41
|
+
".woff2",
|
|
42
|
+
".ttf",
|
|
43
|
+
".otf",
|
|
44
|
+
]);
|
|
45
|
+
|
|
46
|
+
/** Lowercased extension including the dot, or `""` when the name carries none. */
|
|
47
|
+
export function extensionOf(name: string): string {
|
|
48
|
+
const dot = name.lastIndexOf(".");
|
|
49
|
+
return dot > 0 ? name.slice(dot).toLowerCase() : "";
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/** Whether a file extension (with or without the leading dot) is a previewable image. */
|
|
53
|
+
export function isImage(ext: string): boolean {
|
|
54
|
+
return IMAGE_EXTENSIONS.has(ext.startsWith(".") ? ext.toLowerCase() : `.${ext.toLowerCase()}`);
|
|
55
|
+
}
|
|
@@ -3,15 +3,18 @@
|
|
|
3
3
|
* Media upload core — the single path every upload surface goes through.
|
|
4
4
|
*
|
|
5
5
|
* Four surfaces feed it: the image field's Upload button (ui/media-picker), a file dropped on the
|
|
6
|
-
* canvas (editor/file-drop-action), a file dropped on the Files tree (files/files), and the
|
|
7
|
-
*
|
|
8
|
-
* directory is chosen; naming, collision handling, the platform call,
|
|
9
|
-
* shared here.
|
|
6
|
+
* canvas (editor/file-drop-action), a file dropped on the Files tree (files/files), and the Media
|
|
7
|
+
* workspace's drop zone and Upload button (media/media-library, and Add new › Media). They differ
|
|
8
|
+
* only in how the destination directory is chosen; naming, collision handling, the platform call,
|
|
9
|
+
* and cache invalidation are shared here.
|
|
10
10
|
*
|
|
11
|
-
* Destination (specs/site-architecture.md §9.1/§9.3): a document inside a content
|
|
12
|
-
* its media co-located in `content/<collection>/images/` — the entry references it
|
|
13
|
-
* and the collection loader rewrites it to the asset mount. Everything else goes to
|
|
14
|
-
* which is served from the site root
|
|
11
|
+
* Destination (specs/site-architecture.md §9.1/§9.4, studio.md §9.3): a document inside a content
|
|
12
|
+
* collection keeps its media co-located in `content/<collection>/images/` — the entry references it
|
|
13
|
+
* file-relatively and the collection loader rewrites it to the asset mount. Everything else goes to
|
|
14
|
+
* {@link DEFAULT_MEDIA_DIR}, `public/media/`, which is served from the site root as `/media/`.
|
|
15
|
+
*
|
|
16
|
+
* {@link replaceAsset} is the one door that writes over an existing file, and it is asked for by
|
|
17
|
+
* name: the Media workspace's **Replace** (studio.md §9.3).
|
|
15
18
|
*
|
|
16
19
|
* @docs studio/projects/media
|
|
17
20
|
*/
|
|
@@ -19,37 +22,22 @@
|
|
|
19
22
|
import { errorMessage } from "@jxsuite/schema/parse";
|
|
20
23
|
import { getPlatform, hasPlatform } from "../platform";
|
|
21
24
|
import { notify } from "../services/notify";
|
|
25
|
+
import { invalidateUsages } from "../services/references";
|
|
22
26
|
import { activeTab } from "../workspace/workspace";
|
|
23
27
|
import { forgetVersions, notedListing } from "./asset-versions";
|
|
28
|
+
import {
|
|
29
|
+
AUDIO_EXTENSIONS,
|
|
30
|
+
IMAGE_EXTENSIONS,
|
|
31
|
+
MEDIA_EXTENSIONS,
|
|
32
|
+
VIDEO_EXTENSIONS,
|
|
33
|
+
extensionOf,
|
|
34
|
+
} from "./media-types";
|
|
24
35
|
|
|
25
36
|
// ─── File classification ─────────────────────────────────────────────────────
|
|
26
37
|
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
".jpeg",
|
|
31
|
-
".png",
|
|
32
|
-
".gif",
|
|
33
|
-
".svg",
|
|
34
|
-
".webp",
|
|
35
|
-
".avif",
|
|
36
|
-
".ico",
|
|
37
|
-
]);
|
|
38
|
-
|
|
39
|
-
/** Every extension Studio treats as project media. Superset of {@link IMAGE_EXTENSIONS}. */
|
|
40
|
-
export const MEDIA_EXTENSIONS = new Set([
|
|
41
|
-
...IMAGE_EXTENSIONS,
|
|
42
|
-
".mp4",
|
|
43
|
-
".webm",
|
|
44
|
-
".mp3",
|
|
45
|
-
".wav",
|
|
46
|
-
".ogg",
|
|
47
|
-
".pdf",
|
|
48
|
-
".woff",
|
|
49
|
-
".woff2",
|
|
50
|
-
".ttf",
|
|
51
|
-
".otf",
|
|
52
|
-
]);
|
|
38
|
+
/* The classification lives in `media-types.ts`, which imports nothing; it is re-exported here so
|
|
39
|
+
every existing caller of the upload core keeps one import. */
|
|
40
|
+
export { IMAGE_EXTENSIONS, MEDIA_EXTENSIONS, extensionOf, isImage } from "./media-types";
|
|
53
41
|
|
|
54
42
|
/** A byte count as a person reads it, for a refusal that names the limit. */
|
|
55
43
|
function formatBytes(bytes: number): string {
|
|
@@ -104,25 +92,11 @@ export interface UploadedAsset {
|
|
|
104
92
|
kind: AssetKind;
|
|
105
93
|
}
|
|
106
94
|
|
|
107
|
-
/** Lowercased extension including the dot, or `""` when the name carries none. */
|
|
108
|
-
export function extensionOf(name: string): string {
|
|
109
|
-
const dot = name.lastIndexOf(".");
|
|
110
|
-
return dot > 0 ? name.slice(dot).toLowerCase() : "";
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
/** Whether a file extension (with or without the leading dot) is a previewable image. */
|
|
114
|
-
export function isImage(ext: string): boolean {
|
|
115
|
-
return IMAGE_EXTENSIONS.has(ext.startsWith(".") ? ext.toLowerCase() : `.${ext.toLowerCase()}`);
|
|
116
|
-
}
|
|
117
|
-
|
|
118
95
|
/** Whether a filename is project media. */
|
|
119
96
|
export function isMediaFile(name: string): boolean {
|
|
120
97
|
return MEDIA_EXTENSIONS.has(extensionOf(name));
|
|
121
98
|
}
|
|
122
99
|
|
|
123
|
-
const VIDEO_EXTENSIONS = new Set([".mp4", ".webm", ".mov", ".m4v"]);
|
|
124
|
-
const AUDIO_EXTENSIONS = new Set([".mp3", ".wav", ".ogg", ".m4a", ".flac"]);
|
|
125
|
-
|
|
126
100
|
/**
|
|
127
101
|
* Classify a file by MIME type, falling back to its extension. A drop from the OS usually carries a
|
|
128
102
|
* `type`, but it is empty for unrecognized extensions and for some Linux file managers.
|
|
@@ -151,18 +125,59 @@ export function mediaKind(file: { name: string; type?: string }): AssetKind {
|
|
|
151
125
|
return "file";
|
|
152
126
|
}
|
|
153
127
|
|
|
128
|
+
/** The font extensions, which a browser shows as a specimen rather than as a file. */
|
|
129
|
+
export const FONT_EXTENSIONS: ReadonlySet<string> = new Set([".woff", ".woff2", ".ttf", ".otf"]);
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* What a media file IS, as the Media workspace's filters name it: one of five kinds.
|
|
133
|
+
*
|
|
134
|
+
* Distinct from {@link AssetKind}, which answers "what element does this become" and so has no word
|
|
135
|
+
* for a font or a PDF (both become an `<a href>`). Decided by extension alone, because a file
|
|
136
|
+
* already on disk carries no MIME type: a font first, then the three a browser has an element for,
|
|
137
|
+
* and everything else (a PDF) is a document.
|
|
138
|
+
*/
|
|
139
|
+
export type MediaFileKind = "image" | "video" | "audio" | "font" | "document";
|
|
140
|
+
|
|
141
|
+
/** The {@link MediaFileKind} of a file, by its name. */
|
|
142
|
+
export function mediaFileKind(name: string): MediaFileKind {
|
|
143
|
+
if (FONT_EXTENSIONS.has(extensionOf(name))) {
|
|
144
|
+
return "font";
|
|
145
|
+
}
|
|
146
|
+
const kind = mediaKind({ name });
|
|
147
|
+
return kind === "file" ? "document" : kind;
|
|
148
|
+
}
|
|
149
|
+
|
|
154
150
|
// ─── Destination resolution ──────────────────────────────────────────────────
|
|
155
151
|
|
|
156
152
|
/** Matches a path inside a content collection: `content/<collection>/…`. */
|
|
157
153
|
const CONTENT_ENTRY = /^content\/([^/]+)\//;
|
|
158
154
|
|
|
155
|
+
/**
|
|
156
|
+
* Where media with no context lands: `public/media/`, referenced from the site root as `/media/…`.
|
|
157
|
+
*
|
|
158
|
+
* A default and not a rule (site-architecture.md §9.4): a file under any other `public/` path is
|
|
159
|
+
* published and listed exactly the same. Its one definition, so the Media workspace, Add new ›
|
|
160
|
+
* Media and the Library's All view cannot disagree about it.
|
|
161
|
+
*/
|
|
162
|
+
export const DEFAULT_MEDIA_DIR = "public/media";
|
|
163
|
+
|
|
159
164
|
/**
|
|
160
165
|
* The directory an upload lands in when the caller doesn't name one. A document inside a content
|
|
161
|
-
* collection gets `content/<collection>/images`; everything else gets
|
|
166
|
+
* collection gets `content/<collection>/images`; everything else gets {@link DEFAULT_MEDIA_DIR}.
|
|
162
167
|
*/
|
|
163
168
|
export function uploadDirFor(documentPath: string | null): string {
|
|
164
169
|
const match = documentPath ? CONTENT_ENTRY.exec(documentPath.replaceAll("\\", "/")) : null;
|
|
165
|
-
return match ? `content/${match[1]}/images` :
|
|
170
|
+
return match ? `content/${match[1]}/images` : DEFAULT_MEDIA_DIR;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
/**
|
|
174
|
+
* Whether `dir` is a folder {@link uploadDirFor} answers: `public/media`, or a collection's
|
|
175
|
+
* `content/<collection>/images`. An upload creates these on its own, so they are the folders a
|
|
176
|
+
* media delete takes away again once it has emptied them (`media/media-library.ts`).
|
|
177
|
+
*/
|
|
178
|
+
export function isUploadDestination(dir: string): boolean {
|
|
179
|
+
const normalized = dir.replaceAll("\\", "/");
|
|
180
|
+
return normalized === DEFAULT_MEDIA_DIR || /^content\/[^/]+\/images$/.test(normalized);
|
|
166
181
|
}
|
|
167
182
|
|
|
168
183
|
/**
|
|
@@ -223,8 +238,61 @@ export function setMediaChangedHandler(fn: ((dir: string) => void | Promise<void
|
|
|
223
238
|
mediaChangedHandler = fn;
|
|
224
239
|
}
|
|
225
240
|
|
|
241
|
+
/**
|
|
242
|
+
* Say that the media in `dir` changed by some door other than {@link uploadAssets} — a replace, or
|
|
243
|
+
* the Media workspace's delete — so every cache keyed on the listing is dropped the same way.
|
|
244
|
+
*/
|
|
245
|
+
export async function announceMediaChanged(dir: string): Promise<void> {
|
|
246
|
+
await mediaChangedHandler?.(dir);
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
/** The directory part of a project path, `.` for a file at the root. */
|
|
250
|
+
function parentDir(path: string): string {
|
|
251
|
+
const normalized = path.replaceAll("\\", "/");
|
|
252
|
+
const slash = normalized.lastIndexOf("/");
|
|
253
|
+
return slash > 0 ? normalized.slice(0, slash) : ".";
|
|
254
|
+
}
|
|
255
|
+
|
|
226
256
|
// ─── Upload ──────────────────────────────────────────────────────────────────
|
|
227
257
|
|
|
258
|
+
/**
|
|
259
|
+
* What a media surface may upload: any file the media listing shows (`media`), or only pictures
|
|
260
|
+
* (`image`, a picker for a field that holds one).
|
|
261
|
+
*
|
|
262
|
+
* A file input's `accept` narrows only the chooser; a drop carries whatever the OS hands over, and
|
|
263
|
+
* `image/*` admits types (`.heic`, `.bmp`) no listing names. A surface that selects what it just
|
|
264
|
+
* uploaded must not upload a file it will then fail to show.
|
|
265
|
+
*/
|
|
266
|
+
export type UploadOnly = "media" | "image";
|
|
267
|
+
|
|
268
|
+
/** The files `only` admits; the rest are named in one warning rather than written unseen. */
|
|
269
|
+
function keepListable(files: File[], only: UploadOnly): File[] {
|
|
270
|
+
const admits = (file: File): boolean =>
|
|
271
|
+
only === "image" ? IMAGE_EXTENSIONS.has(extensionOf(file.name)) : isMediaFile(file.name);
|
|
272
|
+
const kept: File[] = [];
|
|
273
|
+
const skipped: string[] = [];
|
|
274
|
+
for (const file of files) {
|
|
275
|
+
if (admits(file)) {
|
|
276
|
+
kept.push(file);
|
|
277
|
+
} else {
|
|
278
|
+
skipped.push(file.name);
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
if (skipped.length > 0) {
|
|
282
|
+
notify.warn(
|
|
283
|
+
skipped.length === 1 ? `Skipped ${skipped[0]}` : `Skipped ${skipped.length} files`,
|
|
284
|
+
{
|
|
285
|
+
detail:
|
|
286
|
+
only === "image"
|
|
287
|
+
? `Only images can be used here: ${skipped.join(", ")}.`
|
|
288
|
+
: `Studio lists images, video, audio, PDFs and fonts as media, so ${skipped.join(", ")} would not appear in it.`,
|
|
289
|
+
source: "Media",
|
|
290
|
+
},
|
|
291
|
+
);
|
|
292
|
+
}
|
|
293
|
+
return kept;
|
|
294
|
+
}
|
|
295
|
+
|
|
228
296
|
/**
|
|
229
297
|
* Upload files into the project and report what landed.
|
|
230
298
|
*
|
|
@@ -235,9 +303,9 @@ export function setMediaChangedHandler(fn: ((dir: string) => void | Promise<void
|
|
|
235
303
|
*/
|
|
236
304
|
export async function uploadAssets(
|
|
237
305
|
files: Iterable<File>,
|
|
238
|
-
opts: { dir?: string } = {},
|
|
306
|
+
opts: { dir?: string; only?: UploadOnly } = {},
|
|
239
307
|
): Promise<UploadedAsset[]> {
|
|
240
|
-
const list = [...files];
|
|
308
|
+
const list = opts.only === undefined ? [...files] : keepListable([...files], opts.only);
|
|
241
309
|
if (list.length === 0) {
|
|
242
310
|
return [];
|
|
243
311
|
}
|
|
@@ -297,3 +365,92 @@ export async function uploadAssets(
|
|
|
297
365
|
}
|
|
298
366
|
return uploaded;
|
|
299
367
|
}
|
|
368
|
+
|
|
369
|
+
/** Extensions that name one format, so either may stand in for the other. */
|
|
370
|
+
const SAME_FORMAT: readonly ReadonlySet<string>[] = [
|
|
371
|
+
new Set([".jpg", ".jpeg"]),
|
|
372
|
+
new Set([".tif", ".tiff"]),
|
|
373
|
+
];
|
|
374
|
+
|
|
375
|
+
/** Every extension that names the same format as `ext` (itself included). */
|
|
376
|
+
function formatSiblings(ext: string): ReadonlySet<string> {
|
|
377
|
+
return SAME_FORMAT.find((group) => group.has(ext)) ?? new Set([ext]);
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
/**
|
|
381
|
+
* The `accept` for a Replace of the file at `path`: its own format, and only that.
|
|
382
|
+
*
|
|
383
|
+
* The path is what every document names, and the server answers it with the type its EXTENSION
|
|
384
|
+
* implies, so bytes of another format under it are a broken picture on every page that uses it.
|
|
385
|
+
*/
|
|
386
|
+
export function replaceAccept(path: string): string {
|
|
387
|
+
return [...formatSiblings(extensionOf(path))].join(",");
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
/** Whether `file` may replace the file at `path`: the same format, by extension. */
|
|
391
|
+
export function canReplaceWith(path: string, file: { name: string }): boolean {
|
|
392
|
+
return formatSiblings(extensionOf(path)).has(extensionOf(file.name));
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
/**
|
|
396
|
+
* Write `file`'s bytes over the existing media file at `path` — the Media workspace's **Replace**.
|
|
397
|
+
*
|
|
398
|
+
* The only overwrite in this module, and asked for by name (studio.md §9.3): every reference to
|
|
399
|
+
* `path` keeps resolving and now shows the new bytes. Nothing about the path tells a cache that the
|
|
400
|
+
* file changed, so this forgets the path's content version (§3.4) and announces the change, which
|
|
401
|
+
* drops the media listing, its metadata and the usage answers derived from it.
|
|
402
|
+
*
|
|
403
|
+
* A backend that reports a DIFFERENT path did not replace anything: it wrote a second file beside
|
|
404
|
+
* the first, and the references still name the old bytes. That is said rather than called success.
|
|
405
|
+
*
|
|
406
|
+
* @returns Whether the bytes now at `path` are the new ones.
|
|
407
|
+
*/
|
|
408
|
+
export async function replaceAsset(path: string, file: File): Promise<boolean> {
|
|
409
|
+
const name = path.split("/").pop() ?? path;
|
|
410
|
+
/* An `accept` narrows only the chooser (and a backend's own picker may ignore it), so the format is
|
|
411
|
+
checked here too: same path, other format, is every reference broken at once. */
|
|
412
|
+
if (!canReplaceWith(path, file)) {
|
|
413
|
+
notify.error(`${file.name} cannot replace ${name}.`, {
|
|
414
|
+
detail: `${name} is ${extensionOf(path) || "a file with no extension"} and ${file.name} is ${extensionOf(file.name) || "a file with no extension"}. Every page that uses ${name} would get bytes its address does not describe; choose a file of the same type, or upload it as a new file.`,
|
|
415
|
+
path,
|
|
416
|
+
source: "Media",
|
|
417
|
+
});
|
|
418
|
+
return false;
|
|
419
|
+
}
|
|
420
|
+
const maxBytes = hasPlatform() ? getPlatform().assetCapabilities?.maxUploadBytes : undefined;
|
|
421
|
+
if (maxBytes !== undefined && file.size > maxBytes) {
|
|
422
|
+
notify.error(`${file.name} is too large.`, {
|
|
423
|
+
detail: `This backend accepts at most ${formatBytes(maxBytes)}; that file is ${formatBytes(file.size)}.`,
|
|
424
|
+
source: "Media",
|
|
425
|
+
});
|
|
426
|
+
return false;
|
|
427
|
+
}
|
|
428
|
+
let written: string;
|
|
429
|
+
try {
|
|
430
|
+
({ path: written } = await getPlatform().uploadFile(path, file));
|
|
431
|
+
} catch (error) {
|
|
432
|
+
notify.error(`Could not replace ${name}.`, {
|
|
433
|
+
detail: errorMessage(error),
|
|
434
|
+
path,
|
|
435
|
+
source: "Media",
|
|
436
|
+
});
|
|
437
|
+
return false;
|
|
438
|
+
}
|
|
439
|
+
forgetVersions([path, written]);
|
|
440
|
+
// The answers were about the old bytes' place in the project; the place is the same, but a stale
|
|
441
|
+
// "Used on" is a list the reader acts on, so it is asked again rather than trusted.
|
|
442
|
+
invalidateUsages();
|
|
443
|
+
await announceMediaChanged(parentDir(path));
|
|
444
|
+
if (written !== path) {
|
|
445
|
+
notify.warn(`${name} was not replaced.`, {
|
|
446
|
+
detail: `The backend stored the new file as ${written}, so every reference still shows the old one.`,
|
|
447
|
+
path,
|
|
448
|
+
source: "Media",
|
|
449
|
+
// A Problem rather than a toast: two files now exist, and the reader has a decision to make.
|
|
450
|
+
tier: "problem",
|
|
451
|
+
});
|
|
452
|
+
return false;
|
|
453
|
+
}
|
|
454
|
+
notify.success(`Replaced ${name}`);
|
|
455
|
+
return true;
|
|
456
|
+
}
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The rules a new file's name is held to, with no dialog and no write attached.
|
|
3
|
+
*
|
|
4
|
+
* `createFileIn` (`files/files.ts`) used to keep these inside one closure over its prompt, which
|
|
5
|
+
* was right while the prompt was the only creation flow there was. Add new (`create/add-new.ts`,
|
|
6
|
+
* `specs/studio.md` §9.1.1) composes a name the same way and must refuse the same things — a blank
|
|
7
|
+
* name, a name that escapes the destination, a name that is already taken — and a second copy of
|
|
8
|
+
* those rules would disagree with the first the first time either was fixed. So the composing and
|
|
9
|
+
* the judging live here, DOM-free, and both flows call them; only the write, and what each flow
|
|
10
|
+
* does around it, differs.
|
|
11
|
+
*
|
|
12
|
+
* Nothing here lists a directory. The caller supplies the `taken` names it listed, because when
|
|
13
|
+
* that listing happens is the caller's concern: before the prompt opens for New File, and once per
|
|
14
|
+
* destination directory for Add new, whose destination moves as the reader types.
|
|
15
|
+
*
|
|
16
|
+
* @docs studio/interface
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* The picker row meaning "I will type the whole name myself".
|
|
21
|
+
*
|
|
22
|
+
* It is what keeps the format picker from being a cage. `New File…` is the only generic
|
|
23
|
+
* file-creation affordance in Studio and both backends create intermediate directories on write, so
|
|
24
|
+
* without this row `styles/main.css`, `public/robots.txt`, `.gitignore` and a `credits.txt` beside
|
|
25
|
+
* a collection's images all become uncreatable — and the picker would have taken away more than it
|
|
26
|
+
* gave. The picker stays the authority on the extension; this is one of its answers.
|
|
27
|
+
*/
|
|
28
|
+
export const OTHER_FORMAT = "__other__";
|
|
29
|
+
|
|
30
|
+
/** How the extension of a new file is settled. */
|
|
31
|
+
export type FormatChoice =
|
|
32
|
+
/** Picker over every creatable format, plus Other…. The field is a NAME, taken verbatim. */
|
|
33
|
+
| { kind: "choose"; defaultExt?: string; docKind?: "page" | "component" | "content" }
|
|
34
|
+
/**
|
|
35
|
+
* Picker holding ONE format, plus Other… — the collection's own extension. The field is a NAME.
|
|
36
|
+
* `because` is the sentence a refusal quotes when Other… would smuggle a foreign document in.
|
|
37
|
+
*/
|
|
38
|
+
| { kind: "locked"; ext: string; because: string }
|
|
39
|
+
/** No picker. The field is a DISPLAY NAME, slugified, and `ext` is appended. */
|
|
40
|
+
| { kind: "fixed"; ext: string };
|
|
41
|
+
|
|
42
|
+
/** A display name, reduced to a file stem. Lowercase by design: this is a slug. */
|
|
43
|
+
export function slugify(input: string): string {
|
|
44
|
+
return input
|
|
45
|
+
.trim()
|
|
46
|
+
.toLowerCase()
|
|
47
|
+
.replaceAll(/\s+/g, "-")
|
|
48
|
+
.replaceAll(/[^a-z\d-]/g, "");
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/** The extension of a file name, including the dot, or `""`. */
|
|
52
|
+
export function extensionOfName(name: string): string {
|
|
53
|
+
const dot = name.lastIndexOf(".");
|
|
54
|
+
return dot > 0 ? name.slice(dot) : "";
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* The file name a field value and a pick compose to — three modes, not two.
|
|
59
|
+
*
|
|
60
|
+
* The verbatim STEM mode is not a convenience. The slugifier lowercases and strips everything
|
|
61
|
+
* outside `[a-z0-9-]`, which turns `[slug]` into `slug` — and eight shipped starters carry
|
|
62
|
+
* `pages/[slug].json`, `[sku].json`, `[...slug].json`. A picker that slugified would make a dynamic
|
|
63
|
+
* route uncreatable and would say nothing about it.
|
|
64
|
+
*
|
|
65
|
+
* @param input What the reader typed.
|
|
66
|
+
* @param picked The picker's value: an extension, {@link OTHER_FORMAT}, or `""` with no picker.
|
|
67
|
+
* @param choice How the extension is settled; absent takes the whole name verbatim.
|
|
68
|
+
*/
|
|
69
|
+
export function composeNewFileName(input: string, picked: string, choice?: FormatChoice): string {
|
|
70
|
+
const trimmed = input.trim();
|
|
71
|
+
if (choice === undefined || picked === OTHER_FORMAT) {
|
|
72
|
+
return trimmed;
|
|
73
|
+
}
|
|
74
|
+
if (choice.kind === "fixed") {
|
|
75
|
+
const slug = slugify(trimmed);
|
|
76
|
+
return slug === "" ? "" : `${slug}${choice.ext}`;
|
|
77
|
+
}
|
|
78
|
+
return trimmed.toLowerCase().endsWith(picked.toLowerCase()) ? trimmed : `${trimmed}${picked}`;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/** What a name is judged against: where it is going, and what is already there. */
|
|
82
|
+
export interface NewFileNameCheck {
|
|
83
|
+
/** Destination directory, project-relative. `"."` is the project root. */
|
|
84
|
+
dir: string;
|
|
85
|
+
choice?: FormatChoice | undefined;
|
|
86
|
+
/** The destination's entries, LOWER-CASED: APFS and NTFS compare without case, and so does this. */
|
|
87
|
+
taken: ReadonlySet<string>;
|
|
88
|
+
/** Extensions that are documents, lower-cased (`knownDocumentExtensions()`). */
|
|
89
|
+
known?: ReadonlySet<string>;
|
|
90
|
+
/** The picker's rows, for naming a pick in a refusal. */
|
|
91
|
+
rows?: readonly { label: string; value: string }[] | null;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* The refusal a name earns, or `""` when it is usable.
|
|
96
|
+
*
|
|
97
|
+
* In order: blank, a display name that slugifies to nothing, a path that starts or ends with a
|
|
98
|
+
* slash, a `..` segment, a subfolder where the field is a name rather than a whole file name, an
|
|
99
|
+
* extension that contradicts the pick, a document smuggled through Other… past a collection's lock,
|
|
100
|
+
* and finally a name already taken in the destination. The first that applies is the sentence.
|
|
101
|
+
*
|
|
102
|
+
* @param value What the reader typed.
|
|
103
|
+
* @param picked The picker's value, as {@link composeNewFileName} takes it.
|
|
104
|
+
* @param check The destination and what it already holds.
|
|
105
|
+
*/
|
|
106
|
+
export function validateNewFileName(
|
|
107
|
+
value: string,
|
|
108
|
+
picked: string,
|
|
109
|
+
check: NewFileNameCheck,
|
|
110
|
+
): string {
|
|
111
|
+
const { choice, dir, known = new Set<string>(), rows = null, taken } = check;
|
|
112
|
+
const trimmed = value.trim();
|
|
113
|
+
if (!trimmed) {
|
|
114
|
+
return choice?.kind === "fixed" ? "Enter a name." : "Enter a file name.";
|
|
115
|
+
}
|
|
116
|
+
if (choice?.kind === "fixed" && slugify(trimmed) === "") {
|
|
117
|
+
return "Enter at least one letter or number.";
|
|
118
|
+
}
|
|
119
|
+
const normalized = trimmed.replaceAll("\\", "/");
|
|
120
|
+
if (normalized.startsWith("/") || normalized.endsWith("/")) {
|
|
121
|
+
return "Enter a name, not a path that starts or ends with a slash.";
|
|
122
|
+
}
|
|
123
|
+
if (normalized.split("/").includes("..")) {
|
|
124
|
+
return "A name cannot step outside the destination folder.";
|
|
125
|
+
}
|
|
126
|
+
// A slash is the tree's ONLY way to make a directory — both backends create them on write — so
|
|
127
|
+
// It is allowed exactly where the field is a whole file name, and refused where it is a name.
|
|
128
|
+
if (normalized.includes("/") && choice !== undefined && picked !== OTHER_FORMAT) {
|
|
129
|
+
return `Pick Other… to create a file in a subfolder of ${dir === "." ? "the project root" : dir}.`;
|
|
130
|
+
}
|
|
131
|
+
/* A name whose extension MATCHES the pick is fine and is composed once, never doubled;
|
|
132
|
+
refusing every trailing extension would reject this dialog's own `untitled.json` default.
|
|
133
|
+
A name ending in an extension no format claims is a stem that happens to have a dot. */
|
|
134
|
+
const typedExt = extensionOfName(normalized);
|
|
135
|
+
if (
|
|
136
|
+
picked !== "" &&
|
|
137
|
+
picked !== OTHER_FORMAT &&
|
|
138
|
+
typedExt !== "" &&
|
|
139
|
+
typedExt.toLowerCase() !== picked.toLowerCase() &&
|
|
140
|
+
known.has(typedExt.toLowerCase())
|
|
141
|
+
) {
|
|
142
|
+
const label = rows?.find((row) => row.value === picked)?.label ?? picked;
|
|
143
|
+
return `You picked ${label}; this name ends in ${typedExt}.`;
|
|
144
|
+
}
|
|
145
|
+
if (choice?.kind === "locked" && picked === OTHER_FORMAT) {
|
|
146
|
+
const foreign = typedExt !== "" && typedExt.toLowerCase() !== choice.ext.toLowerCase();
|
|
147
|
+
if (foreign && known.has(typedExt.toLowerCase())) {
|
|
148
|
+
return choice.because;
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
/* No emptiness check here: every way `composeNewFileName` can answer "" is already refused
|
|
152
|
+
above — a blank field by the first rule, a display name that slugifies to nothing by the
|
|
153
|
+
second. */
|
|
154
|
+
const candidate = composeNewFileName(value, picked, choice);
|
|
155
|
+
return taken.has(candidate.toLowerCase()) ? `${candidate} already exists in ${dir}/.` : "";
|
|
156
|
+
}
|
|
@@ -20,6 +20,7 @@ import {
|
|
|
20
20
|
loadFormats,
|
|
21
21
|
} from "../format/format-host";
|
|
22
22
|
import { serializeJson } from "@jxsuite/schema/json-layout";
|
|
23
|
+
import { restoreBareRepeaters } from "../tabs/bare-repeaters";
|
|
23
24
|
import type { Tab } from "../tabs/tab";
|
|
24
25
|
|
|
25
26
|
/**
|
|
@@ -43,7 +44,11 @@ export async function serializeDocument(tab: Tab): Promise<string> {
|
|
|
43
44
|
const fm = tab.doc.content.frontmatter;
|
|
44
45
|
const doc = tab.doc.document;
|
|
45
46
|
const fullDoc = { ...fm, ...doc, children: doc.children ?? [] };
|
|
46
|
-
|
|
47
|
+
// The text the file was read as, so what the author did not edit keeps its own spelling.
|
|
48
|
+
return formatSerialize(sourceFormat.name, fullDoc, {
|
|
49
|
+
mode: "roundtrip",
|
|
50
|
+
...(tab.doc.source !== null && { source: tab.doc.source }),
|
|
51
|
+
});
|
|
47
52
|
}
|
|
48
53
|
if (tab.doc.mode === "content") {
|
|
49
54
|
const format = defaultContentFormat();
|
|
@@ -62,6 +67,11 @@ export async function serializeDocument(tab: Tab): Promise<string> {
|
|
|
62
67
|
a blank line sits, which characters were escaped — and it is null for a document with no JSON
|
|
63
68
|
source, where every object expands and every array is laid by fit: the formatter's own answer
|
|
64
69
|
for stringified output. Either way the text ends with one newline, as every formatted file
|
|
65
|
-
does.
|
|
66
|
-
|
|
70
|
+
does. A repeater the file wrote as a node's whole `children` is written that way again
|
|
71
|
+
(`tabs/bare-repeaters.ts`), so the member form the editor works in never reaches the disk —
|
|
72
|
+
which is also what keeps the layout's pointers, taken from the file's own text, lined up. */
|
|
73
|
+
return serializeJson(
|
|
74
|
+
restoreBareRepeaters(tab.doc.document, tab.doc.bareRepeaters ?? null),
|
|
75
|
+
tab.doc.layout ?? null,
|
|
76
|
+
);
|
|
67
77
|
}
|
|
@@ -284,6 +284,16 @@ export function documentExtensions(kind?: string): string[] {
|
|
|
284
284
|
return [...exts];
|
|
285
285
|
}
|
|
286
286
|
|
|
287
|
+
/**
|
|
288
|
+
* The extensions the build routes under `pages/`: `.json`, and every extension a page format claims
|
|
289
|
+
* (`discoverPages` in `@jxsuite/compiler` walks with the same set). Waits for the registry, so a
|
|
290
|
+
* Markdown page is never judged unroutable because the formats had not landed yet.
|
|
291
|
+
*/
|
|
292
|
+
export async function routedPageExtensions(): Promise<string[]> {
|
|
293
|
+
await loadFormats();
|
|
294
|
+
return [".json", ...documentExtensions("page")];
|
|
295
|
+
}
|
|
296
|
+
|
|
287
297
|
/** The default format for new content documents (first content-kind serializer). */
|
|
288
298
|
export function defaultContentFormat(): StudioFormat | undefined {
|
|
289
299
|
return _formats.find((f) => f.capabilities.serialize && f.documentKinds.includes("content"));
|
|
@@ -295,6 +305,11 @@ export interface FormatActionOptions {
|
|
|
295
305
|
mode?: string;
|
|
296
306
|
/** Whether the serializer writes a frontmatter block. */
|
|
297
307
|
frontmatter?: boolean;
|
|
308
|
+
/**
|
|
309
|
+
* The text the document was read from: a roundtrip serializer keeps the spelling of whatever did
|
|
310
|
+
* not change (`@jxsuite/parser`'s `source` option).
|
|
311
|
+
*/
|
|
312
|
+
source?: string;
|
|
298
313
|
}
|
|
299
314
|
|
|
300
315
|
/**
|
|
@@ -15,8 +15,8 @@
|
|
|
15
15
|
* **This flow fails, and until now it failed silently.** `https://github.com/login/device/code`
|
|
16
16
|
* sends no `Access-Control-Allow-Origin`, so a browser cannot call it: in Studio-in-a-browser the
|
|
17
17
|
* very first request rejects with a bare `TypeError`. That rejection used to become a thrown
|
|
18
|
-
* `Error` out of {@link authenticateGithub}, into `void createGithubRepository(…)` in
|
|
19
|
-
*
|
|
18
|
+
* `Error` out of {@link authenticateGithub}, into `void createGithubRepository(…)` in the Source
|
|
19
|
+
* Control panel of the time, and from there into an unhandled promise rejection — the user clicked
|
|
20
20
|
* "Create GitHub repository" and the app did nothing at all, forever, with no message anywhere.
|
|
21
21
|
*
|
|
22
22
|
* Three failures now reach a surface, each as the kind of record §16 says it is:
|
|
@@ -175,7 +175,8 @@ export async function createGithubRepository({ projectName }: { projectName: str
|
|
|
175
175
|
// Surfaced as a push failure describing the push.
|
|
176
176
|
activity.log(errorMessage(error));
|
|
177
177
|
activity.fail("The repository was created, but the remote could not be added.", {
|
|
178
|
-
action: "
|
|
178
|
+
action: "view.setWorkspace",
|
|
179
|
+
actionArgs: { workspace: "publish" },
|
|
179
180
|
});
|
|
180
181
|
return false;
|
|
181
182
|
}
|
|
@@ -191,8 +192,8 @@ export async function createGithubRepository({ projectName }: { projectName: str
|
|
|
191
192
|
return false;
|
|
192
193
|
}
|
|
193
194
|
|
|
194
|
-
// Lazy import breaks the github-publish ↔
|
|
195
|
-
const { refreshGitStatus } = await import("../
|
|
195
|
+
// Lazy import breaks the github-publish ↔ source-control module cycle
|
|
196
|
+
const { refreshGitStatus } = await import("../publish/source-control");
|
|
196
197
|
await refreshGitStatus();
|
|
197
198
|
activity.done(repo.html_url);
|
|
198
199
|
notify.success(`Repository created: ${repo.html_url}`, { key: "github.repo" });
|
|
@@ -144,9 +144,13 @@ export function createGridController(tab: Tab, source: GridSource): GridControll
|
|
|
144
144
|
|
|
145
145
|
const reloadRows = async () => {
|
|
146
146
|
const result = await source.rows(state.query);
|
|
147
|
+
/* Index BEFORE the rows are published. The index is not reactive, and assigning `state.rows`
|
|
148
|
+
runs the effects that read it at once: a record's entry form (`content/record-entry.ts`)
|
|
149
|
+
resolves its row key from the new rows and reads that row's cells through the baseline,
|
|
150
|
+
which with the index still empty drew every field blank until something else re-ran it. */
|
|
151
|
+
indexRows(result.rows);
|
|
147
152
|
state.rows = result.rows;
|
|
148
153
|
state.total = result.total;
|
|
149
|
-
indexRows(result.rows);
|
|
150
154
|
};
|
|
151
155
|
|
|
152
156
|
/** Missing required cells block the save (marked as row/cell errors, like a failed commit). */
|