@jxsuite/studio 2.0.1 → 2.1.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/canvas.html +21 -0
- package/dist/chunks/Overlay.dev-qfjzkms8.js +23 -0
- package/dist/chunks/{Overlay.dev-cszx7yn5.js.map → Overlay.dev-qfjzkms8.js.map} +1 -1
- package/dist/chunks/{adapter-z8fkmbmv.js → adapter-qfnesn5v.js} +2 -2
- package/dist/chunks/{cf-settings-mmyv436d.js → cf-settings-73sh5z4p.js} +3 -3
- package/dist/chunks/{components-799jpvhm.js → components-2bpqp4mq.js} +5 -7
- package/dist/chunks/{components-799jpvhm.js.map → components-2bpqp4mq.js.map} +1 -1
- package/dist/chunks/content-source-34gnkbnn.js +38 -0
- package/dist/chunks/content-source-34gnkbnn.js.map +9 -0
- package/dist/chunks/convert-to-repeater-t8kxre5s.js +258 -0
- package/dist/chunks/convert-to-repeater-t8kxre5s.js.map +10 -0
- package/dist/chunks/extension-sections-6mqydz35.js +301 -0
- package/dist/chunks/extension-sections-6mqydz35.js.map +11 -0
- package/dist/chunks/files-8kn321h1.js +70 -0
- package/dist/chunks/files-8kn321h1.js.map +9 -0
- package/dist/chunks/{format-host-8jxwts2p.js → format-host-mkwsmt8d.js} +3 -3
- package/dist/chunks/git-panel-tjbr191d.js +51 -0
- package/dist/chunks/git-panel-tjbr191d.js.map +9 -0
- package/dist/chunks/{index-baeg2x3y.js → index-7pxchcce.js} +4 -3
- package/dist/chunks/{index-baeg2x3y.js.map → index-7pxchcce.js.map} +3 -3
- package/dist/chunks/{jx-validate-qx8degj7.js → jx-validate-v6qgknrs.js} +3 -3
- package/dist/chunks/layers-panel-87pb25km.js +58 -0
- package/dist/chunks/layers-panel-87pb25km.js.map +9 -0
- package/dist/chunks/{monaco-setup-zjdp9zdw.js → monaco-setup-e0yh25j4.js} +4 -75
- package/dist/chunks/{monaco-setup-zjdp9zdw.js.map → monaco-setup-e0yh25j4.js.map} +3 -4
- package/dist/chunks/publish-panel-m0fcgdrt.js +21 -0
- package/dist/chunks/publish-panel-m0fcgdrt.js.map +9 -0
- package/dist/chunks/quick-search-rhs6cbsb.js +53 -0
- package/dist/chunks/quick-search-rhs6cbsb.js.map +9 -0
- package/dist/chunks/right-panel-zpdxd98t.js +37 -0
- package/dist/chunks/right-panel-zpdxd98t.js.map +9 -0
- package/dist/chunks/section-registry-zmckaxf4.js +30 -0
- package/dist/chunks/section-registry-zmckaxf4.js.map +9 -0
- package/dist/chunks/sp-overlay.dev-9ran0sfq.js +18 -0
- package/dist/chunks/sp-overlay.dev-9ran0sfq.js.map +9 -0
- package/dist/chunks/sp-popover.dev-rebmdncr.js +9 -0
- package/dist/chunks/sp-popover.dev-rebmdncr.js.map +9 -0
- package/dist/chunks/sp-progress-circle.dev-5vkppfkt.js +15 -0
- package/dist/chunks/sp-progress-circle.dev-5vkppfkt.js.map +9 -0
- package/dist/chunks/sp-tooltip.dev-ykqrg8h5.js +15 -0
- package/dist/chunks/sp-tooltip.dev-ykqrg8h5.js.map +9 -0
- package/dist/chunks/{sp-tray.dev-pb2fsd65.js → sp-tray.dev-ey59vfh7.js} +10 -8
- package/dist/chunks/{sp-tray.dev-pb2fsd65.js.map → sp-tray.dev-ey59vfh7.js.map} +2 -2
- package/dist/chunks/{studio-9qr6fq7p.js → studio-0rqqhrdy.js} +10 -378
- package/dist/chunks/{studio-9qr6fq7p.js.map → studio-0rqqhrdy.js.map} +4 -14
- package/dist/chunks/{studio-5x6tqqec.js → studio-2sj1jj3v.js} +10 -8
- package/dist/chunks/{studio-5x6tqqec.js.map → studio-2sj1jj3v.js.map} +2 -2
- package/dist/chunks/{studio-h3fw0wn8.js → studio-3fk1ac9z.js} +6 -4
- package/dist/chunks/{studio-h3fw0wn8.js.map → studio-3fk1ac9z.js.map} +2 -2
- package/dist/chunks/studio-41exgzhq.js +6690 -0
- package/dist/chunks/studio-41exgzhq.js.map +38 -0
- package/dist/chunks/{studio-7v2bpweq.js → studio-4mqb9a0j.js} +6 -4
- package/dist/chunks/{studio-7v2bpweq.js.map → studio-4mqb9a0j.js.map} +2 -2
- package/dist/chunks/studio-50k77cca.js +1787 -0
- package/dist/chunks/studio-50k77cca.js.map +18 -0
- package/dist/chunks/{studio-56v3zwk6.js → studio-6a0h7sne.js} +10 -8
- package/dist/chunks/{studio-56v3zwk6.js.map → studio-6a0h7sne.js.map} +2 -2
- package/dist/chunks/studio-6f0am8f2.js +53 -0
- package/dist/chunks/studio-6f0am8f2.js.map +10 -0
- package/dist/chunks/studio-6nmjc5qk.js +7002 -0
- package/dist/chunks/studio-6nmjc5qk.js.map +32 -0
- package/dist/chunks/studio-7afd21nw.js +67 -0
- package/dist/chunks/studio-7afd21nw.js.map +10 -0
- package/dist/chunks/{studio-xze4zrs1.js → studio-7bmdvzne.js} +2 -2
- package/dist/chunks/studio-7bmdvzne.js.map +10 -0
- package/dist/chunks/studio-8jpzqk5s.js +128 -0
- package/dist/chunks/studio-8jpzqk5s.js.map +10 -0
- package/dist/chunks/studio-94m77tgk.js +1482 -0
- package/dist/chunks/studio-94m77tgk.js.map +13 -0
- package/dist/chunks/studio-9je6w5sq.js +1000 -0
- package/dist/chunks/studio-9je6w5sq.js.map +16 -0
- package/dist/chunks/{studio-w7c6kvk7.js → studio-a7pzveq1.js} +2 -2
- package/dist/chunks/{studio-gs6kqdt1.js → studio-adn0z1k3.js} +14 -8
- package/dist/chunks/{studio-gs6kqdt1.js.map → studio-adn0z1k3.js.map} +3 -3
- package/dist/chunks/{studio-kr88ryr7.js → studio-bsxxx3x2.js} +17 -15
- package/dist/chunks/{studio-kr88ryr7.js.map → studio-bsxxx3x2.js.map} +7 -6
- package/dist/chunks/studio-bw5dz6km.js +608 -0
- package/dist/chunks/studio-bw5dz6km.js.map +21 -0
- package/dist/chunks/studio-c6s5getc.js +4232 -0
- package/dist/chunks/studio-c6s5getc.js.map +39 -0
- package/dist/chunks/studio-cjm7fdjf.js +451 -0
- package/dist/chunks/studio-cjm7fdjf.js.map +14 -0
- package/dist/chunks/{studio-cx91mnh8.js → studio-ckwz4d3x.js} +53 -53
- package/dist/chunks/{studio-cx91mnh8.js.map → studio-ckwz4d3x.js.map} +5 -5
- package/dist/chunks/studio-dg7zyt5x.js +28 -0
- package/dist/chunks/studio-dg7zyt5x.js.map +10 -0
- package/dist/chunks/studio-dzcyvvvz.js +166 -0
- package/dist/chunks/studio-dzcyvvvz.js.map +10 -0
- package/dist/chunks/{studio-7kqe41sa.js → studio-evkr5d3b.js} +15 -7
- package/dist/chunks/{studio-7kqe41sa.js.map → studio-evkr5d3b.js.map} +5 -4
- package/dist/chunks/studio-f12h6177.js +411 -0
- package/dist/chunks/studio-f12h6177.js.map +13 -0
- package/dist/chunks/studio-hbp1s8m9.js +1817 -0
- package/dist/chunks/studio-hbp1s8m9.js.map +14 -0
- package/dist/chunks/studio-k2sxjph5.js +15295 -0
- package/dist/chunks/studio-k2sxjph5.js.map +235 -0
- package/dist/chunks/studio-myje51t3.js +385 -0
- package/dist/chunks/studio-myje51t3.js.map +12 -0
- package/dist/chunks/studio-na6rwzhw.js +353 -0
- package/dist/chunks/studio-na6rwzhw.js.map +11 -0
- package/dist/chunks/{studio-4k0m4fmp.js → studio-p471gg5n.js} +126 -3
- package/dist/chunks/studio-p471gg5n.js.map +14 -0
- package/dist/chunks/{studio-9bjwf0d4.js → studio-rgwcpast.js} +5 -46
- package/dist/chunks/{studio-9bjwf0d4.js.map → studio-rgwcpast.js.map} +4 -5
- package/dist/chunks/{studio-esrewxap.js → studio-s519zc99.js} +3 -3
- package/dist/chunks/studio-sas7mt3k.js +75 -0
- package/dist/chunks/studio-sas7mt3k.js.map +10 -0
- package/dist/chunks/studio-t5wqwzdh.js +74 -0
- package/dist/chunks/studio-t5wqwzdh.js.map +10 -0
- package/dist/chunks/{studio-9qpspwfn.js → studio-temv5bw4.js} +5 -5
- package/dist/chunks/{studio-9qpspwfn.js.map → studio-temv5bw4.js.map} +1 -1
- package/dist/chunks/{studio-qkvt37c0.js → studio-tr20m8g8.js} +669 -18
- package/dist/chunks/studio-tr20m8g8.js.map +9 -0
- package/dist/chunks/{studio-v7jkjqng.js → studio-v96qfnww.js} +5514 -4291
- package/dist/chunks/studio-v96qfnww.js.map +54 -0
- package/dist/chunks/studio-vcetc91k.js +659 -0
- package/dist/chunks/studio-vcetc91k.js.map +10 -0
- package/dist/chunks/{studio-jm0z592w.js → studio-vmymjefp.js} +2972 -4937
- package/dist/chunks/studio-vmymjefp.js.map +98 -0
- package/dist/chunks/{studio-bghqfmea.js → studio-wwseqyw0.js} +2 -2
- package/dist/chunks/studio-x9hmy5kr.js +390 -0
- package/dist/chunks/studio-x9hmy5kr.js.map +10 -0
- package/dist/chunks/studio-xa0ce0n7.js +335 -0
- package/dist/chunks/studio-xa0ce0n7.js.map +10 -0
- package/dist/chunks/studio-xm3dbxvn.js +378 -0
- package/dist/chunks/studio-xm3dbxvn.js.map +19 -0
- package/dist/chunks/{studio-nqw433kv.js → studio-xmvbhg31.js} +2 -2
- package/dist/chunks/studio-zv37yrfa.js +1054 -0
- package/dist/chunks/studio-zv37yrfa.js.map +13 -0
- package/dist/chunks/{ws-client-q7hkz3j9.js → ws-client-654ts66j.js} +6 -4
- package/dist/chunks/ws-client-654ts66j.js.map +11 -0
- package/dist/iframe-entry.js +668 -230
- package/dist/studio.js +21080 -32585
- package/index.html +35 -4685
- package/package.json +17 -15
- package/src/about/about-modal.ts +42 -11
- package/src/browse/library-commands.ts +240 -0
- package/src/browse/library-layouts.ts +369 -0
- package/src/browse/library-model.ts +396 -0
- package/src/browse/library-pane.ts +1049 -0
- package/src/browse/library-preview.ts +252 -0
- package/src/browse/library-source.ts +150 -0
- package/src/canvas/canvas-helpers.ts +33 -9
- package/src/canvas/canvas-live-render.ts +45 -29
- package/src/canvas/canvas-patcher.ts +151 -31
- package/src/canvas/canvas-perf.ts +46 -7
- package/src/canvas/canvas-render.ts +1092 -220
- package/src/canvas/canvas-surface.ts +240 -0
- package/src/canvas/canvas-utils.ts +1166 -124
- package/src/canvas/iframe-drop.ts +2 -1
- package/src/canvas/iframe-entry.ts +241 -4
- package/src/canvas/iframe-host.ts +1226 -143
- package/src/canvas/iframe-inline-edit.ts +31 -0
- package/src/canvas/iframe-interaction.ts +76 -16
- package/src/canvas/iframe-keys.ts +107 -45
- package/src/canvas/iframe-overlay.ts +38 -1
- package/src/canvas/iframe-protocol.ts +137 -2
- package/src/canvas/iframe-render.ts +70 -2
- package/src/canvas/iframe-slash.ts +6 -0
- package/src/canvas/path-mapping.ts +56 -6
- package/src/canvas/surface-registry.ts +292 -0
- package/src/collab/collab-commands.ts +244 -0
- package/src/collab/collab-session.ts +198 -32
- package/src/collab/collab-state.ts +28 -2
- package/src/collab/presence-chips.ts +100 -9
- package/src/commands/active-registry.ts +42 -0
- package/src/commands/app-commands.ts +176 -0
- package/src/commands/budget.ts +158 -0
- package/src/commands/command-args.ts +253 -0
- package/src/commands/context.ts +387 -0
- package/src/commands/defaults.ts +580 -0
- package/src/commands/keymap.ts +571 -0
- package/src/commands/levels.ts +345 -0
- package/src/commands/live-context.ts +193 -0
- package/src/commands/reference.ts +227 -0
- package/src/commands/registry.ts +293 -0
- package/src/component-props.ts +3 -2
- package/src/content/draft-state.ts +106 -0
- package/src/content/entry-commands.ts +282 -0
- package/src/content/entry-editor.ts +327 -0
- package/src/content/entry-fields.ts +116 -0
- package/src/content/entry-model.ts +175 -0
- package/src/editor/canvas-context-menu.ts +17 -11
- package/src/editor/canvas-slash-bridge.ts +11 -4
- package/src/editor/context-menu.ts +804 -204
- package/src/editor/convert-to-component.ts +31 -10
- package/src/editor/convert-to-repeater.ts +3 -2
- package/src/editor/inline-edit-apply.ts +5 -5
- package/src/editor/inline-edit.ts +86 -50
- package/src/editor/insert-zone-action.ts +1 -1
- package/src/editor/shortcuts.ts +1043 -329
- package/src/editor/slash-menu.ts +1 -0
- package/src/files/elements.ts +166 -0
- package/src/files/file-ops.ts +178 -31
- package/src/files/files.ts +898 -268
- package/src/files/fs-events.ts +21 -0
- package/src/files/media-meta.ts +203 -0
- package/src/files/media-paths.ts +141 -0
- package/src/files/media-upload.ts +7 -3
- package/src/files/media-usage.ts +189 -0
- package/src/github/github-auth.ts +235 -33
- package/src/github/github-publish.ts +80 -21
- package/src/grid/grid-controller.ts +152 -12
- package/src/grid/grid-layout.ts +225 -20
- package/src/grid/grid-open.ts +120 -0
- package/src/grid/grid-panel.ts +554 -39
- package/src/grid/grid-source.ts +27 -1
- package/src/grid/redirects-grid.ts +599 -0
- package/src/grid/redirects.ts +398 -0
- package/src/grid/sources/content-source.ts +108 -2
- package/src/i18n/i18n-commands.ts +322 -0
- package/src/new-project/add-repo-modal.ts +8 -10
- package/src/new-project/import-tab.ts +7 -0
- package/src/new-project/location-fields.ts +9 -1
- package/src/new-project/new-project-modal.ts +221 -245
- package/src/packages/jxsuite-update.ts +97 -48
- package/src/packages/pull-package-sync.ts +18 -9
- package/src/page-params.ts +2 -1
- package/src/panels/activity-bar.ts +152 -91
- package/src/panels/activity-panel.ts +481 -0
- package/src/panels/ai-chat/chat-markdown.ts +8 -3
- package/src/panels/ai-chat/chat-view.ts +245 -18
- package/src/panels/ai-chat/composer.ts +31 -5
- package/src/panels/ai-chat/sessions-view.ts +17 -10
- package/src/panels/ai-panel.ts +279 -53
- package/src/panels/block-action-bar.ts +1115 -224
- package/src/panels/bottom-dock.ts +337 -0
- package/src/panels/canvas-dnd-bridge.ts +37 -8
- package/src/panels/chat-panel.ts +29 -12
- package/src/panels/data-explorer.ts +254 -112
- package/src/panels/data-grid.ts +10 -4
- package/src/panels/dnd.ts +28 -51
- package/src/panels/editors.ts +434 -107
- package/src/panels/elements-panel.ts +66 -2
- package/src/panels/empty-state.ts +112 -0
- package/src/panels/events-panel.ts +596 -82
- package/src/panels/formula-workspace.ts +408 -218
- package/src/panels/frontmatter-fields.ts +51 -10
- package/src/panels/frontmatter-panel.ts +357 -79
- package/src/panels/git-panel.ts +375 -164
- package/src/panels/head-panel.ts +791 -85
- package/src/panels/i18n-panel.ts +592 -0
- package/src/panels/imports-panel.ts +95 -85
- package/src/panels/jump-bar.ts +584 -0
- package/src/panels/layers-panel.ts +1106 -328
- package/src/panels/left-panel.ts +109 -204
- package/src/panels/navigator-panels.ts +131 -0
- package/src/panels/overlays.ts +7 -2
- package/src/panels/pane-context.ts +1477 -0
- package/src/panels/pane-grid.ts +406 -0
- package/src/panels/panel-registry.ts +340 -0
- package/src/panels/panel-scheduler.ts +75 -0
- package/src/panels/problems-panel.ts +234 -0
- package/src/panels/properties-panel.ts +946 -1139
- package/src/panels/provenance.ts +258 -0
- package/src/panels/quick-search.ts +791 -122
- package/src/panels/right-panel.ts +228 -102
- package/src/panels/seo-modal.ts +395 -0
- package/src/panels/settings-pane.ts +203 -0
- package/src/panels/signals-panel.ts +354 -137
- package/src/panels/statement-editor.ts +41 -25
- package/src/panels/statusbar.ts +342 -106
- package/src/panels/style-panel.ts +974 -463
- package/src/panels/style-utils.ts +3 -3
- package/src/panels/stylebook-doc.ts +4 -2
- package/src/panels/stylebook-layers-panel.ts +3 -2
- package/src/panels/stylebook-panel.ts +51 -26
- package/src/panels/tab-strip.ts +1058 -78
- package/src/panels/target-line.ts +274 -0
- package/src/panels/toolbar.ts +631 -484
- package/src/panels/welcome-screen.ts +301 -198
- package/src/platform-errors.ts +26 -3
- package/src/platform.ts +96 -2
- package/src/platforms/cloud.ts +71 -8
- package/src/platforms/devserver.ts +92 -29
- package/src/publish/deploy-checklist.ts +383 -0
- package/src/publish/pages-service.ts +10 -1
- package/src/publish/publish-commands.ts +218 -0
- package/src/publish/publish-panel.ts +110 -16
- package/src/recent-projects.ts +3 -2
- package/src/services/a11y-report.ts +505 -0
- package/src/services/ai-models.ts +30 -1
- package/src/services/ai-project-tools.ts +29 -10
- package/src/services/ai-system-prompt.ts +53 -14
- package/src/services/ai-tools.ts +39 -3
- package/src/services/ai-writes.ts +154 -0
- package/src/services/announce.ts +97 -0
- package/src/services/automation.ts +424 -175
- package/src/services/cem-export.ts +9 -4
- package/src/services/clock.ts +61 -0
- package/src/services/document-assistant.ts +36 -8
- package/src/services/idle.ts +179 -0
- package/src/services/import-client.ts +26 -0
- package/src/services/monaco-buffer.ts +627 -0
- package/src/services/monaco-lazy.ts +36 -0
- package/src/services/notify.ts +271 -0
- package/src/services/profile.ts +155 -0
- package/src/services/references.ts +0 -0
- package/src/services/token-lint.ts +3 -2
- package/src/services/tool-executor.ts +53 -7
- package/src/services/trusted-types.ts +123 -0
- package/src/settings/contexts-section.ts +427 -0
- package/src/settings/contributed-section.ts +149 -11
- package/src/settings/css-vars-editor.ts +306 -349
- package/src/settings/defs-editor.ts +93 -34
- package/src/settings/dependencies-editor.ts +7 -7
- package/src/settings/extension-sections.ts +37 -5
- package/src/settings/general-settings.ts +204 -121
- package/src/settings/locales-section.ts +347 -0
- package/src/settings/preferences-accounts.ts +140 -0
- package/src/settings/preferences-dialog.ts +620 -0
- package/src/settings/preferences-keymap.ts +270 -0
- package/src/settings/project-sections.ts +260 -0
- package/src/settings/section-registry.ts +169 -0
- package/src/settings/settings-document.ts +385 -0
- package/src/shell.ts +1549 -0
- package/src/site-context.ts +45 -20
- package/src/state.ts +14 -275
- package/src/store.ts +88 -43
- package/src/studio.ts +790 -346
- package/src/style/live-preview.ts +36 -0
- package/src/style/project-styles.ts +349 -0
- package/src/style/token-ref.ts +123 -0
- package/src/tabs/patch-ops.ts +16 -4
- package/src/tabs/project-config.ts +416 -0
- package/src/tabs/selection.ts +323 -0
- package/src/tabs/tab.ts +182 -47
- package/src/tabs/transact.ts +209 -28
- package/src/types.ts +68 -13
- package/src/ui/ai-credentials-form.ts +39 -35
- package/src/ui/ai-managed-connect.ts +7 -1
- package/src/ui/dynamic-slot.ts +224 -81
- package/src/ui/expression-editor.ts +71 -63
- package/src/ui/field-input.ts +4 -6
- package/src/ui/field-row.ts +215 -38
- package/src/ui/form-controls.ts +194 -7
- package/src/ui/layers.ts +492 -69
- package/src/ui/media-picker.ts +115 -25
- package/src/ui/panel-resize.ts +109 -91
- package/src/ui/progress-modal.ts +131 -50
- package/src/ui/regions.ts +462 -0
- package/src/ui/schema-form.ts +229 -45
- package/src/ui/spectrum.ts +24 -8
- package/src/ui/timing.ts +49 -0
- package/src/ui/value-selector.ts +20 -0
- package/src/ui/value-source.ts +348 -0
- package/src/ui/virtual-window.ts +400 -0
- package/src/utils/edit-display.ts +3 -2
- package/src/utils/inherited-style.ts +67 -37
- package/src/view.ts +64 -78
- package/src/workspace/pane-derive.ts +1258 -0
- package/src/workspace/session.ts +317 -0
- package/src/workspace/workspace.ts +1345 -23
- package/styles/canvas.css +653 -0
- package/styles/forced-colors.css +106 -0
- package/styles/inspector.css +1454 -0
- package/styles/overlays.css +2704 -0
- package/styles/panels.css +1701 -0
- package/styles/shell.css +1423 -0
- package/styles/tokens.css +151 -0
- package/dist/chunks/Overlay.dev-cszx7yn5.js +0 -22
- package/dist/chunks/extension-sections-qr3drkkw.js +0 -531
- package/dist/chunks/extension-sections-qr3drkkw.js.map +0 -12
- package/dist/chunks/files-j8a1t06k.js +0 -50
- package/dist/chunks/files-j8a1t06k.js.map +0 -9
- package/dist/chunks/git-panel-xtq5z81x.js +0 -28
- package/dist/chunks/git-panel-xtq5z81x.js.map +0 -9
- package/dist/chunks/layers-panel-ancn2wpq.js +0 -26
- package/dist/chunks/layers-panel-ancn2wpq.js.map +0 -9
- package/dist/chunks/settings-modal-17ttr96g.js +0 -29
- package/dist/chunks/settings-modal-17ttr96g.js.map +0 -9
- package/dist/chunks/sp-overlay.dev-earn5as8.js +0 -17
- package/dist/chunks/sp-overlay.dev-earn5as8.js.map +0 -9
- package/dist/chunks/sp-popover.dev-frxtfejn.js +0 -8
- package/dist/chunks/sp-popover.dev-frxtfejn.js.map +0 -9
- package/dist/chunks/sp-progress-circle.dev-7tvw34h8.js +0 -14
- package/dist/chunks/sp-progress-circle.dev-7tvw34h8.js.map +0 -9
- package/dist/chunks/sp-tooltip.dev-a9fywesm.js +0 -21
- package/dist/chunks/sp-tooltip.dev-a9fywesm.js.map +0 -10
- package/dist/chunks/studio-0xgs7xrs.js +0 -429
- package/dist/chunks/studio-0xgs7xrs.js.map +0 -13
- package/dist/chunks/studio-4d8hzcz5.js +0 -3427
- package/dist/chunks/studio-4d8hzcz5.js.map +0 -26
- package/dist/chunks/studio-4k0m4fmp.js.map +0 -13
- package/dist/chunks/studio-9smfzqhc.js +0 -19
- package/dist/chunks/studio-9smfzqhc.js.map +0 -10
- package/dist/chunks/studio-jm0z592w.js.map +0 -115
- package/dist/chunks/studio-m2t4mdg5.js +0 -1854
- package/dist/chunks/studio-m2t4mdg5.js.map +0 -18
- package/dist/chunks/studio-n4p11dfy.js +0 -853
- package/dist/chunks/studio-n4p11dfy.js.map +0 -12
- package/dist/chunks/studio-qkvt37c0.js.map +0 -9
- package/dist/chunks/studio-sjh1wt1a.js +0 -3744
- package/dist/chunks/studio-sjh1wt1a.js.map +0 -22
- package/dist/chunks/studio-v7jkjqng.js.map +0 -46
- package/dist/chunks/studio-xq0t7n5t.js +0 -140
- package/dist/chunks/studio-xq0t7n5t.js.map +0 -10
- package/dist/chunks/studio-xze4zrs1.js.map +0 -10
- package/dist/chunks/studio-yfcstdzy.js +0 -399
- package/dist/chunks/studio-yfcstdzy.js.map +0 -12
- package/dist/chunks/studio-zp7jncs8.js +0 -469
- package/dist/chunks/studio-zp7jncs8.js.map +0 -14
- package/dist/chunks/ws-client-q7hkz3j9.js.map +0 -11
- package/src/browse/browse-modal.ts +0 -69
- package/src/browse/browse.ts +0 -904
- package/src/canvas/canvas-diff.ts +0 -216
- package/src/canvas/nested-site-style.ts +0 -58
- package/src/new-project/design-fields.ts +0 -264
- package/src/new-project/templates.ts +0 -61
- package/src/panels/tab-bar.ts +0 -548
- package/src/settings/settings-modal.ts +0 -203
- /package/dist/chunks/{adapter-z8fkmbmv.js.map → adapter-qfnesn5v.js.map} +0 -0
- /package/dist/chunks/{cf-settings-mmyv436d.js.map → cf-settings-73sh5z4p.js.map} +0 -0
- /package/dist/chunks/{format-host-8jxwts2p.js.map → format-host-mkwsmt8d.js.map} +0 -0
- /package/dist/chunks/{jx-validate-qx8degj7.js.map → jx-validate-v6qgknrs.js.map} +0 -0
- /package/dist/chunks/{studio-w7c6kvk7.js.map → studio-a7pzveq1.js.map} +0 -0
- /package/dist/chunks/{studio-esrewxap.js.map → studio-s519zc99.js.map} +0 -0
- /package/dist/chunks/{studio-bghqfmea.js.map → studio-wwseqyw0.js.map} +0 -0
- /package/dist/chunks/{studio-nqw433kv.js.map → studio-xmvbhg31.js.map} +0 -0
|
@@ -0,0 +1,282 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The content-entry verbs: create one, open one as a form, mark one a draft, and choose whether
|
|
3
|
+
* drafts are listed at all.
|
|
4
|
+
*
|
|
5
|
+
* Each is a record in its owning module, so the palette row, the assistant's tool, the automation
|
|
6
|
+
* step and (for the two that declare one) the context-menu item are renderings of the same
|
|
7
|
+
* declaration. Two of them are **setters and not toggles** — `content.setDraft {draft}` and
|
|
8
|
+
* `content.setIncludeDrafts {include}` name the state they reach, because "toggle the draft flag"
|
|
9
|
+
* run twice by a script leaves the entry exactly where it started and run once against an unknown
|
|
10
|
+
* default publishes something private.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import {
|
|
14
|
+
argsSchema,
|
|
15
|
+
booleanArg,
|
|
16
|
+
booleanProperty,
|
|
17
|
+
enumArg,
|
|
18
|
+
stringArg,
|
|
19
|
+
stringProperty,
|
|
20
|
+
} from "../commands/command-args";
|
|
21
|
+
import {
|
|
22
|
+
defaultContentFormat,
|
|
23
|
+
formatByName,
|
|
24
|
+
formatForPath,
|
|
25
|
+
formatSerialize,
|
|
26
|
+
loadFormats,
|
|
27
|
+
} from "../format/format-host";
|
|
28
|
+
import { notify } from "../services/notify";
|
|
29
|
+
import { activeTab } from "../workspace/workspace";
|
|
30
|
+
import { invalidateReferenceEntries } from "../ui/form-controls";
|
|
31
|
+
import { reloadDraftAwareGrids } from "../grid/sources/content-source";
|
|
32
|
+
import { setIncludeDrafts } from "./draft-state";
|
|
33
|
+
import { collectionOfPath, entryCollection, entryCollections, seedEntry } from "./entry-model";
|
|
34
|
+
import { openEntryEditor, setEntryDraft } from "./entry-editor";
|
|
35
|
+
import { errorMessage } from "@jxsuite/schema/parse";
|
|
36
|
+
import type { EntryCollection } from "./entry-model";
|
|
37
|
+
import type { AnyCommand, CommandRegistry } from "../commands/registry";
|
|
38
|
+
import type { Tab } from "../tabs/tab";
|
|
39
|
+
|
|
40
|
+
/** Collection names a New Entry can be created in — the `enum` behind `content.newEntry`. */
|
|
41
|
+
function creatableCollections(): string[] {
|
|
42
|
+
return entryCollections().map((collection) => collection.name);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* An enum property whose values are derived WHEN THE SCHEMA IS READ, not when the record is
|
|
47
|
+
* defined.
|
|
48
|
+
*
|
|
49
|
+
* `enumProperty(creatableCollections(), …)` looks identical and is wrong here, and the difference
|
|
50
|
+
* is invisible in a unit test: a command record is built at module scope —
|
|
51
|
+
* `commands/app-commands.ts` and `studio.ts` both call {@link contentCommands} before a project is
|
|
52
|
+
* open — so the array is frozen at `[]`, and `panels/quick-search.ts`'s `paletteArgs` offered an
|
|
53
|
+
* empty choice list forever after. `run` re-derived the list, which is why the programmatic call
|
|
54
|
+
* worked and the palette did not: two answers to "which collections exist", one of them a snapshot
|
|
55
|
+
* of the empty boot state.
|
|
56
|
+
*
|
|
57
|
+
* A getter is the whole fix — the palette reads `property.enum` when it opens the prompt, the AI
|
|
58
|
+
* tool's parameters serialise it when the tool list is built, and `scripts/check-shot-contract.ts`
|
|
59
|
+
* reads it in a bare Bun process where the honest answer really is "none declared".
|
|
60
|
+
*/
|
|
61
|
+
function derivedEnumProperty(declared: () => readonly string[], description: string): object {
|
|
62
|
+
return {
|
|
63
|
+
description,
|
|
64
|
+
get enum() {
|
|
65
|
+
return [...declared()];
|
|
66
|
+
},
|
|
67
|
+
type: "string",
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* The source text a new entry starts as.
|
|
73
|
+
*
|
|
74
|
+
* A `json` collection is serialized here rather than through the format registry, because JSON is
|
|
75
|
+
* the one native collection shape (site-architecture.md §6.2) and has no format class to ask.
|
|
76
|
+
*
|
|
77
|
+
* `collectionInfo` derives `.json` exactly when no format class claimed the collection — the
|
|
78
|
+
* extension and the serializer name come off the same three lookups in the same order — so "the
|
|
79
|
+
* extension is .json" and "no format can write this" are one condition, and the second is not a
|
|
80
|
+
* separate failure to report. Writing JSON into a `.md` file is therefore not a state this can
|
|
81
|
+
* reach.
|
|
82
|
+
*/
|
|
83
|
+
async function seedText(collection: EntryCollection): Promise<string> {
|
|
84
|
+
const seed = seedEntry(collection.schema);
|
|
85
|
+
const formatName =
|
|
86
|
+
(collection.def.format ? formatByName(collection.def.format)?.name : undefined) ??
|
|
87
|
+
formatForPath(`untitled${collection.ext}`)?.name ??
|
|
88
|
+
defaultContentFormat()?.name;
|
|
89
|
+
if (collection.ext === ".json" || formatName === undefined) {
|
|
90
|
+
return JSON.stringify(seed, null, 2);
|
|
91
|
+
}
|
|
92
|
+
return formatSerialize(
|
|
93
|
+
formatName,
|
|
94
|
+
{ ...seed, children: [] },
|
|
95
|
+
{ frontmatter: Object.keys(seed).length > 0, mode: "roundtrip" },
|
|
96
|
+
);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Create an entry in a collection and open it in its form editor.
|
|
101
|
+
*
|
|
102
|
+
* The file goes through `files/files.ts`'s single creation flow — the same name field, the same
|
|
103
|
+
* "already exists" refusal, the same Problem on failure as the Files tree and the Library — with
|
|
104
|
+
* two things this caller supplies that a generic "New File" cannot: the collection's **extension**,
|
|
105
|
+
* so the field asks for a display name and the file is actually matched by the collection it was
|
|
106
|
+
* created in, and a **body seeded from the schema**, so the entry is valid the moment it exists
|
|
107
|
+
* instead of being a pile of absent required fields.
|
|
108
|
+
*
|
|
109
|
+
* @returns The created path, or null when the author cancelled or the write failed.
|
|
110
|
+
*/
|
|
111
|
+
export async function createEntry(collectionName: string): Promise<string | null> {
|
|
112
|
+
const collection = entryCollection(collectionName);
|
|
113
|
+
if (!collection) {
|
|
114
|
+
notify.error(`No content collection named "${collectionName}" to create an entry in.`, {
|
|
115
|
+
detail:
|
|
116
|
+
"A collection needs a `content` entry in project.json whose `source` is a directory. A " +
|
|
117
|
+
"collection whose source names a single file (a CSV catalogue) has rows, not entry files.",
|
|
118
|
+
source: "Content",
|
|
119
|
+
});
|
|
120
|
+
return null;
|
|
121
|
+
}
|
|
122
|
+
await loadFormats().catch(() => {
|
|
123
|
+
/* An unreachable format registry is not a reason to refuse: `seedText` falls back to the
|
|
124
|
+
native JSON shape, and the serializer's own failure is reported below with the path. */
|
|
125
|
+
});
|
|
126
|
+
let content: string;
|
|
127
|
+
try {
|
|
128
|
+
content = await seedText(collection);
|
|
129
|
+
} catch (error) {
|
|
130
|
+
notify.error(`Could not prepare a new ${collection.name} entry.`, {
|
|
131
|
+
detail: errorMessage(error),
|
|
132
|
+
path: collection.dir,
|
|
133
|
+
source: "Content",
|
|
134
|
+
});
|
|
135
|
+
return null;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
const { createFileIn } = await import("../files/files");
|
|
139
|
+
const created = await createFileIn({
|
|
140
|
+
content,
|
|
141
|
+
dir: collection.dir,
|
|
142
|
+
ext: collection.ext,
|
|
143
|
+
source: "Content",
|
|
144
|
+
suggestedName: "untitled",
|
|
145
|
+
title: `New ${collection.name} entry`,
|
|
146
|
+
});
|
|
147
|
+
if (created === null) {
|
|
148
|
+
return null;
|
|
149
|
+
}
|
|
150
|
+
// The picker's choices just changed; a cached list would keep offering the old set.
|
|
151
|
+
invalidateReferenceEntries(collection.name);
|
|
152
|
+
await openEntryEditor(created);
|
|
153
|
+
return created;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
/** The active tab, when it is a content entry — the subject of the document-level verbs. */
|
|
157
|
+
function activeEntryTab(): Tab | null {
|
|
158
|
+
const tab = activeTab.value as Tab | null;
|
|
159
|
+
return tab && collectionOfPath(tab.documentPath) ? tab : null;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
/** The content-entry command records. */
|
|
163
|
+
export function contentCommands(): AnyCommand[] {
|
|
164
|
+
return [
|
|
165
|
+
{
|
|
166
|
+
args: argsSchema({
|
|
167
|
+
collection: derivedEnumProperty(
|
|
168
|
+
creatableCollections,
|
|
169
|
+
"The content collection the entry is created in.",
|
|
170
|
+
),
|
|
171
|
+
}),
|
|
172
|
+
category: "File",
|
|
173
|
+
id: "content.newEntry",
|
|
174
|
+
level: "project",
|
|
175
|
+
menus: ["palette"],
|
|
176
|
+
group: "1_file",
|
|
177
|
+
requires: "a project with a directory-backed content collection",
|
|
178
|
+
when: (ctx) => ctx.project.open,
|
|
179
|
+
enablement: () => creatableCollections().length > 0,
|
|
180
|
+
undo: "project",
|
|
181
|
+
aiTool: {
|
|
182
|
+
description:
|
|
183
|
+
"Create a content entry in one of the project's collections, seeded from that " +
|
|
184
|
+
"collection's schema defaults, and open it in the entry form.",
|
|
185
|
+
name: "new_content_entry",
|
|
186
|
+
},
|
|
187
|
+
run: async (_ctx, args) => {
|
|
188
|
+
const names = creatableCollections();
|
|
189
|
+
await createEntry(enumArg("content.newEntry", args, "collection", names));
|
|
190
|
+
},
|
|
191
|
+
title: "New Entry",
|
|
192
|
+
},
|
|
193
|
+
{
|
|
194
|
+
args: argsSchema({
|
|
195
|
+
path: stringProperty("Project-relative path of the entry file to open."),
|
|
196
|
+
}),
|
|
197
|
+
category: "File",
|
|
198
|
+
id: "content.openEntry",
|
|
199
|
+
level: "project",
|
|
200
|
+
menus: ["context/file", "palette"],
|
|
201
|
+
group: "1_file",
|
|
202
|
+
requires: "an open project",
|
|
203
|
+
when: (ctx) => ctx.project.open,
|
|
204
|
+
aiTool: {
|
|
205
|
+
description:
|
|
206
|
+
"Open a content entry as a schema-driven form — its collection's fields, with a picker " +
|
|
207
|
+
"for every reference to another collection.",
|
|
208
|
+
name: "open_entry_form",
|
|
209
|
+
},
|
|
210
|
+
run: async (_ctx, args) => {
|
|
211
|
+
await openEntryEditor(stringArg("content.openEntry", args, "path"));
|
|
212
|
+
},
|
|
213
|
+
title: "Open Entry Form",
|
|
214
|
+
},
|
|
215
|
+
{
|
|
216
|
+
args: argsSchema({
|
|
217
|
+
draft: booleanProperty(
|
|
218
|
+
"True marks the open entry a draft; false marks it published. Written as a `draft` " +
|
|
219
|
+
"field on the entry.",
|
|
220
|
+
),
|
|
221
|
+
}),
|
|
222
|
+
category: "Document",
|
|
223
|
+
id: "content.setDraft",
|
|
224
|
+
level: "document",
|
|
225
|
+
menus: ["context/tab", "palette"],
|
|
226
|
+
group: "3_document",
|
|
227
|
+
requires: "a content entry open",
|
|
228
|
+
when: (ctx) => ctx.document.open,
|
|
229
|
+
enablement: () => activeEntryTab() !== null,
|
|
230
|
+
undo: "document",
|
|
231
|
+
aiTool: {
|
|
232
|
+
description:
|
|
233
|
+
"Mark the open content entry a draft, or published. Studio filters drafts out of its " +
|
|
234
|
+
"own lists; the build does not exclude them yet.",
|
|
235
|
+
name: "set_entry_draft",
|
|
236
|
+
},
|
|
237
|
+
run: (_ctx, args) => {
|
|
238
|
+
const tab = activeEntryTab();
|
|
239
|
+
if (!tab) {
|
|
240
|
+
throw new Error("content.setDraft: the active document is not a content entry");
|
|
241
|
+
}
|
|
242
|
+
setEntryDraft(tab, booleanArg("content.setDraft", args, "draft"));
|
|
243
|
+
},
|
|
244
|
+
title: "Set Draft",
|
|
245
|
+
},
|
|
246
|
+
{
|
|
247
|
+
args: argsSchema({
|
|
248
|
+
include: booleanProperty("True lists entries marked `draft: true` alongside the rest."),
|
|
249
|
+
}),
|
|
250
|
+
category: "View",
|
|
251
|
+
id: "content.setIncludeDrafts",
|
|
252
|
+
level: "project",
|
|
253
|
+
menus: ["palette"],
|
|
254
|
+
group: "2_view",
|
|
255
|
+
requires: "an open project",
|
|
256
|
+
when: (ctx) => ctx.project.open,
|
|
257
|
+
aiTool: {
|
|
258
|
+
description: "Choose whether draft entries appear in Studio's content listings.",
|
|
259
|
+
name: "set_including_drafts",
|
|
260
|
+
},
|
|
261
|
+
/*
|
|
262
|
+
* Set the flag, then repaint what reads it.
|
|
263
|
+
*
|
|
264
|
+
* The second half is the wiring, and without it this command sets a value nobody looks at
|
|
265
|
+
* again: a collection grid holds the rows it loaded once, so an author who asks to see their
|
|
266
|
+
* drafts watches an unchanged table. `draft-state.ts` cannot do it — it is imported BY the
|
|
267
|
+
* listings and reaching back into the workspace from there is the cycle. The command can,
|
|
268
|
+
* because a command is exactly the place where "the state changed" becomes "the app changed".
|
|
269
|
+
*/
|
|
270
|
+
run: async (_ctx, args) => {
|
|
271
|
+
setIncludeDrafts(booleanArg("content.setIncludeDrafts", args, "include"));
|
|
272
|
+
await reloadDraftAwareGrids();
|
|
273
|
+
},
|
|
274
|
+
title: "Include Drafts",
|
|
275
|
+
},
|
|
276
|
+
];
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
/** Register the content-entry verbs. */
|
|
280
|
+
export function registerContentCommands(registry: CommandRegistry): void {
|
|
281
|
+
registry.registerAll(contentCommands());
|
|
282
|
+
}
|
|
@@ -0,0 +1,327 @@
|
|
|
1
|
+
/// <reference lib="dom" />
|
|
2
|
+
/**
|
|
3
|
+
* The **Entry editor** — a content entry's fields, as a form, in a pane (§7.4, plan §9.2).
|
|
4
|
+
*
|
|
5
|
+
* Until now a markdown entry could be edited two ways and neither was its schema: the canvas edited
|
|
6
|
+
* its BODY, and the Document Header card drew a field list from `panels/frontmatter-fields.ts`'s
|
|
7
|
+
* own type→widget ladder, which is a second implementation of `ui/schema-form.ts` with no reference
|
|
8
|
+
* branch, no validation and no value-source ladder. A JSON entry (`authors/ada.json`) had no field
|
|
9
|
+
* editor at all — it opened as a component tree, so `bio` was a node's property and `links` was a
|
|
10
|
+
* child array.
|
|
11
|
+
*
|
|
12
|
+
* This is one editor over `renderForm`, so every improvement to the engine reaches content entries,
|
|
13
|
+
* settings and the inspector at once — and the `reference` control in particular arrives here for
|
|
14
|
+
* free, which is what makes `author: { "$ref": "#/content/authors" }` a picker instead of a text
|
|
15
|
+
* field you have to type an id into.
|
|
16
|
+
*
|
|
17
|
+
* **It is an editor of the same tab, not a second document.** Like `settings/settings-document.ts`,
|
|
18
|
+
* it unshifts its mode onto the tab the file already has, so ⌘S, the dirty flag, the transaction
|
|
19
|
+
* log and undo are the ordinary document verbs. Every field commits through
|
|
20
|
+
* {@link mutateEntryField}, so an edit made here is undone by ⌘Z and replayed by a collaborator
|
|
21
|
+
* exactly as one made in the Document Header card.
|
|
22
|
+
*
|
|
23
|
+
* **One editor, two storage shapes.** A markdown entry keeps its fields in frontmatter; a JSON
|
|
24
|
+
* entry IS its fields. `content/entry-fields.ts` owns that single question, and this file never
|
|
25
|
+
* touches `content.frontmatter` directly — reading it unconditionally is what made a JSON entry's
|
|
26
|
+
* form blank, its valid required fields look absent, and every edit vanish at save time.
|
|
27
|
+
*
|
|
28
|
+
* **The mode is registered in all three tables.** `commands/context.ts` holds the one map from mode
|
|
29
|
+
* to editor kind; a mode missing from it does not fail, it silently answers `"canvas"`, and that is
|
|
30
|
+
* how ⌘V once inserted an element node into `project.json`. `entry` → `entry` → "Entry" is added to
|
|
31
|
+
* `EDITOR_KIND_BY_MODE`, `EditorKind` and `EDITOR_KIND_LABELS` in the same change as this file.
|
|
32
|
+
*
|
|
33
|
+
* @docs studio/projects/content-types
|
|
34
|
+
*/
|
|
35
|
+
|
|
36
|
+
import { html, nothing, render as litRender } from "lit-html";
|
|
37
|
+
import { activeRegistry } from "../commands/active-registry";
|
|
38
|
+
import { effect, effectScope } from "../reactivity";
|
|
39
|
+
import { projectState } from "../store";
|
|
40
|
+
import { resolveContextPointer } from "../services/context-resolver";
|
|
41
|
+
import { transactDoc } from "../tabs/transact";
|
|
42
|
+
import { renderForm } from "../ui/schema-form";
|
|
43
|
+
import { paneRegion } from "../ui/regions";
|
|
44
|
+
import { activateTab, workspace } from "../workspace/workspace";
|
|
45
|
+
import { commitEntryFields, entryFields, mutateEntryField } from "./entry-fields";
|
|
46
|
+
import { DRAFT_FIELD, DRAFT_MEANING, hasDraftAxis, isDraftEntry } from "./draft-state";
|
|
47
|
+
import { collectionOfPath, missingRequired } from "./entry-model";
|
|
48
|
+
import type { EntryCollection } from "./entry-model";
|
|
49
|
+
import type { JsonSchema, SchemaFormContext } from "../ui/schema-form";
|
|
50
|
+
import type { Tab } from "../tabs/tab";
|
|
51
|
+
import type { CanvasSurface } from "../canvas/canvas-surface";
|
|
52
|
+
import type { JsonValue } from "../types";
|
|
53
|
+
|
|
54
|
+
/** The `canvasMode` the entry form draws under. */
|
|
55
|
+
export const ENTRY_MODE = "entry";
|
|
56
|
+
|
|
57
|
+
/** What an entry tab's mode list becomes when the Entry editor opens it: the form leads. */
|
|
58
|
+
const ENTRY_TAB_MODES = [ENTRY_MODE];
|
|
59
|
+
|
|
60
|
+
// ─── Mounting ────────────────────────────────────────────────────────────────
|
|
61
|
+
|
|
62
|
+
interface ActiveEntryPane {
|
|
63
|
+
/** The pane whose stage this form is drawn on. */
|
|
64
|
+
paneId: string;
|
|
65
|
+
tabId: string;
|
|
66
|
+
wrap: HTMLElement;
|
|
67
|
+
scope: { stop: () => void; run: <T>(fn: () => T) => T | undefined };
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* The entry form mounted in each pane, keyed by pane id.
|
|
72
|
+
*
|
|
73
|
+
* `entry` is one of the kinds the side pane may host, so this singleton was reachable from two
|
|
74
|
+
* panes at once the day the grid drew a second cell: pane B's mount stopped pane A's effect scope,
|
|
75
|
+
* leaving a form on screen that no longer tracked its own frontmatter.
|
|
76
|
+
*/
|
|
77
|
+
const _active = new Map<string, ActiveEntryPane>();
|
|
78
|
+
|
|
79
|
+
/** The form mounted in a pane, or null. */
|
|
80
|
+
function activeIn(paneId: string): ActiveEntryPane | null {
|
|
81
|
+
return _active.get(paneId) ?? null;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/** Whether this tab's entry form is already mounted in this pane and still in the document. */
|
|
85
|
+
export function entryPaneMounted(paneId: string, tab: Tab): boolean {
|
|
86
|
+
const panel = activeIn(paneId);
|
|
87
|
+
return panel !== null && panel.tabId === tab.id && panel.wrap.isConnected;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/** Tear one pane's entry form down (mode change, tab switch, project close). Idempotent. */
|
|
91
|
+
export function detachEntryPane(paneId: string): void {
|
|
92
|
+
const panel = _active.get(paneId);
|
|
93
|
+
if (!panel) {
|
|
94
|
+
return;
|
|
95
|
+
}
|
|
96
|
+
panel.scope.stop();
|
|
97
|
+
_active.delete(paneId);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// ─── Drafts ──────────────────────────────────────────────────────────────────
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Set this entry's draft flag. A setter, never a toggle — the pill, the command and the assistant
|
|
104
|
+
* all reach the same state whatever it was before.
|
|
105
|
+
*
|
|
106
|
+
* `draft: false` is written rather than removed. The absence of the key and the value `false` mean
|
|
107
|
+
* the same thing to a reader, but only one of them survives a glance at the file, and an author who
|
|
108
|
+
* has explicitly published something should be able to see that they did.
|
|
109
|
+
*/
|
|
110
|
+
export function setEntryDraft(tab: Tab, draft: boolean): void {
|
|
111
|
+
transactDoc(tab, (t) => mutateEntryField(t, DRAFT_FIELD, draft as JsonValue));
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* The draft pill for a tab, or `nothing`.
|
|
116
|
+
*
|
|
117
|
+
* Drawn on the pane's tab chip (`panels/tab-strip.ts`) rather than only inside the editor, because
|
|
118
|
+
* the failure this exists to prevent is publishing something you thought was private — and that
|
|
119
|
+
* mistake is made while looking at a tab, not while looking at a form. It renders for any entry
|
|
120
|
+
* whose collection declares the field, so "Published" is as visible as "Draft"; a collection with
|
|
121
|
+
* no draft workflow shows neither.
|
|
122
|
+
*/
|
|
123
|
+
export function entryDraftPill(tab: Tab) {
|
|
124
|
+
const collection = collectionOfPath(tab.documentPath);
|
|
125
|
+
const fields = entryFields(tab);
|
|
126
|
+
if (!collection || !hasDraftAxis(collection.schema, fields)) {
|
|
127
|
+
return nothing;
|
|
128
|
+
}
|
|
129
|
+
const draft = isDraftEntry(fields);
|
|
130
|
+
return html`<span
|
|
131
|
+
class=${draft ? "entry-pill entry-pill--draft" : "entry-pill"}
|
|
132
|
+
title=${draft ? DRAFT_MEANING : "Not marked a draft."}
|
|
133
|
+
>${draft ? "Draft" : "Published"}</span
|
|
134
|
+
>`;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
// ─── The form ────────────────────────────────────────────────────────────────
|
|
138
|
+
|
|
139
|
+
/** The form's host context: enum pointers resolve against this project's configuration. */
|
|
140
|
+
function formContext(tab: Tab): SchemaFormContext {
|
|
141
|
+
return {
|
|
142
|
+
fieldKeyPrefix: `entry:${tab.id}`,
|
|
143
|
+
resolvePointer: (pointer, scope) =>
|
|
144
|
+
resolveContextPointer(pointer, {
|
|
145
|
+
projectConfig: (projectState?.projectConfig ?? {}) as Record<string, unknown>,
|
|
146
|
+
scope,
|
|
147
|
+
}),
|
|
148
|
+
};
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* Required fields this entry does not have.
|
|
153
|
+
*
|
|
154
|
+
* Deliberately keyed on ABSENCE, not emptiness. A seeded entry's `title: ""` is present and the
|
|
155
|
+
* author has not done anything wrong yet, so §7.1 says not to paint it red; a required key that is
|
|
156
|
+
* missing from the file is a fact about the document, and saying so is the whole reason the form
|
|
157
|
+
* knows the schema.
|
|
158
|
+
*/
|
|
159
|
+
function absentRequiredErrors(
|
|
160
|
+
collection: EntryCollection,
|
|
161
|
+
fields: Record<string, unknown>,
|
|
162
|
+
): Record<string, string> {
|
|
163
|
+
const errors: Record<string, string> = {};
|
|
164
|
+
for (const field of missingRequired(collection.schema, fields)) {
|
|
165
|
+
errors[field] = "Required — this entry does not have one.";
|
|
166
|
+
}
|
|
167
|
+
return errors;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
/** The header: what this is, and whether it is a draft. */
|
|
171
|
+
function headerTpl(tab: Tab, collection: EntryCollection) {
|
|
172
|
+
const fields = entryFields(tab);
|
|
173
|
+
const draftAxis = hasDraftAxis(collection.schema, fields);
|
|
174
|
+
const draft = isDraftEntry(fields);
|
|
175
|
+
return html`
|
|
176
|
+
<div class="entry-editor-header">
|
|
177
|
+
<h3>${tab.documentPath?.split("/").pop() ?? "Untitled"}</h3>
|
|
178
|
+
<span class="entry-editor-collection">${collection.name}</span>
|
|
179
|
+
${
|
|
180
|
+
draftAxis
|
|
181
|
+
? html`
|
|
182
|
+
<sp-switch
|
|
183
|
+
size="s"
|
|
184
|
+
class="entry-draft-switch"
|
|
185
|
+
?checked=${draft}
|
|
186
|
+
title=${DRAFT_MEANING}
|
|
187
|
+
@change=${(e: Event) => setEntryDraft(tab, (e.target as HTMLInputElement).checked)}
|
|
188
|
+
>Draft</sp-switch
|
|
189
|
+
>
|
|
190
|
+
`
|
|
191
|
+
: nothing
|
|
192
|
+
}
|
|
193
|
+
</div>
|
|
194
|
+
${draft ? html`<p class="entry-editor-note">${DRAFT_MEANING}</p>` : nothing}
|
|
195
|
+
`;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
/**
|
|
199
|
+
* What the editor says when the document is not an entry.
|
|
200
|
+
*
|
|
201
|
+
* The mode is reachable from the palette and from `__jxAutomation` on any tab, so this state is
|
|
202
|
+
* real and must name the reason and the fix rather than drawing an empty form — "no fields" and
|
|
203
|
+
* "this file belongs to no collection" are different sentences, and only one of them is true here.
|
|
204
|
+
*/
|
|
205
|
+
function notAnEntryTpl(tab: Tab) {
|
|
206
|
+
return html`
|
|
207
|
+
<div class="entry-editor-empty">
|
|
208
|
+
<p>
|
|
209
|
+
${tab.documentPath ?? "This document"} is not an entry of any content collection, so there
|
|
210
|
+
is no schema to draw a form from.
|
|
211
|
+
</p>
|
|
212
|
+
<p class="entry-editor-note">
|
|
213
|
+
A collection is a <code>content</code> entry in <code>project.json</code> whose
|
|
214
|
+
<code>source</code> directory contains this file and which declares a <code>schema</code>.
|
|
215
|
+
</p>
|
|
216
|
+
<sp-action-button
|
|
217
|
+
size="s"
|
|
218
|
+
@click=${() => {
|
|
219
|
+
void activeRegistry()?.run("settings.open", { section: "content" });
|
|
220
|
+
}}
|
|
221
|
+
>Content types…</sp-action-button
|
|
222
|
+
>
|
|
223
|
+
</div>
|
|
224
|
+
`;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
/**
|
|
228
|
+
* Mount the entry form into the pane.
|
|
229
|
+
*
|
|
230
|
+
* The same non-iframe-editor pattern as the grid, the Library and Project Settings: this owns its
|
|
231
|
+
* own effect scope from here, so a field commit repaints the form and nothing else — repainting
|
|
232
|
+
* through the canvas pipeline would remount the document's iframe on every keystroke.
|
|
233
|
+
*/
|
|
234
|
+
export function renderEntryMode(surface: CanvasSurface, tab: Tab): void {
|
|
235
|
+
const { paneId, wrap: canvasWrap } = surface;
|
|
236
|
+
if (entryPaneMounted(paneId, tab)) {
|
|
237
|
+
return;
|
|
238
|
+
}
|
|
239
|
+
detachEntryPane(paneId);
|
|
240
|
+
|
|
241
|
+
const scope = effectScope();
|
|
242
|
+
const panel: ActiveEntryPane = { paneId, scope, tabId: tab.id, wrap: canvasWrap };
|
|
243
|
+
_active.set(paneId, panel);
|
|
244
|
+
|
|
245
|
+
const rerender = () => {
|
|
246
|
+
if (activeIn(paneId) === panel) {
|
|
247
|
+
draw();
|
|
248
|
+
}
|
|
249
|
+
};
|
|
250
|
+
|
|
251
|
+
const draw = () => {
|
|
252
|
+
const collection = collectionOfPath(tab.documentPath);
|
|
253
|
+
if (!collection) {
|
|
254
|
+
litRender(
|
|
255
|
+
html`<div class="entry-editor" data-jx-region=${paneRegion(paneId, "entry")}>
|
|
256
|
+
${notAnEntryTpl(tab)}
|
|
257
|
+
</div>`,
|
|
258
|
+
canvasWrap,
|
|
259
|
+
);
|
|
260
|
+
return;
|
|
261
|
+
}
|
|
262
|
+
const fields = entryFields(tab);
|
|
263
|
+
litRender(
|
|
264
|
+
html`
|
|
265
|
+
<div class="entry-editor" data-jx-region=${paneRegion(paneId, "entry")}>
|
|
266
|
+
${headerTpl(tab, collection)}
|
|
267
|
+
<div class="entry-editor-fields" data-jx-region=${paneRegion(paneId, "entry/fields")}>
|
|
268
|
+
${renderForm(collection.schema as JsonSchema, fields, {
|
|
269
|
+
context: formContext(tab),
|
|
270
|
+
errors: absentRequiredErrors(collection, fields),
|
|
271
|
+
onChange: (patch) => commitEntryFields(tab, patch),
|
|
272
|
+
rerender,
|
|
273
|
+
})}
|
|
274
|
+
</div>
|
|
275
|
+
</div>
|
|
276
|
+
`,
|
|
277
|
+
canvasWrap,
|
|
278
|
+
);
|
|
279
|
+
};
|
|
280
|
+
|
|
281
|
+
scope.run(() => {
|
|
282
|
+
effect(() => {
|
|
283
|
+
if (activeIn(paneId) !== panel) {
|
|
284
|
+
return;
|
|
285
|
+
}
|
|
286
|
+
// Everything the form draws from: the entry's own fields, and the schema that types them.
|
|
287
|
+
// BOTH stores are read, because which one holds the fields is `entry-fields.ts`'s answer,
|
|
288
|
+
// Never this effect's guess. `tab.doc.document` is also the reference every transaction
|
|
289
|
+
// Replaces, so it is how an undo reaches the form whichever store the edit was in.
|
|
290
|
+
void tab.doc.document;
|
|
291
|
+
void tab.doc.content?.frontmatter;
|
|
292
|
+
void projectState?.projectConfig?.content;
|
|
293
|
+
draw();
|
|
294
|
+
});
|
|
295
|
+
});
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
// ─── Opening ─────────────────────────────────────────────────────────────────
|
|
299
|
+
|
|
300
|
+
/**
|
|
301
|
+
* Open a content entry in its form editor.
|
|
302
|
+
*
|
|
303
|
+
* Reveals the tab the file already has rather than building a second one — same id, same history,
|
|
304
|
+
* same dirty flag — and only then switches the editor, so an author who was editing the body keeps
|
|
305
|
+
* their undo stack. Returns null when the file could not be opened at all.
|
|
306
|
+
*/
|
|
307
|
+
export async function openEntryEditor(path: string): Promise<Tab | null> {
|
|
308
|
+
/* Dynamic: `files/files.ts` reaches the platform, the format registry and the packages layer, and
|
|
309
|
+
a static edge from here would drag all of it into every importer of the content model. */
|
|
310
|
+
const { openFileInTab } = await import("../files/files");
|
|
311
|
+
await openFileInTab(path);
|
|
312
|
+
for (const [id, tab] of workspace.tabs.entries()) {
|
|
313
|
+
if (tab.documentPath !== path) {
|
|
314
|
+
continue;
|
|
315
|
+
}
|
|
316
|
+
for (const mode of ENTRY_TAB_MODES) {
|
|
317
|
+
if (!tab.capabilities.modes.includes(mode)) {
|
|
318
|
+
tab.capabilities.modes.unshift(mode);
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
tab.session.ui.canvasMode = ENTRY_MODE;
|
|
322
|
+
tab.session.ui.preview = false;
|
|
323
|
+
activateTab(id);
|
|
324
|
+
return tab as Tab;
|
|
325
|
+
}
|
|
326
|
+
return null;
|
|
327
|
+
}
|