@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,395 @@
|
|
|
1
|
+
/// <reference lib="dom" />
|
|
2
|
+
/**
|
|
3
|
+
* Seo-modal.ts — Search appearance, as a surface of its own.
|
|
4
|
+
*
|
|
5
|
+
* It was a collapsible block inside the Document Header card: two rendered previews, a resolved
|
|
6
|
+
* field list, a warning list, the page and Open Graph meta rows, and an icon picker — all disclosed
|
|
7
|
+
* under one summary row, inside a card whose job is the four or five fields you fill in while
|
|
8
|
+
* writing. A previewed SERP result is not a field; it is a picture you study, and studying it in a
|
|
9
|
+
* strip above the canvas meant the card grew taller than the thing it describes.
|
|
10
|
+
*
|
|
11
|
+
* So it opens as a modal, from two places — the Document Header card and the Navigator's Page
|
|
12
|
+
* panel. Two doors because the two are different moments: one while writing the page, one while
|
|
13
|
+
* working on its head material. Both run `document.openSeo`, so there is a third door in the
|
|
14
|
+
* palette and no surface owns the capability (§2 principle 1).
|
|
15
|
+
*
|
|
16
|
+
* **The card keeps the fields; the modal keeps the picture.** Title still lives on the card,
|
|
17
|
+
* because it is the one head value you type while writing. Everything the modal holds is either a
|
|
18
|
+
* rendering of what the build will emit or a field you set once and leave.
|
|
19
|
+
*
|
|
20
|
+
* The mutation path is the card's, unchanged: a markdown page commits through
|
|
21
|
+
* `applyContentMutation` and a JSON one through `transact`, both taking the tab so the modal edits
|
|
22
|
+
* the document it was opened over rather than whichever pane has focus.
|
|
23
|
+
*/
|
|
24
|
+
|
|
25
|
+
import { html, nothing } from "lit-html";
|
|
26
|
+
import { activeRegistry } from "../commands/active-registry";
|
|
27
|
+
import { loopbackAssetSrc } from "../canvas/canvas-origin";
|
|
28
|
+
import { previewAssetSrc } from "../canvas/content-assets";
|
|
29
|
+
import { renderFieldRow } from "../ui/field-row";
|
|
30
|
+
import { renderMediaPicker } from "../ui/media-picker";
|
|
31
|
+
import { renderProvenanceChip } from "./provenance";
|
|
32
|
+
import { tabLabel } from "./tab-strip";
|
|
33
|
+
import { openModal } from "../ui/layers";
|
|
34
|
+
import { transact } from "../tabs/transact";
|
|
35
|
+
import { activeTab } from "../workspace/workspace";
|
|
36
|
+
import {
|
|
37
|
+
OG_FIELDS,
|
|
38
|
+
PAGE_FIELDS,
|
|
39
|
+
applyContentMutation,
|
|
40
|
+
buildHeadDoc,
|
|
41
|
+
findLinkEntry,
|
|
42
|
+
renderMetaFieldRow,
|
|
43
|
+
seoField,
|
|
44
|
+
reportSeoProblems,
|
|
45
|
+
seoPreviewFor,
|
|
46
|
+
upsertLink,
|
|
47
|
+
visibleLength,
|
|
48
|
+
} from "./head-panel";
|
|
49
|
+
import type { SeoField, SeoPreview } from "./head-panel";
|
|
50
|
+
import type { FieldProvenance } from "./provenance";
|
|
51
|
+
import type { JxHeadEntry, JxMutableNode } from "@jxsuite/schema/types";
|
|
52
|
+
import type { Tab } from "../tabs/tab";
|
|
53
|
+
import type { AnyCommand, CommandRegistry } from "../commands/registry";
|
|
54
|
+
import type { TemplateResult } from "lit-html";
|
|
55
|
+
|
|
56
|
+
// ─── The body ─────────────────────────────────────────────────────────────────
|
|
57
|
+
|
|
58
|
+
/*
|
|
59
|
+
* Two rendered previews, a resolved-field list and a warning list, over the MERGED head — and no
|
|
60
|
+
* score. A number out of a hundred aggregates unrelated facts into a verdict, and a verdict is
|
|
61
|
+
* what gets optimised; a count beside a limit and a named consequence say the same thing without
|
|
62
|
+
* ranking anything (plan §9.2, §14).
|
|
63
|
+
*
|
|
64
|
+
* The previews are pictures of what the build emits, so a value the page did not author is marked
|
|
65
|
+
* as inherited with the donor NAMED — the third cascade to use `panels/provenance.ts`'s vocabulary
|
|
66
|
+
* after the style cascade and component props, and deliberately not a fourth vocabulary. It is the
|
|
67
|
+
* whole reason the block can say "no description reaches this page" without saying it to a page
|
|
68
|
+
* that inherits one from the site.
|
|
69
|
+
*/
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* A resolved field's provenance chip, in the shared vocabulary.
|
|
73
|
+
*
|
|
74
|
+
* The two chips that can go somewhere do: a value from the site's own `$head` opens Project
|
|
75
|
+
* Settings › Site head, and one from the site `name` opens Overview. The layout and build donors
|
|
76
|
+
* get a `<span>` rather than a `<button>`, because the card has no verb for "open that layout" and
|
|
77
|
+
* a control that looks pressable and does nothing is the defect §6.2 exists to remove.
|
|
78
|
+
*
|
|
79
|
+
* @param {SeoField} field
|
|
80
|
+
* @returns {FieldProvenance}
|
|
81
|
+
*/
|
|
82
|
+
function seoProvenance(field: SeoField): FieldProvenance {
|
|
83
|
+
const open = (section: string) => () => {
|
|
84
|
+
void activeRegistry()?.run("settings.open", { section });
|
|
85
|
+
};
|
|
86
|
+
switch (field.source) {
|
|
87
|
+
case "page": {
|
|
88
|
+
return { state: "set", title: `${field.label} is set on this page` };
|
|
89
|
+
}
|
|
90
|
+
case "layout": {
|
|
91
|
+
const donor = field.donor ?? "the layout";
|
|
92
|
+
return {
|
|
93
|
+
donor,
|
|
94
|
+
state: "inherited",
|
|
95
|
+
title: `${field.label} comes from the ${donor} layout — this page does not set it`,
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
case "site": {
|
|
99
|
+
const fromName = field.donor === "Site name";
|
|
100
|
+
return {
|
|
101
|
+
donor: field.donor ?? "the site",
|
|
102
|
+
onClick: open(fromName ? "overview" : "head"),
|
|
103
|
+
state: "inherited",
|
|
104
|
+
title: `${field.label} comes from ${fromName ? "the project's name" : "the site-level $head"} — click to open it`,
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
case "build": {
|
|
108
|
+
return {
|
|
109
|
+
donor: "the build",
|
|
110
|
+
state: "inherited",
|
|
111
|
+
title: `Nothing declares ${field.label}, so the build supplies “${field.value}”`,
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
default: {
|
|
115
|
+
return { state: "default" };
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/** A previewed line of text, or the placeholder that says nothing supplies it. */
|
|
121
|
+
function previewText(field: SeoField): TemplateResult {
|
|
122
|
+
const text = field.value.trim();
|
|
123
|
+
return text
|
|
124
|
+
? html`<span>${text}</span>`
|
|
125
|
+
: html`<span class="seo-unset">No ${field.label.toLowerCase()}</span>`;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/** The mock search result: the breadcrumb the canonical produces, the title, the description. */
|
|
129
|
+
function serpCard(preview: SeoPreview): TemplateResult {
|
|
130
|
+
return html`
|
|
131
|
+
<figure class="seo-card seo-card--serp" aria-label="Search result preview">
|
|
132
|
+
<figcaption class="seo-card-label">Search result</figcaption>
|
|
133
|
+
<div class="seo-serp-url">${preview.url.crumb}</div>
|
|
134
|
+
<div class="seo-serp-title">${seoField(preview, "title").value}</div>
|
|
135
|
+
<div class="seo-serp-desc">${previewText(seoField(preview, "description"))}</div>
|
|
136
|
+
</figure>
|
|
137
|
+
`;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* The mock social card.
|
|
142
|
+
*
|
|
143
|
+
* The image is resolved the same way every other image in the studio chrome is —
|
|
144
|
+
* `loopbackAssetSrc(previewAssetSrc(…))` — so a content-relative `./images/hero.jpg` previews at
|
|
145
|
+
* its asset-mount URL while the authored ref stays exactly as written.
|
|
146
|
+
*/
|
|
147
|
+
function socialCard(preview: SeoPreview): TemplateResult {
|
|
148
|
+
const image = seoField(preview, "og:image").value.trim();
|
|
149
|
+
return html`
|
|
150
|
+
<figure class="seo-card seo-card--social" aria-label="Social card preview">
|
|
151
|
+
<figcaption class="seo-card-label">Social card</figcaption>
|
|
152
|
+
<div class="seo-social-media">
|
|
153
|
+
${
|
|
154
|
+
image
|
|
155
|
+
? html`<img src=${loopbackAssetSrc(previewAssetSrc(image))} alt="" />`
|
|
156
|
+
: html`<span class="seo-unset">No image</span>`
|
|
157
|
+
}
|
|
158
|
+
</div>
|
|
159
|
+
<div class="seo-social-text">
|
|
160
|
+
<span class="seo-social-domain"
|
|
161
|
+
>${preview.url.host || html`<span class="seo-unset">No site URL</span>`}</span
|
|
162
|
+
>
|
|
163
|
+
<span class="seo-social-title">${previewText(seoField(preview, "og:title"))}</span>
|
|
164
|
+
<span class="seo-social-desc">${previewText(seoField(preview, "og:description"))}</span>
|
|
165
|
+
</div>
|
|
166
|
+
</figure>
|
|
167
|
+
`;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
/** One row per resolved field: what reaches the browser, how long it is, and where it came from. */
|
|
171
|
+
function seoFieldList(preview: SeoPreview): TemplateResult {
|
|
172
|
+
return html`
|
|
173
|
+
<ul class="seo-fields">
|
|
174
|
+
${preview.fields.map((field) => {
|
|
175
|
+
const length = visibleLength(field.value);
|
|
176
|
+
const over = field.limit !== null && length > field.limit;
|
|
177
|
+
return html`
|
|
178
|
+
<li class="seo-field" data-seo-field=${field.key}>
|
|
179
|
+
<span class="seo-field-label">${field.label}</span>
|
|
180
|
+
<span class="seo-field-value" title=${field.value}>${previewText(field)}</span>
|
|
181
|
+
${
|
|
182
|
+
field.limit === null
|
|
183
|
+
? nothing
|
|
184
|
+
: html`<span
|
|
185
|
+
class=${over ? "seo-field-count seo-field-count--over" : "seo-field-count"}
|
|
186
|
+
>${length}/${field.limit}</span
|
|
187
|
+
>`
|
|
188
|
+
}
|
|
189
|
+
${renderProvenanceChip(field.key, seoProvenance(field))}
|
|
190
|
+
</li>
|
|
191
|
+
`;
|
|
192
|
+
})}
|
|
193
|
+
</ul>
|
|
194
|
+
`;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
/** The named warnings. A list, never a total — see the note at the top of this section. */
|
|
198
|
+
function seoWarningList(preview: SeoPreview): TemplateResult {
|
|
199
|
+
if (preview.warnings.length === 0) {
|
|
200
|
+
return html`<p class="doc-header-empty">
|
|
201
|
+
Nothing to flag — every previewed field resolves to a value.
|
|
202
|
+
</p>`;
|
|
203
|
+
}
|
|
204
|
+
return html`
|
|
205
|
+
<ul class="seo-warnings">
|
|
206
|
+
${preview.warnings.map(
|
|
207
|
+
(warning) => html`
|
|
208
|
+
<li class="seo-warning" data-seo-warning=${warning.id}>
|
|
209
|
+
<code class="seo-warning-field">${warning.field}</code>
|
|
210
|
+
<span>${warning.message}</span>
|
|
211
|
+
</li>
|
|
212
|
+
`,
|
|
213
|
+
)}
|
|
214
|
+
</ul>
|
|
215
|
+
`;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
/**
|
|
219
|
+
* The previews, the resolved fields, then the controls that change them.
|
|
220
|
+
*
|
|
221
|
+
* That order on purpose: what it looks like, what is wrong with it, and only then the form. The
|
|
222
|
+
* form was all this block used to be, and a form cannot tell you that the description you are about
|
|
223
|
+
* to write is already coming from the site.
|
|
224
|
+
*/
|
|
225
|
+
function seoBody(
|
|
226
|
+
tab: Tab,
|
|
227
|
+
headDoc: JxMutableNode,
|
|
228
|
+
head: JxHeadEntry[],
|
|
229
|
+
applyMutation: (fn: (doc: JxMutableNode) => void) => void,
|
|
230
|
+
): TemplateResult {
|
|
231
|
+
const iconHref = String(findLinkEntry(head, "icon")?.attributes?.href ?? "");
|
|
232
|
+
// The card's tab, so the SERP row shows this document's route and this document's layout layer.
|
|
233
|
+
const preview = seoPreviewFor(tab, headDoc);
|
|
234
|
+
return html`
|
|
235
|
+
<div class="seo-previews">${serpCard(preview)} ${socialCard(preview)}</div>
|
|
236
|
+
${seoFieldList(preview)} ${seoWarningList(preview)}
|
|
237
|
+
<!-- GROUPED, because the two sets collide by name: Open Graph has its own Title, Description
|
|
238
|
+
and Image, and eight unlabelled rows in a column made "Description" mean two things. Each
|
|
239
|
+
group is headed by the preview card it feeds, so a row and the picture it changes are
|
|
240
|
+
nameable together. -->
|
|
241
|
+
<div class="seo-modal-group">
|
|
242
|
+
<h3 class="seo-modal-group-title">Search result</h3>
|
|
243
|
+
${PAGE_FIELDS.map((field) => renderMetaFieldRow(field, head, applyMutation))}
|
|
244
|
+
${renderFieldRow({
|
|
245
|
+
hasValue: Boolean(iconHref),
|
|
246
|
+
label: "Icon",
|
|
247
|
+
onClear: () => applyMutation((d) => upsertLink(d, "icon", "")),
|
|
248
|
+
prop: "icon",
|
|
249
|
+
widget: renderMediaPicker("icon", iconHref, (v: string) => {
|
|
250
|
+
applyMutation((d) => upsertLink(d, "icon", v || ""));
|
|
251
|
+
}),
|
|
252
|
+
})}
|
|
253
|
+
</div>
|
|
254
|
+
<div class="seo-modal-group">
|
|
255
|
+
<h3 class="seo-modal-group-title">Social card</h3>
|
|
256
|
+
<!-- No fallback is promised here, because the build emits none: the warning list above says
|
|
257
|
+
an unset og:title means "a shared link carries no headline of its own", and a note
|
|
258
|
+
claiming otherwise would contradict the app two inches higher. -->
|
|
259
|
+
<p class="seo-modal-group-note">Open Graph — what a shared link shows.</p>
|
|
260
|
+
${OG_FIELDS.map((field) => renderMetaFieldRow(field, head, applyMutation))}
|
|
261
|
+
</div>
|
|
262
|
+
`;
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
// ─── The modal ───────────────────────────────────────────────────────────────
|
|
266
|
+
|
|
267
|
+
/** The open modal, or `null`. One at a time: it is about the focused document. */
|
|
268
|
+
let _handle: { update: (tpl: TemplateResult) => void; close: () => void } | null = null;
|
|
269
|
+
|
|
270
|
+
/** The tab it was opened over, so a re-render draws the same document the author opened. */
|
|
271
|
+
let _tab: Tab | null = null;
|
|
272
|
+
|
|
273
|
+
/**
|
|
274
|
+
* What the SEO body needs from a tab, resolved once.
|
|
275
|
+
*
|
|
276
|
+
* Both realms in one place: a markdown page keeps its head material in frontmatter and a JSON one
|
|
277
|
+
* in root properties, and `buildHeadDoc` is the card's own view of that difference.
|
|
278
|
+
*/
|
|
279
|
+
function seoContextFor(tab: Tab): {
|
|
280
|
+
headDoc: JxMutableNode;
|
|
281
|
+
head: JxHeadEntry[];
|
|
282
|
+
applyMutation: (fn: (doc: JxMutableNode) => void) => void;
|
|
283
|
+
} {
|
|
284
|
+
const isContent = tab.doc.mode === "content";
|
|
285
|
+
const fm = (tab.doc.content?.frontmatter ?? {}) as Record<string, unknown>;
|
|
286
|
+
const headDoc = isContent ? buildHeadDoc(tab.doc.document, fm) : tab.doc.document;
|
|
287
|
+
return {
|
|
288
|
+
applyMutation: isContent
|
|
289
|
+
? (fn: (doc: JxMutableNode) => void) => applyContentMutation(tab, renderSeoModal, fn)
|
|
290
|
+
: (fn: (doc: JxMutableNode) => void) => {
|
|
291
|
+
transact(tab, fn);
|
|
292
|
+
},
|
|
293
|
+
head: headDoc.$head ?? [],
|
|
294
|
+
headDoc,
|
|
295
|
+
};
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
/** Repaint the modal if it is open. Every field commits live, so the picture follows the edit. */
|
|
299
|
+
export function renderSeoModal(): void {
|
|
300
|
+
if (!_handle || !_tab) {
|
|
301
|
+
return;
|
|
302
|
+
}
|
|
303
|
+
const { applyMutation, head, headDoc } = seoContextFor(_tab);
|
|
304
|
+
const tab = _tab;
|
|
305
|
+
_handle.update(html`
|
|
306
|
+
<!-- The region goes on the BODY, not on the layer slot: the slot is a zero-height wrapper
|
|
307
|
+
around a fixed-position body, so a shot capturing it would capture nothing. The publish
|
|
308
|
+
panel stamps its own for the same reason. An overlay.instance:id name is a DERIVED shape,
|
|
309
|
+
so this costs the manifest no non-derived-region budget. -->
|
|
310
|
+
<div class="seo-modal" data-jx-region="overlay.dialog:seo">
|
|
311
|
+
<div class="settings-modal-header">
|
|
312
|
+
<h2 class="settings-modal-title">Search appearance</h2>
|
|
313
|
+
<!-- WHICH document, in the header. A modal has no tab strip behind it to say so, and every
|
|
314
|
+
field below resolves through this document's layout and site head. -->
|
|
315
|
+
<span class="seo-modal-doc">${tab.documentPath ?? tabLabel(tab)}</span>
|
|
316
|
+
<sp-action-button quiet size="s" title="Close" @click=${closeSeoModal}>
|
|
317
|
+
<sp-icon-close slot="icon"></sp-icon-close>
|
|
318
|
+
</sp-action-button>
|
|
319
|
+
</div>
|
|
320
|
+
<div class="seo-modal-body">${seoBody(tab, headDoc, head, applyMutation)}</div>
|
|
321
|
+
</div>
|
|
322
|
+
`);
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
/** Open it over `tab`. Idempotent — opening it again re-points it at the current document. */
|
|
326
|
+
export function openSeoModal(tab: Tab): void {
|
|
327
|
+
_tab = tab;
|
|
328
|
+
if (!_handle) {
|
|
329
|
+
_handle = openModal(html``, { label: "Search appearance", onDismiss: closeSeoModal });
|
|
330
|
+
}
|
|
331
|
+
renderSeoModal();
|
|
332
|
+
/*
|
|
333
|
+
* File the same warnings as Problems on the way in.
|
|
334
|
+
*
|
|
335
|
+
* A window someone has to open is not where a fact should live alone: a page shipped with no
|
|
336
|
+
* description is worth knowing whether or not you thought to look, and Problems is where this app
|
|
337
|
+
* keeps the records that outlive the frame you were not watching. Same list, keyed by warning id,
|
|
338
|
+
* so the two surfaces are naming one thing rather than two.
|
|
339
|
+
*/
|
|
340
|
+
reportSeoProblems(seoPreviewFor(tab, tab.doc.document), tab.documentPath ?? undefined);
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
/** Close it, and forget the document it was about. */
|
|
344
|
+
export function closeSeoModal(): void {
|
|
345
|
+
_handle?.close();
|
|
346
|
+
_handle = null;
|
|
347
|
+
_tab = null;
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
/**
|
|
351
|
+
* `document.openSeo` — the one capability behind both buttons.
|
|
352
|
+
*
|
|
353
|
+
* The Document Header card and the Page panel each render a control that runs this, so neither owns
|
|
354
|
+
* it and the palette has it by name. A record rather than two click handlers, for the reason the
|
|
355
|
+
* rendering-context axes became records: a surface that IS the capability is a capability the
|
|
356
|
+
* palette, the assistant and `__jxAutomation` cannot reach.
|
|
357
|
+
*
|
|
358
|
+
* @returns {AnyCommand[]}
|
|
359
|
+
*/
|
|
360
|
+
export function seoCommands(): AnyCommand[] {
|
|
361
|
+
return [
|
|
362
|
+
{
|
|
363
|
+
category: "Document",
|
|
364
|
+
id: "document.openSeo",
|
|
365
|
+
level: "document",
|
|
366
|
+
menus: ["palette"],
|
|
367
|
+
group: "2_document",
|
|
368
|
+
requires: "an open document",
|
|
369
|
+
when: (ctx) => ctx.document.open,
|
|
370
|
+
aiTool: {
|
|
371
|
+
description:
|
|
372
|
+
"Open Search appearance for the current document — the SERP and social previews, the " +
|
|
373
|
+
"resolved head fields with their donors, and the page/Open Graph meta fields.",
|
|
374
|
+
name: "open_seo",
|
|
375
|
+
},
|
|
376
|
+
run: () => {
|
|
377
|
+
const tab = activeTab.value;
|
|
378
|
+
if (!tab) {
|
|
379
|
+
throw new RangeError(`command "document.openSeo" needs an open document`);
|
|
380
|
+
}
|
|
381
|
+
openSeoModal(tab);
|
|
382
|
+
},
|
|
383
|
+
title: "Search Appearance",
|
|
384
|
+
},
|
|
385
|
+
];
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
/**
|
|
389
|
+
* Register it.
|
|
390
|
+
*
|
|
391
|
+
* @param {CommandRegistry} registry
|
|
392
|
+
*/
|
|
393
|
+
export function registerSeoCommands(registry: CommandRegistry): void {
|
|
394
|
+
registry.registerAll(seoCommands());
|
|
395
|
+
}
|
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
/// <reference lib="dom" />
|
|
2
|
+
/**
|
|
3
|
+
* The Project Settings editor, drawn inside the pane — the stage, not an overlay.
|
|
4
|
+
*
|
|
5
|
+
* Two columns: the section list (inner nav) and the section body. It is deliberately the same
|
|
6
|
+
* two-column shape the modal had, because the modal's IA was never the problem — its MODALITY was.
|
|
7
|
+
* Moving the identical layout into `#canvas-wrap` is what makes the seven screenshot crops change
|
|
8
|
+
* frame (`overlay.dialog:settings` → `pane.primary`) without one manifest STEP changing, which is
|
|
9
|
+
* §13.7's proof that the command boundary was drawn in the right place.
|
|
10
|
+
*
|
|
11
|
+
* Mounting follows `grid/grid-panel.ts`: `canvas/canvas-render.ts` calls {@link renderSettingsPane}
|
|
12
|
+
* when the tab enters this mode, and this module owns its reactivity from there — a subscription to
|
|
13
|
+
* the section registry and to the chosen section, so an extension's section appearing a tick later
|
|
14
|
+
* redraws the nav without the canvas being re-rendered.
|
|
15
|
+
*
|
|
16
|
+
* **It imports the registry, never the sections.** `canvas-render.ts` reaches this module, so
|
|
17
|
+
* anything it imports joins the canvas's import graph; the section RENDERERS are registered by
|
|
18
|
+
* `settings/settings-document.ts`, which the app loads when it registers its commands.
|
|
19
|
+
*
|
|
20
|
+
* Nothing here is stamped with a region of its own. `#canvas-wrap` already carries `pane.primary`
|
|
21
|
+
* (`ui/regions.ts`'s `SHELL_REGION_HOSTS`), the entry rows and the entry editor are stamped by
|
|
22
|
+
* `settings/contributed-section.ts` as `pane.primary/entry:<key>` and `pane.primary/editor`, and a
|
|
23
|
+
* third id for "the settings body" would be a hand-stamped region with nothing to say.
|
|
24
|
+
*
|
|
25
|
+
* @docs studio/projects/settings
|
|
26
|
+
*/
|
|
27
|
+
|
|
28
|
+
import { html, render as litRender } from "lit-html";
|
|
29
|
+
import { classMap } from "lit-html/directives/class-map.js";
|
|
30
|
+
import { ref } from "lit-html/directives/ref.js";
|
|
31
|
+
import {
|
|
32
|
+
onSettingsDocumentChanged,
|
|
33
|
+
settingsSection,
|
|
34
|
+
settingsDocumentSection,
|
|
35
|
+
setSettingsSection,
|
|
36
|
+
sortedSettingsSections,
|
|
37
|
+
} from "../settings/section-registry";
|
|
38
|
+
import type { CanvasSurface } from "../canvas/canvas-surface";
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* One mounted editor per PANE.
|
|
42
|
+
*
|
|
43
|
+
* `config` is a kind the side pane may host, so the previous single `_host` had both panes' stages
|
|
44
|
+
* competing for it: `settingsPaneMounted` answered false for whichever host lost, and that pane's
|
|
45
|
+
* fast path rebuilt the whole section body on every render — throwing away an open inline form
|
|
46
|
+
* mid-keystroke, which is the exact failure the idempotence was written to prevent.
|
|
47
|
+
*/
|
|
48
|
+
interface ActiveSettingsPane {
|
|
49
|
+
host: HTMLElement;
|
|
50
|
+
/** The section body container, handed to whichever section renderer is current. */
|
|
51
|
+
body: HTMLElement | null;
|
|
52
|
+
/** The section the body currently holds, so an idle re-render does not rebuild it. */
|
|
53
|
+
rendered: string | null;
|
|
54
|
+
/** Unsubscribe from the document's change notifications. */
|
|
55
|
+
off: (() => void) | null;
|
|
56
|
+
/**
|
|
57
|
+
* The body's `ref` callback, allocated ONCE per record.
|
|
58
|
+
*
|
|
59
|
+
* Lit re-invokes a `ref` whose callback IDENTITY changed — the old one with `undefined`, the new
|
|
60
|
+
* one with the element. Built inline in the template it was a fresh closure every render, so
|
|
61
|
+
* every render ran `panel.body = null; panel.rendered = null` and then re-bound: `rendered` was
|
|
62
|
+
* null again by the time the guard in {@link draw} read it, on every single pass. The guard could
|
|
63
|
+
* not fire and `force` decided nothing.
|
|
64
|
+
*
|
|
65
|
+
* What that did NOT cost, despite appearances: the mid-keystroke rebuild. `canvas-render.ts`
|
|
66
|
+
* returns at its own fast path (`canvasMode === "settings" && settingsPaneMounted(surface) &&
|
|
67
|
+
* !modeChanged`) before it ever calls back in, and on a real mode change it detaches this record
|
|
68
|
+
* first — so `draw(paneId, false)` over a SURVIVING record has no caller in the app. That fast
|
|
69
|
+
* path is what keeps an open inline form alive today; this guard is the second line, and it was
|
|
70
|
+
* vacuous. Do not read the two as redundant and delete either.
|
|
71
|
+
*
|
|
72
|
+
* Per RECORD, not per pane id: a same-host remount after `detachSettingsPane` needs the identity
|
|
73
|
+
* to change so lit re-binds the element onto the new record.
|
|
74
|
+
*/
|
|
75
|
+
bindBody: (el: Element | undefined) => void;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
const _active = new Map<string, ActiveSettingsPane>();
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Build one record's body binder.
|
|
82
|
+
*
|
|
83
|
+
* The guard is an IDENTITY test, not a presence test, and that distinction is the whole of it. A
|
|
84
|
+
* binder outlives its record — lit calls the previous one with `undefined` whenever the callback
|
|
85
|
+
* changes, and again when the part disconnects — so by the time it runs, `_active` may hold no
|
|
86
|
+
* record at all, or a DIFFERENT one for the same pane. Writing into whatever `_active` happens to
|
|
87
|
+
* return would be worse than doing nothing: it would null the live record's `body`, and since a
|
|
88
|
+
* stable callback over an unchanged element is never re-invoked, nothing would ever re-bind it —
|
|
89
|
+
* `draw` would return at `!panel.body` forever, `force` included. A superseded binder therefore
|
|
90
|
+
* touches nothing; its own record is detached and no one reads it.
|
|
91
|
+
*/
|
|
92
|
+
function bodyBinder(paneId: string): (el: Element | undefined) => void {
|
|
93
|
+
const self = (el: Element | undefined) => {
|
|
94
|
+
const panel = _active.get(paneId);
|
|
95
|
+
if (panel?.bindBody !== self) {
|
|
96
|
+
return;
|
|
97
|
+
}
|
|
98
|
+
const next = (el as HTMLElement | undefined) ?? null;
|
|
99
|
+
if (next !== panel.body) {
|
|
100
|
+
panel.body = next;
|
|
101
|
+
panel.rendered = null;
|
|
102
|
+
}
|
|
103
|
+
};
|
|
104
|
+
return self;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Mount (or refresh) the settings editor inside the pane.
|
|
109
|
+
*
|
|
110
|
+
* Idempotent for the same host: canvas-render calls this on every render for this mode, and
|
|
111
|
+
* rebuilding the section body on each one would throw away an open inline form mid-keystroke.
|
|
112
|
+
*
|
|
113
|
+
* @param {CanvasSurface} surface - The pane whose stage hosts the editor
|
|
114
|
+
*/
|
|
115
|
+
export function renderSettingsPane(surface: CanvasSurface): void {
|
|
116
|
+
const { paneId, wrap: host } = surface;
|
|
117
|
+
let panel = _active.get(paneId);
|
|
118
|
+
if (panel?.host !== host) {
|
|
119
|
+
detachSettingsPane(paneId);
|
|
120
|
+
panel = {
|
|
121
|
+
bindBody: bodyBinder(paneId),
|
|
122
|
+
body: null,
|
|
123
|
+
host,
|
|
124
|
+
off: onSettingsDocumentChanged(() => draw(paneId, true)),
|
|
125
|
+
rendered: null,
|
|
126
|
+
};
|
|
127
|
+
_active.set(paneId, panel);
|
|
128
|
+
}
|
|
129
|
+
draw(paneId, false);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/** Tear one pane's editor down — the mode-change and project-close path. */
|
|
133
|
+
export function detachSettingsPane(paneId: string): void {
|
|
134
|
+
const panel = _active.get(paneId);
|
|
135
|
+
if (!panel) {
|
|
136
|
+
return;
|
|
137
|
+
}
|
|
138
|
+
panel.off?.();
|
|
139
|
+
_active.delete(paneId);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* Whether the editor is mounted on this pane's stage — canvas-render's "did I already build this".
|
|
144
|
+
*
|
|
145
|
+
* @param {CanvasSurface} surface
|
|
146
|
+
* @returns {boolean}
|
|
147
|
+
*/
|
|
148
|
+
export function settingsPaneMounted(surface: CanvasSurface): boolean {
|
|
149
|
+
return _active.get(surface.paneId)?.host === surface.wrap;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* Draw the nav, then the body.
|
|
154
|
+
*
|
|
155
|
+
* @param {boolean} force - Re-run the section renderer even when the section has not changed. True
|
|
156
|
+
* for a real state change (a nav click, a section registering, an entry being selected by
|
|
157
|
+
* command); false for canvas-render's idempotent remount.
|
|
158
|
+
*/
|
|
159
|
+
function draw(paneId: string, force: boolean): void {
|
|
160
|
+
const panel = _active.get(paneId);
|
|
161
|
+
if (!panel) {
|
|
162
|
+
return;
|
|
163
|
+
}
|
|
164
|
+
const active = settingsDocumentSection();
|
|
165
|
+
const sections = sortedSettingsSections();
|
|
166
|
+
|
|
167
|
+
const tpl = html`
|
|
168
|
+
<div class="settings-doc">
|
|
169
|
+
<nav class="settings-doc-nav" aria-label="Project Settings sections">
|
|
170
|
+
${sections.map(
|
|
171
|
+
(section) => html`
|
|
172
|
+
<button
|
|
173
|
+
class=${classMap({
|
|
174
|
+
active: active === section.key,
|
|
175
|
+
"settings-nav-item": true,
|
|
176
|
+
})}
|
|
177
|
+
aria-current=${active === section.key ? "page" : "false"}
|
|
178
|
+
@click=${() => setSettingsSection(section.key)}
|
|
179
|
+
>
|
|
180
|
+
${section.label}
|
|
181
|
+
</button>
|
|
182
|
+
`,
|
|
183
|
+
)}
|
|
184
|
+
</nav>
|
|
185
|
+
<div class="settings-doc-content" ${ref(panel.bindBody)}></div>
|
|
186
|
+
</div>
|
|
187
|
+
`;
|
|
188
|
+
|
|
189
|
+
litRender(tpl, panel.host);
|
|
190
|
+
|
|
191
|
+
if (!panel.body || (!force && panel.rendered === active)) {
|
|
192
|
+
return;
|
|
193
|
+
}
|
|
194
|
+
panel.rendered = active;
|
|
195
|
+
const section = settingsSection(active);
|
|
196
|
+
if (section) {
|
|
197
|
+
section.render(panel.body);
|
|
198
|
+
return;
|
|
199
|
+
}
|
|
200
|
+
/* Every section unregistered at once — a project closing while the editor is open. A blank
|
|
201
|
+
content area beside a nav reads as a broken pane, so it says which it is. */
|
|
202
|
+
litRender(html`<div class="settings-empty-state">No settings sections.</div>`, panel.body);
|
|
203
|
+
}
|