@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
|
@@ -14,9 +14,9 @@
|
|
|
14
14
|
* fill in and "absent" is a validation failure they did not cause.
|
|
15
15
|
* 2. **Where the entry files are** is `collectionInfo`'s answer, imported rather than recomputed.
|
|
16
16
|
* Three copies of "which collection owns this path, and what extension do its entries carry"
|
|
17
|
-
*
|
|
18
|
-
* source.ts`'s `collectionInfo`, `browse/library-model.ts`'s
|
|
19
|
-
* no fourth and calls the
|
|
17
|
+
* existed when this was written (`utils/studio-utils.ts`'s `findContentTypeSchema`,
|
|
18
|
+
* `grid/sources/content-source.ts`'s `collectionInfo`, and `browse/library-model.ts`'s
|
|
19
|
+
* `contentTypeFor`, since deleted); this module adds no fourth and calls the exported ones.
|
|
20
20
|
*
|
|
21
21
|
* **The draft axis is NOT here.** `content/draft-state.ts` owns what a draft is and whether drafts
|
|
22
22
|
* are listed, because this module imports `content-source.ts` and the grid source must be able to
|
|
@@ -27,7 +27,15 @@ import { collectionInfo } from "../grid/sources/content-source";
|
|
|
27
27
|
import { projectState } from "../store";
|
|
28
28
|
import { findContentTypeSchema } from "../utils/studio-utils";
|
|
29
29
|
import type { ContentSectionEntry } from "../types";
|
|
30
|
-
import
|
|
30
|
+
import { isNonNullObject, isString } from "@jxsuite/schema/guards";
|
|
31
|
+
import type {
|
|
32
|
+
ContentTypeSchema,
|
|
33
|
+
ContentTypeSchemaField,
|
|
34
|
+
ContentTypeSchemaValue,
|
|
35
|
+
JsonObject,
|
|
36
|
+
JsonValue,
|
|
37
|
+
} from "@jxsuite/schema/types";
|
|
38
|
+
import type { EntryFields } from "./entry-fields";
|
|
31
39
|
|
|
32
40
|
// ─── Collections ─────────────────────────────────────────────────────────────
|
|
33
41
|
|
|
@@ -44,7 +52,7 @@ export interface EntryCollection {
|
|
|
44
52
|
|
|
45
53
|
/** Every content type the project declares, in declaration order. */
|
|
46
54
|
export function collectionNames(): string[] {
|
|
47
|
-
return Object.keys(
|
|
55
|
+
return Object.keys(projectState?.projectConfig?.content ?? {});
|
|
48
56
|
}
|
|
49
57
|
|
|
50
58
|
/**
|
|
@@ -91,15 +99,30 @@ export function collectionOfPath(path: string | null): EntryCollection | null {
|
|
|
91
99
|
return matched ? entryCollection(matched.name) : null;
|
|
92
100
|
}
|
|
93
101
|
|
|
102
|
+
/**
|
|
103
|
+
* The collection a file-backed collection's FILE holds the records of (`content/projects.csv`), or
|
|
104
|
+
* null. {@link entryCollection} refuses one, rightly, for everything that makes a new entry FILE; a
|
|
105
|
+
* record's form only reads its schema and its name, and `dir` is the file itself.
|
|
106
|
+
*/
|
|
107
|
+
export function recordCollectionOfPath(path: string | null): EntryCollection | null {
|
|
108
|
+
const matched = findContentTypeSchema(path ?? null, projectState?.projectConfig);
|
|
109
|
+
const info = matched ? collectionInfo(matched.name) : null;
|
|
110
|
+
if (!matched || !info) {
|
|
111
|
+
return null;
|
|
112
|
+
}
|
|
113
|
+
return { def: info.def, dir: info.dir, ext: info.ext, name: info.name, schema: matched.schema };
|
|
114
|
+
}
|
|
115
|
+
|
|
94
116
|
// ─── Seeding a new entry ─────────────────────────────────────────────────────
|
|
95
117
|
|
|
96
118
|
/** Deep copy of a schema `default`, so two entries seeded from one schema never share an object. */
|
|
97
|
-
function cloneDefault(value:
|
|
98
|
-
if (value === null ||
|
|
119
|
+
function cloneDefault(value: ContentTypeSchemaValue): JsonValue {
|
|
120
|
+
if (value === null || !isNonNullObject(value)) {
|
|
99
121
|
return value;
|
|
100
122
|
}
|
|
123
|
+
// SAFETY: `JSON.parse` of what `JSON.stringify` just wrote is JSON by construction.
|
|
101
124
|
// oxlint-disable-next-line unicorn/prefer-structured-clone
|
|
102
|
-
return JSON.parse(JSON.stringify(value)) as
|
|
125
|
+
return JSON.parse(JSON.stringify(value)) as JsonValue;
|
|
103
126
|
}
|
|
104
127
|
|
|
105
128
|
/**
|
|
@@ -109,8 +132,8 @@ function cloneDefault(value: unknown): unknown {
|
|
|
109
132
|
* and picking an arbitrary entry of the target collection on the author's behalf would be inventing
|
|
110
133
|
* a relationship they never stated.
|
|
111
134
|
*/
|
|
112
|
-
function emptyForField(field: ContentTypeSchemaField):
|
|
113
|
-
if (
|
|
135
|
+
function emptyForField(field: ContentTypeSchemaField): JsonValue {
|
|
136
|
+
if (isString(field.$ref)) {
|
|
114
137
|
return "";
|
|
115
138
|
}
|
|
116
139
|
switch (field.type) {
|
|
@@ -125,7 +148,7 @@ function emptyForField(field: ContentTypeSchemaField): unknown {
|
|
|
125
148
|
return [];
|
|
126
149
|
}
|
|
127
150
|
case "object": {
|
|
128
|
-
return seedEntry(field
|
|
151
|
+
return seedEntry(field);
|
|
129
152
|
}
|
|
130
153
|
default: {
|
|
131
154
|
return "";
|
|
@@ -145,13 +168,13 @@ function emptyForField(field: ContentTypeSchemaField): unknown {
|
|
|
145
168
|
* author's file full of keys they never asked for, and "the schema mentions it" is not a reason for
|
|
146
169
|
* it to exist on disk — the form still draws a row for it, because the form draws the SCHEMA.
|
|
147
170
|
*/
|
|
148
|
-
export function seedEntry(schema: ContentTypeSchema | null | undefined):
|
|
171
|
+
export function seedEntry(schema: ContentTypeSchema | null | undefined): JsonObject {
|
|
149
172
|
const properties = schema?.properties;
|
|
150
173
|
if (!properties) {
|
|
151
174
|
return {};
|
|
152
175
|
}
|
|
153
|
-
const required = new Set(Array.isArray(schema
|
|
154
|
-
const seed:
|
|
176
|
+
const required = new Set(Array.isArray(schema.required) ? schema.required : []);
|
|
177
|
+
const seed: JsonObject = {};
|
|
155
178
|
for (const [name, field] of Object.entries(properties)) {
|
|
156
179
|
if (field.default !== undefined) {
|
|
157
180
|
seed[name] = cloneDefault(field.default);
|
|
@@ -168,7 +191,7 @@ export function seedEntry(schema: ContentTypeSchema | null | undefined): Record<
|
|
|
168
191
|
*/
|
|
169
192
|
export function missingRequired(
|
|
170
193
|
schema: ContentTypeSchema | null | undefined,
|
|
171
|
-
value:
|
|
194
|
+
value: EntryFields,
|
|
172
195
|
): string[] {
|
|
173
196
|
const required = Array.isArray(schema?.required) ? schema.required : [];
|
|
174
197
|
return required.filter((name) => value[name] === undefined);
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Opening a content entry: on the canvas in its writing editor, or in the form when it has no body.
|
|
3
|
+
*
|
|
4
|
+
* `specs/studio.md` §22.5: an entry is written on the canvas, inside its template's layout. Opening
|
|
5
|
+
* one opens the entry's OWN file as an ordinary tab — same id, history and dirty flag as a click in
|
|
6
|
+
* any file list — in its writing editor (`edit`), which the Content workspace owns (§22.1), so the
|
|
7
|
+
* Inspector's Entry tab and the writing-mode banner come with it. Every door into an entry goes
|
|
8
|
+
* through {@link openEntry}: a row in the Content home, an entry in the Collections panel,
|
|
9
|
+
* `content.openEntry`, and the entry Add new just made.
|
|
10
|
+
*
|
|
11
|
+
* The form-only `entry` editor (`content/entry-editor.ts`) stays as the fallback for an entry the
|
|
12
|
+
* canvas cannot write: a JSON entry IS its fields and has no body, and a file-backed collection's
|
|
13
|
+
* entries are rows of one file, which open in the grid.
|
|
14
|
+
*
|
|
15
|
+
* @docs studio/projects/content-types
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
import { activateTab, workspace } from "../workspace/workspace";
|
|
19
|
+
import { collectionForFile } from "./collection-match";
|
|
20
|
+
import { writesEntryOnCanvas } from "./content-collections";
|
|
21
|
+
import { openEntryEditor } from "./entry-editor";
|
|
22
|
+
import type { Tab } from "../tabs/tab";
|
|
23
|
+
|
|
24
|
+
/** The editor an entry is written in on the canvas. */
|
|
25
|
+
export const WRITING_MODE = "edit";
|
|
26
|
+
|
|
27
|
+
/** The editors a tab already writes on the canvas in, which an open leaves alone. */
|
|
28
|
+
const CANVAS_MODES: ReadonlySet<string> = new Set(["edit", "design"]);
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* The open tab for a path, if any (a document tab's id is its path, but a renamed one keeps its
|
|
32
|
+
* id).
|
|
33
|
+
*/
|
|
34
|
+
function tabFor(path: string): Tab | null {
|
|
35
|
+
for (const tab of workspace.tabs.values()) {
|
|
36
|
+
if (tab.documentPath === path) {
|
|
37
|
+
return tab;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
return null;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Open a content entry where it is written.
|
|
45
|
+
*
|
|
46
|
+
* - An entry with a body opens on the canvas in `edit`. A tab already writing it on the canvas
|
|
47
|
+
* (`edit` or `design`) keeps its view; a tab showing its form is switched back to writing.
|
|
48
|
+
* - A JSON entry opens in the form editor.
|
|
49
|
+
* - Any other file opens as `openFileInTab` would open it.
|
|
50
|
+
*
|
|
51
|
+
* @returns The tab, or `null` when the file could not be opened.
|
|
52
|
+
*/
|
|
53
|
+
export async function openEntry(path: string): Promise<Tab | null> {
|
|
54
|
+
if (!writesEntryOnCanvas(path)) {
|
|
55
|
+
const match = collectionForFile(path);
|
|
56
|
+
if (match && !match.fileBacked) {
|
|
57
|
+
return openEntryEditor(path);
|
|
58
|
+
}
|
|
59
|
+
/* Dynamic for the reason `entry-editor.ts` gives: `files/files.ts` reaches the platform, the
|
|
60
|
+
format registry and the packages layer. */
|
|
61
|
+
const { openFileInTab } = await import("../files/files");
|
|
62
|
+
await openFileInTab(path);
|
|
63
|
+
return tabFor(path);
|
|
64
|
+
}
|
|
65
|
+
const { openFileInTab } = await import("../files/files");
|
|
66
|
+
await openFileInTab(path);
|
|
67
|
+
const tab = tabFor(path);
|
|
68
|
+
if (!tab) {
|
|
69
|
+
return null;
|
|
70
|
+
}
|
|
71
|
+
if (
|
|
72
|
+
!CANVAS_MODES.has(tab.session.ui.canvasMode) &&
|
|
73
|
+
tab.capabilities.modes.includes(WRITING_MODE)
|
|
74
|
+
) {
|
|
75
|
+
tab.session.ui.canvasMode = WRITING_MODE;
|
|
76
|
+
tab.session.ui.preview = false;
|
|
77
|
+
}
|
|
78
|
+
activateTab(tab.id);
|
|
79
|
+
return tab;
|
|
80
|
+
}
|
|
@@ -0,0 +1,440 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What the Collections panel says about each entry while one is being written: its title, its
|
|
3
|
+
* status and a thumbnail (`specs/studio.md` §22.5, the Content-Entry board's list).
|
|
4
|
+
*
|
|
5
|
+
* The scan behind the Content home knows each entry's FILE (`browse/library-model.ts`); a title and
|
|
6
|
+
* a draft flag are inside it. So this reads each entry once — a JSON entry's root, a Markdown
|
|
7
|
+
* entry's frontmatter, through the same parse the editor uses — and keeps the answer until a file
|
|
8
|
+
* event drops it ({@link invalidateEntrySummaries}, wired beside the scan's own invalidation in
|
|
9
|
+
* `studio.ts`). A collection is read when its list is first shown, not before: the panel lists one
|
|
10
|
+
* collection's entries at a time.
|
|
11
|
+
*
|
|
12
|
+
* The read never rejects. An entry that cannot be read is listed under its file name with no
|
|
13
|
+
* status, which is what the file list would have said about it anyway.
|
|
14
|
+
*
|
|
15
|
+
* @docs studio/projects/browse
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
import {
|
|
19
|
+
isBoolean,
|
|
20
|
+
isJsonObject,
|
|
21
|
+
isNonNullObject,
|
|
22
|
+
isNumber,
|
|
23
|
+
isString,
|
|
24
|
+
} from "@jxsuite/schema/guards";
|
|
25
|
+
import { getPlatform, hasPlatform } from "../platform";
|
|
26
|
+
import { isImage } from "../files/media-upload";
|
|
27
|
+
import { previewFileSrc } from "../files/media-paths";
|
|
28
|
+
import { hasDraftAxis, isDraftEntry } from "./draft-state";
|
|
29
|
+
import { entryCollection } from "./entry-model";
|
|
30
|
+
import { PAGES_COLLECTION, collectionKeyOfPath } from "./content-collections";
|
|
31
|
+
import type { EntryFields } from "./entry-fields";
|
|
32
|
+
import type { ContentTypeSchema, ContentTypeSchemaField } from "@jxsuite/schema/types";
|
|
33
|
+
|
|
34
|
+
/** One entry, as the Collections panel lists it. */
|
|
35
|
+
export interface EntrySummary {
|
|
36
|
+
path: string;
|
|
37
|
+
/** The entry's `title`, or its file name without the extension. */
|
|
38
|
+
title: string;
|
|
39
|
+
/** "Draft", "Published", or "" for a collection with no draft workflow. */
|
|
40
|
+
status: string;
|
|
41
|
+
/** A preview URL for the entry's first image field, or "" for none. */
|
|
42
|
+
thumb: string;
|
|
43
|
+
/** The entry's own date as `YYYY-MM-DD`, from its schema's date field, or "" for none. */
|
|
44
|
+
date: string;
|
|
45
|
+
/**
|
|
46
|
+
* Each field the collection's schema declares and the entry holds, as the text a list cell shows.
|
|
47
|
+
* Absent from a summary the read did not reach.
|
|
48
|
+
*/
|
|
49
|
+
fields?: EntryFieldTexts;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/** An entry's declared fields as list text, keyed by field name. */
|
|
53
|
+
export type EntryFieldTexts = Readonly<Record<string, string>>;
|
|
54
|
+
|
|
55
|
+
/** Field names that mean "what this entry is called", in the order they are preferred. */
|
|
56
|
+
const TITLE_FIELD_NAMES = ["title", "name", "label", "heading"];
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* The field an entry is called by: `title` where there is one, else the first conventionally named
|
|
60
|
+
* field (`name`, `label`, `heading`) the schema declares or the entry holds. A menu item with a
|
|
61
|
+
* `name` and no `title` is "Espresso Tiramisu", not `tiramisu`.
|
|
62
|
+
*/
|
|
63
|
+
export function titleFieldOf(
|
|
64
|
+
schema: ContentTypeSchema | null | undefined,
|
|
65
|
+
fields: EntryFields = {},
|
|
66
|
+
): string | null {
|
|
67
|
+
// Whatever its capitalisation: a CSV's header row is often `Title`, and it is still the title.
|
|
68
|
+
const named = (keys: readonly string[]): string | null => {
|
|
69
|
+
for (const wanted of TITLE_FIELD_NAMES) {
|
|
70
|
+
const key = keys.find((candidate) => candidate.toLowerCase() === wanted);
|
|
71
|
+
if (key !== undefined) {
|
|
72
|
+
return key;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
return null;
|
|
76
|
+
};
|
|
77
|
+
return (
|
|
78
|
+
named(Object.keys(schema?.properties ?? {})) ??
|
|
79
|
+
named(Object.keys(fields).filter((key) => isString(fields[key])))
|
|
80
|
+
);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/** A field value as list text: a scalar as written, a list of scalars joined, anything else "". */
|
|
84
|
+
export function fieldText(value: EntryFields[string] | Date | undefined): string {
|
|
85
|
+
if (value instanceof Date) {
|
|
86
|
+
return entryDay(value);
|
|
87
|
+
}
|
|
88
|
+
if (isString(value)) {
|
|
89
|
+
return value;
|
|
90
|
+
}
|
|
91
|
+
if (isNumber(value) || isBoolean(value)) {
|
|
92
|
+
return String(value);
|
|
93
|
+
}
|
|
94
|
+
if (Array.isArray(value)) {
|
|
95
|
+
return value
|
|
96
|
+
.filter((item) => isString(item) || isNumber(item))
|
|
97
|
+
.map(String)
|
|
98
|
+
.join(", ");
|
|
99
|
+
}
|
|
100
|
+
return "";
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/** The text of every field the schema declares and the entry holds. */
|
|
104
|
+
function fieldTexts(
|
|
105
|
+
schema: ContentTypeSchema | null | undefined,
|
|
106
|
+
fields: EntryFields,
|
|
107
|
+
): EntryFieldTexts {
|
|
108
|
+
return Object.fromEntries(
|
|
109
|
+
Object.keys(schema?.properties ?? {})
|
|
110
|
+
.filter((name) => name in fields)
|
|
111
|
+
.map((name) => [name, fieldText(fields[name])]),
|
|
112
|
+
);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/** Field names that mean "when this entry is dated" when the schema types no field as a date. */
|
|
116
|
+
const DATE_FIELD_NAMES = ["date", "published", "publishedAt", "pubDate", "publishDate"];
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* The field an entry is dated by: the first schema property typed `format: date` or `date-time`,
|
|
120
|
+
* else the first conventionally named one present in the entry.
|
|
121
|
+
*/
|
|
122
|
+
export function dateFieldOf(
|
|
123
|
+
schema: ContentTypeSchema | null | undefined,
|
|
124
|
+
fields: EntryFields,
|
|
125
|
+
): string | null {
|
|
126
|
+
for (const [name, field] of Object.entries(schema?.properties ?? {})) {
|
|
127
|
+
if (field.format === "date" || field.format === "date-time") {
|
|
128
|
+
return name;
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
return DATE_FIELD_NAMES.find((name) => name in fields) ?? null;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* The field a collection's SCHEMA says its entries are dated by, before any entry is read: the
|
|
136
|
+
* first property typed `format: date` or `date-time`, else the first conventionally named property
|
|
137
|
+
* it declares (a `date` with no format is still the date). The Table draws that field in its Date
|
|
138
|
+
* column, so it is the one field column it must not draw a second time.
|
|
139
|
+
*/
|
|
140
|
+
export function declaredDateField(schema: ContentTypeSchema | null | undefined): string | null {
|
|
141
|
+
const properties = schema?.properties ?? {};
|
|
142
|
+
return dateFieldOf(schema, Object.fromEntries(Object.keys(properties).map((name) => [name, ""])));
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* A date value as `YYYY-MM-DD`, or "". A value that already starts with a calendar date is taken as
|
|
147
|
+
* written, so a date is never shifted a day by a time zone; anything else must parse.
|
|
148
|
+
*/
|
|
149
|
+
export function entryDay(value: EntryFields[string] | Date): string {
|
|
150
|
+
if (value instanceof Date) {
|
|
151
|
+
return Number.isNaN(value.getTime()) ? "" : value.toISOString().slice(0, 10);
|
|
152
|
+
}
|
|
153
|
+
if (!isString(value)) {
|
|
154
|
+
return "";
|
|
155
|
+
}
|
|
156
|
+
const written = /^(\d{4}-\d{2}-\d{2})/.exec(value.trim());
|
|
157
|
+
if (written) {
|
|
158
|
+
return written[1]!;
|
|
159
|
+
}
|
|
160
|
+
const parsed = new Date(value);
|
|
161
|
+
if (value.trim() === "" || Number.isNaN(parsed.getTime())) {
|
|
162
|
+
return "";
|
|
163
|
+
}
|
|
164
|
+
if (!value.includes(":")) {
|
|
165
|
+
/* A date with no time of day (`6/3/2026`, `March 4, 2026`) parses as LOCAL midnight, so its
|
|
166
|
+
UTC day is the day before anywhere east of Greenwich: the day is read where it was parsed. */
|
|
167
|
+
const month = String(parsed.getMonth() + 1).padStart(2, "0");
|
|
168
|
+
const day = String(parsed.getDate()).padStart(2, "0");
|
|
169
|
+
return `${parsed.getFullYear()}-${month}-${day}`;
|
|
170
|
+
}
|
|
171
|
+
return parsed.toISOString().slice(0, 10);
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
/** The file name without its extension: `content/posts/hello.md` → `hello`. */
|
|
175
|
+
export function entryStem(path: string): string {
|
|
176
|
+
const base = path.split("/").pop() ?? path;
|
|
177
|
+
const dot = base.lastIndexOf(".");
|
|
178
|
+
return dot > 0 ? base.slice(0, dot) : base;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
/**
|
|
182
|
+
* The project path an image field names, resolved the way the entry's own page resolves it: a
|
|
183
|
+
* root-relative URL is served from `public/`, anything else is relative to the entry's folder.
|
|
184
|
+
*/
|
|
185
|
+
export function imageFieldPath(entryPath: string, value: string): string {
|
|
186
|
+
if (value.startsWith("/")) {
|
|
187
|
+
return `public${value}`;
|
|
188
|
+
}
|
|
189
|
+
const dir = entryPath.includes("/") ? entryPath.slice(0, entryPath.lastIndexOf("/")) : "";
|
|
190
|
+
const parts = [...(dir === "" ? [] : dir.split("/")), ...value.split("/")];
|
|
191
|
+
const out: string[] = [];
|
|
192
|
+
for (const part of parts) {
|
|
193
|
+
if (part === "" || part === ".") {
|
|
194
|
+
continue;
|
|
195
|
+
}
|
|
196
|
+
if (part === "..") {
|
|
197
|
+
out.pop();
|
|
198
|
+
} else {
|
|
199
|
+
out.push(part);
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
return out.join("/");
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
/** A URL with a scheme (`https://…`, `data:…`): a picture somewhere else, shown as it is. */
|
|
206
|
+
const ABSOLUTE_URL = /^[a-z][\d+.a-z-]*:/i;
|
|
207
|
+
|
|
208
|
+
/** An image field's value as the URL a thumbnail is drawn from, or "" for no picture. */
|
|
209
|
+
function thumbSrc(entryPath: string, value: string): string {
|
|
210
|
+
const trimmed = value.trim();
|
|
211
|
+
if (trimmed === "") {
|
|
212
|
+
return "";
|
|
213
|
+
}
|
|
214
|
+
return ABSOLUTE_URL.test(trimmed) ? trimmed : previewFileSrc(imageFieldPath(entryPath, trimmed));
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
/**
|
|
218
|
+
* The picture a field holds, when it is one: an image path, an image object's `src`
|
|
219
|
+
* (`featuredImage: { src, alt }`), or a list of them (its first).
|
|
220
|
+
*/
|
|
221
|
+
function pictureIn(value: EntryFields[string]): string {
|
|
222
|
+
if (isString(value)) {
|
|
223
|
+
// A CSV keeps a gallery as one cell, its URLs separated by commas.
|
|
224
|
+
return value.split(",")[0]?.trim() ?? "";
|
|
225
|
+
}
|
|
226
|
+
if (Array.isArray(value)) {
|
|
227
|
+
const [first] = value;
|
|
228
|
+
return first === undefined ? "" : pictureIn(first);
|
|
229
|
+
}
|
|
230
|
+
if (isJsonObject(value)) {
|
|
231
|
+
const { src } = value;
|
|
232
|
+
return isString(src) ? src : "";
|
|
233
|
+
}
|
|
234
|
+
return "";
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
/** Whether a schema property holds a picture: `format: image`, an image object, or a list of either. */
|
|
238
|
+
function isPictureField(field: ContentTypeSchemaField): boolean {
|
|
239
|
+
if (field.format === "image") {
|
|
240
|
+
return true;
|
|
241
|
+
}
|
|
242
|
+
const src = field.properties?.["src"];
|
|
243
|
+
if (field.type === "object" && src?.type === "string") {
|
|
244
|
+
return true;
|
|
245
|
+
}
|
|
246
|
+
return field.type === "array" && field.items !== undefined && isPictureField(field.items);
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
/**
|
|
250
|
+
* An entry's thumbnail: the first field its SCHEMA declares as a picture that holds one, so a post
|
|
251
|
+
* is shown by its `cover` and not by whatever image path happens to come first; else the first
|
|
252
|
+
* field whose value is an image file. A preview URL, or "" for none.
|
|
253
|
+
*/
|
|
254
|
+
export function entryThumb(
|
|
255
|
+
entryPath: string,
|
|
256
|
+
fields: EntryFields,
|
|
257
|
+
schema: ContentTypeSchema | null | undefined,
|
|
258
|
+
): string {
|
|
259
|
+
for (const [name, field] of Object.entries(schema?.properties ?? {})) {
|
|
260
|
+
if (isPictureField(field)) {
|
|
261
|
+
const src = thumbSrc(entryPath, pictureIn(fields[name]));
|
|
262
|
+
if (src !== "") {
|
|
263
|
+
return src;
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
for (const value of Object.values(fields)) {
|
|
268
|
+
if (!isString(value) || ABSOLUTE_URL.test(value)) {
|
|
269
|
+
continue;
|
|
270
|
+
}
|
|
271
|
+
const dot = value.lastIndexOf(".");
|
|
272
|
+
if (dot > 0 && isImage(value.slice(dot))) {
|
|
273
|
+
return previewFileSrc(imageFieldPath(entryPath, value));
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
return "";
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
/** One entry's summary, from its fields. Pure. */
|
|
280
|
+
export function summarize(path: string, collection: string, fields: EntryFields): EntrySummary {
|
|
281
|
+
const schema = entryCollection(collection)?.schema ?? null;
|
|
282
|
+
const titleField = titleFieldOf(schema, fields);
|
|
283
|
+
const title = titleField === null ? undefined : fields[titleField];
|
|
284
|
+
const draft = isDraftEntry(fields);
|
|
285
|
+
const dateField = dateFieldOf(schema, fields);
|
|
286
|
+
return {
|
|
287
|
+
date: dateField === null ? "" : entryDay(fields[dateField]),
|
|
288
|
+
fields: fieldTexts(schema, fields),
|
|
289
|
+
path,
|
|
290
|
+
status: hasDraftAxis(schema, fields) ? (draft ? "Draft" : "Published") : "",
|
|
291
|
+
thumb: entryThumb(path, fields, schema),
|
|
292
|
+
title: isString(title) && title.trim() !== "" ? title : entryStem(path),
|
|
293
|
+
};
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
/** Read one entry's fields: a JSON entry's root, any other entry's frontmatter. */
|
|
297
|
+
async function readFields(path: string): Promise<EntryFields> {
|
|
298
|
+
try {
|
|
299
|
+
const source = await getPlatform().readFile(path);
|
|
300
|
+
if (path.endsWith(".json")) {
|
|
301
|
+
const parsed: unknown = JSON.parse(source);
|
|
302
|
+
/* SAFETY: a JSON entry is an object of fields (its collection's schema says so); a value
|
|
303
|
+
that is not an object has no fields, which is what the fallback below says. */
|
|
304
|
+
return isNonNullObject(parsed) && !Array.isArray(parsed) ? (parsed as EntryFields) : {};
|
|
305
|
+
}
|
|
306
|
+
const { parseSourceForPath } = await import("../files/file-ops");
|
|
307
|
+
const parsed = await parseSourceForPath(path, source);
|
|
308
|
+
return parsed.frontmatter;
|
|
309
|
+
} catch {
|
|
310
|
+
return {};
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
// ─── The cache ───────────────────────────────────────────────────────────────
|
|
315
|
+
|
|
316
|
+
/** Entries read, keyed by collection and then by the paths listed, so a changed list re-reads. */
|
|
317
|
+
const _cache = new Map<string, { key: string; entries: EntrySummary[] }>();
|
|
318
|
+
const _loading = new Map<string, Promise<EntrySummary[]>>();
|
|
319
|
+
let _epoch = 0;
|
|
320
|
+
const _listeners = new Set<() => void>();
|
|
321
|
+
|
|
322
|
+
/** At most this many entries are read for one list; the rest are listed by file name. */
|
|
323
|
+
export const SUMMARY_READ_LIMIT = 200;
|
|
324
|
+
|
|
325
|
+
/** Read a collection's entries. */
|
|
326
|
+
async function readSummaries(
|
|
327
|
+
collection: string,
|
|
328
|
+
paths: readonly string[],
|
|
329
|
+
): Promise<EntrySummary[]> {
|
|
330
|
+
const read = paths.slice(0, SUMMARY_READ_LIMIT);
|
|
331
|
+
const summaries = await Promise.all(
|
|
332
|
+
read.map(async (path) => summarize(path, collection, await readFields(path))),
|
|
333
|
+
);
|
|
334
|
+
const rest = paths.slice(SUMMARY_READ_LIMIT).map((path): EntrySummary => ({
|
|
335
|
+
date: "",
|
|
336
|
+
path,
|
|
337
|
+
status: "",
|
|
338
|
+
thumb: "",
|
|
339
|
+
title: entryStem(path),
|
|
340
|
+
}));
|
|
341
|
+
return [...summaries, ...rest];
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
/**
|
|
345
|
+
* A collection's entries as they stand: the summaries, or `null` while they are being read (which
|
|
346
|
+
* starts the read; listeners are told when it lands).
|
|
347
|
+
*
|
|
348
|
+
* @param collection - The collection's name.
|
|
349
|
+
* @param paths - Its entry files, in the order to list them.
|
|
350
|
+
*/
|
|
351
|
+
export function peekEntrySummaries(
|
|
352
|
+
collection: string,
|
|
353
|
+
paths: readonly string[],
|
|
354
|
+
): EntrySummary[] | null {
|
|
355
|
+
const key = paths.join("\n");
|
|
356
|
+
const cached = _cache.get(collection);
|
|
357
|
+
if (cached?.key === key) {
|
|
358
|
+
return cached.entries;
|
|
359
|
+
}
|
|
360
|
+
if (!hasPlatform()) {
|
|
361
|
+
return paths.map((path) => ({ date: "", path, status: "", thumb: "", title: entryStem(path) }));
|
|
362
|
+
}
|
|
363
|
+
const inFlight = `${collection}\n${key}`;
|
|
364
|
+
if (!_loading.has(inFlight)) {
|
|
365
|
+
const epoch = _epoch;
|
|
366
|
+
const read: Promise<EntrySummary[]> = readSummaries(collection, paths).then((entries) => {
|
|
367
|
+
/* Only this read's own record: an invalidation in between may have handed the key to a newer
|
|
368
|
+
read, and deleting THAT record would let the next peek start a duplicate of it. */
|
|
369
|
+
if (_loading.get(inFlight) === read) {
|
|
370
|
+
_loading.delete(inFlight);
|
|
371
|
+
}
|
|
372
|
+
if (epoch === _epoch) {
|
|
373
|
+
_cache.set(collection, { entries, key });
|
|
374
|
+
for (const listener of _listeners) {
|
|
375
|
+
listener();
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
return entries;
|
|
379
|
+
});
|
|
380
|
+
_loading.set(inFlight, read);
|
|
381
|
+
}
|
|
382
|
+
return null;
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
/**
|
|
386
|
+
* Restate one entry from the fields Studio just saved, without reading the file back.
|
|
387
|
+
*
|
|
388
|
+
* A save is Studio's own write, so the fields it wrote are the answer, and re-reading them is both
|
|
389
|
+
* slower and wrong: the dev server lets a file read be cached for five seconds, and its watcher
|
|
390
|
+
* skips some trees outright, so the list kept the old title after an author renamed an entry and
|
|
391
|
+
* pressed Save. An entry no cached list holds is left to that list's own first read.
|
|
392
|
+
*
|
|
393
|
+
* @returns Whether a cached list held the entry and now states the saved fields.
|
|
394
|
+
*/
|
|
395
|
+
export function noteEntrySaved(path: string, collection: string, fields: EntryFields): boolean {
|
|
396
|
+
const cached = _cache.get(collection);
|
|
397
|
+
const index = cached?.entries.findIndex((entry) => entry.path === path) ?? -1;
|
|
398
|
+
if (!cached || index === -1) {
|
|
399
|
+
return false;
|
|
400
|
+
}
|
|
401
|
+
const entries = cached.entries.with(index, summarize(path, collection, fields));
|
|
402
|
+
_cache.set(collection, { entries, key: cached.key });
|
|
403
|
+
for (const listener of _listeners) {
|
|
404
|
+
listener();
|
|
405
|
+
}
|
|
406
|
+
return true;
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
/**
|
|
410
|
+
* After a document saved: restate its entry, when it is one, from the fields it saved.
|
|
411
|
+
*
|
|
412
|
+
* The caller says where the fields live (`entryFields(tab)`, the live record, not a copy), which
|
|
413
|
+
* keeps the editor's field store out of this data module's graph.
|
|
414
|
+
*
|
|
415
|
+
* @param path The saved document's path, or `null` for one with no file.
|
|
416
|
+
* @param fields The saved document's fields: a JSON entry's root, any other entry's frontmatter.
|
|
417
|
+
* @returns Whether a cached list changed, so the caller knows a repaint is due.
|
|
418
|
+
*/
|
|
419
|
+
export function noteSavedEntry(path: string | null, fields: EntryFields): boolean {
|
|
420
|
+
const collection = collectionKeyOfPath(path);
|
|
421
|
+
if (path === null || collection === null || collection === PAGES_COLLECTION) {
|
|
422
|
+
return false;
|
|
423
|
+
}
|
|
424
|
+
return noteEntrySaved(path, collection, fields);
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
/** Forget every summary. The next list re-reads its entries. */
|
|
428
|
+
export function invalidateEntrySummaries(): void {
|
|
429
|
+
_epoch += 1;
|
|
430
|
+
_cache.clear();
|
|
431
|
+
_loading.clear();
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
/** Be told when a read lands. Returns the function that stops it. */
|
|
435
|
+
export function onEntrySummariesRead(listener: () => void): () => void {
|
|
436
|
+
_listeners.add(listener);
|
|
437
|
+
return () => {
|
|
438
|
+
_listeners.delete(listener);
|
|
439
|
+
};
|
|
440
|
+
}
|