@jxsuite/studio 6.1.1 → 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/{action-group-2mqby73c.js → action-group-4aan7g6g.js} +3 -3
- 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-6rbra5dy.js +4 -0
- package/dist/chunks/color-field-gy56c94g.js +4 -0
- package/dist/chunks/color-m7r1vk54.js +4 -0
- package/dist/chunks/color-slider-sqqdyf6c.js +4 -0
- package/dist/chunks/combobox-72h32ejk.js +4 -0
- 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/dialog-c4x05vyx.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-qg1b5eqx.js +51 -0
- package/dist/chunks/icons-63bjtbs7.js +4 -0
- package/dist/chunks/iframe-entry-2zggzajv.js +5 -0
- package/dist/chunks/iframe-entry-76j8vwwx.js +5 -0
- package/dist/chunks/iframe-entry-99nr5zfb.js +5 -0
- package/dist/chunks/iframe-entry-pkgmcgp0.js +5 -0
- package/dist/chunks/iframe-host-ssv7kyj0.js +4 -0
- package/dist/chunks/{index-2veqmnhr.js → index-32eqzv1k.js} +3 -3
- package/dist/chunks/index-f0jn7nnp.js +4 -0
- package/dist/chunks/{javascript-8nbvs12s.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-c421cf8c.js +4 -0
- 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-bdpzbfvs.js +4 -0
- package/dist/chunks/monaco-binding-vq1hgnn0.js +4 -0
- 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/number-field-y62xq0ty.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/select-rzds841e.js +4 -0
- package/dist/chunks/source-control-kwktr9y6.js +4 -0
- package/dist/chunks/split-c9zfdgxf.js +4 -0
- 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-6m380wp7.js +5 -0
- package/dist/chunks/studio-80jyh1xh.js +4 -0
- package/dist/chunks/studio-83mk4bgg.js +9 -0
- package/dist/chunks/{studio-kggey1jt.js → studio-881z1xrx.js} +4 -4
- package/dist/chunks/studio-8m0y2qe7.js +5 -0
- package/dist/chunks/studio-8tr6cfrc.js +5 -0
- 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-bbe4c90a.js +5 -0
- 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-8grwxtqk.js → studio-jcnsdaep.js} +4 -4
- package/dist/chunks/studio-jsgf2q5k.js +7 -0
- package/dist/chunks/{studio-1q1sh2ax.js → studio-k3xxbagr.js} +3 -3
- package/dist/chunks/studio-k7evxqh4.js +5 -0
- 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-rv11kfwa.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-h46vtzk8.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-wszjyh0j.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-pre0skq6.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-t6b6qe31.js → studio-r5qy30vx.js} +4 -4
- 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/swatch-group-ewh24n1f.js +4 -0
- package/dist/chunks/tabs-f5cw3tgp.js +4 -0
- package/dist/chunks/textfield-wewc8rq2.js +4 -0
- package/dist/chunks/toast-1gv0h53h.js +4 -0
- package/dist/chunks/toast-host-1yg5ar8d.js +4 -0
- package/dist/chunks/{toolbar-2nnzqhrj.js → toolbar-44cm5cta.js} +3 -3
- package/dist/chunks/tooltip-yypz28gq.js +4 -0
- package/dist/chunks/tree-9xa4hm7n.js +4 -0
- 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-bv552sfx.js +4 -0
- package/dist/iframe-entry.js +20 -16
- package/dist/manifest.json +77 -63
- package/dist/studio.js +106 -107
- package/dist/workers/ts.worker.js +3 -3
- package/package.json +17 -16
- package/src/about/about-modal.ts +4 -3
- package/src/account/action-flow.ts +45 -38
- package/src/account-status.ts +7 -2
- package/src/browse/component-examples.ts +272 -0
- package/src/browse/library-commands.ts +64 -104
- package/src/browse/library-layouts.ts +270 -53
- package/src/browse/library-model.ts +194 -134
- package/src/browse/library-pane.ts +748 -440
- package/src/browse/library-preview.ts +161 -26
- 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 +40 -10
- package/src/canvas/asset-resolve.ts +19 -23
- package/src/canvas/canvas-helpers.ts +7 -6
- package/src/canvas/canvas-live-render.ts +260 -77
- package/src/canvas/canvas-origin.ts +37 -12
- package/src/canvas/canvas-page-context.ts +112 -0
- package/src/canvas/canvas-patcher.ts +30 -30
- package/src/canvas/canvas-perf.ts +11 -5
- package/src/canvas/canvas-render.ts +175 -148
- package/src/canvas/canvas-surface.ts +5 -3
- package/src/canvas/canvas-utils.ts +147 -61
- package/src/canvas/dialog-path.ts +9 -4
- package/src/canvas/dialog-state.ts +1 -2
- package/src/canvas/diff-marks.ts +9 -16
- package/src/canvas/diff-toolbar.ts +8 -7
- package/src/canvas/edit-width-drag.ts +4 -1
- package/src/canvas/edit-width.ts +1 -3
- package/src/canvas/iframe-channel.ts +27 -13
- package/src/canvas/iframe-drop.ts +120 -12
- package/src/canvas/iframe-editable-root.ts +29 -16
- package/src/canvas/iframe-entry.ts +104 -75
- package/src/canvas/iframe-eval.ts +73 -49
- package/src/canvas/iframe-host.ts +269 -158
- package/src/canvas/iframe-inline-edit.ts +49 -30
- package/src/canvas/iframe-insert.ts +5 -4
- package/src/canvas/iframe-interaction.ts +30 -25
- package/src/canvas/iframe-keys.ts +11 -0
- package/src/canvas/iframe-overlay.ts +1 -1
- package/src/canvas/iframe-patch.ts +25 -21
- package/src/canvas/iframe-position.ts +12 -6
- package/src/canvas/iframe-protocol.ts +36 -21
- package/src/canvas/iframe-render.ts +84 -35
- package/src/canvas/iframe-slash.ts +24 -11
- package/src/canvas/iframe-subtree.ts +10 -3
- package/src/canvas/npm-modules.ts +22 -0
- package/src/canvas/path-mapping.ts +19 -12
- package/src/canvas/popover-path.ts +7 -3
- package/src/canvas/popover-state.ts +1 -2
- package/src/canvas/preview-navigate.ts +16 -0
- package/src/canvas/repeater-sources.ts +200 -0
- package/src/canvas/serialize-scope.ts +13 -5
- package/src/canvas/stage-scrollbar.ts +7 -2
- package/src/canvas/surface-registry.ts +27 -3
- package/src/collab/collab-commands.ts +7 -2
- package/src/collab/collab-session.ts +84 -45
- package/src/collab/collab-state.ts +4 -3
- package/src/collab/monaco-binding.ts +16 -13
- package/src/collab/monaco-cursors.ts +5 -2
- package/src/commands/app-commands.ts +16 -6
- package/src/commands/budget.ts +13 -11
- package/src/commands/command-args.ts +103 -71
- package/src/commands/context.ts +55 -28
- package/src/commands/defaults.ts +69 -45
- package/src/commands/keymap.ts +56 -43
- package/src/commands/levels.ts +14 -8
- package/src/commands/live-context.ts +9 -7
- package/src/commands/reference.ts +2 -1
- package/src/commands/registry.ts +22 -11
- package/src/commands/run-reported.ts +15 -10
- package/src/component-props.ts +46 -30
- package/src/content/collection-match.ts +11 -9
- package/src/content/collection-pages.ts +412 -0
- package/src/content/content-collections.ts +270 -0
- package/src/content/draft-state.ts +4 -3
- package/src/content/entry-commands.ts +54 -62
- package/src/content/entry-editor.ts +137 -32
- package/src/content/entry-fields.ts +25 -8
- package/src/content/entry-inspector.ts +225 -0
- package/src/content/entry-model.ts +38 -15
- 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/canvas-context-menu.ts +1 -2
- package/src/editor/canvas-slash-bridge.ts +3 -6
- package/src/editor/content-slice.ts +16 -14
- package/src/editor/context-menu.ts +35 -22
- package/src/editor/convert-targets.ts +30 -35
- package/src/editor/convert-to-component.ts +78 -48
- package/src/editor/convert-to-repeater.ts +34 -22
- package/src/editor/editable-tags.ts +17 -2
- package/src/editor/file-drop-action.ts +29 -22
- package/src/editor/inline-edit-apply.ts +79 -17
- package/src/editor/inline-edit.ts +76 -50
- package/src/editor/inline-format.ts +59 -56
- package/src/editor/inline-link.ts +40 -15
- package/src/editor/insert-component.ts +91 -0
- package/src/editor/insert-zone-action.ts +14 -5
- package/src/editor/merge-tags.ts +58 -19
- package/src/editor/repeater-scope.ts +39 -28
- package/src/editor/shortcuts.ts +76 -42
- package/src/editor/slash-menu.ts +52 -14
- package/src/files/asset-versions.ts +4 -4
- package/src/files/components.ts +89 -20
- package/src/files/elements.ts +5 -6
- package/src/files/file-ops.ts +92 -41
- package/src/files/files.ts +248 -239
- package/src/files/fs-events.ts +18 -7
- package/src/files/media-meta.ts +97 -2
- package/src/files/media-paths.ts +3 -0
- 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 +15 -5
- package/src/format/constraints.ts +1 -1
- package/src/format/convert-file.ts +27 -13
- package/src/format/format-choices.ts +2 -1
- package/src/format/format-host.ts +91 -50
- package/src/github/github-auth-status.ts +27 -3
- package/src/github/github-auth.ts +21 -11
- package/src/github/github-publish.ts +28 -23
- package/src/grid/cell-editors.ts +46 -31
- package/src/grid/cell-popovers.ts +5 -3
- package/src/grid/edit-buffer.ts +10 -9
- package/src/grid/grid-controller.ts +25 -23
- package/src/grid/grid-layout.ts +21 -5
- package/src/grid/grid-open.ts +3 -33
- package/src/grid/grid-panel.ts +65 -6
- package/src/grid/grid-reveal.ts +34 -0
- package/src/grid/grid-source.ts +9 -31
- package/src/grid/grid-view.ts +45 -23
- package/src/grid/redirects-grid.ts +14 -5
- package/src/grid/redirects.ts +16 -14
- package/src/grid/schema-columns.ts +25 -19
- package/src/grid/sources/connector-source.ts +47 -20
- package/src/grid/sources/content-source.ts +100 -31
- package/src/grid/sources/csv-file-source.ts +2 -2
- package/src/grid/tabulator-tables.d.ts +61 -34
- package/src/hosting/document.ts +2 -2
- package/src/hosting/stage.ts +7 -7
- package/src/i18n/i18n-commands.ts +3 -3
- 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 +51 -20
- package/src/new-project/add-repo-modal.ts +20 -19
- package/src/new-project/import-tab.ts +10 -14
- package/src/new-project/location-fields.ts +2 -1
- package/src/new-project/new-project-modal.ts +12 -9
- package/src/packages/jxsuite-update.ts +4 -3
- package/src/packages/pull-package-sync.ts +26 -25
- package/src/page-params.ts +97 -68
- package/src/panels/activity-panel.ts +38 -14
- package/src/panels/ai-chat/attached-context.ts +13 -7
- package/src/panels/ai-chat/chat-markdown.ts +8 -8
- package/src/panels/ai-chat/chat-view.ts +80 -73
- package/src/panels/ai-chat/composer.ts +30 -33
- package/src/panels/ai-chat/sessions-view.ts +1 -2
- package/src/panels/ai-panel.ts +23 -20
- package/src/panels/block-action-bar.ts +57 -46
- package/src/panels/bottom-dock.ts +5 -3
- package/src/panels/canvas-dnd-bridge.ts +36 -26
- 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 +63 -26
- package/src/panels/data-grid.ts +18 -11
- package/src/panels/dnd.ts +115 -138
- package/src/panels/editors.ts +52 -21
- package/src/panels/elements-panel.ts +19 -5
- package/src/panels/empty-state.ts +2 -2
- package/src/panels/events-panel.ts +84 -69
- package/src/panels/formula-workspace.ts +78 -54
- package/src/panels/frontmatter-fields.ts +33 -28
- package/src/panels/git-diff-open.ts +10 -6
- package/src/panels/head-panel.ts +488 -202
- package/src/panels/i18n-panel.ts +20 -12
- package/src/panels/imports-panel.ts +25 -47
- package/src/panels/jump-bar.ts +75 -31
- package/src/panels/layers-panel.ts +130 -101
- package/src/panels/left-panel.ts +25 -11
- package/src/panels/navigator-panels.ts +23 -11
- package/src/panels/pane-context.ts +140 -49
- package/src/panels/pane-grid.ts +55 -26
- package/src/panels/panel-registry.ts +36 -46
- package/src/panels/properties-panel.ts +182 -124
- package/src/panels/quick-search.ts +53 -31
- package/src/panels/right-panel.ts +201 -45
- package/src/panels/seo-modal.ts +54 -125
- package/src/panels/settings-menu.ts +63 -61
- package/src/panels/settings-pane.ts +268 -162
- package/src/panels/signals-panel.ts +156 -135
- package/src/panels/site-model.ts +367 -0
- package/src/panels/site-panel.ts +638 -0
- package/src/panels/statement-editor.ts +230 -140
- package/src/panels/style-panel.ts +253 -206
- package/src/panels/style-utils.ts +114 -51
- package/src/panels/stylebook-doc.ts +195 -60
- package/src/panels/stylebook-layers-panel.ts +48 -89
- package/src/panels/stylebook-panel.ts +60 -27
- package/src/panels/styles-panel.ts +327 -0
- package/src/panels/tab-drop.ts +27 -11
- package/src/panels/tab-strip.ts +143 -49
- package/src/platform-errors.ts +57 -43
- package/src/platform.ts +59 -31
- package/src/platforms/cloud.ts +373 -176
- package/src/platforms/default-platform.ts +14 -8
- package/src/platforms/devserver.ts +180 -142
- package/src/platforms/read-batcher.ts +16 -8
- package/src/project-list.ts +2 -1
- package/src/publish/deploy-checklist.ts +20 -9
- package/src/publish/pages-service.ts +66 -27
- package/src/publish/publish-changes.ts +491 -0
- package/src/publish/publish-commands.ts +88 -22
- package/src/publish/publish-ids.ts +49 -0
- package/src/publish/publish-panel.ts +10 -6
- 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/recent-projects.ts +15 -8
- package/src/resize-edges.ts +26 -36
- package/src/services/a11y-report.ts +26 -19
- package/src/services/agent-seed.ts +23 -11
- package/src/services/ai-ask.ts +9 -12
- package/src/services/ai-command-tools.ts +68 -62
- package/src/services/ai-import-tools.ts +80 -67
- package/src/services/ai-models.ts +17 -13
- package/src/services/ai-project-tools.ts +68 -32
- package/src/services/ai-session-store.ts +19 -10
- package/src/services/ai-system-prompt.ts +46 -20
- package/src/services/ai-tools.ts +71 -27
- package/src/services/ai-write-report.ts +20 -12
- package/src/services/ai-writes.ts +4 -4
- package/src/services/automation.ts +74 -51
- package/src/services/cem-export.ts +149 -58
- package/src/services/cf-settings.ts +4 -4
- package/src/services/clock.ts +6 -1
- package/src/services/code-services.ts +37 -31
- package/src/services/connection.ts +2 -2
- package/src/services/context-manager.ts +14 -10
- package/src/services/context-resolver.ts +20 -8
- package/src/services/data-service.ts +19 -15
- package/src/services/directory-picker.ts +20 -14
- package/src/services/document-assistant.ts +39 -19
- package/src/services/gated-registry.ts +10 -6
- package/src/services/harness/chat-reducer.ts +23 -1
- package/src/services/import-client.ts +91 -38
- package/src/services/import-seed.ts +8 -2
- package/src/services/jx-validate.ts +28 -31
- package/src/services/live-preview.ts +0 -0
- package/src/services/live-surfaces.ts +6 -3
- package/src/services/model-uri.ts +10 -2
- package/src/services/module-preloads.ts +12 -8
- package/src/services/monaco-buffer.ts +43 -24
- package/src/services/monaco-lazy.ts +7 -8
- package/src/services/monaco-setup.ts +23 -16
- package/src/services/notify.ts +44 -17
- package/src/services/popover-report.ts +25 -10
- package/src/services/preview-eval.ts +20 -8
- package/src/services/profile.ts +13 -6
- package/src/services/project-path.ts +13 -10
- package/src/services/references.ts +0 -0
- package/src/services/render-critic.ts +29 -9
- package/src/services/settings/definitions.ts +25 -14
- package/src/services/settings/kernel.ts +13 -4
- package/src/services/settings/write-queue.ts +9 -9
- package/src/services/token-lint.ts +12 -10
- package/src/services/tool-executor.ts +17 -5
- package/src/services/tool-outcomes.ts +44 -24
- package/src/services/trusted-types.ts +24 -7
- package/src/settings/contexts-section.ts +11 -8
- package/src/settings/contributed-section.ts +128 -70
- package/src/settings/css-vars-editor.ts +119 -32
- package/src/settings/defs-editor.ts +38 -35
- package/src/settings/developer-panels.ts +43 -0
- package/src/settings/extension-rows.ts +16 -8
- package/src/settings/extension-sections.ts +42 -14
- package/src/settings/extensions-section.ts +4 -1
- package/src/settings/general-settings.ts +101 -28
- package/src/settings/locales-section.ts +20 -12
- package/src/settings/preferences-dialog.ts +17 -7
- package/src/settings/preferences-keymap.ts +7 -5
- package/src/settings/preferences-sections.ts +11 -5
- package/src/settings/project-sections.ts +28 -11
- package/src/settings/schema-field-ui.ts +12 -1
- package/src/settings/section-registry.ts +116 -11
- package/src/settings/settings-dialog.ts +192 -0
- package/src/settings/settings-document.ts +186 -166
- package/src/shell/tree.ts +6 -5
- package/src/shell.ts +536 -477
- package/src/site-context.ts +101 -32
- package/src/state.ts +68 -57
- package/src/store.ts +54 -34
- package/src/studio.ts +219 -116
- package/src/style/live-preview.ts +1 -1
- package/src/style/project-styles.ts +40 -7
- 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/token-ref.ts +5 -4
- package/src/style/variables-focus.ts +61 -0
- package/src/style/variables-view.ts +235 -0
- package/src/surfaces/about.ts +18 -19
- package/src/surfaces/add-new.json +1482 -0
- package/src/surfaces/add-new.ts +325 -0
- package/src/surfaces/add-repo.ts +17 -19
- package/src/surfaces/ai-chat.ts +33 -38
- package/src/surfaces/ai-credentials-form.ts +6 -16
- package/src/surfaces/ai-managed-connect.ts +3 -4
- package/src/surfaces/ai-model-picker.ts +3 -5
- package/src/surfaces/block-action-bar.ts +22 -11
- package/src/surfaces/boot-failure.json +4 -3
- package/src/surfaces/boot-failure.ts +4 -5
- package/src/surfaces/bottom-dock.json +1 -1
- package/src/surfaces/bottom-dock.ts +9 -18
- package/src/surfaces/canvas-stage.json +30 -19
- package/src/surfaces/canvas-stage.ts +17 -16
- package/src/surfaces/cf-account-picker.ts +17 -18
- 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 +145 -105
- package/src/surfaces/convert-repeater.ts +17 -19
- package/src/surfaces/data-actions.ts +9 -7
- package/src/surfaces/dialog.ts +26 -28
- package/src/surfaces/diff-toolbar.ts +9 -7
- package/src/surfaces/empty-state.json +1 -1
- package/src/surfaces/empty-state.ts +7 -10
- package/src/surfaces/entry-editor.json +136 -0
- package/src/surfaces/entry-editor.ts +35 -11
- package/src/surfaces/entry-inspector.json +371 -0
- package/src/surfaces/entry-inspector.ts +143 -0
- package/src/surfaces/expression-editor.ts +11 -25
- package/src/surfaces/files-panel.ts +27 -14
- package/src/surfaces/formula-palette.ts +4 -5
- package/src/surfaces/github-auth.ts +16 -20
- package/src/surfaces/github-publish.ts +17 -22
- package/src/surfaces/grid-cell.ts +30 -31
- package/src/surfaces/grid-open.ts +10 -14
- package/src/surfaces/grid-panel.ts +13 -9
- package/src/surfaces/grid-replace.ts +13 -13
- package/src/surfaces/grid-views.ts +15 -18
- package/src/surfaces/inspector-dock.json +162 -14
- package/src/surfaces/inspector-dock.ts +59 -31
- package/src/surfaces/jump-bar.ts +5 -6
- package/src/surfaces/jxsuite-update.ts +16 -22
- package/src/surfaces/library-pane.json +670 -437
- package/src/surfaces/library-pane.ts +111 -77
- package/src/surfaces/logic-panel.ts +17 -30
- package/src/surfaces/logic-workspace.ts +13 -15
- package/src/surfaces/media-browser.json +62 -6
- package/src/surfaces/media-browser.ts +26 -11
- package/src/surfaces/media-field.json +1 -1
- package/src/surfaces/media-field.ts +4 -5
- package/src/surfaces/media-library.json +1586 -0
- package/src/surfaces/media-library.ts +282 -0
- package/src/surfaces/media-pane.ts +8 -7
- package/src/surfaces/media-pick-dialog.json +71 -0
- package/src/surfaces/media-pick-dialog.ts +132 -0
- package/src/surfaces/menu.ts +47 -23
- package/src/surfaces/navigator-dock.ts +11 -12
- package/src/surfaces/new-project.ts +27 -26
- package/src/surfaces/pane-context.json +144 -77
- package/src/surfaces/pane-context.ts +44 -25
- package/src/surfaces/pane-grid.ts +15 -10
- package/src/surfaces/panel-activity.ts +5 -6
- package/src/surfaces/panel-data.ts +3 -4
- package/src/surfaces/panel-deploy-checklist.json +12 -6
- package/src/surfaces/panel-deploy-checklist.ts +8 -7
- package/src/surfaces/panel-elements.ts +5 -7
- package/src/surfaces/panel-i18n.ts +5 -7
- package/src/surfaces/panel-imports.ts +6 -13
- package/src/surfaces/panel-outline.ts +25 -19
- package/src/surfaces/panel-page.json +72 -2
- package/src/surfaces/panel-page.ts +49 -25
- package/src/surfaces/panel-problems.ts +5 -7
- package/src/surfaces/panel-signals.json +1 -1
- package/src/surfaces/panel-signals.ts +25 -39
- package/src/surfaces/panel-stylebook-layers.json +71 -204
- package/src/surfaces/panel-stylebook-layers.ts +5 -29
- package/src/surfaces/preferences.json +120 -48
- package/src/surfaces/preferences.ts +29 -11
- package/src/surfaces/progress-modal.ts +15 -9
- package/src/surfaces/properties-panel.json +1 -1
- package/src/surfaces/properties-panel.ts +20 -42
- 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/publish.ts +18 -18
- package/src/surfaces/push-plan.ts +7 -8
- package/src/surfaces/rail.json +1 -159
- package/src/surfaces/rail.ts +22 -79
- package/src/surfaces/reference-field.ts +11 -15
- package/src/surfaces/schema-builder.ts +16 -22
- package/src/surfaces/schema-form.json +10 -2
- package/src/surfaces/schema-form.ts +17 -25
- package/src/surfaces/secret-field.ts +9 -6
- package/src/surfaces/seo.json +1 -1
- package/src/surfaces/seo.ts +21 -15
- package/src/surfaces/settings-contexts.ts +9 -7
- package/src/surfaces/settings-contributed.json +5 -5
- package/src/surfaces/settings-contributed.ts +32 -12
- package/src/surfaces/settings-css-vars.json +44 -36
- package/src/surfaces/settings-css-vars.ts +82 -16
- package/src/surfaces/settings-defs.json +2 -2
- package/src/surfaces/settings-defs.ts +17 -21
- package/src/surfaces/settings-deploy.json +2 -15
- package/src/surfaces/settings-deploy.ts +9 -16
- package/src/surfaces/settings-dialog.json +51 -0
- package/src/surfaces/settings-dialog.ts +169 -0
- package/src/surfaces/settings-extensions.ts +5 -6
- package/src/surfaces/settings-head.ts +33 -39
- package/src/surfaces/settings-locales.ts +8 -20
- package/src/surfaces/settings-overview.json +3 -3
- package/src/surfaces/settings-overview.ts +14 -8
- package/src/surfaces/settings-packages.ts +8 -7
- package/src/surfaces/settings-pane.json +269 -46
- package/src/surfaces/settings-pane.ts +88 -36
- package/src/surfaces/settings-rawjson.ts +5 -6
- package/src/surfaces/shell.json +2 -1
- package/src/surfaces/shell.ts +1 -2
- 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 +42 -9
- package/src/surfaces/statements.ts +13 -18
- package/src/surfaces/statusbar.ts +56 -29
- package/src/surfaces/style-panel.ts +18 -31
- package/src/surfaces/stylebook-chrome.ts +3 -5
- 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/tab-strip.ts +14 -13
- package/src/surfaces/target-line.ts +6 -7
- package/src/surfaces/welcome.ts +6 -8
- 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/patch-ops.ts +4 -3
- package/src/tabs/project-config.ts +32 -21
- package/src/tabs/selection.ts +36 -8
- package/src/tabs/tab.ts +99 -48
- package/src/tabs/transact.ts +98 -53
- package/src/types.ts +53 -43
- package/src/ui/ai-credentials-form.ts +10 -13
- package/src/ui/ai-model-picker.ts +3 -3
- package/src/ui/cf-account-picker.ts +7 -9
- package/src/ui/color-selector.ts +9 -5
- package/src/ui/dynamic-slot.ts +7 -8
- package/src/ui/expression-editor.ts +112 -105
- package/src/ui/form-controls.ts +104 -56
- package/src/ui/formula-catalog.ts +239 -230
- package/src/ui/formula-chips.ts +33 -18
- package/src/ui/layers.ts +57 -31
- package/src/ui/media-picker.ts +300 -47
- package/src/ui/popover-surface.ts +20 -12
- package/src/ui/progress-modal.ts +13 -8
- package/src/ui/regions.ts +14 -12
- package/src/ui/schema-form.ts +372 -97
- package/src/ui/surface.ts +71 -8
- package/src/ui/timing.ts +9 -0
- package/src/ui/value-source.ts +44 -31
- package/src/ui/virtual-window.ts +29 -3
- package/src/utils/base64.ts +6 -4
- package/src/utils/edit-display.ts +366 -111
- package/src/utils/element-selectors.ts +3 -2
- package/src/utils/google-fonts.ts +15 -14
- package/src/utils/inherited-style.ts +18 -20
- package/src/utils/link-target.ts +1 -1
- package/src/utils/local-day.ts +18 -0
- package/src/utils/preview-format.ts +25 -3
- package/src/utils/strip-events.ts +49 -22
- package/src/utils/studio-utils.ts +29 -14
- package/src/view.ts +2 -3
- package/src/workspace/pane-derive.ts +14 -16
- package/src/workspace/session.ts +81 -50
- 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 +165 -47
- package/styles/shell-frame.css +30 -7
- package/styles/shell-frame.json +15 -3
- package/dist/chunks/2020-va2wwra4.js +0 -4
- package/dist/chunks/adapter-f93pafd7.js +0 -4
- package/dist/chunks/cf-account-picker-af1h381v.js +0 -4
- package/dist/chunks/cf-settings-125tdm0w.js +0 -4
- package/dist/chunks/color-area-jqhna8zy.js +0 -4
- package/dist/chunks/color-field-embq5ctg.js +0 -4
- package/dist/chunks/color-fpezvnkt.js +0 -4
- package/dist/chunks/color-slider-1qmw7wqy.js +0 -4
- package/dist/chunks/combobox-773hdzyj.js +0 -4
- package/dist/chunks/components-ax2f1fnn.js +0 -4
- package/dist/chunks/content-source-108aqtk1.js +0 -4
- package/dist/chunks/convert-to-repeater-ry5rmppr.js +0 -4
- package/dist/chunks/dialog-mtm9fq5t.js +0 -4
- package/dist/chunks/editor-pfgdmkb0.js +0 -4
- package/dist/chunks/entry-commands-t0k1q0kx.js +0 -4
- package/dist/chunks/extension-sections-6mwh2hf8.js +0 -4
- package/dist/chunks/file-ops-meej5mqf.js +0 -4
- package/dist/chunks/files-44vp8a2s.js +0 -4
- package/dist/chunks/format-host-7q1petb9.js +0 -4
- package/dist/chunks/fs-events-h2mwnxaz.js +0 -4
- package/dist/chunks/git-panel-zngbymvd.js +0 -4
- package/dist/chunks/grid-view-sx9vynsp.js +0 -51
- package/dist/chunks/icons-p44xhjf0.js +0 -4
- package/dist/chunks/iframe-entry-f7mqjwpm.js +0 -5
- package/dist/chunks/iframe-entry-haw7jkvz.js +0 -5
- package/dist/chunks/iframe-entry-z6qrytv3.js +0 -5
- package/dist/chunks/iframe-host-qk3ycq87.js +0 -4
- package/dist/chunks/index-wkyfjeqz.js +0 -4
- package/dist/chunks/jsonMode-s8kt9dsj.js +0 -13
- package/dist/chunks/jx-validate-psgd7vsx.js +0 -4
- package/dist/chunks/layers-panel-1kq2jyfk.js +0 -4
- package/dist/chunks/layers-xd92fg8d.js +0 -4
- package/dist/chunks/listbox-s8fkmnq8.js +0 -4
- package/dist/chunks/lit-html-ac4cwwr4.js +0 -4
- package/dist/chunks/media-picker-0svzkdyb.js +0 -4
- package/dist/chunks/menu-37d07z9m.js +0 -4
- package/dist/chunks/menu-5c55ea3d.js +0 -4
- package/dist/chunks/monaco-binding-z6r70pdt.js +0 -4
- package/dist/chunks/monaco-buffer-fz6gq5y7.js +0 -4
- package/dist/chunks/monaco-setup-q4axkgj7.js +0 -4
- package/dist/chunks/notify-0xgtn8q1.js +0 -4
- package/dist/chunks/number-field-gw7sq8rn.js +0 -4
- package/dist/chunks/parse-897fvqah.js +0 -4
- package/dist/chunks/platform-v1qctbe6.js +0 -4
- package/dist/chunks/popover-8htra4er.js +0 -4
- package/dist/chunks/publish-panel-ym1fmgq5.js +0 -4
- package/dist/chunks/quick-search-a54e6t5n.js +0 -4
- package/dist/chunks/references-gmqp82fs.js +0 -4
- package/dist/chunks/right-panel-v7t9awbk.js +0 -4
- package/dist/chunks/section-registry-vhyf3zsr.js +0 -4
- package/dist/chunks/select-n3v7gwas.js +0 -4
- package/dist/chunks/split-pff8f3ze.js +0 -4
- package/dist/chunks/studio-0bhx5hz6.js +0 -5
- package/dist/chunks/studio-0k1ddnyy.js +0 -6
- package/dist/chunks/studio-1b04pd0j.js +0 -64
- package/dist/chunks/studio-4dwbj6gb.js +0 -5
- package/dist/chunks/studio-4tanh828.js +0 -5
- package/dist/chunks/studio-55h4mstj.js +0 -4
- package/dist/chunks/studio-5tnvjkz4.js +0 -5
- package/dist/chunks/studio-60dqgb0c.js +0 -5
- package/dist/chunks/studio-61dp09qg.js +0 -9
- package/dist/chunks/studio-63epcrj2.js +0 -5
- package/dist/chunks/studio-74k29dy9.js +0 -5
- package/dist/chunks/studio-7vg6jcd3.js +0 -5
- package/dist/chunks/studio-7zx31knw.js +0 -9
- package/dist/chunks/studio-8bxk97km.js +0 -5
- package/dist/chunks/studio-933e30n4.js +0 -5
- package/dist/chunks/studio-98wnwhbg.js +0 -5
- package/dist/chunks/studio-9mccjwjh.js +0 -14
- package/dist/chunks/studio-a2ke1e4t.js +0 -5
- package/dist/chunks/studio-e3a2a6v3.js +0 -4
- package/dist/chunks/studio-e81q1vb9.js +0 -5
- package/dist/chunks/studio-epgrmqqm.js +0 -5
- package/dist/chunks/studio-ery42sqr.js +0 -4
- package/dist/chunks/studio-g1shz58g.js +0 -5
- package/dist/chunks/studio-gf0fz03c.js +0 -5
- package/dist/chunks/studio-gjtpe62q.js +0 -6
- package/dist/chunks/studio-gkskhg7m.js +0 -5
- package/dist/chunks/studio-gx4dnjzd.js +0 -5
- package/dist/chunks/studio-j03zga3y.js +0 -9
- package/dist/chunks/studio-jhpxeckz.js +0 -5
- package/dist/chunks/studio-k01r7ykp.js +0 -5
- package/dist/chunks/studio-kk7h30ne.js +0 -7
- package/dist/chunks/studio-kt1zqney.js +0 -5
- package/dist/chunks/studio-m3cwhvye.js +0 -5
- package/dist/chunks/studio-mcayt6hz.js +0 -5
- package/dist/chunks/studio-myjcghv3.js +0 -5
- package/dist/chunks/studio-n8bxwc3y.js +0 -4
- package/dist/chunks/studio-ngm8gvpb.js +0 -5
- package/dist/chunks/studio-nj5a01jy.js +0 -928
- package/dist/chunks/studio-pcgz8heb.js +0 -5
- package/dist/chunks/studio-qcsq0t15.js +0 -5
- package/dist/chunks/studio-qkstvve9.js +0 -5
- package/dist/chunks/studio-qv1pwd3c.js +0 -5
- package/dist/chunks/studio-rc6dvxjb.js +0 -7
- package/dist/chunks/studio-s8dmmz66.js +0 -5
- package/dist/chunks/studio-sp5xzbj4.js +0 -6
- package/dist/chunks/studio-szsjbj0g.js +0 -5
- package/dist/chunks/studio-t1xzawhr.js +0 -16
- package/dist/chunks/studio-tdcprbqb.js +0 -5
- package/dist/chunks/studio-vrmcjf9g.js +0 -7
- package/dist/chunks/studio-vyc0b4he.js +0 -10
- package/dist/chunks/studio-w0cgf6jg.js +0 -5
- package/dist/chunks/studio-w3hskzqk.js +0 -5
- package/dist/chunks/studio-xgrgcdyh.js +0 -9
- package/dist/chunks/studio-xz2yxfcb.js +0 -5
- package/dist/chunks/studio-y6n42tgs.js +0 -5
- package/dist/chunks/studio-yb560vxr.js +0 -5
- package/dist/chunks/studio-yn9f8vtx.js +0 -5
- package/dist/chunks/studio-yw05w8f0.js +0 -7
- package/dist/chunks/studio-z16sgavc.js +0 -5
- package/dist/chunks/studio-zey7zsdw.js +0 -12
- package/dist/chunks/studio-zyqn40w4.js +0 -5
- package/dist/chunks/swatch-group-8qh9a3bm.js +0 -4
- package/dist/chunks/tabs-jwthg40w.js +0 -4
- package/dist/chunks/textfield-spy3hc77.js +0 -4
- package/dist/chunks/toast-4d35zd64.js +0 -4
- package/dist/chunks/toast-host-8sjmfed3.js +0 -4
- package/dist/chunks/tooltip-fd6g9xy3.js +0 -4
- package/dist/chunks/tree-h19v1jfz.js +0 -4
- package/dist/chunks/tsMode-2frah6h1.js +0 -14
- package/dist/chunks/workspace-zt81g1ms.js +0 -4
- package/dist/chunks/ws-client-n2bh4hkf.js +0 -4
- package/src/panels/frontmatter-panel.ts +0 -701
- package/src/panels/git-panel.ts +0 -1120
- package/src/surfaces/doc-header.json +0 -776
- package/src/surfaces/doc-header.ts +0 -231
- package/src/surfaces/git-panel.json +0 -1880
- package/src/surfaces/git-panel.ts +0 -272
package/src/files/fs-events.ts
CHANGED
|
@@ -237,11 +237,11 @@ export async function relistLoadedDirs(
|
|
|
237
237
|
}
|
|
238
238
|
|
|
239
239
|
/**
|
|
240
|
-
* Modes only a tab whose `document` is a placeholder offers: the media viewer
|
|
241
|
-
*
|
|
242
|
-
*
|
|
240
|
+
* Modes only a tab whose `document` is a placeholder offers: the media viewer and a CSV grid.
|
|
241
|
+
* Spelled out as strings, not imported, because each constant's module drags its whole editor in
|
|
242
|
+
* behind it. A workspace home has no file at all, so it never reaches the re-read.
|
|
243
243
|
*/
|
|
244
|
-
const STUB_DOCUMENT_MODES = new Set(["media", "grid"
|
|
244
|
+
const STUB_DOCUMENT_MODES = new Set(["media", "grid"]);
|
|
245
245
|
|
|
246
246
|
/**
|
|
247
247
|
* Whether a tab's `document` is a placeholder rather than its parsed file. Such a tab reads its
|
|
@@ -293,8 +293,16 @@ export interface FsSyncContext {
|
|
|
293
293
|
* and this module is imported BY the file layer those panels sit on top of — reaching up would be
|
|
294
294
|
* a cycle. The bootstrap owns the list because the bootstrap is where the panels are already in
|
|
295
295
|
* scope.
|
|
296
|
+
*
|
|
297
|
+
* Handed the project-relative paths the batch touched, so a cache that only one directory can
|
|
298
|
+
* change (the media listing, the component registry) can ignore the rest. `undefined` means the
|
|
299
|
+
* stream had a gap and any path may have changed.
|
|
300
|
+
*
|
|
301
|
+
* `structural` says whether the batch added, removed or renamed anything, as opposed to only
|
|
302
|
+
* rewriting files that already existed: a cache of the file SET can ignore a batch of plain
|
|
303
|
+
* `change` events, which is every save. It is `true` after a gap.
|
|
296
304
|
*/
|
|
297
|
-
invalidateDerivedCaches?: () => void;
|
|
305
|
+
invalidateDerivedCaches?: (paths?: readonly string[], structural?: boolean) => void;
|
|
298
306
|
/**
|
|
299
307
|
* After a reconnect, the clean, non-collab open tabs whose files may have changed while the
|
|
300
308
|
* stream was down. The handler re-reads them; it should leave a tab whose content did not change
|
|
@@ -363,7 +371,7 @@ export function startFsSync(ctx: FsSyncContext): () => void {
|
|
|
363
371
|
);
|
|
364
372
|
if (reason === "reconnect") {
|
|
365
373
|
invalidateUsages();
|
|
366
|
-
ctx.invalidateDerivedCaches?.();
|
|
374
|
+
ctx.invalidateDerivedCaches?.(undefined, true);
|
|
367
375
|
await Promise.all([relist, reloadIgnoreCache()]);
|
|
368
376
|
} else {
|
|
369
377
|
await relist;
|
|
@@ -413,7 +421,10 @@ export function startFsSync(ctx: FsSyncContext): () => void {
|
|
|
413
421
|
// Removed layout's `$head` to the open page, a `$paths` enumeration listing entries that are
|
|
414
422
|
// Gone.
|
|
415
423
|
invalidateUsages();
|
|
416
|
-
ctx.invalidateDerivedCaches?.(
|
|
424
|
+
ctx.invalidateDerivedCaches?.(
|
|
425
|
+
events.map((event) => event.path),
|
|
426
|
+
events.some((event) => event.type !== "change"),
|
|
427
|
+
);
|
|
417
428
|
// Content versions are derived state of the same kind, and Studio's own writes change bytes:
|
|
418
429
|
// A version kept past its echo would let an immutable cache answer with the old file.
|
|
419
430
|
forgetEventVersions(events);
|
package/src/files/media-meta.ts
CHANGED
|
@@ -11,6 +11,12 @@
|
|
|
11
11
|
* renders anyway. {@link recordImageSize} is how that surface hands the number back, and until
|
|
12
12
|
* one does, the dimensions are `null` and the summary simply omits them. Downloading the file a
|
|
13
13
|
* second time to measure it would be a network request per thumbnail for a line of text.
|
|
14
|
+
* - **An SVG's dimensions are its own**, read from the file: `width`/`height` on the root element, or
|
|
15
|
+
* its `viewBox` when either is missing ({@link svgIntrinsicSize}). A vector image has no pixels
|
|
16
|
+
* to decode, and an `<img>` of one that declares no size reports the replaced element's default
|
|
17
|
+
* object size (150 × 150 in Chromium), a number about the browser rather than the file. So
|
|
18
|
+
* {@link recordImageSize} ignores SVGs, and {@link loadMediaMeta} reads the text instead, which
|
|
19
|
+
* for a vector is a few hundred bytes the thumbnail already fetched.
|
|
14
20
|
*
|
|
15
21
|
* A missing number is `null` everywhere, never `0`. `0 × 0` and `0 B` are things a real file can
|
|
16
22
|
* almost be, so they must not double as "we did not find out" — the same rule
|
|
@@ -23,6 +29,7 @@
|
|
|
23
29
|
* @docs studio/projects/media
|
|
24
30
|
*/
|
|
25
31
|
|
|
32
|
+
import { isNumber } from "@jxsuite/schema/guards";
|
|
26
33
|
import { getPlatform } from "../platform";
|
|
27
34
|
import { baseName, dirName, normalizeProjectPath } from "./media-paths";
|
|
28
35
|
import { notedListing } from "./asset-versions";
|
|
@@ -62,12 +69,87 @@ const inFlight = new Map<string, Promise<MediaMeta>>();
|
|
|
62
69
|
*/
|
|
63
70
|
const measured = new Map<string, { width: number; height: number }>();
|
|
64
71
|
|
|
72
|
+
/**
|
|
73
|
+
* SVG paths whose own size has been read (or found unreadable), so {@link loadMediaMeta} reads each
|
|
74
|
+
* file once rather than on every paint that finds no dimensions: most SVGs declare none at all.
|
|
75
|
+
*/
|
|
76
|
+
const svgRead = new Set<string>();
|
|
77
|
+
|
|
78
|
+
function isSvg(path: string): boolean {
|
|
79
|
+
return extensionOf(path) === ".svg";
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/** A `width`/`height` attribute in user units (`64`, `64px`), or null for anything relative. */
|
|
83
|
+
function svgLength(value: string | null): number | null {
|
|
84
|
+
const match = value === null ? null : /^\s*(\d+(?:\.\d+)?)\s*(?:px)?\s*$/.exec(value);
|
|
85
|
+
const length = match ? Number(match[1]) : Number.NaN;
|
|
86
|
+
return length > 0 ? length : null;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* The size an SVG document declares for itself, or null when it declares none (the intrinsic
|
|
91
|
+
* dimensions of an outermost `<svg>` element, SVG 2 §8.2): `width` and `height` where both are
|
|
92
|
+
* absolute lengths; one of them with the `viewBox`'s aspect ratio supplying the other; or the
|
|
93
|
+
* `viewBox`'s own width and height when neither is given. A percentage or a font-relative length is
|
|
94
|
+
* a size relative to whatever embeds the file, which a file on its own does not have.
|
|
95
|
+
*/
|
|
96
|
+
export function svgIntrinsicSize(text: string): { width: number; height: number } | null {
|
|
97
|
+
const root = new DOMParser().parseFromString(text, "image/svg+xml").documentElement;
|
|
98
|
+
if (root.localName !== "svg") {
|
|
99
|
+
return null;
|
|
100
|
+
}
|
|
101
|
+
const width = svgLength(root.getAttribute("width"));
|
|
102
|
+
const height = svgLength(root.getAttribute("height"));
|
|
103
|
+
const box = (root.getAttribute("viewBox") ?? "")
|
|
104
|
+
.trim()
|
|
105
|
+
.split(/[\s,]+/)
|
|
106
|
+
.map(Number);
|
|
107
|
+
const boxWidth = box[2] ?? Number.NaN;
|
|
108
|
+
const boxHeight = box[3] ?? Number.NaN;
|
|
109
|
+
const hasBox = box.length === 4 && boxWidth > 0 && boxHeight > 0;
|
|
110
|
+
let size: { width: number; height: number } | null = null;
|
|
111
|
+
if (width !== null && height !== null) {
|
|
112
|
+
size = { height, width };
|
|
113
|
+
} else if (hasBox && width !== null) {
|
|
114
|
+
size = { height: (width * boxHeight) / boxWidth, width };
|
|
115
|
+
} else if (hasBox && height !== null) {
|
|
116
|
+
size = { height, width: (height * boxWidth) / boxHeight };
|
|
117
|
+
} else if (hasBox) {
|
|
118
|
+
size = { height: boxHeight, width: boxWidth };
|
|
119
|
+
}
|
|
120
|
+
return size && { height: Math.round(size.height), width: Math.round(size.width) };
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/** Read an SVG's declared size into {@link measured}, once per path. */
|
|
124
|
+
async function readSvgSize(path: string): Promise<void> {
|
|
125
|
+
svgRead.add(path);
|
|
126
|
+
try {
|
|
127
|
+
const size = svgIntrinsicSize(await getPlatform().readFile(path));
|
|
128
|
+
if (size && size.width > 0 && size.height > 0) {
|
|
129
|
+
measured.set(path, size);
|
|
130
|
+
}
|
|
131
|
+
} catch {
|
|
132
|
+
// Unreadable: the dimensions stay unknown, which the summary already says by omitting them.
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* Whether {@link loadMediaMeta} has nothing left to learn about a file: its record is settled and,
|
|
138
|
+
* for an SVG, its own declared size has been read. A surface asks this, rather than whether
|
|
139
|
+
* {@link peekMediaMeta} is null, before starting a load: a listing seeds every SVG's record without
|
|
140
|
+
* the dimensions only the file itself can give.
|
|
141
|
+
*/
|
|
142
|
+
export function mediaMetaSettled(path: string): boolean {
|
|
143
|
+
const key = normalizeProjectPath(path);
|
|
144
|
+
return settled.has(key) && (!isSvg(key) || svgRead.has(key));
|
|
145
|
+
}
|
|
146
|
+
|
|
65
147
|
/** Build a meta record from a path and whatever the listing knew about it. */
|
|
66
148
|
function metaFrom(path: string, entry: DirEntry | undefined): MediaMeta {
|
|
67
149
|
const name = baseName(path);
|
|
68
150
|
const dims = measured.get(path);
|
|
69
151
|
return {
|
|
70
|
-
bytes:
|
|
152
|
+
bytes: isNumber(entry?.size) ? entry.size : null,
|
|
71
153
|
ext: extensionOf(name),
|
|
72
154
|
height: dims?.height ?? null,
|
|
73
155
|
kind: mediaKind({ name }),
|
|
@@ -117,7 +199,7 @@ export function seedMediaMeta(entries: Iterable<DirEntry>): void {
|
|
|
117
199
|
export async function loadMediaMeta(path: string): Promise<MediaMeta> {
|
|
118
200
|
const key = normalizeProjectPath(path);
|
|
119
201
|
const done = settled.get(key);
|
|
120
|
-
if (done) {
|
|
202
|
+
if (done && mediaMetaSettled(key)) {
|
|
121
203
|
return done;
|
|
122
204
|
}
|
|
123
205
|
const running = inFlight.get(key);
|
|
@@ -125,6 +207,14 @@ export async function loadMediaMeta(path: string): Promise<MediaMeta> {
|
|
|
125
207
|
return running;
|
|
126
208
|
}
|
|
127
209
|
const pending = (async (): Promise<MediaMeta> => {
|
|
210
|
+
if (isSvg(key) && !svgRead.has(key)) {
|
|
211
|
+
await readSvgSize(key);
|
|
212
|
+
}
|
|
213
|
+
if (done) {
|
|
214
|
+
// Seeded by a listing already: only the SVG's own size was missing.
|
|
215
|
+
const dims = measured.get(key);
|
|
216
|
+
return { ...done, height: dims?.height ?? null, width: dims?.width ?? null };
|
|
217
|
+
}
|
|
128
218
|
let entry: DirEntry | undefined;
|
|
129
219
|
try {
|
|
130
220
|
const listing = await notedListing(() => getPlatform().listDirectory(dirName(key)));
|
|
@@ -153,6 +243,10 @@ export function recordImageSize(path: string, width: number, height: number): bo
|
|
|
153
243
|
return false; // A broken or still-decoding image reports 0 — that is not a measurement.
|
|
154
244
|
}
|
|
155
245
|
const key = normalizeProjectPath(path);
|
|
246
|
+
if (isSvg(key)) {
|
|
247
|
+
// The decode of a vector measures the browser's default object size, not the file.
|
|
248
|
+
return false;
|
|
249
|
+
}
|
|
156
250
|
const known = measured.get(key);
|
|
157
251
|
if (known && known.width === width && known.height === height) {
|
|
158
252
|
return false;
|
|
@@ -169,6 +263,7 @@ export function recordImageSize(path: string, width: number, height: number): bo
|
|
|
169
263
|
export function invalidateMediaMeta(): void {
|
|
170
264
|
settled.clear();
|
|
171
265
|
measured.clear();
|
|
266
|
+
svgRead.clear();
|
|
172
267
|
}
|
|
173
268
|
|
|
174
269
|
/** `84 KB`, `1.2 MB`, `903 B` — one significant fraction, because this is a caption. */
|
package/src/files/media-paths.ts
CHANGED
|
@@ -66,6 +66,9 @@ export function dirName(path: string): string {
|
|
|
66
66
|
/** The open project's content sections, or null when no project (or no `content:`) is loaded. */
|
|
67
67
|
function contentSections(): Record<string, ContentSectionEntry> | null {
|
|
68
68
|
const content = projectState?.projectConfig?.content;
|
|
69
|
+
/* SAFETY: `content` is the parser extension's section, which the schema types leave opaque; its
|
|
70
|
+
Project fragment schema shapes every entry as a `ContentSectionEntry`, and `contentMountFor`
|
|
71
|
+
(the only reader) touches nothing but an entry's optional `source` behind `def?.source`. */
|
|
69
72
|
return (content as Record<string, ContentSectionEntry> | undefined) ?? null;
|
|
70
73
|
}
|
|
71
74
|
|
|
@@ -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
|
+
}
|